@fluentcommerce/ai-skills 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/LICENSE +21 -0
- package/README.md +622 -0
- package/bin/cli.mjs +1973 -0
- package/content/cli/agents/fluent-cli/agent.json +149 -0
- package/content/cli/agents/fluent-cli.md +132 -0
- package/content/cli/skills/fluent-bootstrap/SKILL.md +181 -0
- package/content/cli/skills/fluent-cli-index/SKILL.md +63 -0
- package/content/cli/skills/fluent-cli-mcp-cicd/SKILL.md +77 -0
- package/content/cli/skills/fluent-cli-reference/SKILL.md +1031 -0
- package/content/cli/skills/fluent-cli-retailer/SKILL.md +85 -0
- package/content/cli/skills/fluent-cli-settings/SKILL.md +106 -0
- package/content/cli/skills/fluent-connect/SKILL.md +886 -0
- package/content/cli/skills/fluent-module-deploy/SKILL.md +349 -0
- package/content/cli/skills/fluent-profile/SKILL.md +180 -0
- package/content/cli/skills/fluent-workflow/SKILL.md +310 -0
- package/content/dev/agents/fluent-dev/agent.json +88 -0
- package/content/dev/agents/fluent-dev.md +525 -0
- package/content/dev/reference-modules/catalog.json +4754 -0
- package/content/dev/skills/fluent-build/SKILL.md +192 -0
- package/content/dev/skills/fluent-connection-analysis/SKILL.md +386 -0
- package/content/dev/skills/fluent-custom-code/SKILL.md +895 -0
- package/content/dev/skills/fluent-data-module-scaffold/SKILL.md +714 -0
- package/content/dev/skills/fluent-e2e-test/SKILL.md +394 -0
- package/content/dev/skills/fluent-event-api/SKILL.md +945 -0
- package/content/dev/skills/fluent-feature-explain/SKILL.md +603 -0
- package/content/dev/skills/fluent-feature-plan/PLAN_TEMPLATE.md +695 -0
- package/content/dev/skills/fluent-feature-plan/SKILL.md +227 -0
- package/content/dev/skills/fluent-job-batch/SKILL.md +138 -0
- package/content/dev/skills/fluent-mermaid-validate/SKILL.md +86 -0
- package/content/dev/skills/fluent-module-scaffold/SKILL.md +1928 -0
- package/content/dev/skills/fluent-module-validate/SKILL.md +775 -0
- package/content/dev/skills/fluent-pre-deploy-check/SKILL.md +1108 -0
- package/content/dev/skills/fluent-retailer-config/SKILL.md +1111 -0
- package/content/dev/skills/fluent-rule-scaffold/SKILL.md +385 -0
- package/content/dev/skills/fluent-scope-decompose/SKILL.md +1021 -0
- package/content/dev/skills/fluent-session-audit-export/SKILL.md +632 -0
- package/content/dev/skills/fluent-session-summary/SKILL.md +195 -0
- package/content/dev/skills/fluent-settings/SKILL.md +1058 -0
- package/content/dev/skills/fluent-source-onboard/SKILL.md +632 -0
- package/content/dev/skills/fluent-system-monitoring/SKILL.md +767 -0
- package/content/dev/skills/fluent-test-data/SKILL.md +513 -0
- package/content/dev/skills/fluent-trace/SKILL.md +1143 -0
- package/content/dev/skills/fluent-transition-api/SKILL.md +346 -0
- package/content/dev/skills/fluent-version-manage/SKILL.md +744 -0
- package/content/dev/skills/fluent-workflow-analyzer/SKILL.md +959 -0
- package/content/dev/skills/fluent-workflow-builder/SKILL.md +319 -0
- package/content/dev/skills/fluent-workflow-deploy/SKILL.md +267 -0
- package/content/mcp-extn/agents/fluent-mcp.md +69 -0
- package/content/mcp-extn/skills/fluent-mcp-tools/SKILL.md +461 -0
- package/content/mcp-official/agents/fluent-mcp-core.md +91 -0
- package/content/mcp-official/skills/fluent-mcp-core/SKILL.md +94 -0
- package/content/rfl/agents/fluent-rfl.md +56 -0
- package/content/rfl/skills/fluent-rfl-assess/SKILL.md +172 -0
- package/docs/CAPABILITY_MAP.md +77 -0
- package/docs/CLI_COVERAGE.md +47 -0
- package/docs/DEV_WORKFLOW.md +802 -0
- package/docs/FLOW_RUN.md +142 -0
- package/docs/USE_CASES.md +404 -0
- package/metadata.json +156 -0
- package/package.json +51 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fluent-test-data
|
|
3
|
+
description: Discover retailer configuration and generate valid test entities dynamically via GraphQL. No hardcoded refs — everything is queried from the live environment. Triggers on "create test order", "test data", "create test entity", "discover products", "discover locations".
|
|
4
|
+
user-invocable: true
|
|
5
|
+
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
|
|
6
|
+
argument-hint: [entity-type] [--type HD|CC|SFS] [--count N]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Dynamic Test Data Generator
|
|
10
|
+
|
|
11
|
+
Discover retailer configuration (locations, products, catalogues, networks, inventory, customers) via live GraphQL queries and generate valid entity creation payloads. Everything is queried — nothing is hardcoded.
|
|
12
|
+
|
|
13
|
+
## Ownership Boundary
|
|
14
|
+
|
|
15
|
+
This skill owns environment discovery and entity payload generation.
|
|
16
|
+
|
|
17
|
+
Canonical MCP extension tool syntax/limits are owned by `/fluent-mcp-tools`.
|
|
18
|
+
|
|
19
|
+
## When to Use
|
|
20
|
+
|
|
21
|
+
- Before running E2E tests — discover what's available in the target environment
|
|
22
|
+
- Creating test orders, fulfilments, or other entities with valid refs
|
|
23
|
+
- Exploring retailer configuration (what locations exist, what products are in stock)
|
|
24
|
+
- Generating bulk test data for load/stress testing
|
|
25
|
+
|
|
26
|
+
## Core Principle
|
|
27
|
+
|
|
28
|
+
**Never hardcode entity refs, product SKUs, location refs, or addresses.** Always discover them from the live environment via GraphQL. Different retailers have different catalogues, locations, and products — hardcoded values will fail.
|
|
29
|
+
|
|
30
|
+
## Phase 1: Environment Discovery
|
|
31
|
+
|
|
32
|
+
Run these queries in sequence. Each subsequent query uses refs discovered in the previous step.
|
|
33
|
+
|
|
34
|
+
### Step 1 — Retailer Context
|
|
35
|
+
|
|
36
|
+
```graphql
|
|
37
|
+
{
|
|
38
|
+
me {
|
|
39
|
+
id
|
|
40
|
+
username
|
|
41
|
+
primaryRetailer { id ref tradingName }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Captures: `retailerId`, `retailerRef`. If `primaryRetailer` is null, use `FLUENT_RETAILER_ID` from config.
|
|
47
|
+
|
|
48
|
+
### Step 1B — Retailer Scope Safety Gate (mandatory)
|
|
49
|
+
|
|
50
|
+
Before selecting any location, catalogue, product, or customer ref, verify scope:
|
|
51
|
+
|
|
52
|
+
1. **Prefer retailer-scoped query args when schema supports them.**
|
|
53
|
+
- Use `graphql.introspect` to check whether fields accept retailer-scoped filters.
|
|
54
|
+
2. **If schema does not support retailer filters**, treat unfiltered results as potentially cross-retailer.
|
|
55
|
+
3. **Validate candidate refs in context of target retailer** before use:
|
|
56
|
+
- Confirm the candidate is used by entities already created for the target retailer (for example, recent orders/fulfilments).
|
|
57
|
+
- If uncertain, pick another candidate and re-check.
|
|
58
|
+
|
|
59
|
+
If this safety gate is skipped, the order may be created but downstream fulfilment creation can fail because refs belong to a different retailer scope.
|
|
60
|
+
|
|
61
|
+
### Step 2 — Locations (warehouses and stores)
|
|
62
|
+
|
|
63
|
+
```graphql
|
|
64
|
+
{
|
|
65
|
+
locations(first: 20) {
|
|
66
|
+
edges {
|
|
67
|
+
node {
|
|
68
|
+
id
|
|
69
|
+
ref
|
|
70
|
+
name
|
|
71
|
+
type
|
|
72
|
+
status
|
|
73
|
+
primaryAddress { street city state postcode country }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Filter results by status = `ACTIVE`. Group by type:
|
|
81
|
+
- `WAREHOUSE` — for HD (home delivery) sourcing
|
|
82
|
+
- `STORE` — for CC (click & collect) or SFS (ship from store)
|
|
83
|
+
|
|
84
|
+
Pick the **first active warehouse** for HD tests. Pick the **first active store** for CC tests.
|
|
85
|
+
|
|
86
|
+
### Step 3 — Networks
|
|
87
|
+
|
|
88
|
+
```graphql
|
|
89
|
+
{
|
|
90
|
+
networks(first: 20) {
|
|
91
|
+
edges {
|
|
92
|
+
node {
|
|
93
|
+
id
|
|
94
|
+
ref
|
|
95
|
+
type
|
|
96
|
+
status
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Look for:
|
|
104
|
+
- HD network (type contains `HD` or ref contains `HD`)
|
|
105
|
+
- CC network (type contains `CC` or ref contains `CC`)
|
|
106
|
+
|
|
107
|
+
### Step 4 — Product Catalogues
|
|
108
|
+
|
|
109
|
+
```graphql
|
|
110
|
+
{
|
|
111
|
+
productCatalogues(first: 10) {
|
|
112
|
+
edges {
|
|
113
|
+
node {
|
|
114
|
+
id
|
|
115
|
+
ref
|
|
116
|
+
type
|
|
117
|
+
status
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Pick the **first active product catalogue** (usually ref like `PC:MASTER:*` or similar).
|
|
125
|
+
|
|
126
|
+
### Step 5 — Products with inventory
|
|
127
|
+
|
|
128
|
+
```graphql
|
|
129
|
+
{
|
|
130
|
+
standardProducts(first: 10, catalogue: { ref: "<CATALOGUE_REF>" }) {
|
|
131
|
+
edges {
|
|
132
|
+
node {
|
|
133
|
+
id
|
|
134
|
+
ref
|
|
135
|
+
name
|
|
136
|
+
status
|
|
137
|
+
variants(first: 5) {
|
|
138
|
+
edges {
|
|
139
|
+
node {
|
|
140
|
+
id
|
|
141
|
+
ref
|
|
142
|
+
name
|
|
143
|
+
status
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
If no `standardProducts`, try `variantProducts` directly:
|
|
154
|
+
|
|
155
|
+
```graphql
|
|
156
|
+
{
|
|
157
|
+
variantProducts(first: 10, catalogue: { ref: "<CATALOGUE_REF>" }) {
|
|
158
|
+
edges {
|
|
159
|
+
node {
|
|
160
|
+
id
|
|
161
|
+
ref
|
|
162
|
+
name
|
|
163
|
+
status
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Pick the **first active product**. Note its `ref` — this becomes the `productRef` in order items.
|
|
171
|
+
|
|
172
|
+
### Step 6 — Inventory Positions (verify stock)
|
|
173
|
+
|
|
174
|
+
```graphql
|
|
175
|
+
{
|
|
176
|
+
inventoryPositions(first: 5, ref: ["<PRODUCT_REF>"], locationRef: "<LOCATION_REF>") {
|
|
177
|
+
edges {
|
|
178
|
+
node {
|
|
179
|
+
id
|
|
180
|
+
ref
|
|
181
|
+
type
|
|
182
|
+
status
|
|
183
|
+
onHand
|
|
184
|
+
quantity {
|
|
185
|
+
... on Count { value }
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
If onHand > 0 at the target location, the product is in stock. If not, try another product or location.
|
|
194
|
+
|
|
195
|
+
### Step 7 — Virtual Catalogues
|
|
196
|
+
|
|
197
|
+
```graphql
|
|
198
|
+
{
|
|
199
|
+
virtualCatalogues(first: 5) {
|
|
200
|
+
edges {
|
|
201
|
+
node {
|
|
202
|
+
id
|
|
203
|
+
ref
|
|
204
|
+
type
|
|
205
|
+
status
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Step 8 — Existing Customers
|
|
213
|
+
|
|
214
|
+
```graphql
|
|
215
|
+
{
|
|
216
|
+
customers(first: 5) {
|
|
217
|
+
edges {
|
|
218
|
+
node {
|
|
219
|
+
id
|
|
220
|
+
ref
|
|
221
|
+
firstName
|
|
222
|
+
lastName
|
|
223
|
+
primaryEmail
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Pick the **first customer**. If none exist, create one (see Entity Creation below).
|
|
231
|
+
|
|
232
|
+
### Step 9 — Settings (optional — for consignment prefix, etc.)
|
|
233
|
+
|
|
234
|
+
```graphql
|
|
235
|
+
{
|
|
236
|
+
settings(first: 50, context: "RETAILER") {
|
|
237
|
+
edges {
|
|
238
|
+
node {
|
|
239
|
+
id
|
|
240
|
+
name
|
|
241
|
+
value
|
|
242
|
+
context
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Look for settings related to consignment prefix, fulfilment routing, etc.
|
|
250
|
+
|
|
251
|
+
## Phase 2: Build Discovery Summary
|
|
252
|
+
|
|
253
|
+
After running all queries, compile a summary:
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
=== Environment Discovery ===
|
|
257
|
+
Retailer: <ref> (ID: <id>)
|
|
258
|
+
|
|
259
|
+
Locations:
|
|
260
|
+
Warehouses: <ref1> (<name1>), <ref2> (<name2>)
|
|
261
|
+
Stores: <ref3> (<name3>), <ref4> (<name4>)
|
|
262
|
+
|
|
263
|
+
Networks:
|
|
264
|
+
HD: <network_ref>
|
|
265
|
+
CC: <network_ref>
|
|
266
|
+
|
|
267
|
+
Product Catalogue: <catalogue_ref>
|
|
268
|
+
|
|
269
|
+
Products (in stock):
|
|
270
|
+
<product_ref> at <location_ref> (onHand: <N>)
|
|
271
|
+
|
|
272
|
+
Virtual Catalogue: <vc_ref>
|
|
273
|
+
|
|
274
|
+
Customer: <customer_id> (<name>)
|
|
275
|
+
|
|
276
|
+
Settings:
|
|
277
|
+
consignmentPrefix: <value> (or default "A_")
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
This summary is the input to entity creation. Every ref comes from live queries — nothing invented.
|
|
281
|
+
|
|
282
|
+
## Phase 3: Entity Creation
|
|
283
|
+
|
|
284
|
+
### Create Test Order (HD)
|
|
285
|
+
|
|
286
|
+
Use discovered values to build the mutation. The `<TIMESTAMP>` should be generated as `YYYYMMDD_HHmmss` at execution time.
|
|
287
|
+
|
|
288
|
+
```graphql
|
|
289
|
+
mutation($input: CreateOrderInput!) {
|
|
290
|
+
createOrder(input: $input) {
|
|
291
|
+
id
|
|
292
|
+
ref
|
|
293
|
+
status
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Variables — all values from discovery:
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"input": {
|
|
303
|
+
"ref": "E2E_HD_<TIMESTAMP>",
|
|
304
|
+
"type": "MULTI",
|
|
305
|
+
"retailer": { "id": "<discovered.retailerId>" },
|
|
306
|
+
"customer": { "id": "<discovered.customerId>" },
|
|
307
|
+
"items": [
|
|
308
|
+
{
|
|
309
|
+
"ref": "<discovered.productRef>",
|
|
310
|
+
"quantity": 1,
|
|
311
|
+
"productRef": "<discovered.productRef>",
|
|
312
|
+
"productCatalogueRef": "<discovered.catalogueRef>",
|
|
313
|
+
"fulfilmentChoiceRef": "E2E_HD_<TIMESTAMP>-FC-HD"
|
|
314
|
+
}
|
|
315
|
+
],
|
|
316
|
+
"fulfilmentChoice": {
|
|
317
|
+
"ref": "E2E_HD_<TIMESTAMP>-FC-HD",
|
|
318
|
+
"type": "HD",
|
|
319
|
+
"deliveryType": "STANDARD",
|
|
320
|
+
"deliveryAddress": {
|
|
321
|
+
"ref": "E2E_HD_<TIMESTAMP>-ADDR",
|
|
322
|
+
"name": "<discovered.location.primaryAddress.name or 'Test Delivery'>",
|
|
323
|
+
"street": "<discovered.location.primaryAddress.street or '1 Discovery Lane'>",
|
|
324
|
+
"city": "<discovered.location.primaryAddress.city or 'Sydney'>",
|
|
325
|
+
"postcode": "<discovered.location.primaryAddress.postcode or '2000'>",
|
|
326
|
+
"country": "<discovered.location.primaryAddress.country or 'AU'>"
|
|
327
|
+
},
|
|
328
|
+
"attributes": [
|
|
329
|
+
{ "name": "sourcingLocationRef", "type": "STRING", "value": "<discovered.warehouseRef>" },
|
|
330
|
+
{ "name": "consignmentPrefix", "type": "STRING", "value": "<discovered.consignmentPrefix or 'A_'>" }
|
|
331
|
+
]
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Create Test Order (CC — Click & Collect)
|
|
338
|
+
|
|
339
|
+
Same as HD, except:
|
|
340
|
+
- `fulfilmentChoice.type` = `"CC"`
|
|
341
|
+
- `deliveryType` = `"NONE"` (customer picks up)
|
|
342
|
+
- `pickupLocationRef` attribute instead of delivery address
|
|
343
|
+
- Use a `STORE` location from discovery
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
{
|
|
347
|
+
"input": {
|
|
348
|
+
"ref": "E2E_CC_<TIMESTAMP>",
|
|
349
|
+
"type": "MULTI",
|
|
350
|
+
"retailer": { "id": "<discovered.retailerId>" },
|
|
351
|
+
"customer": { "id": "<discovered.customerId>" },
|
|
352
|
+
"items": [
|
|
353
|
+
{
|
|
354
|
+
"ref": "<discovered.productRef>",
|
|
355
|
+
"quantity": 1,
|
|
356
|
+
"productRef": "<discovered.productRef>",
|
|
357
|
+
"productCatalogueRef": "<discovered.catalogueRef>",
|
|
358
|
+
"fulfilmentChoiceRef": "E2E_CC_<TIMESTAMP>-FC-CC"
|
|
359
|
+
}
|
|
360
|
+
],
|
|
361
|
+
"fulfilmentChoice": {
|
|
362
|
+
"ref": "E2E_CC_<TIMESTAMP>-FC-CC",
|
|
363
|
+
"type": "CC",
|
|
364
|
+
"deliveryType": "NONE",
|
|
365
|
+
"pickupLocationRef": "<discovered.storeRef>",
|
|
366
|
+
"attributes": [
|
|
367
|
+
{ "name": "sourcingLocationRef", "type": "STRING", "value": "<discovered.storeRef>" },
|
|
368
|
+
{ "name": "consignmentPrefix", "type": "STRING", "value": "<discovered.consignmentPrefix or 'A_'>" }
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Create Test Order (ORDER::MULTI — explicit FC routing inputs)
|
|
376
|
+
|
|
377
|
+
Use this when testing `ORDER::MULTI` where downstream FC routing requires fulfilment-choice attributes:
|
|
378
|
+
|
|
379
|
+
```json
|
|
380
|
+
{
|
|
381
|
+
"input": {
|
|
382
|
+
"ref": "E2E_MULTI_<TIMESTAMP>",
|
|
383
|
+
"type": "MULTI",
|
|
384
|
+
"retailer": { "id": "<discovered.retailerId>" },
|
|
385
|
+
"customer": { "id": "<discovered.customerId>" },
|
|
386
|
+
"items": [
|
|
387
|
+
{
|
|
388
|
+
"ref": "<discovered.productRef>",
|
|
389
|
+
"quantity": 1,
|
|
390
|
+
"productRef": "<discovered.productRef>",
|
|
391
|
+
"productCatalogueRef": "<discovered.catalogueRef>",
|
|
392
|
+
"fulfilmentChoiceRef": "E2E_MULTI_<TIMESTAMP>-FC-HD"
|
|
393
|
+
}
|
|
394
|
+
],
|
|
395
|
+
"fulfilmentChoice": {
|
|
396
|
+
"ref": "E2E_MULTI_<TIMESTAMP>-FC-HD",
|
|
397
|
+
"type": "HD",
|
|
398
|
+
"deliveryType": "STANDARD",
|
|
399
|
+
"deliveryAddress": {
|
|
400
|
+
"ref": "E2E_MULTI_<TIMESTAMP>-ADDR",
|
|
401
|
+
"name": "Test Delivery",
|
|
402
|
+
"street": "1 Discovery Lane",
|
|
403
|
+
"city": "Sydney",
|
|
404
|
+
"postcode": "2000",
|
|
405
|
+
"country": "AU"
|
|
406
|
+
},
|
|
407
|
+
"attributes": [
|
|
408
|
+
{ "name": "sourcingLocationRef", "type": "STRING", "value": "<discovered.warehouseRef>" },
|
|
409
|
+
{ "name": "consignmentPrefix", "type": "STRING", "value": "<discovered.consignmentPrefix or 'A_'>" }
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
For `ORDER::MULTI`, `sourcingLocationRef` is a critical input for downstream fulfilment creation rules.
|
|
417
|
+
|
|
418
|
+
### Create Customer (if none found)
|
|
419
|
+
|
|
420
|
+
```graphql
|
|
421
|
+
mutation($input: CreateCustomerInput!) {
|
|
422
|
+
createCustomer(input: $input) {
|
|
423
|
+
id
|
|
424
|
+
username
|
|
425
|
+
firstName
|
|
426
|
+
lastName
|
|
427
|
+
primaryEmail
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Variables:
|
|
433
|
+
```json
|
|
434
|
+
{
|
|
435
|
+
"input": {
|
|
436
|
+
"username": "e2e-test-<TIMESTAMP>",
|
|
437
|
+
"firstName": "E2E",
|
|
438
|
+
"lastName": "Test",
|
|
439
|
+
"primaryEmail": "e2e-test@example.com",
|
|
440
|
+
"promotionOptIn": false,
|
|
441
|
+
"retailer": { "id": "<discovered.retailerId>" }
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Phase 4: Bulk Creation
|
|
447
|
+
|
|
448
|
+
For creating multiple test entities:
|
|
449
|
+
|
|
450
|
+
1. Run discovery once (Phase 1-2)
|
|
451
|
+
2. For each entity, generate a unique ref with incrementing counter: `E2E_HD_<TIMESTAMP>_001`, `_002`, etc.
|
|
452
|
+
3. Use `graphql.batchMutate` for up to 50 entities per batch:
|
|
453
|
+
|
|
454
|
+
```json
|
|
455
|
+
{
|
|
456
|
+
"mutation": "createOrder",
|
|
457
|
+
"inputs": [
|
|
458
|
+
{ "ref": "E2E_HD_<TS>_001", ... },
|
|
459
|
+
{ "ref": "E2E_HD_<TS>_002", ... }
|
|
460
|
+
],
|
|
461
|
+
"returnFields": ["id", "ref", "status"]
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Schema Constraints
|
|
466
|
+
|
|
467
|
+
These constraints are enforced by the GraphQL schema. Violating them produces cryptic errors.
|
|
468
|
+
|
|
469
|
+
### Customer Creation
|
|
470
|
+
- **NO `ref` field** — `username` is the identifier, not `ref`
|
|
471
|
+
- **`promotionOptIn` (Boolean!) is required** — use `false` for test customers
|
|
472
|
+
- **`firstName` and `lastName` are optional** (despite appearing essential)
|
|
473
|
+
- **`username` must be unique** — append timestamps: `e2e-test-<YYYYMMDD_HHmmss>`
|
|
474
|
+
|
|
475
|
+
### Order Creation
|
|
476
|
+
- **`retailer.id` must be a String**: `{ "id": "2" }` not `{ "id": 2 }`
|
|
477
|
+
- **`customer.id` must be a String** — use the ID from discovery or customer creation
|
|
478
|
+
- **`productCatalogueRef`** must match an existing catalogue's exact `ref` — discover it, don't guess
|
|
479
|
+
- **`fulfilmentChoice.ref`** must be unique per order — generate from order ref
|
|
480
|
+
|
|
481
|
+
### Settings Queries (used in discovery)
|
|
482
|
+
- **`context` is a plain String**: `context: "RETAILER"` not `context: { contextType: "RETAILER" }`
|
|
483
|
+
- **Pagination** requires `query($cursor: String)` variable declaration with `variables: { cursor: null }`
|
|
484
|
+
|
|
485
|
+
### Ref Collision Prevention
|
|
486
|
+
Before creating any entity, check if the ref already exists:
|
|
487
|
+
```graphql
|
|
488
|
+
{ orders(first: 1, ref: ["<proposed_ref>"]) { edges { node { id ref status } } } }
|
|
489
|
+
```
|
|
490
|
+
If edges is non-empty, append a counter or new timestamp.
|
|
491
|
+
|
|
492
|
+
## Troubleshooting Discovery
|
|
493
|
+
|
|
494
|
+
| Problem | Cause | Fix |
|
|
495
|
+
|---------|-------|-----|
|
|
496
|
+
| No locations found | Retailer has no locations configured | Check `locations` with no filter, or create via `/fluent-retailer-config` |
|
|
497
|
+
| No products found | Wrong catalogue ref or empty catalogue | Try other catalogues, check `productCatalogues` for all options |
|
|
498
|
+
| Zero inventory | Product exists but no stock at location | Check other locations, or create inventory position via `/fluent-retailer-config` |
|
|
499
|
+
| No customers | Retailer has no customer entities | Create one using the mutation above (remember: no `ref`, need `promotionOptIn`) |
|
|
500
|
+
| Schema error on mutation | Fluent version difference or custom schema | Use `graphql.introspect` to check exact input fields |
|
|
501
|
+
| `productCatalogueRef` rejected | Catalogue might use `ref` format `PC:NAME:VERSION` | Query catalogues and use exact ref format returned |
|
|
502
|
+
| Customer creation fails with "ref" error | `ref` field doesn't exist on `CreateCustomerInput` | Remove `ref`, use `username` as the identifier |
|
|
503
|
+
| Customer creation fails with required field | Missing `promotionOptIn` | Add `"promotionOptIn": false` to input |
|
|
504
|
+
| Order reaches ON_VALIDATION/IN_PROGRESS but no fulfilment created | Discovered refs may be from a different retailer scope | Re-run Step 1B scope checks; replace `productCatalogueRef` and `sourcingLocationRef` with retailer-valid refs |
|
|
505
|
+
|
|
506
|
+
## Tips
|
|
507
|
+
|
|
508
|
+
- **Run discovery fresh each time** — Locations, products, and inventory can change between sessions
|
|
509
|
+
- **Prefer products with inventory** — Orders for out-of-stock products may fail allocation
|
|
510
|
+
- **Use warehouse addresses for delivery** — When no other address is available, use the sourcing location's address as a fallback
|
|
511
|
+
- **Check workflow type** — Different retailers may use `HD`, `CC`, `SFS`, or custom subtypes. Query `workflow.transitions` to discover what's configured
|
|
512
|
+
- **Schema varies by account** — Always use `graphql.introspect` if a mutation fails; field names may differ
|
|
513
|
+
- **Cross-reference with `/fluent-retailer-config`** for entity creation constraints — it has the full validated Schema Patterns table
|