@crvouga/mockingbird-service-wholescripts 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/CHANGELOG.md +5 -0
- package/README.md +135 -0
- package/dist/chunk-ARWCVRNR.js +2785 -0
- package/dist/chunk-ARWCVRNR.js.map +7 -0
- package/dist/chunk-XXM4NA4F.js +376 -0
- package/dist/chunk-XXM4NA4F.js.map +7 -0
- package/dist/cli.js +19 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +966 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +7 -0
- package/dist/server.d.ts +1295 -0
- package/dist/server.js +12 -0
- package/dist/server.js.map +7 -0
- package/package.json +92 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# @crvouga/mockingbird-service-wholescripts
|
|
2
|
+
|
|
3
|
+
Stateful mock of the **Wholescripts** supplement fulfilment API for test suites: the product
|
|
4
|
+
catalog, the private-label (MedPax) catalog, order submit, status polling and cancel. Orders
|
|
5
|
+
move only when a test says so (an admin transition or an auto-advance path on the mock clock).
|
|
6
|
+
Wholescripts sends no webhooks, so the app sees each change on its next status poll.
|
|
7
|
+
|
|
8
|
+
- Operation coverage: [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/wholescripts/SUPPORT.md)
|
|
9
|
+
- The vendor publishes no spec. The contract (`openapi.yaml`) is hand-authored from our
|
|
10
|
+
consumers' zod schemas (backend and EMR `wholescripts.types.ts`) and the Makor Python client.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -D @crvouga/mockingbird-service-wholescripts
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with
|
|
19
|
+
`npx mockingbird-wholescripts serve`, `createServer` from `./server` (Node), or `createRuntime`
|
|
20
|
+
with any Fetch server.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Point `WHOLESCRIPTS_API_URL` at the mock. It is read by the backend
|
|
25
|
+
(`global-services/services/wholescripts`), the EMR (`services/wholescripts`) and Makor
|
|
26
|
+
(`supplement_management`). Any non-empty `WHOLESCRIPTS_USERNAME` / `WHOLESCRIPTS_PASSWORD`
|
|
27
|
+
pair is accepted unless you pin one with `--username/--password`.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx mockingbird-wholescripts serve --port 8803 --auto-advance "2000:Processing,Complete"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { createRuntime } from "@crvouga/mockingbird-service-wholescripts"
|
|
35
|
+
|
|
36
|
+
const ws = createRuntime()
|
|
37
|
+
const auth = { authorization: `Basic ${btoa("geviti:secret")}`, "content-type": "application/json" }
|
|
38
|
+
|
|
39
|
+
const submitted = (await (
|
|
40
|
+
await ws.fetch(
|
|
41
|
+
new Request("http://wholescripts.test/api/Orders/Submit", {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: auth,
|
|
44
|
+
body: JSON.stringify({
|
|
45
|
+
ShippingAddress: { FName: "Ada", LName: "L", Address1: "1 Main St", City: "Phoenix", State: "AZ", Zip: "85004" },
|
|
46
|
+
Items: [{ Sku: "SKU001", Quantity: 1 }],
|
|
47
|
+
ShippingMethod: "Ground",
|
|
48
|
+
}),
|
|
49
|
+
}),
|
|
50
|
+
)
|
|
51
|
+
).json()) as { orderNumber: string }
|
|
52
|
+
|
|
53
|
+
// Ship it the way the vendor would; the next GET /api/Orders/Status poll sees it.
|
|
54
|
+
await ws.fetch(
|
|
55
|
+
new Request(`http://wholescripts.test/__admin/orders/${submitted.orderNumber}/transition`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "content-type": "application/json" },
|
|
58
|
+
body: JSON.stringify({ to: "Complete", trackingNumber: "1Z999", carrier: "UPS" }),
|
|
59
|
+
}),
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Routes
|
|
64
|
+
|
|
65
|
+
| Route | Behaviour |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `GET /api/Orders/PrivateLabelProductList` | `{privateLabelProducts: [], medPaxPills: [{sku, genericName, privateLabelName, quantity}], privateLabelCartons: [{sku, name, cartonImage, quantity}]}`. |
|
|
68
|
+
| `GET /api/Orders/ProductList` | Product rows (`productName`, `sku`, `medPaxSku`, `categories`, prices, `quantity`, `defaultDosing`, optional `medPaxDetails`). `instockonly=true` keeps `quantity > 0` (the EMR), `search` matches name/SKU/brand/categories (Makor), `limit` caps. |
|
|
69
|
+
| `POST /api/Orders/Submit` | `{ShippingAddress, Notes?, Items: [{Sku, Quantity, MedPaxName?, MedPaxPills?: [{Sku, Quantity, ItemTime}]}], ShippingMethod}` → `{orderNumber, success: true, msg}`. A contract violation or an unknown SKU (product, MedPax, pill or carton SKU) is still **200** with `success: false` and the reason in `msg`, which is what both consumers read. |
|
|
70
|
+
| `GET /api/Orders/Status?ordernum=` | `[{orderNumber, orderDate, salesOrder, status, tracking: [{trackingNumber, carrier, trackingUrl}], message, subTotal, shipMethod, shipCharge, discount, tax, serviceFee, orderTotal}]`; `[]` for an unknown order. Totals are priced from the catalog (MedPax SKUs at their `medPaxDetails` price) plus 9.95 shipping (0 for "free" methods). |
|
|
71
|
+
| `POST /api/Orders/Cancel` | `{OrderNumber}` → `{success, msg}`. Succeeds while `Pending`/`Processing` with no tracking; otherwise `success: false`. Unknown order: 404. |
|
|
72
|
+
|
|
73
|
+
Missing or wrong Basic credentials answer 401 `{"Message": "Authorization has been denied for this request."}`.
|
|
74
|
+
|
|
75
|
+
Statuses are `Pending` (new), `Processing`, `Complete`, `Cancelled`, `Error`, or any string a
|
|
76
|
+
test sets. How our consumers read them: Makor maps `Pending`/`Processing` to `placed` (or
|
|
77
|
+
`shipped` once tracking exists), `Complete` to `shipped` (carrier hand-off, not delivery),
|
|
78
|
+
anything containing `cancel`/`error` to `cancelled`/`failed`, and everything else to `unknown`.
|
|
79
|
+
The backend and EMR do not map statuses.
|
|
80
|
+
|
|
81
|
+
### Admin (beyond the standard contract)
|
|
82
|
+
|
|
83
|
+
| Route | Effect |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `POST /__admin/orders/:orderNumber/transition` | `{to, trackingNumber?, carrier?, trackingUrl?, message?}`. A tracking number is appended to `tracking`; `Complete` without one generates a UPS number. `Processing`/`Complete` set `salesOrder`. |
|
|
86
|
+
| `GET /__admin/orders` | The namespace's orders (SKUs and quantities only). |
|
|
87
|
+
| `GET`/`PUT /__admin/catalog` | Read or replace `{products, medPaxPills, privateLabelCartons}` for the namespace. |
|
|
88
|
+
| `GET`/`PUT /__admin/settings` | `{accounts?: [{username, password}], autoAdvance?: {afterMs, path} \| null}`. |
|
|
89
|
+
| `POST /__admin/tick` | Apply every auto-advance step that is due on the mock clock (the served mock ticks every 100 ms). |
|
|
90
|
+
|
|
91
|
+
Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`; `GET /__admin/faults/presets`):
|
|
92
|
+
`submit_rejected` (200 `success: false`), `submit_timeout` (the order is placed, then the
|
|
93
|
+
connection drops: Makor's "order may have been placed" branch), `status_empty` (`[]`),
|
|
94
|
+
`status_schema_drift` (rows the backend's zod rejects, so it returns `null`), `server_error`
|
|
95
|
+
(500, which Makor retries), `unauthorized` (401).
|
|
96
|
+
|
|
97
|
+
### Namespaces
|
|
98
|
+
|
|
99
|
+
`x-mockingbird-namespace`, a `/ns/<name>` prefix on `WHOLESCRIPTS_API_URL`, or by Basic
|
|
100
|
+
username: `PUT /__admin/credentials {"credentials": {"<WHOLESCRIPTS_USERNAME>": "<namespace>"}}`.
|
|
101
|
+
|
|
102
|
+
### Corpus and seed data
|
|
103
|
+
|
|
104
|
+
The default catalog is the rows our consumers' own tests use
|
|
105
|
+
(`geviti-emr-backend/tests/unit/services/wholescripts-service.test.ts`), with their repeated
|
|
106
|
+
`medPaxSku`s made unique, plus the Makor MedPax box `000000000200095263` and `medPaxDetails`
|
|
107
|
+
rows the Makor catalog sync reads. `Protein Powder` (`PP001`) is out of stock. No sandbox
|
|
108
|
+
recording exists; pass `catalog` (or `PUT /__admin/catalog`) to load recorded rows.
|
|
109
|
+
|
|
110
|
+
### Deliberately not modelled
|
|
111
|
+
|
|
112
|
+
- Webhooks: Wholescripts has none; status is polled.
|
|
113
|
+
- Real fulfilment timing, partial shipments and returns: nothing moves unless a test or
|
|
114
|
+
`autoAdvance` moves it.
|
|
115
|
+
- Shipping address and recipient are validated but never stored or echoed back (PHI).
|
|
116
|
+
- Real tax, discounts, service fees and carrier rates: tax/discount/fee are 0, shipping is flat.
|
|
117
|
+
- Payment authorization: a failed payment is only reachable through `submit_rejected` or an
|
|
118
|
+
`Error` transition.
|
|
119
|
+
|
|
120
|
+
## API
|
|
121
|
+
|
|
122
|
+
| Export | Kind | Description |
|
|
123
|
+
| --- | --- | --- |
|
|
124
|
+
| `WholescriptsAPI` | class | The in-process mock: `fetch(request)`, `reset()`, `transition(orderNumber, {to, …})`, `tick()`, `orders()`. Options: `sqlite`, `now`, `namespace`, `catalog`, `settings`. |
|
|
125
|
+
| `createRuntime` | function | The mock with the full service contract (health, admin, namespaces, credentials, presets). Options: `catalog`, `settings`, `tickMs`, `clock`, `seed`, `adminKey`, `onLog`. |
|
|
126
|
+
| `WHOLESCRIPTS_PRESETS` | object | Every named fault preset. |
|
|
127
|
+
| `WHOLESCRIPTS_NAMESPACE` | string | The service name, `"wholescripts"`. |
|
|
128
|
+
| `basicUsername` | function | The Basic username a request carries (how credentials map to namespaces). |
|
|
129
|
+
| `canonicalStatus` | function | The canonical casing of a transition target (`complete` → `Complete`). |
|
|
130
|
+
| `DEFAULT_CATALOG` | object | The default `{products, medPaxPills, privateLabelCartons}`. |
|
|
131
|
+
| `MEDPAX_BOX_SKU` | string | The Makor MedPax box SKU, `000000000200095263`. |
|
|
132
|
+
| `document`, `operationIds`, `supportedOperationIds` | values | The vendored OpenAPI contract and its operation ids. |
|
|
133
|
+
| `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http` (auto-advance ticks every 100 ms); the `serve` CLI target; port 8803. |
|
|
134
|
+
|
|
135
|
+
Part of [mockingbird](https://github.com/crvouga/mockingbird).
|