# BAT-022 Verification - Status: draft - Batch: BAT-022 - Verification Date: 2026-06-28 ## 테스트 실행 결과 ### 신규 테스트 (44 케이스) ``` tests/test_bat022_req052_credential_api.py — 17 passed tests/test_bat022_req053_admin_daemon_crud.py — 15 passed tests/test_bat022_req054_security.py — 12 passed ``` ### 전체 테스트 스위트 ``` 565 passed, 11 warnings in 32.53s ``` 기존 521 테스트 회귀: **0건** (521 → 565, +44 신규만 추가) ## REQ-052 AC별 검증 Evidence | AC | 테스트 | 결과 | | --- | --- | --- | | AC-1: PUT → 200 + 메타데이터, secret 없음 | `test_put_credential_returns_meta_without_secret` | PASS | | AC-2: 회전 시 credential_version 증가 | `test_put_credential_rotation_increments_version` | PASS | | AC-3: CLI·API·service-token 정합 (upsert daemon_id 전달) | `test_put_credential_upsert_called_with_correct_daemon_id` | PASS | | AC-4: GET → 200 + 메타데이터, secret 없음 | `test_get_credential_meta_returns_metadata_without_secret` | PASS | | AC-5: DELETE → 204 + 이후 has_credential=False | `test_delete_credential_returns_204`, `test_get_after_delete_returns_has_credential_false` | PASS | | AC-6 (음성): 타 사용자 토큰 → 403 | `test_put/get/delete_credential_other_user_returns_403` (3개) | PASS | | AC-7: 비인증 → 401 | `test_credential_no_token_returns_401` | PASS | | AC-8: 미존재 daemon → 404 | `test_credential_nonexistent_daemon_returns_404`, `test_put_nonexistent_daemon_returns_404` | PASS | | AC-9 (음성): admin으로 타인 daemon write → 403 | `test_admin_cannot_write/read_other_user_credential` (2개) | PASS | | AC-10 (음성): service-token → 403 | `test_service_token_credential_get/put/delete_returns_403` (3개) | PASS | ## REQ-053 AC별 검증 Evidence | AC | 테스트 | 결과 | | --- | --- | --- | | AC-1: POST 생성 → 201 + DaemonAdminResponse | `test_admin_create_daemon_returns_201` | PASS | | AC-2: 중복 slug → 409 | `test_admin_create_daemon_duplicate_slug_returns_409` | PASS | | AC-3: display_name 변경 → BrokerCredential 유지 | `test_admin_patch_display_name_credential_preserved` | PASS | | AC-4: owner 재할당 → BrokerCredential 자동 삭제 | `test_admin_patch_owner_invalidates_credentials` | PASS | | AC-5: 재할당 후 이전 owner 인증정보 무효화 | `test_admin_patch_owner_then_old_owner_loses_credential` | PASS | | AC-6: 비활성화 → is_active=False + service token revoke | `test_admin_deactivate_daemon_revokes_tokens` | PASS | | AC-7: 재활성화 → is_active=True | `test_admin_activate_daemon_returns_active` | PASS | | AC-8 (음성): DELETE → 405 | `test_admin_delete_daemon_returns_405` | PASS | | AC-9 (음성): user-token(비admin) → 403 | `test_non_admin_user_token_create/patch/deactivate` (3개) | PASS | | AC-10 (음성): service-token → 403 | `test_service_token_admin_crud_returns_403` | PASS | | AC-11: 비인증 → 401 | `test_admin_crud_no_token_returns_401` | PASS | | AC-12: 미존재 daemon → 404 | `test_admin_patch/deactivate_nonexistent_daemon_returns_404` (2개) | PASS | ## REQ-054 AC별 검증 Evidence | AC | 테스트 | 결과 | | --- | --- | --- | | AC-1: PUT 응답에 app_key/app_secret 없음 | `test_put_credential_response_has_no_secret_fields` | PASS | | AC-2: GET 응답에 app_key/app_secret 없음 | `test_get_credential_meta_response_has_no_secret_fields` | PASS | | AC-3: admin CRUD 응답에 credential 필드 없음 | `test_admin_create/patch_response_has_no_credential_fields` (2개) | PASS | | AC-4: caplog — PUT 처리 중 app_key/app_secret 값 미포함 | `test_put_credential_log_does_not_expose_secret` | PASS | | AC-5: Fernet 암호화 저장 확인 (gAAAA... 형태) | `test_put_credential_stores_encrypted_payload` | PASS | | AC-6: PUT 후 service-token user_info → 동일 account_ref | `test_put_then_service_token_user_info_returns_correct_account_ref` | PASS | | AC-7: REQ-051 회귀 (4개 테스트) | `test_req051_regression_*` (4개) | PASS | ## 음성 테스트 체크리스트 | 음성 시나리오 | 케이스 수 | 결과 | | --- | --- | --- | | 소유 스코프 403 (AC-6: 타 사용자 토큰) | 3 | PASS | | admin 소유 스코프 403 (AC-9) | 2 | PASS | | service-token 403 (AC-10, REQ-052) | 3 | PASS | | service-token 403 (AC-10, REQ-053) | 1 | PASS | | DELETE 405 (AC-8, REQ-053) | 1 | PASS | | secret 필드 부재 (AC-1/2/3, REQ-054) | 4 | PASS | | caplog secret 미노출 (AC-4, REQ-054) | 1 | PASS | ## 회귀 테스트 - REQ-051 AC-1~3, AC-5: 신규 API 추가 후 GET /v1/user_info 보안 격리 불변 확인 (4개 테스트 PASS) - 기존 521 테스트 전건 회귀 없음 ## 잔여 위험 / 미해결 1. **AC-3 통합 수준**: REQ-052 AC-3(CLI·API·service-token 3경로 정합)은 이 batch에서 unit-level로 검증(upsert daemon_id 전달 확인). 실 DB 통합 검증은 dev DB 환경 필요(비가역 아님, 선택적 추후 확인 가능). 2. **DELETE credential 기본 broker "kis"**: GET/DELETE 에서 broker 파라미터 없이 "kis" 고정. 다중 broker 지원이 필요해지면 경로 파라미터 추가 필요 — 현재 단일 broker 가정 설계 일치. 3. **update_daemon_owner autocommit 부분 실패**: postgres.py `update_daemon_owner()`는 daemon UPDATE + credential DELETE를 autocommit 2회 연산으로 처리. daemon 업데이트 성공 후 credential 삭제 실패 케이스는 테스트 범위 밖(네트워크 장애 등). 실 운영에서 발생 시 수동 정리 필요.