|
2 | 2 | * A stateful GithubClient derived from the e2e mock's handlers: requests route through the |
3 | 3 | * pipeline's endpoint matching onto the same handlers over a seeded MockState, so a unit proof |
4 | 4 | * runs against the mock's own transformers, not a second hand-written inverse. Two views: one |
5 | | - * section's fragment (a request outside it is refused), or the whole merged registry. |
| 5 | + * section's fragment (a request outside it is refused), or the whole merged registry. GraphQL |
| 6 | + * dispatches by operation name onto the mock's GraphQL handlers the same way. |
6 | 7 | */ |
7 | 8 |
|
8 | 9 | import type { ApiError, GithubClient } from "../../src/github/api.js"; |
9 | 10 | import type { SectionKey } from "../../src/schema.js"; |
10 | 11 | import type { SectionMeta } from "../../src/sections/contract/module.js"; |
11 | | -import { matchEndpoint } from "../e2e/mock/dispatch.js"; |
12 | | -import { HANDLERS } from "../e2e/mock/handlers.js"; |
| 12 | +import { allGraphqlOps } from "../../src/sections/registry.js"; |
| 13 | +import { graphqlOpForBody, matchEndpoint } from "../e2e/mock/dispatch.js"; |
| 14 | +import { GRAPHQL_HANDLERS, HANDLERS } from "../e2e/mock/handlers.js"; |
13 | 15 | import { buildStateForSlug, type LiveState, type MockState } from "../e2e/mock/state.js"; |
14 | | -import type { Handler } from "../e2e/mock/support.js"; |
| 16 | +import type { Handler, Json } from "../e2e/mock/support.js"; |
15 | 17 | import { REPO } from "./section-run.js"; |
16 | 18 |
|
17 | 19 | export interface FragmentFake extends GithubClient { |
18 | 20 | readonly state: MockState; |
19 | | - /** Every non-GET request that reached a handler, as "METHOD /path". */ |
| 21 | + /** Every write that reached a handler: "METHOD /path", or "GRAPHQL <opName>" for a mutation. */ |
20 | 22 | readonly writes: string[]; |
21 | 23 | } |
22 | 24 |
|
@@ -85,8 +87,34 @@ function handlerFake( |
85 | 87 | } |
86 | 88 | return { data: response.body }; |
87 | 89 | }, |
88 | | - async tryGraphql() { |
89 | | - throw new Error(`${only ?? "the registry fake"} issues no GraphQL`); |
| 90 | + async tryGraphql(op, variables) { |
| 91 | + const dispatched = graphqlOpForBody({ operationName: op.name }, allGraphqlOps()); |
| 92 | + const handler = dispatched === null ? undefined : GRAPHQL_HANDLERS[dispatched.key]; |
| 93 | + if ( |
| 94 | + dispatched === null || |
| 95 | + (only !== null && dispatched.op.section !== only) || |
| 96 | + handler === undefined |
| 97 | + ) { |
| 98 | + return { error: { status: 404, message: `unexpected GRAPHQL ${op.name}`, body: "" } }; |
| 99 | + } |
| 100 | + if (op.kind === "write") { |
| 101 | + writes.push(`GRAPHQL ${op.name}`); |
| 102 | + } |
| 103 | + const result = handler({ state, op: dispatched.op, variables: variables as Json }); |
| 104 | + if (result.errors !== undefined) { |
| 105 | + // The client's status fold (apiErrorFromGraphqlErrors): FORBIDDEN 403, NOT_FOUND 404, else 422. |
| 106 | + const types = result.errors.map((entry) => entry.type); |
| 107 | + const status = types.includes("FORBIDDEN") ? 403 : types.includes("NOT_FOUND") ? 404 : 422; |
| 108 | + return { |
| 109 | + error: { |
| 110 | + status, |
| 111 | + message: result.errors.map((entry) => entry.message).join("; "), |
| 112 | + body: JSON.stringify(result.errors), |
| 113 | + graphqlTypes: [...types].sort(), |
| 114 | + }, |
| 115 | + }; |
| 116 | + } |
| 117 | + return { data: result.data }; |
90 | 118 | }, |
91 | 119 | }; |
92 | 120 | } |
0 commit comments