Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4e24b10
feat: improve input and argument usage
StarpTech Nov 18, 2025
9c233aa
fix: operation checks for input and argument changes
JivusAyrus Nov 18, 2025
6169226
chore: add bench
StarpTech Nov 20, 2025
613829a
chore: implement advanced schema usage
StarpTech Nov 21, 2025
70dc6df
chore: add more tests
StarpTech Nov 22, 2025
bbe19fa
chore: track input usage also when variables are empty
StarpTech Nov 24, 2025
4678124
fix: operation checks for input and argument usage
JivusAyrus Nov 28, 2025
ae3c079
refactor: update field type change categories for clarity and consist…
JivusAyrus Nov 28, 2025
58531dc
test: enhance schema change tests for clarity and coverage
JivusAyrus Nov 28, 2025
cd95ed2
refactor: unify inspector schema change handling by removing group st…
JivusAyrus Nov 28, 2025
b42ec8e
fix: correct SQL query formatting in SchemaUsageTrafficInspector
JivusAyrus Nov 28, 2025
52609fe
refactor: remove redundant comment in SchemaUsageTrafficInspector
JivusAyrus Nov 28, 2025
66dd9bf
Merge branch 'main' of github.com:wundergraph/cosmo into dustin/eng-8…
JivusAyrus Dec 1, 2025
06d81a9
fix: pr suggestions
JivusAyrus Dec 1, 2025
a1ebd48
fix: nil check for variables, handle null lists
StarpTech Dec 1, 2025
64e14cd
fix: variable usage for empty list, refactor to use just one walk ove…
StarpTech Dec 1, 2025
d2a5722
fix: improve condition handling for SchemaUsageTrafficInspector
JivusAyrus Dec 1, 2025
9ec6f06
chore: add test for nested arguments
StarpTech Dec 2, 2025
3bcc363
Merge branch 'main' of github.com:wundergraph/cosmo into dustin/eng-8…
JivusAyrus Dec 3, 2025
35b66b6
feat: enhance schema change handling with structured metadata
JivusAyrus Dec 3, 2025
9733d9a
fix: tests
JivusAyrus Dec 3, 2025
251354f
chore: revert to b.loop in benchs
StarpTech Dec 3, 2025
872ec6f
chore: revert dev config
StarpTech Dec 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 288 additions & 10 deletions controlplane/src/core/services/SchemaUsageTrafficInspector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ describe('Schema Change converter', (ctx) => {

const changes = await getBreakingChanges(a, b);

// the below conditions are for what would constitute a breaking change
// if the condition exists, it would be breaking
expect(changes).toEqual<InspectorSchemaChange[]>([
{
isArgument: true,
path: ['a', 'b'],
schemaChangeId: '0',
path: ['a'],
typeName: 'Query',
},
]);
Expand Down Expand Up @@ -51,13 +52,162 @@ describe('Schema Change converter', (ctx) => {

expect(changes).toEqual<InspectorSchemaChange[]>([
{
isArgument: true,
path: ['details', 'all'],
schemaChangeId: '0',
path: ['details'],
typeName: 'Rocket',
},
]);
});

test('Remove a required argument', async () => {
const a = buildSchema(/* GraphQL */ `
type Query {
a(b: Boolean!): String
}
`);
const b = buildSchema(/* GraphQL */ `
type Query {
a: String
}
`);

const changes = await getBreakingChanges(a, b);

expect(changes).toEqual<InspectorSchemaChange[]>([
{
schemaChangeId: '0',
path: ['a'],
typeName: 'Query',
},
]);
});

test('Remove an optional argument', async () => {
const a = buildSchema(/* GraphQL */ `
type Query {
a(b: Boolean): String
}
`);
const b = buildSchema(/* GraphQL */ `
type Query {
a: String
}
`);

const changes = await getBreakingChanges(a, b);

expect(changes).toEqual<InspectorSchemaChange[]>([
{
schemaChangeId: '0',
path: ['a', 'b'],
typeName: 'Query',
isArgument: true,
isNull: false,
},
]);
});

test('Change argument type from optional to required same', async () => {
const a = buildSchema(/* GraphQL */ `
type Query {
a(b: Boolean): String
}
`);
const b = buildSchema(/* GraphQL */ `
type Query {
a(b: Boolean!): String
}
`);

const changes = await getBreakingChanges(a, b);

expect(changes).toEqual<InspectorSchemaChange[]>([
{
schemaChangeId: '0',
path: ['a', 'b'],
typeName: 'Query',
fieldName: 'b',
isArgument: true,
isNull: true,
},
]);
});

test('Change argument type from optional to required different', async () => {
const a = buildSchema(/* GraphQL */ `
type Query {
a(b: Boolean): String
}
`);
const b = buildSchema(/* GraphQL */ `
type Query {
a(b: String!): String
}
`);

const changes = await getBreakingChanges(a, b);

expect(changes).toEqual<InspectorSchemaChange[]>([
{
schemaChangeId: '0',
path: ['a', 'b'],
typeName: 'Query',
fieldName: 'b',
isArgument: true,
},
]);
});

test('Change argument type from required to required different', async () => {
const a = buildSchema(/* GraphQL */ `
type Query {
a(b: Boolean!): String
}
`);
const b = buildSchema(/* GraphQL */ `
type Query {
a(b: String!): String
}
`);

const changes = await getBreakingChanges(a, b);

expect(changes).toEqual<InspectorSchemaChange[]>([
{
schemaChangeId: '0',
path: ['a', 'b'],
typeName: 'Query',
fieldName: 'b',
isArgument: true,
},
]);
});

test('Change argument type from optional to optional different', async () => {
const a = buildSchema(/* GraphQL */ `
type Query {
a(b: Boolean): String
}
`);
const b = buildSchema(/* GraphQL */ `
type Query {
a(b: String): String
}
`);

const changes = await getBreakingChanges(a, b);

expect(changes).toEqual<InspectorSchemaChange[]>([
{
schemaChangeId: '0',
path: ['a', 'b'],
typeName: 'Query',
fieldName: 'b',
isArgument: true,
isNull: false,
},
]);
});
});

