コンテンツにスキップ

AI Design Import — テストケース

このページは apps/design-import/ のテスト計画です。io-definition.md のすべての行 — フィールド、処理ステップ、失敗カテゴリ — は、少なくとも 1 つのテストケースと対応している必要があります。契約の条項に対応するテストがない場合、まずそれを修正してください。

規約: テスト ID は DI-<層>-<NN> 形式。層は U(unit、I/O なし)、C(component、実 S3 / Mongo testcontainer、PydanticAI + OpenAI + Webhook はモック)、E(end-to-end、実クラウド)。各ケースは Given / When / Then を明示。「Then」ブロックがアサーション契約 — そこに書かれていないことは 検証しない(呼び出し元も依存してはならない)。


テスト層とツーリング

これらのケースは 手動 / ローカル テスト計画として設計されています。CI には組み込まれておらず、自動ゲートで強制もされません — 変更が入る際にワーカーが I/O 契約どおりに動くかをエンジニアが確認するためのものです。マージ前にローカルで実行するか、リリースチェックリストの一部として活用してください。

層 スコープ ツーリング
Unit (U) 純粋関数: SQS body パース、body アンラップ、S3 URL パース、画像フォーマット検出、ドキュメントビルダ、Webhook ペイロードビルダ pytest、ネットワークなし。PydanticAI / OpenAI / httpx は SDK 境界でフェイク
Component (C) process_record の end-to-end。実 localstack S3 + testcontainer Mongo(DocumentDB API 互換イメージ)+ モック PydanticAI agent + モック openai.embeddings.create + モック Webhook httpx client pytest、testcontainers、localstack、respx/unittest.mock
End-to-end (E) 実 dev AWS — 実 SQS、S3、DocumentDB、OpenAI dev キー、実 Webhook エンドポイント pytest の @e2e マーカー、スモークのみ

共有フィクスチャ

フィクスチャ 提供内容 使用層
sqs_record(**overrides) 上書き可能な、正しく整形された SQS レコード dict U, C
s3_bucket 360×707 PNG と Figma JSON サンプルを事前投入した localstack バケット C
mock_vision_agent(visual_features=..., semantic_words=[...]) agent.run() から固定の DesignDescription を返す U, C
mock_openai_embed(vector=[0.1]*512) visual、semantic、structural 入力に対して決定的な 512-float ベクトルを返す U, C
mock_webhook(http_status=200) 外向き Webhook ペイロードをキャプチャし、指定したステータスを返す C

sqs_record(...) がペイロード形状の唯一の真実の源です — 個別の dict を手書きするのではなく、必ずここから合成してください。


カバレッジマトリクス

横軸: io-definition.md の契約条項。縦軸: 結果。各セルは 1 つ以上のテスト ID を指します。

ハッピーパス 失敗パス 冪等性
ペイロードスキーマ DI-U-01 DI-U-02 —
Body アンラップ DI-U-03 DI-U-04 —
画像フォーマット検出 DI-U-05 — —
S3 画像 GET DI-C-01 DI-C-02, DI-C-03 —
Vision エージェント DI-C-01 DI-U-06, DI-C-04 —
Embedding(バッチ) DI-C-01 DI-U-07, DI-C-05 —
ドキュメントビルダ DI-U-08 — —
DocumentDB upsert DI-C-01 DI-C-06 DI-C-07
成功 Webhook DI-C-01 DI-C-08 DI-C-09
失敗 Webhook — DI-C-02, DI-C-03 —
失敗時の SQS への再 raise — DI-C-02, DI-C-03 —

Unit (U) ケース

ネットワークなし。純粋な Python。

DI-U-01 — 有効な SQS body がパースされる

  • Given 必須フィールド(organization_id, project_id, img_url, node_id, file_id, design_id, design_name, json_schema_url)を含む正しい body。
  • When パーサが実行される。
  • Then モデルに organization_id, project_id, img_url, node_id, file_id, design_id, design_name, json_schema_url が揃っている。

