Skip to content

Commit 3814331

Browse files
committed
Merge branch 'dev' into sh/sliding-window-log
2 parents a0a6ddc + 24fa089 commit 3814331

File tree

4 files changed

+234
-23
lines changed

4 files changed

+234
-23
lines changed

src/analysis/buildTypeWeights.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,9 @@ function parseObjectFields(
8080
// Iterate through the fields and add the required data to the result
8181
Object.keys(fields).forEach((field: string) => {
8282
// The GraphQL type that this field represents
83-
const fieldType: GraphQLOutputType = fields[field].type;
84-
if (
85-
isScalarType(fieldType) ||
86-
(isNonNullType(fieldType) && isScalarType(fieldType.ofType))
87-
) {
83+
let fieldType: GraphQLOutputType = fields[field].type;
84+
if (isNonNullType(fieldType)) fieldType = fieldType.ofType;
85+
if (isScalarType(fieldType)) {
8886
result.fields[field] = {
8987
weight: typeWeights.scalar,
9088
// resolveTo: fields[field].name.toLowerCase(),
@@ -100,7 +98,8 @@ function parseObjectFields(
10098
};
10199
} else if (isListType(fieldType)) {
102100
// 'listType' is the GraphQL type that the list resolves to
103-
const listType = fieldType.ofType;
101+
let listType = fieldType.ofType;
102+
if (isNonNullType(listType)) listType = listType.ofType;
104103
if (isScalarType(listType) && typeWeights.scalar === 0) {
105104
// list won't compound if weight is zero
106105
result.fields[field] = {
@@ -115,7 +114,6 @@ function parseObjectFields(
115114
fields[field].args.forEach((arg: GraphQLArgument) => {
116115
// If field has an argument matching one of the limiting keywords and resolves to a list
117116
// then the weight of the field should be dependent on both the weight of the resolved type and the limiting argument.
118-
// FIXME: Can nonnull wrap list types?
119117
if (KEYWORDS.includes(arg.name)) {
120118
// Get the type that comprises the list
121119
result.fields[field] = {
@@ -183,6 +181,7 @@ function compareTypes(a: GraphQLOutputType, b: GraphQLOutputType): boolean {
183181
return (
184182
(isObjectType(b) && isObjectType(a) && a.name === b.name) ||
185183
(isUnionType(b) && isUnionType(a) && a.name === b.name) ||
184+
(isEnumType(b) && isEnumType(a) && a.name === b.name) ||
186185
(isInterfaceType(b) && isInterfaceType(a) && a.name === b.name) ||
187186
(isScalarType(b) && isScalarType(a) && a.name === b.name) ||
188187
(isListType(b) && isListType(a) && compareTypes(b.ofType, a.ofType)) ||
@@ -289,24 +288,26 @@ function parseUnionTypes(
289288
* c. objects have a resolveTo type.
290289
* */
291290

292-
const current = commonFields[field].type;
291+
let current = commonFields[field].type;
292+
if (isNonNullType(current)) current = current.ofType;
293293
if (isScalarType(current)) {
294294
fieldTypes[field] = {
295295
weight: commonFields[field].weight,
296296
};
297-
} else if (isObjectType(current) || isInterfaceType(current) || isUnionType(current)) {
297+
} else if (
298+
isObjectType(current) ||
299+
isInterfaceType(current) ||
300+
isUnionType(current) ||
301+
isEnumType(current)
302+
) {
298303
fieldTypes[field] = {
299304
resolveTo: commonFields[field].resolveTo,
300-
weight: typeWeights.object,
301305
};
302306
} else if (isListType(current)) {
303307
fieldTypes[field] = {
304308
resolveTo: commonFields[field].resolveTo,
305309
weight: commonFields[field].weight,
306310
};
307-
} else if (isNonNullType(current)) {
308-
throw new Error('non null types not supported on unions');
309-
// TODO: also a recursive data structure
310311
} else {
311312
throw new Error('Unhandled union type. Should never get here');
312313
}

test/analysis/buildTypeWeights.test.ts

Lines changed: 184 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -460,8 +460,8 @@ describe('Test buildTypeWeightsFromSchema function', () => {
460460
});
461461
});
462462

463-
describe('union types', () => {
464-
test('union types', () => {
463+
describe('union types with ...', () => {
464+
test('lists of union types and scalars', () => {
465465
schema = buildSchema(`
466466
union SearchResult = Human | Droid
467467
type Human{
@@ -510,14 +510,136 @@ describe('Test buildTypeWeightsFromSchema function', () => {
510510
});
511511
});
512512

513-
xtest('additional test cases for ...', () => {
514-
// TODO: unions with non-null types
515-
// unions with lists of non-null types
516-
// lists with > 2 levels of nesting (may need to add these for lists on other types as well)
513+
test('object types', () => {
514+
schema = buildSchema(`
515+
union SearchResult = Human | Droid
516+
type Human{
517+
name: String
518+
homePlanet: String
519+
info: Info
520+
search(first: Int!): [SearchResult]
521+
}
522+
type Droid {
523+
name: String
524+
primaryFunction: String
525+
info: Info
526+
search(first: Int!): [SearchResult]
527+
}
528+
type Info {
529+
height: Int
530+
}
531+
`);
532+
expect(buildTypeWeightsFromSchema(schema)).toEqual({
533+
searchresult: {
534+
weight: 1,
535+
fields: {
536+
name: { weight: 0 },
537+
search: {
538+
resolveTo: 'searchresult',
539+
weight: expect.any(Function),
540+
},
541+
info: { resolveTo: 'info' },
542+
},
543+
},
544+
human: {
545+
weight: 1,
546+
fields: {
547+
name: { weight: 0 },
548+
homePlanet: { weight: 0 },
549+
search: {
550+
resolveTo: 'searchresult',
551+
weight: expect.any(Function),
552+
},
553+
info: { resolveTo: 'info' },
554+
},
555+
},
556+
droid: {
557+
weight: 1,
558+
fields: {
559+
name: { weight: 0 },
560+
primaryFunction: { weight: 0 },
561+
search: {
562+
resolveTo: 'searchresult',
563+
weight: expect.any(Function),
564+
},
565+
info: { resolveTo: 'info' },
566+
},
567+
},
568+
info: {
569+
weight: 1,
570+
fields: {
571+
height: { weight: 0 },
572+
},
573+
},
574+
});
575+
});
576+
577+
test('enum types', () => {
578+
schema = buildSchema(`
579+
union SearchResult = Human | Droid
580+
type Human{
581+
name: String
582+
homePlanet: String
583+
episode: Episode
584+
search(first: Int!): [SearchResult]
585+
}
586+
type Droid {
587+
name: String
588+
primaryFunction: String
589+
episode: Episode
590+
search(first: Int!): [SearchResult]
591+
}
592+
enum Episode {
593+
NEWHOPE
594+
EMPIRE
595+
JEDI
596+
}
597+
`);
598+
expect(buildTypeWeightsFromSchema(schema)).toEqual({
599+
searchresult: {
600+
weight: 1,
601+
fields: {
602+
episode: { resolveTo: 'episode' },
603+
name: { weight: 0 },
604+
search: {
605+
resolveTo: 'searchresult',
606+
weight: expect.any(Function),
607+
},
608+
},
609+
},
610+
human: {
611+
weight: 1,
612+
fields: {
613+
name: { weight: 0 },
614+
homePlanet: { weight: 0 },
615+
search: {
616+
resolveTo: 'searchresult',
617+
weight: expect.any(Function),
618+
},
619+
episode: { resolveTo: 'episode' },
620+
},
621+
},
622+
droid: {
623+
weight: 1,
624+
fields: {
625+
name: { weight: 0 },
626+
primaryFunction: { weight: 0 },
627+
search: {
628+
resolveTo: 'searchresult',
629+
weight: expect.any(Function),
630+
},
631+
episode: { resolveTo: 'episode' },
632+
},
633+
},
634+
episode: {
635+
weight: 0,
636+
fields: {},
637+
},
638+
});
517639
});
518640
});
519641

520-
xdescribe('Not null operator (!) is used', () => {
642+
describe('Not null operator (!) is used', () => {
521643
test('on a scalar, enum or object type', () => {
522644
schema = buildSchema(`
523645
type Human{
@@ -612,6 +734,60 @@ describe('Test buildTypeWeightsFromSchema function', () => {
612734
},
613735
});
614736
});
737+
738+
test('on union types', () => {
739+
schema = buildSchema(`
740+
union SearchResult = Human | Droid
741+
type Human{
742+
age: Int!
743+
name: String
744+
homePlanet: String
745+
search(first: Int!): [SearchResult!]!
746+
}
747+
type Droid {
748+
age: Int!
749+
name: String
750+
primaryFunction: String!
751+
search(first: Int!): [SearchResult!]!
752+
}`);
753+
expect(buildTypeWeightsFromSchema(schema)).toEqual({
754+
searchresult: {
755+
weight: 1,
756+
fields: {
757+
name: { weight: 0 },
758+
age: { weight: 0 },
759+
search: {
760+
resolveTo: 'searchresult',
761+
weight: expect.any(Function),
762+
},
763+
},
764+
},
765+
human: {
766+
weight: 1,
767+
fields: {
768+
name: { weight: 0 },
769+
age: { weight: 0 },
770+
homePlanet: { weight: 0 },
771+
search: {
772+
resolveTo: 'searchresult',
773+
weight: expect.any(Function),
774+
},
775+
},
776+
},
777+
droid: {
778+
weight: 1,
779+
fields: {
780+
name: { weight: 0 },
781+
age: { weight: 0 },
782+
primaryFunction: { weight: 0 },
783+
search: {
784+
resolveTo: 'searchresult',
785+
weight: expect.any(Function),
786+
},
787+
},
788+
},
789+
});
790+
});
615791
});
616792

617793
// TODO: Tests should be written to account for the additional scenarios possible in a schema
@@ -669,7 +845,7 @@ describe('Test buildTypeWeightsFromSchema function', () => {
669845
});
670846

671847
// this is only if we choose to have 'query' as its own property (seperate from object types) in the user configuration options
672-
xtest('query parameter', () => {
848+
test('query parameter', () => {
673849
const typeWeightObject = buildTypeWeightsFromSchema(schema, {
674850
query: 2,
675851
});

test/analysis/typeComplexityAnalysis.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,40 @@ describe('Test getQueryTypeComplexity function', () => {
600600
expect(getQueryTypeComplexity(parse(query), {}, unionTypeWeights)).toBe(5);
601601
});
602602

603+
test('that have greater than 2 levels of nesting', () => {
604+
query = `
605+
query {
606+
hero(episode: EMPIRE) {
607+
name
608+
... on Droid {
609+
primaryFunction
610+
friends(first: 5) {
611+
name
612+
friends(first: 3) {
613+
name
614+
}
615+
}
616+
}
617+
... on Human {
618+
homePlanet
619+
friends(first: 5) {
620+
name
621+
friends(first: 3) {
622+
name
623+
}
624+
}
625+
}
626+
}
627+
}`;
628+
mockCharacterFriendsFunction.mockReturnValue(3);
629+
mockDroidFriendsFunction.mockReturnValueOnce(20);
630+
mockHumanFriendsFunction.mockReturnValueOnce(20);
631+
// Query 1 + 1 hero + 3 friends/character
632+
expect(getQueryTypeComplexity(parse(query), variables, unionTypeWeights)).toBe(
633+
22
634+
);
635+
});
636+
603637
xtest('that include a directive', () => {
604638
query = `
605639
query {

test/analysis/weightFunction.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ describe('Weight Function correctly parses Argument Nodes if', () => {
8484
});
8585
});
8686

87-
xtest('the list is defined with non-null operators (!)', () => {
87+
test('the list is defined with non-null operators (!)', () => {
8888
const villainsQuery = `query { villains(episode: NEWHOPE, limit: 3) { stars, episode } }`;
8989
const willainsQueryAST: DocumentNode = parse(villainsQuery);
9090
expect(getQueryTypeComplexity(willainsQueryAST, {}, typeWeights)).toBe(4);
@@ -93,7 +93,7 @@ describe('Weight Function correctly parses Argument Nodes if', () => {
9393
const charQueryAST: DocumentNode = parse(charQuery);
9494
expect(getQueryTypeComplexity(charQueryAST, {}, typeWeights)).toBe(4);
9595

96-
const droidsQuery = `droidsQuery { droids(episode: NEWHOPE, limit: 3) { stars, episode } }`;
96+
const droidsQuery = `query droidsQuery { droids(episode: NEWHOPE, limit: 3) { stars, episode } }`;
9797
const droidsQueryAST: DocumentNode = parse(droidsQuery);
9898
expect(getQueryTypeComplexity(droidsQueryAST, {}, typeWeights)).toBe(4);
9999
});

0 commit comments

Comments
 (0)