@crvouga/mockingbird-service-genebygene 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -0
- package/dist/generated/openapi.d.ts +7 -0
- package/dist/generated/openapi.d.ts.map +1 -0
- package/dist/generated/openapi.js +5 -0
- package/dist/generated/openapi.js.map +1 -0
- package/dist/handlers.d.ts +9 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +98 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/state.d.ts +25 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +29 -0
- package/dist/state.js.map +1 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# @crvouga/mockingbird-service-genebygene
|
|
2
|
+
|
|
3
|
+
Stateful, in-process mock of the [GeneByGene Nucleus API](https://api.genebygene.com/swagger/index.html)
|
|
4
|
+
for test suites. It covers the OAuth client-credentials token endpoint, the product catalog, and
|
|
5
|
+
order create/read: all 4 operations of the vendored OpenAPI subset, each verified by live parity
|
|
6
|
+
against GeneByGene staging.
|
|
7
|
+
|
|
8
|
+
Use it when your backend orders kits from GeneByGene and you want tests to run offline, with no
|
|
9
|
+
staging credentials.
|
|
10
|
+
|
|
11
|
+
- Operation coverage: [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/genebygene/SUPPORT.md)
|
|
12
|
+
- Developer guide (PDF): https://api.genebygene.com/assets/GxG%20API%20Services%20Developer%20Guide%202022.pdf
|
|
13
|
+
- Swagger UI: https://api.genebygene.com/swagger/index.html
|
|
14
|
+
- Real staging hosts: API `https://staging-api.genebygene.com`, auth
|
|
15
|
+
`https://staging-auth.genebygene.com/connect/token`
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -D @crvouga/mockingbird-service-genebygene
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
ESM only. Requires Node >= 22 or Bun >= 1.2. No native dependencies: state lives in an in-memory
|
|
24
|
+
SQLite engine (`@crvouga/mockingbird-service-sqlite`, pure TypeScript). To serve it over HTTP also
|
|
25
|
+
install `@crvouga/mockingbird-adapter-node` or `@crvouga/mockingbird-adapter-bun`.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Routes and behaviour (all from the source):
|
|
30
|
+
|
|
31
|
+
| Route | Behaviour |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `POST /connect/token` | Form (or JSON) body with `grant_type=client_credentials`, `client_id`, `client_secret`. Any non-empty id/secret is accepted; returns `{ access_token, token_type: "Bearer", expires_in: 3600 }`. The token is deterministic per id/secret pair. Wrong grant type: 400 `unsupported_grant_type`; missing id/secret: 400 `invalid_client`. |
|
|
34
|
+
| `GET /api/v2/products` | Requires `Authorization: Bearer <anything>`. Returns an array; a seed product `product_default` ("Mockingbird Default Kit") always exists. |
|
|
35
|
+
| `POST /api/v2/orders` | Bearer required. JSON `{ productId, quantity }` (`quantity` a positive integer, `productId` must exist, else 400). Returns `{ orderId: "order_...", status: "Pending", quantity, productId, createdAt }`. |
|
|
36
|
+
| `GET /api/v2/orders/{orderId}` | Bearer required. The stored order, or 404 `{ message: "order not found" }`. |
|
|
37
|
+
|
|
38
|
+
- **Any host works.** Routing uses only the path, so the auth host and the API host of the real
|
|
39
|
+
service both map onto one mock instance: point both your token URL and API base URL at it.
|
|
40
|
+
- **The bearer token is not validated.** Any `Authorization: Bearer <value>` passes; a missing or
|
|
41
|
+
non-Bearer header returns 401 `{ error: "unauthorized" }`.
|
|
42
|
+
- Orders stay `Pending`; there are no status transitions.
|
|
43
|
+
|
|
44
|
+
### In-process (inject `fetch`)
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { GeneByGeneAPI } from "@crvouga/mockingbird-service-genebygene"
|
|
48
|
+
|
|
49
|
+
const gbg = new GeneByGeneAPI({ now: () => Date.UTC(2026, 0, 1) })
|
|
50
|
+
|
|
51
|
+
const tokenResponse = await gbg.fetch(
|
|
52
|
+
new Request("https://staging-auth.genebygene.com/connect/token", {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
55
|
+
body: new URLSearchParams({
|
|
56
|
+
grant_type: "client_credentials",
|
|
57
|
+
client_id: "demo",
|
|
58
|
+
client_secret: "demo",
|
|
59
|
+
}),
|
|
60
|
+
}),
|
|
61
|
+
)
|
|
62
|
+
const { access_token } = (await tokenResponse.json()) as { access_token: string }
|
|
63
|
+
const auth = { authorization: `Bearer ${access_token}` }
|
|
64
|
+
|
|
65
|
+
const products = (await (
|
|
66
|
+
await gbg.fetch(new Request("https://staging-api.genebygene.com/api/v2/products", { headers: auth }))
|
|
67
|
+
).json()) as { productId: string }[]
|
|
68
|
+
|
|
69
|
+
const orderResponse = await gbg.fetch(
|
|
70
|
+
new Request("https://staging-api.genebygene.com/api/v2/orders", {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { ...auth, "content-type": "application/json" },
|
|
73
|
+
body: JSON.stringify({ productId: products[0]?.productId, quantity: 1 }),
|
|
74
|
+
}),
|
|
75
|
+
)
|
|
76
|
+
console.log(await orderResponse.json())
|
|
77
|
+
// { orderId: "order_...", status: "Pending", quantity: 1, productId: "product_default", createdAt: "2026-01-01T00:00:00.000Z" }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`now` (milliseconds) drives `createdAt`.
|
|
81
|
+
|
|
82
|
+
### Over HTTP
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { GeneByGeneAPI } from "@crvouga/mockingbird-service-genebygene"
|
|
86
|
+
|
|
87
|
+
const gbg = new GeneByGeneAPI()
|
|
88
|
+
// Equivalent to serve(gbg, { port: 0, hostname: "127.0.0.1" }) from @crvouga/mockingbird-adapter-bun
|
|
89
|
+
const server = Bun.serve({ port: 0, hostname: "127.0.0.1", fetch: (request) => gbg.fetch(request) })
|
|
90
|
+
const baseUrl = `http://127.0.0.1:${server.port}`
|
|
91
|
+
|
|
92
|
+
// Use baseUrl for both the auth host and the API host in your app's config:
|
|
93
|
+
const tokenUrl = `${baseUrl}/connect/token`
|
|
94
|
+
const apiBaseUrl = baseUrl
|
|
95
|
+
|
|
96
|
+
const token = await fetch(tokenUrl, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
body: new URLSearchParams({ grant_type: "client_credentials", client_id: "id", client_secret: "secret" }),
|
|
99
|
+
})
|
|
100
|
+
console.log(token.status, apiBaseUrl) // 200
|
|
101
|
+
|
|
102
|
+
server.stop()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Any Fetch-style server works, since `GeneByGeneAPI` only needs `fetch(request)`. On Node use
|
|
106
|
+
`@crvouga/mockingbird-adapter-node`:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
import { serve } from "@crvouga/mockingbird-adapter-node"
|
|
110
|
+
const server = await serve(gbg, { port: 0, host: "127.0.0.1" })
|
|
111
|
+
const { port } = server.address()
|
|
112
|
+
// ... later: server.close()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Resetting between tests
|
|
116
|
+
|
|
117
|
+
`reset()` deletes all orders and products, then re-seeds `product_default`:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { beforeEach, expect, test } from "bun:test"
|
|
121
|
+
import { GeneByGeneAPI } from "@crvouga/mockingbird-service-genebygene"
|
|
122
|
+
|
|
123
|
+
const gbg = new GeneByGeneAPI()
|
|
124
|
+
beforeEach(() => gbg.reset())
|
|
125
|
+
|
|
126
|
+
test("catalog has the seed product", async () => {
|
|
127
|
+
const response = await gbg.fetch(
|
|
128
|
+
new Request("https://staging-api.genebygene.com/api/v2/products", {
|
|
129
|
+
headers: { authorization: "Bearer test" },
|
|
130
|
+
}),
|
|
131
|
+
)
|
|
132
|
+
expect(await response.json()).toEqual([
|
|
133
|
+
{
|
|
134
|
+
productId: "product_default",
|
|
135
|
+
name: "Mockingbird Default Kit",
|
|
136
|
+
description: "Seed product for GeneByGene mock",
|
|
137
|
+
},
|
|
138
|
+
])
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## API
|
|
143
|
+
|
|
144
|
+
| Export | Description |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| `GeneByGeneAPI` | Class. `new GeneByGeneAPI(options?)`; implements `FetchAPI`. Members: `fetch(request)`, `reset()`, `app` (Hono app), `sqlite` (`SqliteClient`). |
|
|
147
|
+
| `GENEBYGENE_NAMESPACE` | `"genebygene"` — SQLite namespace holding the mock's state when sharing a `sqlite` client. |
|
|
148
|
+
| `document` | The vendored GeneByGene OpenAPI document (Mockingbird subset) that drives routing. |
|
|
149
|
+
| `operationIds` | Every `operationId` in `document`: `PostConnectToken`, `GetProducts`, `PostOrders`, `GetOrder`. |
|
|
150
|
+
| `supportedOperationIds` | The `operationId`s the mock implements (all four). |
|
|
151
|
+
|
|
152
|
+
Options and types:
|
|
153
|
+
|
|
154
|
+
```text
|
|
155
|
+
type APIOptions = { // from @crvouga/mockingbird-service
|
|
156
|
+
sqlite?: SqliteClient // share one client across services; default: fresh in-memory DB
|
|
157
|
+
now?: () => number // clock in ms for createdAt; default Date.now
|
|
158
|
+
}
|
|
159
|
+
type OperationId / SupportedOperationId // string unions of operationIds / supportedOperationIds
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Development
|
|
163
|
+
|
|
164
|
+
For contributors to the mockingbird repo only; the parity script is not shipped in the npm package.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
bun test # offline property suites
|
|
168
|
+
bun run parity # live parity against GeneByGene staging (credentials from env or OpenBao)
|
|
169
|
+
MOCKINGBIRD_GENEBYGENE_CLIENT_ID=... MOCKINGBIRD_GENEBYGENE_CLIENT_SECRET=... bun run parity
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Live parity exercises the token endpoint against staging auth and discovers product ids from the
|
|
173
|
+
real catalog before creating orders (the live catalog differs from the mock's seed product).
|
|
174
|
+
|
|
175
|
+
Part of [mockingbird](https://github.com/crvouga/mockingbird) — agent integration guide: [`@crvouga/mockingbird`](https://github.com/crvouga/mockingbird/tree/main/packages/facade#readme).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { OpenAPIDocument } from "@crvouga/mockingbird-openapi";
|
|
2
|
+
export declare const document: OpenAPIDocument;
|
|
3
|
+
export type OperationId = "PostConnectToken" | "GetProducts" | "PostOrders" | "GetOrder";
|
|
4
|
+
export type SupportedOperationId = "PostConnectToken" | "GetProducts" | "PostOrders" | "GetOrder";
|
|
5
|
+
export declare const operationIds: readonly ["PostConnectToken", "GetProducts", "PostOrders", "GetOrder"];
|
|
6
|
+
export declare const supportedOperationIds: readonly ["PostConnectToken", "GetProducts", "PostOrders", "GetOrder"];
|
|
7
|
+
//# sourceMappingURL=openapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../src/generated/openapi.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAEnE,eAAO,MAAM,QAAQ,EAAE,eAA8tJ,CAAA;AAErvJ,MAAM,MAAM,WAAW,GAAG,kBAAkB,GAAG,aAAa,GAAG,YAAY,GAAG,UAAU,CAAA;AACxF,MAAM,MAAM,oBAAoB,GAAG,kBAAkB,GAAG,aAAa,GAAG,YAAY,GAAG,UAAU,CAAA;AACjG,eAAO,MAAM,YAAY,wEAAsE,CAAA;AAC/F,eAAO,MAAM,qBAAqB,wEAAsE,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Generated by @crvouga/mockingbird-openapi-codegen from openapi.yaml. Do not edit.
|
|
2
|
+
export const document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"GeneByGene API (Mockingbird subset)","description":"Stateful mock subset of the GeneByGene Nucleus API.\\nDeveloper guide: https://api.genebygene.com/assets/GxG%20API%20Services%20Developer%20Guide%202022.pdf\\nSwagger UI: https://api.genebygene.com/swagger/index.html\\nStaging API: https://staging-api.genebygene.com\\nStaging auth: https://staging-auth.genebygene.com/connect/token\\n","version":"2.0","x-mockingbird-upstream":{"docs":"https://api.genebygene.com/assets/GxG%20API%20Services%20Developer%20Guide%202022.pdf","swagger":"https://api.genebygene.com/swagger/index.html","note":"Hand-authored subset from the public developer guide (products + orders + client_credentials token)."}},"servers":[{"url":"https://staging-api.genebygene.com"}],"security":[{"bearerAuth":[]}],"paths":{"/connect/token":{"post":{"operationId":"PostConnectToken","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["grant_type","client_id","client_secret"],"properties":{"grant_type":{"type":"string","enum":["client_credentials"]},"client_id":{"type":"string","minLength":1},"client_secret":{"type":"string","minLength":1}}}}}},"responses":{"200":{"description":"Access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"400":{"description":"Invalid grant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v2/products":{"get":{"operationId":"GetProducts","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Product list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Product"}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v2/orders":{"post":{"operationId":"PostOrders","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderBody"}}}},"responses":{"200":{"description":"Created order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v2/orders/{orderId}":{"get":{"operationId":"GetOrder","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"orderId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"order","missing":"order_missing"}}}],"responses":{"200":{"description":"Order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"TokenResponse":{"type":"object","required":["access_token","token_type","expires_in"],"properties":{"access_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer"}}},"Product":{"type":"object","required":["productId","name"],"properties":{"productId":{"type":"string","x-mockingbird-resource":{"type":"product","identity":true}},"name":{"type":"string"},"description":{"type":["string","null"]}}},"CreateOrderBody":{"type":"object","required":["productId","quantity"],"properties":{"productId":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"product_missing"}},"quantity":{"type":"integer","minimum":1,"maximum":100}}},"Order":{"type":"object","required":["orderId","status","quantity","productId","createdAt"],"properties":{"orderId":{"type":"string","x-mockingbird-resource":{"type":"order","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["Pending","Submitted","Completed","Cancelled"]},"quantity":{"type":"integer"},"productId":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"product_missing"}},"createdAt":{"type":"string","format":"date-time","x-mockingbird-volatile":{"kind":"timestamp"}}}},"ErrorBody":{"type":"object","properties":{"error":{"type":"string"},"error_description":{"type":"string"},"message":{"type":"string"}}}}}}`);
|
|
3
|
+
export const operationIds = ["PostConnectToken", "GetProducts", "PostOrders", "GetOrder"];
|
|
4
|
+
export const supportedOperationIds = ["PostConnectToken", "GetProducts", "PostOrders", "GetOrder"];
|
|
5
|
+
//# sourceMappingURL=openapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../src/generated/openapi.ts"],"names":[],"mappings":"AAAA,oFAAoF;AAIpF,MAAM,CAAC,MAAM,QAAQ,GAAoB,IAAI,CAAC,KAAK,CAAC,6qJAA6qJ,CAAoB,CAAA;AAIrvJ,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,kBAAkB,EAAC,aAAa,EAAC,YAAY,EAAC,UAAU,CAAU,CAAA;AAC/F,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,kBAAkB,EAAC,aAAa,EAAC,YAAY,EAAC,UAAU,CAAU,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type OperationContext } from "@crvouga/mockingbird-service";
|
|
2
|
+
import type { GeneByGeneState } from "./state.js";
|
|
3
|
+
export declare const geneByGeneHandlers: (state: GeneByGeneState) => {
|
|
4
|
+
PostConnectToken: (context: OperationContext) => Promise<Response>;
|
|
5
|
+
GetProducts: (context: OperationContext) => Promise<Response>;
|
|
6
|
+
PostOrders: (context: OperationContext) => Promise<Response>;
|
|
7
|
+
GetOrder: (context: OperationContext) => Promise<Response>;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,gBAAgB,EAEtB,MAAM,8BAA8B,CAAA;AACrC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA0CjD,eAAO,MAAM,kBAAkB,GAAI,OAAO,eAAe;gCACrB,gBAAgB;2BAsBrB,gBAAgB;0BAOjB,gBAAgB;wBAyBlB,gBAAgB;CAO1C,CAAA"}
|
package/dist/handlers.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { HttpError, jsonRes, opaqueToken, } from "@crvouga/mockingbird-service";
|
|
2
|
+
const requireBearer = (context) => {
|
|
3
|
+
const auth = context.request.headers.get("authorization");
|
|
4
|
+
if (!auth?.toLowerCase().startsWith("bearer ")) {
|
|
5
|
+
throw new HttpError(401, { error: "unauthorized", message: "Bearer token required" });
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
const jsonBody = (context) => {
|
|
9
|
+
if (context.body.kind !== "json" ||
|
|
10
|
+
typeof context.body.value !== "object" ||
|
|
11
|
+
context.body.value === null) {
|
|
12
|
+
throw new HttpError(400, { message: "expected a JSON object body" });
|
|
13
|
+
}
|
|
14
|
+
return context.body.value;
|
|
15
|
+
};
|
|
16
|
+
const formBody = (context) => {
|
|
17
|
+
if (context.body.kind === "form") {
|
|
18
|
+
const out = {};
|
|
19
|
+
for (const [key, value] of Object.entries(context.body.value)) {
|
|
20
|
+
if (typeof value === "string")
|
|
21
|
+
out[key] = value;
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
if (context.body.kind === "json" &&
|
|
26
|
+
typeof context.body.value === "object" &&
|
|
27
|
+
context.body.value) {
|
|
28
|
+
const out = {};
|
|
29
|
+
for (const [key, value] of Object.entries(context.body.value)) {
|
|
30
|
+
if (typeof value === "string")
|
|
31
|
+
out[key] = value;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
throw new HttpError(400, { error: "invalid_request", error_description: "expected form body" });
|
|
36
|
+
};
|
|
37
|
+
export const geneByGeneHandlers = (state) => ({
|
|
38
|
+
PostConnectToken: async (context) => {
|
|
39
|
+
const body = formBody(context);
|
|
40
|
+
if (body.grant_type !== "client_credentials") {
|
|
41
|
+
throw new HttpError(400, {
|
|
42
|
+
error: "unsupported_grant_type",
|
|
43
|
+
error_description: "grant_type must be client_credentials",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (!body.client_id || !body.client_secret) {
|
|
47
|
+
throw new HttpError(400, {
|
|
48
|
+
error: "invalid_client",
|
|
49
|
+
error_description: "client_id and client_secret are required",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const token = opaqueToken(`gbg:${body.client_id}:${body.client_secret}`, 32);
|
|
53
|
+
return jsonRes(200, {
|
|
54
|
+
access_token: token,
|
|
55
|
+
token_type: "Bearer",
|
|
56
|
+
expires_in: 3600,
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
GetProducts: async (context) => {
|
|
60
|
+
requireBearer(context);
|
|
61
|
+
state.ensureSeedProducts();
|
|
62
|
+
const products = state.products.list({ order: "oldest" }).map((row) => row.value);
|
|
63
|
+
return jsonRes(200, products);
|
|
64
|
+
},
|
|
65
|
+
PostOrders: async (context) => {
|
|
66
|
+
requireBearer(context);
|
|
67
|
+
const body = jsonBody(context);
|
|
68
|
+
const productId = body.productId;
|
|
69
|
+
const quantity = body.quantity;
|
|
70
|
+
if (typeof productId !== "string")
|
|
71
|
+
throw new HttpError(400, { message: "productId is required" });
|
|
72
|
+
if (typeof quantity !== "number" || !Number.isInteger(quantity) || quantity < 1) {
|
|
73
|
+
throw new HttpError(400, { message: "quantity must be a positive integer" });
|
|
74
|
+
}
|
|
75
|
+
if (!state.products.has(productId)) {
|
|
76
|
+
throw new HttpError(400, { message: `unknown productId ${productId}` });
|
|
77
|
+
}
|
|
78
|
+
const orderId = state.nextOrderId();
|
|
79
|
+
const order = {
|
|
80
|
+
orderId,
|
|
81
|
+
status: "Pending",
|
|
82
|
+
quantity,
|
|
83
|
+
productId,
|
|
84
|
+
createdAt: state.isoNow(context.now),
|
|
85
|
+
};
|
|
86
|
+
state.orders.insert(orderId, order);
|
|
87
|
+
return jsonRes(200, order);
|
|
88
|
+
},
|
|
89
|
+
GetOrder: async (context) => {
|
|
90
|
+
requireBearer(context);
|
|
91
|
+
const id = context.params.orderId ?? "";
|
|
92
|
+
const order = state.orders.get(id);
|
|
93
|
+
if (!order)
|
|
94
|
+
throw new HttpError(404, { message: "order not found" });
|
|
95
|
+
return jsonRes(200, order);
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
//# sourceMappingURL=handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FetchAPI } from "@crvouga/mockingbird-core";
|
|
2
|
+
import { type APIOptions } from "@crvouga/mockingbird-service";
|
|
3
|
+
import type { SqliteClient } from "@crvouga/mockingbird-sqlite";
|
|
4
|
+
import type { Hono } from "hono";
|
|
5
|
+
export type { OperationId, SupportedOperationId } from "./generated/openapi.js";
|
|
6
|
+
export { document, operationIds, supportedOperationIds } from "./generated/openapi.js";
|
|
7
|
+
export declare const GENEBYGENE_NAMESPACE = "genebygene";
|
|
8
|
+
/**
|
|
9
|
+
* Stateful mock of the GeneByGene Nucleus API (token + products + orders).
|
|
10
|
+
*
|
|
11
|
+
* Developer guide: https://api.genebygene.com/assets/GxG%20API%20Services%20Developer%20Guide%202022.pdf
|
|
12
|
+
* Swagger: https://api.genebygene.com/swagger/index.html
|
|
13
|
+
*/
|
|
14
|
+
export declare class GeneByGeneAPI implements FetchAPI {
|
|
15
|
+
readonly app: Hono;
|
|
16
|
+
readonly sqlite: SqliteClient;
|
|
17
|
+
private readonly service;
|
|
18
|
+
private readonly state;
|
|
19
|
+
constructor(options?: APIOptions);
|
|
20
|
+
fetch(request: Request): Promise<Response>;
|
|
21
|
+
reset(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,EACL,KAAK,UAAU,EAOhB,MAAM,8BAA8B,CAAA;AACrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC/D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAKhC,YAAY,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAC/E,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAEtF,eAAO,MAAM,oBAAoB,eAAe,CAAA;AAEhD;;;;;GAKG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAC5C,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAA;IAClB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;gBAE3B,OAAO,GAAE,UAAe;IAsBpC,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAIpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { bootSqlite, createService, defineOperations, HttpError, jsonRes, } from "@crvouga/mockingbird-service";
|
|
2
|
+
import { document } from "./generated/openapi.js";
|
|
3
|
+
import { geneByGeneHandlers } from "./handlers.js";
|
|
4
|
+
import { GeneByGeneState } from "./state.js";
|
|
5
|
+
export { document, operationIds, supportedOperationIds } from "./generated/openapi.js";
|
|
6
|
+
export const GENEBYGENE_NAMESPACE = "genebygene";
|
|
7
|
+
/**
|
|
8
|
+
* Stateful mock of the GeneByGene Nucleus API (token + products + orders).
|
|
9
|
+
*
|
|
10
|
+
* Developer guide: https://api.genebygene.com/assets/GxG%20API%20Services%20Developer%20Guide%202022.pdf
|
|
11
|
+
* Swagger: https://api.genebygene.com/swagger/index.html
|
|
12
|
+
*/
|
|
13
|
+
export class GeneByGeneAPI {
|
|
14
|
+
app;
|
|
15
|
+
sqlite;
|
|
16
|
+
service;
|
|
17
|
+
state;
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
const sqlite = bootSqlite(options.sqlite);
|
|
20
|
+
this.state = new GeneByGeneState(sqlite, GENEBYGENE_NAMESPACE);
|
|
21
|
+
const handlers = defineOperations({
|
|
22
|
+
...geneByGeneHandlers(this.state),
|
|
23
|
+
});
|
|
24
|
+
this.service = createService({
|
|
25
|
+
document,
|
|
26
|
+
handlers,
|
|
27
|
+
sqlite,
|
|
28
|
+
namespace: GENEBYGENE_NAMESPACE,
|
|
29
|
+
now: options.now,
|
|
30
|
+
notFound: () => jsonRes(404, { message: "Not Found" }),
|
|
31
|
+
onError: (error) => {
|
|
32
|
+
if (error instanceof HttpError)
|
|
33
|
+
return error.toResponse();
|
|
34
|
+
throw error;
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
this.app = this.service.app;
|
|
38
|
+
this.sqlite = this.service.sqlite;
|
|
39
|
+
}
|
|
40
|
+
fetch(request) {
|
|
41
|
+
return this.service.fetch(request);
|
|
42
|
+
}
|
|
43
|
+
async reset() {
|
|
44
|
+
await this.service.reset();
|
|
45
|
+
this.state.ensureSeedProducts();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,OAAO,GAER,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,QAAQ,EAA6B,MAAM,wBAAwB,CAAA;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAG5C,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAEtF,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAAA;AAEhD;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IACf,GAAG,CAAM;IACT,MAAM,CAAc;IACZ,OAAO,CAAS;IAChB,KAAK,CAAiB;IAEvC,YAAY,UAAsB,EAAE;QAClC,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QACzC,IAAI,CAAC,KAAK,GAAG,IAAI,eAAe,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAA;QAC9D,MAAM,QAAQ,GAAG,gBAAgB,CAAuB;YACtD,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC;SAClC,CAAC,CAAA;QACF,IAAI,CAAC,OAAO,GAAG,aAAa,CAAC;YAC3B,QAAQ;YACR,QAAQ;YACR,MAAM;YACN,SAAS,EAAE,oBAAoB;YAC/B,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;YACtD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;gBACjB,IAAI,KAAK,YAAY,SAAS;oBAAE,OAAO,KAAK,CAAC,UAAU,EAAE,CAAA;gBACzD,MAAM,KAAK,CAAA;YACb,CAAC;SACF,CAAC,CAAA;QACF,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAA;QAC3B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAA;IACnC,CAAC;IAED,KAAK,CAAC,OAAgB;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACpC,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;QAC1B,IAAI,CAAC,KAAK,CAAC,kBAAkB,EAAE,CAAA;IACjC,CAAC;CACF"}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Collection, IdSequence } from "@crvouga/mockingbird-service";
|
|
2
|
+
import type { SqliteClient } from "@crvouga/mockingbird-sqlite";
|
|
3
|
+
export type ProductRecord = {
|
|
4
|
+
productId: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string | null;
|
|
7
|
+
};
|
|
8
|
+
export type OrderRecord = {
|
|
9
|
+
orderId: string;
|
|
10
|
+
status: "Pending" | "Submitted" | "Completed" | "Cancelled";
|
|
11
|
+
quantity: number;
|
|
12
|
+
productId: string;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
};
|
|
15
|
+
export declare class GeneByGeneState {
|
|
16
|
+
readonly products: Collection<ProductRecord>;
|
|
17
|
+
readonly orders: Collection<OrderRecord>;
|
|
18
|
+
readonly ids: IdSequence;
|
|
19
|
+
constructor(sqlite: SqliteClient, namespace: string);
|
|
20
|
+
/** Sandbox always exposes at least one catalog product. */
|
|
21
|
+
ensureSeedProducts(): void;
|
|
22
|
+
nextOrderId(): string;
|
|
23
|
+
isoNow(now: () => number): string;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAE/D,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAA;IAC3D,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,qBAAa,eAAe;IAC1B,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,CAAA;IAC5C,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,CAAA;IACxC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;gBAEZ,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM;IAOnD,2DAA2D;IAC3D,kBAAkB;IASlB,WAAW,IAAI,MAAM;IAIrB,MAAM,CAAC,GAAG,EAAE,MAAM,MAAM,GAAG,MAAM;CAGlC"}
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Collection, IdSequence } from "@crvouga/mockingbird-service";
|
|
2
|
+
export class GeneByGeneState {
|
|
3
|
+
products;
|
|
4
|
+
orders;
|
|
5
|
+
ids;
|
|
6
|
+
constructor(sqlite, namespace) {
|
|
7
|
+
this.products = new Collection(sqlite, namespace, "products");
|
|
8
|
+
this.orders = new Collection(sqlite, namespace, "orders");
|
|
9
|
+
this.ids = new IdSequence(sqlite, namespace, "genebygene");
|
|
10
|
+
this.ensureSeedProducts();
|
|
11
|
+
}
|
|
12
|
+
/** Sandbox always exposes at least one catalog product. */
|
|
13
|
+
ensureSeedProducts() {
|
|
14
|
+
if (this.products.list().length > 0)
|
|
15
|
+
return;
|
|
16
|
+
this.products.insert("product_default", {
|
|
17
|
+
productId: "product_default",
|
|
18
|
+
name: "Mockingbird Default Kit",
|
|
19
|
+
description: "Seed product for GeneByGene mock",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
nextOrderId() {
|
|
23
|
+
return this.ids.next("order_");
|
|
24
|
+
}
|
|
25
|
+
isoNow(now) {
|
|
26
|
+
return new Date(now()).toISOString();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AAiBrE,MAAM,OAAO,eAAe;IACjB,QAAQ,CAA2B;IACnC,MAAM,CAAyB;IAC/B,GAAG,CAAY;IAExB,YAAY,MAAoB,EAAE,SAAiB;QACjD,IAAI,CAAC,QAAQ,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,CAAA;QAC7D,IAAI,CAAC,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAA;QACzD,IAAI,CAAC,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,CAAC,CAAA;QAC1D,IAAI,CAAC,kBAAkB,EAAE,CAAA;IAC3B,CAAC;IAED,2DAA2D;IAC3D,kBAAkB;QAChB,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;YAAE,OAAM;QAC3C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,iBAAiB,EAAE;YACtC,SAAS,EAAE,iBAAiB;YAC5B,IAAI,EAAE,yBAAyB;YAC/B,WAAW,EAAE,kCAAkC;SAChD,CAAC,CAAA;IACJ,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAChC,CAAC;IAED,MAAM,CAAC,GAAiB;QACtB,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IACtC,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crvouga/mockingbird-service-genebygene",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Stateful mock of the GeneByGene Nucleus API (token, products, orders) verified by differential property tests against the GeneByGene staging sandbox.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public",
|
|
20
|
+
"provenance": true
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/crvouga/mockingbird.git",
|
|
25
|
+
"directory": "packages/service/genebygene"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/crvouga/mockingbird/tree/main/packages/service/genebygene#readme",
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mockingbird",
|
|
30
|
+
"service",
|
|
31
|
+
"genebygene"
|
|
32
|
+
],
|
|
33
|
+
"mockingbird": {
|
|
34
|
+
"runtime": "portable",
|
|
35
|
+
"layer": "service"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"generate": "bun ../../openapi/codegen/dist/cli.js",
|
|
39
|
+
"generate:check": "bun ../../openapi/codegen/dist/cli.js --check",
|
|
40
|
+
"openapi:check": "bun ../../openapi/codegen/dist/cli.js --validate",
|
|
41
|
+
"build": "tsc -p tsconfig.build.json",
|
|
42
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
43
|
+
"lint": "biome check .",
|
|
44
|
+
"test": "bun test",
|
|
45
|
+
"parity": "bun scripts/parity.ts",
|
|
46
|
+
"portability": "bun ../../../scripts/portability.ts",
|
|
47
|
+
"pack:check": "bun ../../../scripts/pack-check.ts"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@crvouga/mockingbird-core": "0.0.0-development",
|
|
51
|
+
"@crvouga/mockingbird-sqlite": "0.0.0-development",
|
|
52
|
+
"@crvouga/mockingbird-openapi": "0.0.0-development",
|
|
53
|
+
"@crvouga/mockingbird-service": "0.0.0-development",
|
|
54
|
+
"@crvouga/mockingbird-http-codec": "0.0.0-development",
|
|
55
|
+
"hono": "4.11.9"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"fast-check": "4.9.0",
|
|
59
|
+
"@crvouga/mockingbird-testing": "0.1.0",
|
|
60
|
+
"@crvouga/mockingbird-service-sqlite": "0.0.0-development",
|
|
61
|
+
"@crvouga/mockingbird-parity": "0.0.0-development",
|
|
62
|
+
"@crvouga/mockingbird-openbao": "0.0.0-development",
|
|
63
|
+
"@crvouga/mockingbird-openapi-codegen": "0.1.0",
|
|
64
|
+
"@crvouga/mockingbird-openapi-metadata": "0.0.0-development"
|
|
65
|
+
}
|
|
66
|
+
}
|