DI-U-02 — 必須フィールド欠落で拒否

  • 各必須フィールドをパラメトライズする。
  • Given 該当フィールドを 1 つ削除した body。
  • Then ValidationError。フィールド名がエラーに現れる。

DI-U-03 — Body アンラップ: SQS-in-SQS

  • Given body = json.dumps({"Records": [{"body": json.dumps(payload)}]})。
  • Then パーサは 1 段アンラップし、内側のペイロードモデルを返す。

DI-U-04 — Body アンラップ: API Gateway + base64

  • Given body = {"body": base64(json.dumps(payload)), "isBase64Encoded": true}。
  • Then デコード、パース、モデル返却。

DI-U-05 — 画像フォーマット検出(magic bytes)

  • 実装に magic-byte フォーマット検出器が含まれている場合のみ適用。契約は検出器を必須としない。
  • パラメトライズ: PNG、JPEG、GIF、WEBP サンプル、未知のバイト列 1 つ。
  • Then 検出器がそれぞれ "png", "jpeg", "gif", "webp", None を返す。
  • And 実装が検出結果の MIME をデータ URL に入れているならそれをアサート。固定 MIME ならそれをアサート。実装が選択した方をピン留めする。

DI-U-06 — Vision エージェントが空の semantic_words を返す

  • Given モック Vision エージェントが DesignDescription(visual_features="x", semantic_words=[]) を返す。
  • Then 下流の embedding が実行できない(入力が空)。ワーカーが raise し、ハンドラが再 raise の前に失敗 Webhook を送出する。

DI-U-07 — Embedding レスポンスの次元数が不正

  • Given モック embeddings client が 512 ではなく 1536 個の float ベクトルを返す。
  • Then (ドキュメントビルダに長さチェックがある場合)ワーカーが拒否し、ハンドラが失敗 Webhook を送出して再 raise する。
  • 注: このアサーションは実装が長さチェックを含む場合にのみ適用される。契約は必須としていない。実装にない場合は、合格を偽装するよりこのケースを削除すること。

DI-U-08 — ドキュメントビルダのフィールドコピー

  • Given パース済み SQS body と DesignDescription。
  • When ドキュメントビルダが実行される。
  • Then 結果の dict に以下が含まれる:
  • _id == body.design_id
  • organization_id == body.organization_id
  • project_id, name == body.design_name
  • visual.metadata.name == body.design_name
  • visual.metadata.image_url == body.img_url
  • visual.vector_embedding の長さは 512
  • semantics.metadata.words == agent.semantic_words
  • semantics.vector_embedding の長さは 512
  • structural.vector_embedding の長さは 512
  • query_terms が入っている
  • figma_node.file_id == body.file_id
  • figma_node.node_id == body.node_id
  • json_schema キーは 存在しない
  • components キーは 存在しない
  • トップレベルの id または design_id キーは 存在しない
  • トップレベル img_url — 実装が overview.ja.md の DocumentDB ドキュメント構造に従う場合は不在をアサートする。実装で 1 つの方針を決め、テストをピン留めする。
  • type と based_on は I/O contract の default に従う。

ドキュメントのトップレベルに img_url を置くかどうかは実装で決定し、このテストをその方針に合わせて更新してください。overview.ja.md の DocumentDB ドキュメント構造を正とします。


Component (C) ケース

実 localstack S3 + testcontainer Mongo。PydanticAI agent、OpenAI embeddings、Webhook httpx client は SDK 境界でモックする。

