fix: test_api_key.py の9件が失敗(api-key-login 未実装・sync/upsert 契約不一致) #35

Closed
opened 2026-09-16 12:25:15 +00:00 by joe · 2 comments
Owner

概要

tests/test_api_key.py の 9 件が main で失敗している。本 Issue は回帰テストを緑に戻すための記録。本問題は作業ブランチ fix/hollow-implementations の変更とは無関係(main HEAD 単体でも同様に失敗することを確認済み)。

9 failed, 6282 passed, 1 xpassed

失敗内容

A. /api/auth/api-key-login が存在しない(4件)

  • tests/test_api_key.py:385,396,415,435 が POST /api/auth/api-key-login を呼ぶが 404
  • src/o2/api/ に当該ルートの定義が無い(rg -n 'api-key-login' src/o2 でヒット無し)
  • テストは access_token / token_type == "bearer" / expires_in == 3600 を期待
  • 設計は docs/superpowers/specs/2026-09-16-api-token-design.md に存在(v50 APIトークン機構、パターンA)

B. /api/sync/upsert のレスポンス形状がテストと不一致(5件)

  • 実装 src/o2/api/sync.py:147 の UpsertBatchResponse は customers_inserted / products_inserted を返す
  • テストは customers_created / customers_updated / products_created / journals_created を期待(tests/test_api_key.py:455-573)
  • テストは journals の upsert を期待するが、リクエストスキーマ UpsertBatchRequest(sync.py:141)に journals フィールドが無い
  • h1-c2 連携ドキュメント(AGENTS.md)は /api/sync/upsert を「顧客・商品マスタのバッチ upsert」と記載しており、仕訳(journals)を含むかを契約として明確化する必要がある

影響

  • main のテストが常時レッド(回帰検知が機能しない)
  • h1-c2 側の /api/sync/upsert クライアントと契約が食い違うと連携が破綻する
  • v50 APIトークン機構(POST /api/auth/api-key-login)が未実装のまま issue #28 が進行している

対応方針(要判断)

  1. POST /api/auth/api-key-login を実装する(token を検証し JWT を発行、期限切れ/無効/無効化を 401)
  2. /api/sync/upsert のレスポンスキーをテスト(=h1-c2 側の期待)に合わせる。後方互換のため旧キーを併記するか、破壊的変更として h1-c2 を追随させる
  3. journals を upsert 対象に含めるかを契約として決定し、実装・ドキュメント・テストを一致させる

受け入れ条件

  • tests/test_api_key.py が全て緑
  • /api/sync/upsert の契約(キー・対応エンティティ)がコード・テスト・AGENTS.md で一致している

証拠

  • tests/test_api_key.py:385-574
  • src/o2/api/sync.py:121-247
  • docs/superpowers/specs/2026-09-16-api-token-design.md

関連

  • Issue #28(web dashboard / APIトークン)
  • 監査レポート: docs/AUDIT_HOLLOW_IMPLEMENTATIONS_2026-09-16.md
## 概要 `tests/test_api_key.py` の 9 件が main で失敗している。本 Issue は回帰テストを緑に戻すための記録。**本問題は作業ブランチ `fix/hollow-implementations` の変更とは無関係**(main HEAD 単体でも同様に失敗することを確認済み)。 ``` 9 failed, 6282 passed, 1 xpassed ``` ## 失敗内容 ### A. `/api/auth/api-key-login` が存在しない(4件) - `tests/test_api_key.py:385,396,415,435` が `POST /api/auth/api-key-login` を呼ぶが 404 - `src/o2/api/` に当該ルートの定義が無い(`rg -n 'api-key-login' src/o2` でヒット無し) - テストは `access_token` / `token_type == "bearer"` / `expires_in == 3600` を期待 - 設計は `docs/superpowers/specs/2026-09-16-api-token-design.md` に存在(v50 APIトークン機構、パターンA) ### B. `/api/sync/upsert` のレスポンス形状がテストと不一致(5件) - 実装 `src/o2/api/sync.py:147` の `UpsertBatchResponse` は `customers_inserted` / `products_inserted` を返す - テストは `customers_created` / `customers_updated` / `products_created` / `journals_created` を期待(`tests/test_api_key.py:455-573`) - テストは **journals の upsert** を期待するが、リクエストスキーマ `UpsertBatchRequest`(`sync.py:141`)に `journals` フィールドが無い - h1-c2 連携ドキュメント(AGENTS.md)は `/api/sync/upsert` を「顧客・商品マスタのバッチ upsert」と記載しており、仕訳(journals)を含むかを契約として明確化する必要がある ## 影響 - main のテストが常時レッド(回帰検知が機能しない) - h1-c2 側の `/api/sync/upsert` クライアントと契約が食い違うと連携が破綻する - v50 APIトークン機構(`POST /api/auth/api-key-login`)が未実装のまま issue #28 が進行している ## 対応方針(要判断) 1. `POST /api/auth/api-key-login` を実装する(token を検証し JWT を発行、期限切れ/無効/無効化を 401) 2. `/api/sync/upsert` のレスポンスキーをテスト(=h1-c2 側の期待)に合わせる。後方互換のため旧キーを併記するか、破壊的変更として h1-c2 を追随させる 3. `journals` を upsert 対象に含めるかを契約として決定し、実装・ドキュメント・テストを一致させる ## 受け入れ条件 - [ ] `tests/test_api_key.py` が全て緑 - [ ] `/api/sync/upsert` の契約(キー・対応エンティティ)がコード・テスト・AGENTS.md で一致している ## 証拠 - `tests/test_api_key.py:385-574` - `src/o2/api/sync.py:121-247` - `docs/superpowers/specs/2026-09-16-api-token-design.md` ## 関連 - Issue #28(web dashboard / APIトークン) - 監査レポート: `docs/AUDIT_HOLLOW_IMPLEMENTATIONS_2026-09-16.md`
Author
Owner

