Files
portfolio-hub/docs/batches/bat-014_20260524_portfolioitem-symbol-response-contract/verification.md
T

7.4 KiB

BAT-014 Verification

  • Status: confirmed
  • Batch: BAT-014

Acceptance Mapping

REQ-025: POST /v1/portfolios

  • AC-1 (admin + user_id → target owner): tests/test_v1_portfolios_crud.py::test_post_admin_creates_for_target_user PASSED — owner_user_id == _OTHER_USER_ID.
  • AC-2 (session/env token → own user/fallback): tests/test_v1_portfolios_crud.py::test_post_user_creates_for_self PASSED.
  • AC-3 (service token → 403): tests/test_v1_portfolios_crud.py::test_post_service_token_returns_403 PASSED.
  • AC-5 (inactive/missing asset → 422): tests/test_v1_portfolios_crud.py::test_post_inactive_asset_returns_422, tests/test_v1_portfolios_crud.py::test_post_missing_asset_returns_422 PASSED.
  • AC-6 (no token → 401): tests/test_v1_portfolios_crud.py::test_post_no_bearer_returns_401 PASSED.
  • AC-7 (non-admin body user_id ignored): tests/test_v1_portfolios_crud.py::test_post_non_admin_user_id_ignored PASSED.
  • AC-8 (POST 응답 symbol == joined assets.symbol): tests/test_v1_portfolios_crud.py::test_post_admin_creates_for_target_user PASSED — response body symbol == "005930".

REQ-026: PATCH /v1/portfolios/{portfolio_id}

  • AC-1 (admin → any portfolio): tests/test_v1_portfolios_crud.py::test_patch_admin_updates_any_portfolio PASSED — broker == "ebest".
  • AC-2 (session token + own portfolio → 200): tests/test_v1_portfolios_crud.py::test_patch_user_updates_own_portfolio PASSED.
  • AC-3 (session token + other portfolio → 403): tests/test_v1_portfolios_crud.py::test_patch_user_other_portfolio_returns_403 PASSED.
  • AC-4 (service token → 403): tests/test_v1_portfolios_crud.py::test_patch_service_token_returns_403 PASSED.
  • AC-6 (no token → 401): tests/test_v1_portfolios_crud.py::test_patch_no_bearer_returns_401 PASSED.
  • AC-7 (non-existent → 404): tests/test_v1_portfolios_crud.py::test_patch_nonexistent_portfolio_returns_404 PASSED.
  • AC-8 (disabled + non-status field → 409): tests/test_v1_portfolios_crud.py::test_patch_disabled_portfolio_non_status_returns_409 PASSED.
  • AC-9 (disabled + status=enabled → 200): tests/test_v1_portfolios_crud.py::test_patch_disabled_portfolio_status_restore_returns_200 PASSED.
  • AC-10 (PATCH 응답 symbol == joined assets.symbol): tests/test_v1_portfolios_crud.py::test_patch_admin_updates_any_portfolio, tests/test_v1_portfolios_crud.py::test_patch_disabled_portfolio_status_restore_returns_200 PASSED — both assert symbol == "005930".

REQ-027: DELETE /v1/portfolios/{portfolio_id}

  • AC-1 (session + own → 200, disabled, daemon_id NULL): tests/test_v1_portfolios_crud.py::test_delete_user_own_active_portfolio_soft_deletes PASSED.
  • AC-3 (admin + other user → 200): tests/test_v1_portfolios_crud.py::test_delete_admin_other_user_portfolio PASSED.
  • AC-4 (session + other → 403): tests/test_v1_portfolios_crud.py::test_delete_user_other_portfolio_returns_403 PASSED.
  • AC-5 (service token → 403): tests/test_v1_portfolios_crud.py::test_delete_service_token_returns_403 PASSED.
  • AC-7 (no token → 401): tests/test_v1_portfolios_crud.py::test_delete_no_bearer_returns_401 PASSED.
  • AC-8 (non-existent → 404): tests/test_v1_portfolios_crud.py::test_delete_nonexistent_portfolio_returns_404 PASSED.
  • AC-11 (DELETE 200 응답 symbol == joined assets.symbol): tests/test_v1_portfolios_crud.py::test_delete_user_own_active_portfolio_soft_deletes, tests/test_v1_portfolios_crud.py::test_delete_admin_other_user_portfolio PASSED — both assert symbol == "005930".

