Skip to content

Commit

Permalink
fix: do not report operations that don't pass GraphQL validation
Browse files Browse the repository at this point in the history
  • Loading branch information
n1ru4l committed Jan 16, 2024
1 parent 3fdcba7 commit 1f3909f
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/eight-swans-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@graphql-hive/client": patch
---

Do not report operations that do not pass GraphQL validation.
1 change: 1 addition & 0 deletions packages/libraries/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@apollo/server": "4.10.0",
"@apollo/subgraph": "2.6.3",
"@envelop/types": "5.0.0",
"@graphql-yoga/plugin-disable-introspection": "2.1.1",
"@types/async-retry": "1.4.8",
"graphql": "16.8.1",
"graphql-yoga": "5.1.1",
Expand Down
44 changes: 24 additions & 20 deletions packages/libraries/client/src/yoga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export function useHive(clientOrOptions: HiveClient | HivePluginOptions): Plugin
return;
}

// Report if execution happened (aka executionArgs have been set within onExecute)
if (record.executionArgs) {
record.callback(
{
Expand All @@ -83,27 +84,30 @@ export function useHive(clientOrOptions: HiveClient | HivePluginOptions): Plugin
return;
}

if (!record.paramsArgs.query || !latestSchema) {
return;
}

try {
let document = parsedDocumentCache.get(record.paramsArgs.query);
if (document === undefined) {
document = parse(record.paramsArgs.query);
parsedDocumentCache.set(record.paramsArgs.query, document);
// Report if execution was skipped due to response cache ( Symbol.for('servedFromResponseCache') in context.result)
if (
record.paramsArgs.query &&
latestSchema &&
Symbol.for('servedFromResponseCache') in context.result
) {
try {
let document = parsedDocumentCache.get(record.paramsArgs.query);
if (document === undefined) {
document = parse(record.paramsArgs.query);
parsedDocumentCache.set(record.paramsArgs.query, document);
}
record.callback(
{
document,
schema: latestSchema,
variableValues: record.paramsArgs.variables,
operationName: record.paramsArgs.operationName,
},
context.result,
);
} catch (err) {
console.error(err);
}
record.callback(
{
document,
schema: latestSchema,
variableValues: record.paramsArgs.variables,
operationName: record.paramsArgs.operationName,
},
context.result,
);
} catch {
// ignore
}
},
};
Expand Down
102 changes: 102 additions & 0 deletions packages/libraries/client/tests/yoga.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import axios from 'axios';
import { createSchema, createYoga } from 'graphql-yoga';
// eslint-disable-next-line import/no-extraneous-dependencies
import nock from 'nock';
import { useDisableIntrospection } from '@graphql-yoga/plugin-disable-introspection';

Check failure on line 6 in packages/libraries/client/tests/yoga.spec.ts

View workflow job for this annotation

GitHub Actions / code-style / eslint-and-prettier

'@graphql-yoga/plugin-disable-introspection' should be listed in the project's dependencies, not devDependencies
// eslint-disable-next-line import/no-extraneous-dependencies
import { useResponseCache } from '@graphql-yoga/plugin-response-cache';
import { useHive } from '../src/yoga.js';
Expand Down Expand Up @@ -225,3 +226,104 @@ it('reports usage with response cache', async () => {
}, 1000);
});
});

it('does not report usage for operation that does not pass validation', async () => {
const callback = vi.fn();
const graphqlScope = nock('http://localhost')
.post('/graphql')
.reply(200, {
data: {
__typename: 'Query',
tokenInfo: {
__typename: 'TokenInfo',
token: {
name: 'brrrt',
},
organization: {
name: 'mom',
cleanId: 'ur-mom',
},
project: {
name: 'projecto',
type: 'FEDERATION',
cleanId: 'projecto',
},
target: {
name: 'projecto',
cleanId: 'projecto',
},
canReportSchema: true,
canCollectUsage: true,
canReadOperations: true,
},
},
});

const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
type Query {
hi: String
}
`,
}),
plugins: [
useDisableIntrospection(),
useHive({
enabled: true,
debug: true,
token: 'brrrt',
selfHosting: {
applicationUrl: 'http://localhost/foo',
graphqlEndpoint: 'http://localhost/graphql',
usageEndpoint: 'http://localhost/usage',
},
usage: {
endpoint: 'http://localhost/usage',
clientInfo() {
return {
name: 'brrr',
version: '1',
};
},
},
agent: {
maxSize: 1,
},
}),
],
});

// eslint-disable-next-line no-async-promise-executor
await new Promise<void>(async (resolve, reject) => {
nock.emitter.once('no match', (req: any) => {
reject(new Error(`Unexpected request was sent to ${req.path}`));
});

const res = await yoga.fetch('http://localhost/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: /* GraphQL */ `
{
__schema {
types {
name
}
}
}
`,
}),
});
expect(res.status).toBe(200);
expect(await res.text()).toContain('GraphQL introspection has been disabled');

setTimeout(() => {
graphqlScope.done();
expect(callback).not.toHaveBeenCalled();
resolve();
}, 1000);
});
});
2 changes: 1 addition & 1 deletion packages/services/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@escape.tech/graphql-armor-max-tokens": "2.3.0",
"@graphql-hive/client": "workspace:*",
"@graphql-yoga/plugin-persisted-operations": "3.1.1",
"@graphql-yoga/plugin-response-cache": "3.2.1",
"@graphql-yoga/plugin-response-cache": "3.3.0-alpha-20240116103344-655e7299",
"@hive/api": "workspace:*",
"@hive/cdn-script": "workspace:*",
"@hive/service-common": "workspace:*",
Expand Down
24 changes: 19 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1f3909f

Please sign in to comment.