DI-C-01 — フルハッピーパス

  • Given:
  • S3 の img_url に画像がある。
  • json_schema_url が S3 上の有効な Figma node JSON object を指す。
  • mock_vision_agent が DesignDescription(visual_features="blue rounded card", semantic_words=["login","auth","form"]) を返す。
  • mock_openai_embed が 3 つの入力すべてに [0.1] * 512 を返す。
  • mock_webhook(http_status=200)。
  • When ハンドラがメッセージを処理する。
  • Then:
  • Mongo design コレクションに _id == design_id、organization_id == body.organization_id、visual/semantic/structural vector 長さ 512、semantics.metadata.words == ["login","auth","form"]、SQS の img_url / json_schema_url と一致、figma_node.node_id == node_id、query_terms が入っている、name == design_name のドキュメントがある。
  • Webhook が {"type": "design-import", "recordId": design_id, "projectId": <pid>, "organizationId": <org_id>, "status": "success", "result": {"keywords": ["login","auth","form"], "embeddingDims": 512, "vectorsProduced": ["visual","semantic","structural"]}} で ちょうど 1 回 呼ばれた。
  • 例外は再 raise されない。
  • worker は json_schema_url を GET し、Figma node JSON をパースできない場合は record を失敗させる。

DI-C-02 — S3 画像が 404 (NoSuchKey)

  • Given img_url が存在しないキーを指す。
  • Then:
  • 失敗 Webhook が {"type":"design-import","recordId":...,"projectId":...,"organizationId":...,"status":"failed"} で呼ばれる(result なし)。
  • ハンドラが再 raise し、SQS が失敗を見る。
  • Mongo 書き込みなし。
  • 成功 Webhook なし。

DI-C-03 — S3 画像が 500

  • Given localstack chaos が画像キーで 500 を返す。
  • Then:
  • 失敗 Webhook 送出。
  • 例外再 raise(SQS リトライ)。
  • Mongo 書き込みなし。

DI-C-04 — Vision エージェントが raise

  • Given mock_vision_agent が PydanticAI 例外(プロバイダ 5xx など)を raise する。
  • Then:
  • 失敗 Webhook 送出。
  • 例外再 raise。
  • Embeddings 呼び出しなし。Mongo 書き込みなし。

DI-C-05 — Embeddings API が raise

  • Given Vision エージェントは成功、mock_openai_embed が一時的な APIError を raise する。
  • Then:
  • 失敗 Webhook 送出。
  • 例外再 raise。
  • 部分的な Mongo ドキュメントは存在しない — db.design.find_one({"_id": design_id}) is None をアサート。

DI-C-06 — Mongo 書き込みが raise(一時的)

  • Given Vision + embeddings が成功。Mongo testcontainer を一時停止する。
  • Then:
  • 失敗 Webhook 送出。
  • 例外再 raise。

DI-C-07 — 再配送がドキュメントをアトミックに上書きする

  • Given DI-C-01 が 1 度実行され、ベクトル vA のドキュメントが残っている。
  • And 同じ design_id で同じメッセージが再配送されるが、今度はモックがベクトル vB を返す。
  • Then:
  • Mongo design コレクションには _id == design_id のドキュメントがちょうど 1 つ。
  • ドキュメントの visual.vector_embedding が vB を反映している(最後の書き込みが勝つ)。
  • 2 回の実行を通じて success Webhook が 2 回観測される(at-least-once)。

DI-C-08 — 成功パスで Webhook POST が失敗

  • Given Mongo 書き込み成功。mock_webhook(http_status=500)。
  • Then:
  • WARN ログライン(design_import.webhook.failed または同等)。
  • ハンドラは再 raise する、または record を batchItemFailures に追加する。backend が authoritative status notification を受け取っていないため、SQS が retry する。

DI-C-09 — Webhook は試行を跨いだ at-least-once

  • Given 試行 1: Vision エージェントが raise(DI-C-04)。
  • And 試行 2: すべて成功(DI-C-01)。
  • Then:
  • 2 回の Webhook が観測される: failed 1 件、success 1 件。
  • Mongo には試行 2 のドキュメントのみ。

DI-C-10 — 永続化されたドキュメントに json_schema フィールドがない

  • Given DI-C-01 のハッピーパス。
  • Then db.design.find_one({"_id": design_id}) に json_schema キーが 存在しない。DocumentDB ドキュメントは embedding とメタデータのみ。Figma JSON は S3 にあり、SQS body の json_schema_url から参照される。