再現確認済み(既存不具合)

main HEAD(本ブランチの変更なし)でも同様に失敗することを確認しました。本 Issue の内容は正確です。

# main HEAD 単体(本作業の変更を stash して実行)
9 failed, 13 passed
FAILED tests/test_api_key.py::TestApiKeyLoginAPI::test_login_success ... (4件: /api/auth/api-key-login が 404)
FAILED tests/test_api_key.py::TestUpsertAPI::test_upsert_customers_create ... (5件: sync/upsert のレスポンスキー不一致)
  • rg -n 'api-key-login' src/o2 → ヒット無し(エンドポイント未実装を確認)
  • 実装 src/o2/api/sync.py は customers_inserted を返すが、テストは customers_created / journals_created を期待
  • UpsertBatchRequest に journals フィールドが無い

本ブランチのフルテストでも同じ 9 件のみが失敗しています(6282 passed, 1 xpassed, 9 failed)。対応は別途必要です。

## 再現確認済み(既存不具合) main HEAD(本ブランチの変更なし)でも同様に失敗することを確認しました。本 Issue の内容は正確です。 ``` # main HEAD 単体(本作業の変更を stash して実行) 9 failed, 13 passed FAILED tests/test_api_key.py::TestApiKeyLoginAPI::test_login_success ... (4件: /api/auth/api-key-login が 404) FAILED tests/test_api_key.py::TestUpsertAPI::test_upsert_customers_create ... (5件: sync/upsert のレスポンスキー不一致) ``` - `rg -n 'api-key-login' src/o2` → ヒット無し(エンドポイント未実装を確認) - 実装 `src/o2/api/sync.py` は `customers_inserted` を返すが、テストは `customers_created` / `journals_created` を期待 - `UpsertBatchRequest` に `journals` フィールドが無い 本ブランチのフルテストでも同じ 9 件のみが失敗しています(`6282 passed, 1 xpassed, 9 failed`)。対応は別途必要です。
Author
Owner

対応完了

tests/test_api_key.py の 9 件の失敗を解消し、コアスイートを全緑(6293 passed / 0 failed) に戻しました。

対応内容

A. POST /api/auth/api-key-login の実装

  • トークンを検証し、無効 / 期限切れ / 無効化済みは 401
  • {access_token, token_type:"bearer", expires_in:3600} を返却
  • APIキー権限(READ/READ_WRITE/ADMIN)を実効ロールへ写像し、DBユーザーのロールを超えない範囲で強制(effective_role_for_api_key)
  • purpose="api_key" + api_key_id を JWT に付与。_get_current_user は毎リクエストでキーの有効性と所有者一致を再検証(失効が即時反映)
  • purpose が api_key 以外(mfa 等)は従来どおり拒否。通常トークンの経路は挙動不変

