AI Read API (mcp-api) โ Test Cases
This page is the test plan for apps/mcp-api/ in guinness-ai-v2. Every row in io-definition.md โ endpoints, query parameters, DocumentDB operations, and error classes โ must be traceable to at least one test case here. If a contract clause has no test, that is the highest-priority bug to fix.
Convention. Test IDs follow
AM-<layer>-<NN>. Layers:U(unit, no I/O),C(integration, real DocumentDB testcontainer, mocked auth),E(E2E, real cloud). Each case states Given / When / Then explicitly.
Test Layers and Tools
| Layer | Scope | Tools |
|---|---|---|
| Unit (U) | Pure functions: auth validation, query parameter parsing, DocumentDB filter builders, response builders | pytest, no network. DocumentDB / httpx are mocked at the boundary |
| Integration (C) | Real testcontainer Mongo (DocumentDB API-compatible mongo:7 image) + full request lifecycle using a token that is valid by default |
pytest, testcontainers[mongodb], httpx TestClient. Requires Docker |
| End-to-end (E) | Real MCP Server โ AI Read API โ DocumentDB round-trip (dev environment) | Manual / smoke only |
Shared Fixtures
| Fixture | Provides | Used by |
|---|---|---|
valid_token |
The AI_SERVICE_TOKEN value for tests |
U, C |
design_doc(**overrides) |
A well-formed DocumentDB design document |
U, C |
code_doc(**overrides) |
A well-formed DocumentDB code document with _id, name, source_code, and visual.metadata.image_url |
U, C |
seed_design(collection, doc) |
Inserts a design document into the testcontainer Mongo | C |
seed_design_batch(collection, count, project_id) |
Inserts N design documents with the specified project_id |
C |
seed_code_batch(collection, count, project_id) |
Inserts N code documents with the specified project_id |
C |
Coverage Matrix
| Happy path | Failure path | Idempotence | |
|---|---|---|---|
| Auth (token) | AM-U-01 | AM-U-02, AM-U-03 | โ |
| Parameter validation | AM-U-04 | AM-U-05 | โ |
GET /internal/designs/{design_id} |
AM-U-06, AM-C-01 | AM-C-02 | AM-C-05 |
GET /internal/designs |
AM-U-07, AM-C-03 | โ | AM-C-05 |
GET /internal/code |
AM-U-08, AM-C-04 | โ | AM-C-05 |
GET /internal/code/{code_id} |
AM-U-09, AM-C-06 | AM-C-07 | AM-C-05 |
| Pagination (limit/offset) | AM-U-10 | โ | โ |
| Invalid limit (negative / non-int) | AM-U-11 | โ | โ |
| Empty list result | AM-C-08 | โ | โ |
| DocumentDB error | โ | AM-C-09 | โ |
Unit (U) Tests
No network. Pure Python.
AM-U-01 โ Valid X-AI-Service-Token is accepted
- Given A request with an
X-AI-Service-Tokenthat matches the configured environment variable value. - When The auth middleware validates the token.
- Then Validation passes (no 401 is thrown).
AM-U-02 โ Missing X-AI-Service-Token โ 401
- Given A request with no
X-AI-Service-Tokenheader. - When The auth middleware validates.
- Then HTTP 401,
{ ok: false, error: { code: "UNAUTHORIZED" } }.
AM-U-03 โ Wrong token โ 401
- Given A request with an
X-AI-Service-Tokenthat does not match the environment variable value. - When The auth middleware validates.
- Then HTTP 401,
{ ok: false, error: { code: "UNAUTHORIZED" } }. - And The comparison is timing-safe (verify that the implementation uses
hmac.compare_digestor equivalent โ no short-circuiting).
AM-U-04 โ Route is selected by path
- Given Request paths
/internal/designs,/internal/designs/{design_id},/internal/code, and/internal/code/{code_id}. - When The router determines which handler to execute.
- Then It selects list-designs, get-design-detail, list-code, and get-code-detail respectively.
AM-U-05 โ Missing project_id on list endpoints โ 400
- Given
GET /internal/designsorGET /internal/codewithoutproject_id. - When The router validates parameters.
- Then HTTP 400,
{ ok: false, error: { code: "BAD_REQUEST" } }.
AM-U-06 โ design_id query builds the correct DocumentDB filter
- Given
design_id = "1_abc_def". - When The repository builds the query.
- Then The filter is
{"_id": "1_abc_def"}. - And The operation is
find_one(notfind).
AM-U-07 โ list-designs builds the correct filter with projection
- Given
project_id = 42,organization_id = 1,limit = 10,offset = 5. - When The repository builds the design list query.
- Then The filter is
{"project_id": 42, "organization_id": 1}. - And The projection excludes every
vector_embeddingfield. - And The cursor has
.skip(5).limit(10).
AM-U-08 โ list-code builds the correct filter with projection
- Given
project_id = 42,organization_id = 1,limit = 10,offset = 5. - When The repository builds the code list query.
- Then The filter is
{"project_id": 42}. - And The projection includes
_id,organization_id,project_id,name,type,based_on, andvisual.metadata.image_url. - And The projection excludes
source_code,css_code, and everyvector_embeddingfield. - And The cursor has
.skip(5).limit(10).
AM-U-09 โ code detail builds the correct DocumentDB filter
- Given
code_id = "660e8400-e29b-41d4-a716-446655440001". - When The repository builds the query.
- Then The filter is
{"_id": "660e8400-e29b-41d4-a716-446655440001"}. - And The operation is
find_one. - And The projection excludes every
vector_embeddingfield.
AM-U-10 โ limit/offset are applied correctly
- Parameterized:
- No
limitโ defaults to 20. limit = 200โ capped to 100.- No
offsetโ defaults to 0. offset = 50โ.skip(50).- Then The correct values are passed to the DocumentDB cursor.
AM-U-11 โ Invalid limit falls back to default
- Parameterized:
limit = -1โ falls back to default 20.limit = 0โ falls back to default 20.limit = "abc"(non-integer string) โ falls back to default 20.- Then
.limit(20)is passed to the DocumentDB cursor. - And No error is returned โ invalid values are silently corrected per the I/O contract.
AM-U-12 โ offset exceeds total count โ empty page
- Given
project_id = 42with 5 documents in thecodecollection. - When
GET /internal/code?project_id=42&offset=100. - Then HTTP 200.
{ ok: true, data: { project_id: 42, total: 5, items: [] } }.- And
totalreflects the actual count (5), not 0.
Integration (C) Tests
Real testcontainer Mongo (DocumentDB API-compatible). Auth is mocked (token valid by default).
AM-C-01 โ GET by design_id โ document found
- Given:
- A document in the
designcollection with_id = "1_abc_def",project_id = 42,name = "Login Screen",visual.metadata.image_url = "s3://". - A valid auth token.
- When
GET /internal/designs/1_abc_def. - Then:
- HTTP 200.
{ ok: true, data: { id: "1_abc_def", project_id: 42, name: "Login Screen", image_url: "s3://..." } }.datadoes not containvector_embeddingor any other internal fields.
AM-C-02 โ GET by design_id โ document not found
- Given:
- No document with
_id = "nonexistent"in thedesigncollection. - A valid auth token.
- When
GET /internal/designs/nonexistent. - Then:
- HTTP 404.
{ ok: false, data: null, error: { code: "NOT_FOUND", message: "Design not found" } }.
AM-C-03 โ list-designs by project_id with pagination
- Given:
- 25 documents with
project_id = 42in thedesigncollection. - A valid auth token.
- When
GET /internal/designs?project_id=42&limit=10&offset=5. - Then:
- HTTP 200.
{ ok: true, data: { project_id: 42, total: 25, items: [...] } }.- The
itemsarray contains exactly 10 items. - No
vector_embeddingfield is leaked.
AM-C-04 โ list-code by project_id with pagination
- Given:
- 25 documents with
project_id = 42in thecodecollection. - A valid auth token.
- When
GET /internal/code?project_id=42&organization_id=1&limit=10&offset=5. - Then:
- HTTP 200.
{ ok: true, data: { project_id: 42, total: 25, items: [...] } }.- The
itemsarray contains exactly 10 items (items 6โ15 of the collection). - Each code item has
id,project_id,organization_id,name, andimage_url. - Code list items do not contain
source_codeorcss_code. - No
vector_embeddingfield is leaked.
AM-C-05 โ Repeated identical requests return the same data
- Given The setup from AM-C-01 (design found).
- When The same
GET /internal/designs/1_abc_defis called twice. - Then Both responses are identical (stateless, idempotent).
AM-C-06 โ get-code-detail โ document found
- Given:
- A document in the
codecollection with_id = "660e8400-e29b-41d4-a716-446655440001",project_id = 42,organization_id = 1,name = "Button", andsource_code = "...". - A valid auth token.
- When
GET /internal/code/660e8400-e29b-41d4-a716-446655440001. - Then:
- HTTP 200.
{ ok: true, data: { code: { _id: "...", project_id: 42, organization_id: 1, source_code: "..." } } }.data.codedoes not containvector_embedding.
AM-C-07 โ get-code-detail โ document not found
- Given No code document with
_id = "missing"and a valid auth token. - When
GET /internal/code/missing. - Then HTTP 404 with
{ ok: false, data: null, error: { code: "NOT_FOUND" } }.
AM-C-08 โ list endpoint collection is empty
- Given:
- No documents with
project_id = 99in thecodecollection. - A valid auth token.
- When
GET /internal/code?project_id=99&organization_id=1. - Then:
- HTTP 200 (not 404 โ an empty result is not an error).
{ ok: true, data: { project_id: 99, total: 0, items: [] } }.
AM-C-09 โ DocumentDB connection error
- Given:
- The Mongo testcontainer is stopped / paused (simulating a connection failure).
- A valid auth token.
- When
GET /internal/designs/1_abc_def. - Then:
- HTTP 500.
{ ok: false, data: null, error: { code: "INTERNAL_ERROR", message: "..." } }.- The error is logged at ERROR level (
ai-mcp.query.failed).
E2E (E) Tests
Smoke test set. Run manually against real services.
AM-E-01 โ MCP Server โ AI Read API round-trip
- Given MCP Server configured against dev with real
AI_SERVICE_URLandAI_SERVICE_TOKEN. - When An MCP client calls
list-designs,list-code,get-design-detail, andget-code-detailwith known IDs. - Then Within 30 seconds:
- The MCP responses contain design and code data from DocumentDB.
- CloudWatch shows:
ai-mcp.request.receivedโai-mcp.query.completedโai-mcp.response.sent. - No 401 or 500 errors.
Cross-cutting Checks
Conditions that must hold across all applicable tests. Verify these during review or execution of the tests above, not as separate test rows.
- No Postgres connections are opened. Run tests with Postgres unreachable (or set a guard environment variable). Incorrect driver imports surface immediately.
- Responses always follow the
{ ok, data, error }envelope. Assert envelope structure in all tests โ error responses must also conform. - Auth is timing-safe. Verify that the implementation uses
hmac.compare_digestor equivalent โ do not use==for secret string comparison. - No DocumentDB writes in any test. Assert that
collection.count_documents()is unchanged before and after each test. - No
vector_embeddingfield is leaked. In every test that asserts response data, verify that the output does not contain avector_embeddingkey. - Detail routes do not return
totalor anitemsarray. List routes return paginated lists; detail routes return{ design: ... }or{ code: ... }. - Error responses do not include
data. Whenok = false,datamust benull(not omitted, not an empty object).
Intentionally Out of Scope
Listed explicitly so that future tests are placed in the correct bucket.
- MCP Server auth and tool routing. Governed by the
mcp/test case design. This API only validates its ownX-AI-Service-Token. - DocumentDB index management. Governed by
packages/models/. This API assumes indexes exist. - SQS interactions. This API is HTTP only. SQS is not involved.
- CORS / CloudFront configuration. Infrastructure level. This API is internal to the VPC only.
- Rate limiting / throttling. TBD. No tests until specified.
- Generated-code endpoints. Future endpoints. Not in this contract.
- Data migration or seeding. Operational concern. Test fixtures manage their own data.
Related
- I/O Definition โ the contract these tests enforce
- Overview โ request flow, DocumentDB query patterns, and auth details