DI-C-11 — 永続化されたドキュメントに components フィールドがない

  • Given DI-C-01 のハッピーパス。
  • Then ドキュメントに components キーが 存在しない。コンポーネントマッチングは下流の des2code で計算されるため、design ドキュメントはそれを保持しない。

End-to-end (E) ケース

スモークセットのみ。実 dev 環境、実サービス、実 OpenAI dev キーで手動実行。コストはゼロではないため、スイートは小さく保つこと。

DI-E-01 — 実 dev SQS の往復

  • Given guinness-backend dev が実際の design-create フロー経由でメッセージを enqueue する。
  • Then 90 秒以内に:
  • DocumentDB の design コレクションに、512-float のベクトル 2 つを持つドキュメントが存在する。
  • バックエンドが success Webhook を受信した(バックエンドのログまたは design 行状態で確認)。
  • CloudWatch にワーカーのライフサイクルログラインが現れる。
  • (バックエンド status = 1 は record_id の解決 次第。解決するまで informational として扱う。)

DI-E-02 — 実 OpenAI 429 でのリトライ

  • (手動のみ。スロットリングが起きている時間帯に実行。)
  • Given dev OpenAI キーがタイトなレートリミット下にある。
  • Then メッセージは SQS が再配送し、最終的に成功する。最後の Webhook は success。

横断的チェック

該当する全ケースで成り立つべき条件。単独のテスト行ではない — 上記のテストをレビュー / 実行する際に確認する。

  • 部分的な DocumentDB ドキュメントは存在しない: Vision と Mongo 書き込みの間で失敗するテストはすべて db.design.find_one({"_id": design_id}) is None をアサート。
  • Embedding 長は常に 512: 永続化を行うテストはすべて両方のベクトル長をアサート。
  • process_record 1 回の呼び出しにつき Webhook はちょうど 1 回: success または failed のいずれかで、1 回の呼び出し内で両方は送らない。
  • 失敗 Webhook は再 raise の 前 に送る: Webhook モックと raise 地点の両方をインターセプトし、順序をアサートする。
  • スタックトレース文字列が Webhook body に漏れない: Webhook ペイロードのアサーションは完全一致 — 余計なフィールドなし。
  • PostgreSQL / MySQL 接続は決して開かれない: localhost で DB ドライバが到達できない状態でテストを実行する(またはインポート時に大きな声で失敗するガード環境変数を設定する)。誤って DB ドライバを使う経路が即座に表面化する。
  • ドキュメントの識別子は 1 つだけ: 永続化されるドキュメントは _id のみを保持する。トップレベルの id または design_id キーがあれば失敗させる — design id は _id として流れ、他の場所には現れない。

意図的にスコープ外

将来のテストが正しいバケットに着地するよう、明示的に列挙する。

  • バックエンドの design.status ステートマシン: guinness-backend のテストが所有。ワーカーは Webhook を送出するだけ。
  • ベクトル検索 / Des2Code のフュージョン: apps/des2code/ のテストスイートが所有。
  • 認証: ワーカーには HTTP 入口がなく、認証は上流。
  • DLQ 運用 / SQS インフラ: インフラテストが所有。ワーカーは再 raise の挙動のみ検証。
  • Code preview rendering: code import/rendering pipeline が所有。このワーカーは code entry をレンダリングしない。
  • テナント間分離: 純粋に RBAC。バックエンドで起こる。ワーカーのテストはメッセージが既に認可済みであることを前提とする。
  • record_id 解決戦略: 現在 未解決の契約事項。テストは 現状 の挙動をピン留めし、将来の修正がテスト変更として可視化されるようにする。

関連リンク

  • I/O 定義 — これらのテストが守る契約。
  • 概要 — 処理フロー図とコンポーネント分解、エージェント定義、DocumentDB ドキュメント構造。