Creating Endpoint Validation Fixtures
When endpoint validation fails in production, the error response now includes diagnostic context that can be used to create local regression tests.
# Creating Endpoint Validation Fixtures
When endpoint validation fails in production, the error response now includes diagnostic context that can be used to create local regression tests.
## Diagnostic Context Structure
When a random/weighted step fails to resolve its item source, the error includes:
```yml
error:
code: ENDPOINT_ERROR.RANDOM
message: "Random \"fish\": could not resolve item source (game values table \"fish_types\"\
\ not found. Available tables: [ore_types, loot_table])."
step:
id: fish
type: random
diagnostic:
projectId: 85ed638340264f44
endpointId: ep-123
endpointSlug: cast-fish
method: POST
stepId: fish
stepType: random
sourceReference:
type: values
table: fish_types
from: null
weightField: rarity
availableSources:
tables:
- ore_types
- loot_table
collections:
- players
- inventory
failureReason: "game values table \"fish_types\" not found. Available tables:\
\ [ore_types, loot_table]"
missing: table
resolvedTo: null
fixture:
_fixtureComment: Auto-generated from validation failure. Replace <redacted>
values with test data.
projectId: test_project
endpoint:
slug: cast-fish
method: POST
failingStep:
id: fish
type: random
source: values
table: fish_types
weightField: rarity
availableTables:
- ore_types
- loot_table
gameValuesFixture:
fish_types:
- id: example_row
rarity: 100
expectedOutcome: pass after adding missing table/source
```
## Creating a Local Regression Test
### Step 1: Copy the Diagnostic Context
From a production error log or admin validation report, copy the `diagnostic` and `fixture` objects from the error response.
### Step 2: Create a Test File
Add a new test case in `tests/endpoint-runner.test.js`:
```javascript
test("REGRESSION: cast-fish missing fish_types table", async () => {
// Endpoint definition matching production config
const endpoint = {
slug: "cast-fish",
method: "POST",
steps: [
{
id: "fish",
type: "random",
mode: "weighted",
source: "values",
table: "fish_types", // The table that was missing
weightField: "rarity",
},
],
response: { status: 200, body: { caught: "{{fish.name}}" } },
};
// Reproduce the failure: no fish_types table
const failResult = await executeEndpoint(endpoint, dryCtx({
gameValues: {
ore_types: [{ id: "gold", weight: 10 }], // Available tables from diagnostic
loot_table: [{ id: "sword", weight: 50 }],
},
}));
expect(failResult.ok).toBe(false);
expect(failResult.body.error.step.diagnostic.sourceReference.table).toBe("fish_types");
expect(failResult.body.error.step.diagnostic.availableSources.tables).toContain("ore_types");
// Verify the fix: add the missing table
const passResult = await executeEndpoint(endpoint, dryCtx({
gameValues: {
fish_types: [
{ name: "Bass", rarity: 50 },
{ name: "Salmon", rarity: 30 },
],
},
}));
expect(passResult.ok).toBe(true);
});
```
### Step 3: Use the Fixture Hint
The `fixture.gameValuesFixture` object shows the minimum data structure needed to make the step pass:
```javascript
// From fixture.gameValuesFixture
gameValues: {
fish_types: [{ id: "example_row", rarity: 100 }]
}
```
## Common Failure Patterns
### Missing Table
**Error**: `game values table "X" not found`
**Diagnostic fields**:
- `sourceReference.type`: "values"
- `sourceReference.table`: the table that was requested
- `availableSources.tables`: tables that do exist
**Fix**: Add the missing table to game values, or correct the table name if it's a typo.
### Missing From Step
**Error**: `from "{{X}}" resolved to null/undefined`
**Diagnostic fields**:
- `sourceReference.type`: "from"
- `sourceReference.fromRef`: the step reference that failed
- `resolvedTo`: what the reference resolved to
**Fix**: Ensure the referenced step exists and returns an array.
### Empty Items Array
**Error**: `inline items resolved to an empty array`
**Diagnostic fields**:
- `sourceReference.type`: "inline"
- `missing`: "items"
**Fix**: Add at least one item to the items array.
## Static Validation Diagnostics
The `validateEndpointStatic` function also returns structured diagnostics:
```javascript
const { warnings, diagnostics } = validateEndpointStatic(endpoint, gameValues, collections);
for (const diag of diagnostics) {
console.log(`Step ${diag.stepId}: ${diag.issueType}`);
console.log(` Available tables: ${diag.availableTables.join(", ")}`);
console.log(` Fix hint: ${diag.fixtureHint}`);
}
```
## Sanitization
Diagnostic context is sanitized before being returned:
- No API keys, auth tokens, or secrets
- No raw player data or large payloads
- Collection names only (no schema details)
- Where clause values are redacted if over 100 characters