describe('Input', (ctx) => {
Expand All @@ -78,15 +228,67 @@ describe('Schema Change converter', (ctx) => {

expect(changes).toEqual<InspectorSchemaChange[]>([
{
fieldName: 'b',
schemaChangeId: '0',
path: ['Foo'],
isInput: true,
isNull: false,
},
]);
});

test('Remove a required input field', async () => {
const a = buildSchema(/* GraphQL */ `
input Foo {
a: String!
b: String!
}
`);
const b = buildSchema(/* GraphQL */ `
input Foo {
a: String!
}
`);

const changes = await getBreakingChanges(a, b);

expect(changes).toEqual<InspectorSchemaChange[]>([
{
schemaChangeId: '0',
typeName: 'Foo',
path: ['Foo'],
isInput: true,
isNull: false,
},
]);
});

test('Change the type of an Input field', async () => {
test('Remove an optional input field', async () => {
const a = buildSchema(/* GraphQL */ `
input Foo {
a: String!
b: String
}
`);
const b = buildSchema(/* GraphQL */ `
input Foo {
a: String!
}
`);

const changes = await getBreakingChanges(a, b);

// As we dont know whether the field is optional or required, we use the same condition as required fields
// We will not miss any breaking ops but will have some ops which might not be breaking
expect(changes).toEqual<InspectorSchemaChange[]>([
{
schemaChangeId: '0',
path: ['Foo'],
isInput: true,
isNull: false,
},
]);
});

test('Change input field type from required to required different', async () => {
const a = buildSchema(/* GraphQL */ `
input Foo {
a: String!
Expand All @@ -102,10 +304,84 @@ describe('Schema Change converter', (ctx) => {

expect(changes).toEqual<InspectorSchemaChange[]>([
{
schemaChangeId: '0',
path: ['Foo'],
isInput: true,
isNull: false,
},
]);
});

test('Change input field type from optional to required same', async () => {
const a = buildSchema(/* GraphQL */ `
input Foo {
a: String
}
`);
const b = buildSchema(/* GraphQL */ `
input Foo {
a: String!
}
`);

const changes = await getBreakingChanges(a, b);

expect(changes).toEqual<InspectorSchemaChange[]>([
{
schemaChangeId: '0',
typeName: 'Foo',
fieldName: 'a',
isInput: true,
isNull: true,
},
]);
});

test('Change input field type from optional to required different', async () => {
const a = buildSchema(/* GraphQL */ `
input Foo {
a: String
}
`);
const b = buildSchema(/* GraphQL */ `
input Foo {
a: Int!
}
`);

const changes = await getBreakingChanges(a, b);

expect(changes).toEqual<InspectorSchemaChange[]>([
{
schemaChangeId: '0',
path: ['Foo'],
isInput: true,
isNull: false,
},
]);
});

test('Change input field type from optional to optional different', async () => {
const a = buildSchema(/* GraphQL */ `
input Foo {
a: String
}
`);
const b = buildSchema(/* GraphQL */ `
input Foo {
a: Int
}
`);

const changes = await getBreakingChanges(a, b);

expect(changes).toEqual<InspectorSchemaChange[]>([
{
schemaChangeId: '0',
typeName: 'Foo',
fieldName: 'a',
isInput: true,
isNull: false,
},
]);
});
Expand Down Expand Up @@ -135,8 +411,8 @@ describe('Schema Change converter', (ctx) => {
typeName: 'Rocket',
},
{
fieldName: 'a',
schemaChangeId: '1',
fieldName: 'a',
typeName: 'Query',
},
]);
Expand Down Expand Up @@ -170,8 +446,8 @@ describe('Schema Change converter', (ctx) => {

expect(changes).toEqual<InspectorSchemaChange[]>([
{
namedType: 'enumA',
schemaChangeId: '0',
namedType: 'enumA',
},
]);
});
Expand Down Expand Up @@ -199,7 +475,7 @@ describe('Schema Change converter', (ctx) => {

async function getBreakingChanges(a: GraphQLSchema, b: GraphQLSchema): Promise<InspectorSchemaChange[]> {
const changes = await getSchemaDiff(a, b);
return changes
const groups = changes
.map((c, i) =>
toInspectorChange(
{
Expand All @@ -212,4 +488,6 @@ async function getBreakingChanges(a: GraphQLSchema, b: GraphQLSchema): Promise<I
),
)
.filter((c) => c !== null) as InspectorSchemaChange[];

return groups;
}
Loading
Loading