REQ-028: GET /v1/portfolios

  • AC-1 (service + daemon_id → own daemon portfolios): tests/test_v1_portfolios_list.py::test_get_service_token_daemon_bound_returns_own_portfolios PASSED.
  • AC-2 (service no daemon → 403): tests/test_v1_portfolios_list.py::test_get_service_token_no_daemon_returns_403 PASSED.
  • AC-3 (session → own portfolios): tests/test_v1_portfolios_list.py::test_get_user_token_returns_own_portfolios PASSED.
  • AC-4 (admin no ?user_id → default branch): tests/test_v1_portfolios_list.py::test_get_admin_default_returns_own_portfolios PASSED.
  • AC-5 (admin + ?user_id → target): tests/test_v1_portfolios_list.py::test_get_admin_with_user_id_returns_target_portfolios PASSED.
  • AC-7 (disabled excluded by default): tests/test_v1_portfolios_list.py::test_get_excludes_disabled_by_default PASSED.
  • AC-8 (include_disabled=true → all): tests/test_v1_portfolios_list.py::test_get_include_disabled_returns_all PASSED.
  • AC-9 (client token → 403): tests/test_v1_portfolios_list.py::test_get_client_token_returns_403 PASSED.
  • AC-10 (no token → 401): tests/test_v1_portfolios_list.py::test_get_no_bearer_returns_401 PASSED.
  • AC-11 (asset_id response field): tests/test_v1_portfolios_list.py::test_get_response_has_asset_id_field PASSED.
  • AC-12 (symbol == joined assets.symbol): tests/test_v1_portfolios_list.py::test_get_service_token_daemon_bound_returns_own_portfolios, tests/test_v1_portfolios_list.py::test_get_response_has_asset_id_field PASSED — both assert symbol == "005930".

REQ-032: portfolio v1 baseline 문서 갱신

  • AC-1 (docs/interface-contract.md 에 v1 portfolio route entries 존재): 문서 유지 + POST/PATCH/DELETE/GET /v1/portfolios 섹션 갱신 완료.
  • AC-6 (사람 리뷰 기준 baseline 충돌 없음): 이번 batch 범위에서 docs/interface-contract.md 의 portfolio response contract를 코드와 정합화했다.
  • AC-7 (symbol 필드 + GET join 설명): docs/interface-contract.md 에서 POST/PATCH/DELETE/GET /v1/portfolios 응답 설명에 symbol 추가, GET 섹션에 portfolios JOIN assets 기반 직렬화 설명 추가 완료.

Evidence

  • Test:
    • ./.venv/bin/python -m pytest tests/test_v1_portfolios_crud.py tests/test_v1_portfolios_list.py -q → 32 passed in 2.13s
    • ./.venv/bin/python -m pytest -q → 371 passed in 11.00s
  • Manual / code inspection:
    • app/repositories/postgres.py::_row_to_portfolio() 는 symbol=row[4] 를 Portfolio.symbol 로 매핑한다.
    • app/repositories/postgres.py::get() / create_portfolio() / update_portfolio() / soft_delete_portfolio() / list_portfolios() 는 _SELECT_PORTFOLIO 기반 join 결과를 재사용한다.
    • 따라서 GET 뿐 아니라 POST/PATCH/DELETE 응답도 linked asset row의 assets.symbol 을 유지한다.
  • Logs:
    • pytest 출력은 implementation.md Execution Log 8~9 항목 참조.

Architecture Impact Sync

  • Profile이 batch-lite 이고 planning.md 의 Design Gate는 no 다.
  • 실제 구현은 schema/API/test/docs contract augmentation에 한정되며 신규 쿼리 전략/모듈/마이그레이션이 없다.
  • app/repositories/postgres.py 의 기존 join shape를 재사용했으므로 no architecture impact confirmed.
  • Baseline docs sync는 REQ-032 범위에 맞춰 docs/interface-contract.md 갱신으로 충족했다. docs/project-structure.md / docs/runtime-flows.md / docs/data-model.md 는 이번 변경으로 추가 수정이 필요한 구조 변화가 없다.

Result

  • Ready for Release: true
  • Blocking Issues:
    • 없음 (본 batch 범위 내 AC evidence 확보)
  • Follow-up:
    • BAT-009 verification의 REQ-025/026/027/028 response-contract evidence는 BAT-014 기준으로 superseded 처리.
    • BAT-010 verification의 REQ-032 interface-contract evidence는 BAT-014 기준으로 superseded 처리.
    • 기존 422 deprecation warning(HTTP_422_UNPROCESSABLE_ENTITY) 정리는 별도 housekeeping 성격의 후속 change로 분리 가능.