EduSync Learning API 데모 검증 보고서
2026-08-07T02:11:42Z에 Docker Compose, HTTP API, SQL Server로 검증했습니다. 자격 증명, HMAC 서명, 데이터베이스 비밀번호는 제외했습니다.
전체 결과: PASS
{"runtime":"Docker Compose / PHP 8.3.33","database":"PDO_SQLSRV를 통한 SQL Server","authentication":"로컬 테스트 자격 증명"}
시나리오 결과
| 시나리오 | 결과 | HTTP / 테스트 상태 | 요청 | 응답 | 세부 정보 |
|---|---|---|---|---|---|
| 상태 확인 | PASS | 200 | {"method":"GET","path":"/health"} | {"status":"ok","database":"connected","driver":"pdo_sqlsrv","probe":1} | |
| 인증 필수 | PASS | 401 | {"method":"POST","path":"/api/v1/learning-events","authentication":"미포함"} | {"error":{"status":401,"message":"Bearer authentication failed.","correlation_id":"c18fd77a6b6d0ab5fc6f6400e49f08ad"}} | |
| 유효한 수강 등록 필수 | PASS | 403 | {"method":"POST","path":"/api/v1/learning-events","authentication":"학습자 Bearer 값 제외"} | {"error":{"status":403,"message":"Active enrollment is required.","correlation_id":"37ca2c8a8ed86b25ec2f8865f620202d"}} | |
| 신규 체크포인트 | PASS | 200 | {"method":"POST","path":"/api/v1/learning-events","body":{"event_id":"demo-checkpoint","learner_id":2001,"lecture_id":940000041,"session_id":"demo-session","sequence_no":1,"event_type":"CHECKPOINT","position_seconds":100,"occurred_at":"2026-08-07T02:09:42.433Z"}} | {"applied":true,"duplicate":false} | {"snapshot":{"resume_position_seconds":"100","furthest_position_seconds":"100","last_session_id":"demo-session","last_sequence_no":"1","completed_at":null}} |
| 늦은 체크포인트는 이어보기를 유지하고 최대 위치를 높임 | PASS | 200 | {"method":"POST","path":"/api/v1/learning-events"} | {"applied":true,"duplicate":false} | {"snapshot":{"resume_position_seconds":"100","furthest_position_seconds":"150","last_session_id":"demo-session","last_sequence_no":"1","completed_at":null}} |
| 최신 되감기는 이어보기를 낮추고 최대 위치를 유지함 | PASS | 200 | {"method":"POST","path":"/api/v1/learning-events"} | {"applied":true,"duplicate":false} | {"snapshot":{"resume_position_seconds":"50","furthest_position_seconds":"150","last_session_id":"demo-session","last_sequence_no":"2","completed_at":null}} |
| 의미상 동일한 중복 요청 | PASS | 200 | {"method":"POST","path":"/api/v1/learning-events","body":"필드 순서 변경, 자격 증명 제외"} | {"applied":false,"duplicate":true} | |
| 같은 이벤트 ID의 다른 payload | PASS | 409 | {"method":"POST","path":"/api/v1/learning-events"} | {"error":{"status":409,"message":"event_id is already used with a different payload.","correlation_id":"052a0aeefba4e972ef8630ccf3eaa8f6"}} | |
| 서로 다른 source의 같은 이벤트 ID | PASS | 200 | {"method":"POST","path":"/api/v1/player-events","authentication":"HMAC 헤더 값 제외"} | {"applied":true,"duplicate":false} | {"event_count":2} |
| 5분을 초과한 미래 이벤트는 저장하지 않음 | PASS | 422 | {"method":"POST","path":"/api/v1/learning-events"} | {"error":{"status":422,"message":"occurred_at is more than five minutes in the future.","correlation_id":"9d4a1aa7bad044da009cf53c4f92159c"}} | {"event_count":0} |
| 최초 completed_at 보존 | PASS | 200 | {"method":"POST","path":"/api/v1/learning-events"} | {"applied":true,"duplicate":false} | {"before":{"resume_position_seconds":"160","furthest_position_seconds":"160","last_session_id":"demo-complete","last_sequence_no":"1","completed_at":"2026-08-07 02:10:42.433"},"after":{"resume_position_seconds":"70","furthest_position_seconds":"160","last_session_id":"demo-complete","last_sequence_no":"2","completed_at":"2026-08-07 02:10:42.433"}} |
| 보호자 경로 주체 불일치 | PASS | 403 | {"method":"GET","path":"/api/v1/guardians/{other}/learners/{seed}/progress","authentication":"보호자 Bearer 값 제외"} | {"error":{"status":403,"message":"Authenticated guardian does not match guardianId.","correlation_id":"4b6cffe2e76c0068f7acdebfe2fcdf76"}} | |
| 보호자 연결 필수 | PASS | 403 | {"method":"GET","path":"/api/v1/guardians/{seed}/learners/{unlinked}/progress","authentication":"보호자 Bearer 값 제외"} | {"error":{"status":403,"message":"Guardian is not linked to this learner.","correlation_id":"a0d7b25ea78ef75022810e72a4aa9986"}} | |
| 연결된 보호자의 진행 상태 조회 | PASS | 200 | {"method":"GET","path":"/api/v1/guardians/{seed}/learners/{seed}/progress","authentication":"보호자 Bearer 값 제외"} | {"guardian_id":1001,"learner_id":2001,"progress":[{"lecture_id":4001,"resume_position_seconds":120,"furthest_position_seconds":180,"last_studied_at":"2026-07-01T10:00:00Z","completed_at":null},{"lecture_id":940000041,"resume_position_seconds":70,"furthest_position_seconds":160,"last_studied_at":"2026-08-07T02:10:52.433Z","completed_at":"2026-08-07T02:10:42.433Z"}]} | |
| Classic ASP 계약 | PASS | 200 | {"command":"php tests/contract.php","target":"legacy/progress.asp"} | "PASS Classic ASP contract and PDO_SQLSRV configuration" | |
| 동시 최초 INSERT와 MSSQL 원자성 | PASS | 200 | {"method":"병렬 HTTP 테스트","path":"tests/concurrency.php"} | {"passed":true,"scenarios":["T01","T02","T03","T04","T08","T12"]} | |
최종 상태와 정리 결과
{
"event_count": 9,
"snapshot": {
"resume_position_seconds": "70",
"furthest_position_seconds": "160",
"last_session_id": "demo-complete",
"last_sequence_no": "2",
"completed_at": "2026-08-07 02:10:42.433"
},
"fixture_cleaned": true
}검증 범위와 한계
- Compose 실행은 Classic ASP 소스와 JSON 계약을 확인합니다. 선택적 Windows IIS 엔드포인트는 scripts/setup-iis-classic-asp.ps1로 별도 구성합니다.
- 시나리오는 정확성을 검증하며 운영 부하나 처리 용량을 측정하지 않습니다.