Skip to content

Commit d1019c2

Browse files
committed
add expectedFieldsStack
1 parent db089f7 commit d1019c2

File tree

5 files changed

+103
-27
lines changed

5 files changed

+103
-27
lines changed

src/methods/validate-fixture-input.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,24 @@ import {
1111
} from "graphql";
1212
import { inlineNamedFragmentSpreads } from "../utils/inline-named-fragment-spreads.js";
1313

14+
function checkForExtraFields(
15+
fixtureObjects: any[],
16+
expectedFields: Set<string>
17+
): string[] {
18+
const errors: string[] = [];
19+
for (const fixtureObject of fixtureObjects) {
20+
if (typeof fixtureObject === "object" && fixtureObject !== null && !Array.isArray(fixtureObject)) {
21+
const fixtureFields = Object.keys(fixtureObject);
22+
for (const fixtureField of fixtureFields) {
23+
if (!expectedFields.has(fixtureField)) {
24+
errors.push(`Extra field "${fixtureField}" found in fixture data`);
25+
}
26+
}
27+
}
28+
}
29+
return errors;
30+
}
31+
1432
export interface ValidateFixtureInputResult {
1533
valid: boolean;
1634
errors: string[];
@@ -24,6 +42,7 @@ export function validateFixtureInput(
2442
const inlineFragmentSpreadsAst = inlineNamedFragmentSpreads(queryAST);
2543
const typeInfo = new TypeInfo(schema);
2644
const valueStack: any[] = [[value]];
45+
const expectedFieldsStack: Set<string>[] = [new Set()]; // Initial set tracks root level fields
2746
const errors: string[] = [];
2847
visit(
2948
inlineFragmentSpreadsAst,
@@ -35,6 +54,9 @@ export function validateFixtureInput(
3554

3655
const responseKey = node.alias?.value || node.name.value;
3756

57+
// Track this field as expected in the parent's set
58+
expectedFieldsStack[expectedFieldsStack.length - 1].add(responseKey);
59+
3860
const fieldDefinition = typeInfo.getFieldDef();
3961
const fieldType = fieldDefinition?.type;
4062

@@ -76,10 +98,21 @@ export function validateFixtureInput(
7698
}
7799
}
78100

101+
// If this field has nested selections, prepare to track expected child fields
102+
if (node.selectionSet) {
103+
expectedFieldsStack.push(new Set<string>());
104+
}
105+
79106
valueStack.push(nestedValues);
80107
},
81-
leave() {
82-
valueStack.pop();
108+
leave(node) {
109+
const nestedValues = valueStack.pop()!;
110+
111+
// If this field had nested selections, check for extra fields
112+
if (node.selectionSet) {
113+
const expectedFields = expectedFieldsStack.pop()!;
114+
errors.push(...checkForExtraFields(nestedValues, expectedFields));
115+
}
83116
},
84117
},
85118
SelectionSet: {
@@ -97,6 +130,7 @@ export function validateFixtureInput(
97130
selection.kind == Kind.INLINE_FRAGMENT
98131
).length;
99132

133+
// We only need to check for __typename if there are multiple fragment spreads
100134
if (!hasTypename && fragmentSpreadCount > 1) {
101135
errors.push(
102136
`Missing __typename field for abstract type ${getNamedType(typeInfo.getType())?.name}`
@@ -107,5 +141,10 @@ export function validateFixtureInput(
107141
},
108142
})
109143
);
144+
145+
// The query's root SelectionSet has no parent Field node, so there's no Field.leave event to check it.
146+
// We manually perform the same check here that would happen in Field.leave for nested objects.
147+
errors.push(...checkForExtraFields(valueStack[0], expectedFieldsStack[0]));
148+
110149
return { valid: errors.length === 0, errors };
111150
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"payload": {
3+
"export": "test-data-processing",
4+
"target": "data.processing.generate.run",
5+
"input": {
6+
"data": {
7+
"items": [
8+
{
9+
"id": "gid://test/Item/1",
10+
"count": 2,
11+
"details": {
12+
"id": "gid://test/ItemDetails/123",
13+
"name": "Test Item"
14+
}
15+
}
16+
],
17+
"metadata": {
18+
"email": "[email protected]"
19+
}
20+
},
21+
"unexpectedRootField": "This should not be at the root level"
22+
},
23+
"output": {
24+
"title": "Test Processing Result",
25+
"count": 42,
26+
"items": []
27+
}
28+
}
29+
}

test/fixtures/data/valid/multiple-aliases-same-field.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,12 @@
77
"firstItems": [
88
{
99
"id": "gid://test/Item/1",
10-
"count": 5,
11-
"details": {
12-
"name": "First Item"
13-
}
10+
"count": 5
1411
}
1512
],
1613
"secondItems": [
1714
{
1815
"id": "gid://test/Item/1",
19-
"count": 5,
2016
"details": {
2117
"name": "First Item"
2218
}

test/methods/validate-fixture-input.test.ts

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -206,24 +206,21 @@ describe("validateFixtureInput", () => {
206206
);
207207
});
208208

209-
// // Skipping this test because it's not yet supported
210-
// it("invalid/extra-fields.json + queries/valid/basic.graphql", async () => {
211-
// const queryAST = await loadInputQuery(
212-
// "./test/fixtures/queries/valid/basic.graphql"
213-
// );
214-
// const fixture = await loadFixture(
215-
// "./test/fixtures/data/invalid/extra-fields.json"
216-
// );
217-
// const result = await validateFixtureInput(queryAST, schema, fixture.input);
209+
it("invalid/extra-fields.json + queries/valid/basic.graphql", async () => {
210+
const queryAST = await loadInputQuery(
211+
"./test/fixtures/queries/valid/basic.graphql"
212+
);
213+
const fixture = await loadFixture(
214+
"./test/fixtures/data/invalid/extra-fields.json"
215+
);
216+
const result = await validateFixtureInput(queryAST, schema, fixture.input);
218217

219-
// expect(result.valid).toBe(false);
220-
// expect(result.errors.length).toBe(3);
221-
// expect(result.errors[0]).toBe('Missing field "metadata" at data');
222-
// expect(result.errors[1]).toBe('Missing field "details" at data.items[0]');
223-
// expect(result.errors[2]).toBe(
224-
// 'Extra field "extraField" at data.items[0]'
225-
// );
226-
// });
218+
expect(result.valid).toBe(false);
219+
expect(result.errors.length).toBe(3);
220+
expect(result.errors[0]).toBe('Missing expected fixture data for details');
221+
expect(result.errors[1]).toBe('Extra field "extraField" found in fixture data');
222+
expect(result.errors[2]).toBe('Missing expected fixture data for metadata');
223+
});
227224
it("invalid/scalar-mismatch.json + queries/valid/basic.graphql", async () => {
228225
const queryAST = await loadInputQuery(
229226
"./test/fixtures/queries/valid/basic.graphql"
@@ -273,5 +270,19 @@ describe("validateFixtureInput", () => {
273270
"Missing expected fixture data for metadata"
274271
);
275272
});
273+
274+
it("invalid/extra-fields-at-root.json + queries/valid/basic.graphql", async () => {
275+
const queryAST = await loadInputQuery(
276+
"./test/fixtures/queries/valid/basic.graphql"
277+
);
278+
const fixture = await loadFixture(
279+
"./test/fixtures/data/invalid/extra-fields-at-root.json"
280+
);
281+
282+
const result = validateFixtureInput(queryAST, schema, fixture.input);
283+
expect(result.valid).toBe(false);
284+
expect(result.errors.length).toBe(1);
285+
expect(result.errors[0]).toBe('Extra field "unexpectedRootField" found in fixture data');
286+
});
276287
});
277288
});

test/methods/validate-test-assets.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,12 @@ describe('validateTestAssets', () => {
149149

150150
expect(result.inputQuery.valid).toBe(true);
151151

152-
// Input fixture should be invalid due to missing fields
152+
// Input fixture should be invalid due to missing fields and extra field
153153
expect(result.inputFixture.valid).toBe(false);
154-
expect(result.inputFixture.errors.length).toBe(2);
154+
expect(result.inputFixture.errors.length).toBe(3);
155155
expect(result.inputFixture.errors[0]).toBe('Missing expected fixture data for details');
156-
expect(result.inputFixture.errors[1]).toBe('Missing expected fixture data for metadata');
156+
expect(result.inputFixture.errors[1]).toBe('Extra field "invalidField" found in fixture data');
157+
expect(result.inputFixture.errors[2]).toBe('Missing expected fixture data for metadata');
157158

158159
expect(result.outputFixture.valid).toBe(true);
159160
});

0 commit comments

Comments
 (0)