B. /api/sync/upsert の契約統一

  • 同一パスに重複していた2つのハンドラを一本化(先発 upsert_batch を削除。後発 sync_upsert が正典)
  • customers_created 系(新契約)と customers_inserted 系(h1-c2 の Dart クライアントが読む互換別名)を同一レスポンスで両方返却
  • journals upsert を有効化。科目未解決時は空・不均衡な仕訳を作らず type="journal_line" を返す
  • 実行時に ImportError になる from o2.api import get_tenant_id(存在しない)を撤去し resolve_write_tenant に修正
  • ProductUpsertItem.tax_category の不正な既定値 "TAXABLE_10" を修正(ValueError の原因)

変更ファイル

  • src/o2/services/jwt_service.py / api_key_service.py / permission_checker.py
  • src/o2/api/auth.py / schemas.py / sync.py
  • tests/test_api_key.py(権限強制・MFA拒否テストを追加。既存アサーションは不変)

テスト結果

tests/test_api_key.py tests/test_sync_upsert_api.py tests/test_sync_api.py tests/test_sync_service.py → 45 passed
auth/permission 系 7 スイート → 75 passed
フルコアスイート → 6293 passed, 1 xpassed, 0 failed
実験スイート → 860 passed, 1 xpassed
テナント分離 → 68 passed

成果物

  • コミット: 529cc89
  • main マージ: 82cf7a2(push 済)
  • 関連: 5e3431b まで含め origin/main に反映

次のステップ / 残課題

  • /api/dashboard/api-keys の管理API(一覧/生成/更新/削除/toggle/reissue/QR)は未実装 → #36 で起票
  • h1-c2 が読む customers_inserted は互換別名として維持。将来的に customers_created への統一を検討(h1-c2 側の追随が必要)
## 対応完了 `tests/test_api_key.py` の 9 件の失敗を解消し、**コアスイートを全緑(6293 passed / 0 failed)** に戻しました。 ### 対応内容 #### A. `POST /api/auth/api-key-login` の実装 - [x] トークンを検証し、無効 / 期限切れ / 無効化済みは **401** - [x] `{access_token, token_type:"bearer", expires_in:3600}` を返却 - [x] APIキー権限(READ/READ_WRITE/ADMIN)を実効ロールへ写像し、**DBユーザーのロールを超えない**範囲で強制(`effective_role_for_api_key`) - [x] `purpose="api_key"` + `api_key_id` を JWT に付与。`_get_current_user` は毎リクエストでキーの有効性と所有者一致を再検証(失効が即時反映) - [x] `purpose` が `api_key` 以外(`mfa` 等)は従来どおり拒否。通常トークンの経路は挙動不変 #### B. `/api/sync/upsert` の契約統一 - [x] 同一パスに**重複していた2つのハンドラ**を一本化(先発 `upsert_batch` を削除。後発 `sync_upsert` が正典) - [x] `customers_created` 系(新契約)と `customers_inserted` 系(h1-c2 の Dart クライアントが読む互換別名)を**同一レスポンスで両方返却** - [x] `journals` upsert を有効化。科目未解決時は空・不均衡な仕訳を作らず `type="journal_line"` を返す - [x] 実行時に ImportError になる `from o2.api import get_tenant_id`(存在しない)を撤去し `resolve_write_tenant` に修正 - [x] `ProductUpsertItem.tax_category` の不正な既定値 `"TAXABLE_10"` を修正(ValueError の原因) ### 変更ファイル - `src/o2/services/jwt_service.py` / `api_key_service.py` / `permission_checker.py` - `src/o2/api/auth.py` / `schemas.py` / `sync.py` - `tests/test_api_key.py`(権限強制・MFA拒否テストを追加。既存アサーションは不変) ### テスト結果 ``` tests/test_api_key.py tests/test_sync_upsert_api.py tests/test_sync_api.py tests/test_sync_service.py → 45 passed auth/permission 系 7 スイート → 75 passed フルコアスイート → 6293 passed, 1 xpassed, 0 failed 実験スイート → 860 passed, 1 xpassed テナント分離 → 68 passed ``` ### 成果物 - コミット: `529cc89` - main マージ: `82cf7a2`(push 済) - 関連: `5e3431b` まで含め origin/main に反映 ### 次のステップ / 残課題 - `/api/dashboard/api-keys` の管理API(一覧/生成/更新/削除/toggle/reissue/QR)は未実装 → **#36** で起票 - h1-c2 が読む `customers_inserted` は互換別名として維持。将来的に `customers_created` への統一を検討(h1-c2 側の追随が必要)
joe closed this issue 2026-09-16 13:33:13 +00:00
Sign in to join this conversation.
No labels
h1-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
joe/o2#35
No description provided.