mirror of
https://github.com/graphql-hive/console
synced 2026-04-25 08:27:17 +00:00
425 lines
9.5 KiB
TypeScript
425 lines
9.5 KiB
TypeScript
import { type IncomingMessage } from 'node:http';
|
|
import nock from 'nock';
|
|
import { beforeEach, expect, test } from 'vitest';
|
|
import { ApolloServer } from '@apollo/server';
|
|
import { startStandaloneServer } from '@apollo/server/standalone';
|
|
import { useHive } from '../src';
|
|
|
|
beforeEach(() => {
|
|
nock.cleanAll();
|
|
});
|
|
|
|
type ApolloServerContext = {
|
|
req: IncomingMessage;
|
|
};
|
|
|
|
const logger = {
|
|
info: () => {},
|
|
error: () => {},
|
|
};
|
|
|
|
test('use persisted documents (GraphQL over HTTP "documentId")', async () => {
|
|
const httpScope = nock('http://artifacts-cdn.localhost', {
|
|
reqheaders: {
|
|
'X-Hive-CDN-Key': value => {
|
|
expect(value).toBe('foo');
|
|
return true;
|
|
},
|
|
},
|
|
})
|
|
.get('/apps/client-name/client-version/hash')
|
|
.reply(200, 'query { hi }');
|
|
|
|
const testServer = new ApolloServer({
|
|
typeDefs: /* GraphQL */ `
|
|
type Query {
|
|
hi: String
|
|
}
|
|
`,
|
|
plugins: [
|
|
useHive({
|
|
token: 'token',
|
|
experimental__persistedDocuments: {
|
|
cdn: {
|
|
endpoint: 'http://artifacts-cdn.localhost',
|
|
accessToken: 'foo',
|
|
},
|
|
},
|
|
agent: {
|
|
logger,
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
const { url } = await startStandaloneServer(testServer, {
|
|
async context({ req }): Promise<ApolloServerContext> {
|
|
return { req };
|
|
},
|
|
listen: {
|
|
port: 0,
|
|
},
|
|
});
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
documentId: 'client-name~client-version~hash',
|
|
}),
|
|
});
|
|
expect(response.status).toBe(200);
|
|
expect(await response.json()).toEqual({ data: { hi: null } });
|
|
|
|
httpScope.done();
|
|
await testServer.stop();
|
|
});
|
|
|
|
test('persisted document not found (GraphQL over HTTP "documentId")', async () => {
|
|
const httpScope = nock('http://artifacts-cdn.localhost', {
|
|
reqheaders: {
|
|
'X-Hive-CDN-Key': value => {
|
|
expect(value).toBe('foo');
|
|
return true;
|
|
},
|
|
},
|
|
})
|
|
.get('/apps/client-name/client-version/hash')
|
|
.reply(404);
|
|
|
|
const testServer = new ApolloServer({
|
|
typeDefs: /* GraphQL */ `
|
|
type Query {
|
|
hi: String
|
|
}
|
|
`,
|
|
plugins: [
|
|
useHive({
|
|
token: 'token',
|
|
experimental__persistedDocuments: {
|
|
cdn: {
|
|
endpoint: 'http://artifacts-cdn.localhost',
|
|
accessToken: 'foo',
|
|
},
|
|
},
|
|
agent: {
|
|
logger,
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
const { url } = await startStandaloneServer(testServer, {
|
|
async context({ req }): Promise<ApolloServerContext> {
|
|
return { req };
|
|
},
|
|
listen: {
|
|
port: 0,
|
|
},
|
|
});
|
|
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
documentId: 'client-name~client-version~hash',
|
|
}),
|
|
});
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(await response.json()).toEqual({
|
|
errors: [
|
|
{
|
|
message: 'Persisted document not found.',
|
|
extensions: {
|
|
code: 'PERSISTED_DOCUMENT_NOT_FOUND',
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
httpScope.done();
|
|
});
|
|
|
|
test('arbitrary options are rejected with allowArbitraryDocuments=false (GraphQL over HTTP)', async () => {
|
|
const testServer = new ApolloServer({
|
|
typeDefs: /* GraphQL */ `
|
|
type Query {
|
|
hi: String
|
|
}
|
|
`,
|
|
plugins: [
|
|
useHive({
|
|
token: 'token',
|
|
experimental__persistedDocuments: {
|
|
cdn: {
|
|
endpoint: 'http://artifacts-cdn.localhost',
|
|
accessToken: 'foo',
|
|
},
|
|
allowArbitraryDocuments: false,
|
|
},
|
|
agent: {
|
|
logger,
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
const { url } = await startStandaloneServer(testServer, {
|
|
async context({ req }): Promise<ApolloServerContext> {
|
|
return { req };
|
|
},
|
|
listen: {
|
|
port: 0,
|
|
},
|
|
});
|
|
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
query: 'query { hi }',
|
|
}),
|
|
});
|
|
expect(response.status).toBe(400);
|
|
expect(await response.json()).toEqual({
|
|
errors: [
|
|
{
|
|
message: 'No persisted document provided.',
|
|
extensions: { code: 'PERSISTED_DOCUMENT_REQUIRED' },
|
|
},
|
|
],
|
|
});
|
|
await testServer.stop();
|
|
});
|
|
|
|
test('arbitrary options are allowed with allowArbitraryDocuments=true (GraphQL over HTTP)', async () => {
|
|
const testServer = new ApolloServer({
|
|
typeDefs: /* GraphQL */ `
|
|
type Query {
|
|
hi: String
|
|
}
|
|
`,
|
|
plugins: [
|
|
useHive({
|
|
token: 'token',
|
|
experimental__persistedDocuments: {
|
|
cdn: {
|
|
endpoint: 'http://artifacts-cdn.localhost',
|
|
accessToken: 'foo',
|
|
},
|
|
allowArbitraryDocuments: true,
|
|
},
|
|
agent: {
|
|
logger,
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
const { url } = await startStandaloneServer(testServer, {
|
|
async context({ req }): Promise<ApolloServerContext> {
|
|
return { req };
|
|
},
|
|
listen: {
|
|
port: 0,
|
|
},
|
|
});
|
|
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
query: 'query { hi }',
|
|
}),
|
|
});
|
|
expect(response.status).toBe(200);
|
|
expect(await response.json()).toEqual({
|
|
data: {
|
|
hi: null,
|
|
},
|
|
});
|
|
await testServer.stop();
|
|
});
|
|
|
|
test('usage reporting for persisted document', async () => {
|
|
const httpScope = nock('http://artifacts-cdn.localhost', {
|
|
reqheaders: {
|
|
'X-Hive-CDN-Key': value => {
|
|
expect(value).toBe('foo');
|
|
return true;
|
|
},
|
|
},
|
|
})
|
|
.get('/apps/client-name/client-version/hash')
|
|
.reply(200, 'query { hi }');
|
|
|
|
const usageScope = nock('http://localhost', {
|
|
reqheaders: {
|
|
Authorization: value => {
|
|
expect(value).toBe('Bearer brrrt');
|
|
return true;
|
|
},
|
|
},
|
|
})
|
|
.post('/usage', body => {
|
|
expect(body.map).toMatchInlineSnapshot(`
|
|
{
|
|
ace78a32bbf8a79071356e5d5b13c5c83baf4e14: {
|
|
fields: [
|
|
Query.hi,
|
|
],
|
|
operation: {hi},
|
|
operationName: anonymous,
|
|
},
|
|
}
|
|
`);
|
|
|
|
expect(body.operations).toMatchObject([
|
|
{
|
|
metadata: {},
|
|
operationMapKey: 'ace78a32bbf8a79071356e5d5b13c5c83baf4e14',
|
|
persistedDocumentHash: 'client-name~client-version~hash',
|
|
},
|
|
]);
|
|
|
|
return true;
|
|
})
|
|
.reply(200);
|
|
|
|
const testServer = new ApolloServer({
|
|
typeDefs: /* GraphQL */ `
|
|
type Query {
|
|
hi: String
|
|
}
|
|
`,
|
|
plugins: [
|
|
useHive({
|
|
enabled: true,
|
|
debug: false,
|
|
token: 'brrrt',
|
|
experimental__persistedDocuments: {
|
|
cdn: {
|
|
endpoint: 'http://artifacts-cdn.localhost',
|
|
accessToken: 'foo',
|
|
},
|
|
},
|
|
selfHosting: {
|
|
applicationUrl: 'http://localhost/foo',
|
|
graphqlEndpoint: 'http://localhost/graphql',
|
|
usageEndpoint: 'http://localhost/usage',
|
|
},
|
|
usage: {
|
|
endpoint: 'http://localhost/usage',
|
|
},
|
|
agent: {
|
|
maxSize: 1,
|
|
logger,
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
const { url } = await startStandaloneServer(testServer, {
|
|
async context({ req }): Promise<ApolloServerContext> {
|
|
return { req };
|
|
},
|
|
listen: {
|
|
port: 0,
|
|
},
|
|
});
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
resolve();
|
|
}, 1000);
|
|
let requestCount = 0;
|
|
|
|
usageScope.on('request', () => {
|
|
requestCount = requestCount + 1;
|
|
if (requestCount === 1) {
|
|
clearTimeout(timeout);
|
|
resolve();
|
|
}
|
|
});
|
|
|
|
(async () => {
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
documentId: 'client-name~client-version~hash',
|
|
}),
|
|
});
|
|
expect(response.status).toBe(200);
|
|
expect(await response.json()).toEqual({ data: { hi: null } });
|
|
})().catch(reject);
|
|
});
|
|
|
|
await testServer.stop();
|
|
|
|
httpScope.done();
|
|
usageScope.done();
|
|
});
|
|
|
|
test('handles validation errors from malformed document IDs', async () => {
|
|
const testServer = new ApolloServer({
|
|
typeDefs: /* GraphQL */ `
|
|
type Query {
|
|
hi: String
|
|
}
|
|
`,
|
|
plugins: [
|
|
useHive({
|
|
token: 'token',
|
|
experimental__persistedDocuments: {
|
|
cdn: {
|
|
endpoint: 'http://artifacts-cdn.localhost',
|
|
accessToken: 'foo',
|
|
},
|
|
},
|
|
agent: {
|
|
logger,
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
const { url } = await startStandaloneServer(testServer, {
|
|
async context({ req }): Promise<ApolloServerContext> {
|
|
return { req };
|
|
},
|
|
listen: {
|
|
port: 0,
|
|
},
|
|
});
|
|
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
documentId: 'client-name~client-version~', // Malformed - missing hash
|
|
}),
|
|
});
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(await response.json()).toEqual({
|
|
errors: [
|
|
{
|
|
message:
|
|
'Invalid document ID "client-name~client-version~": Hash cannot be empty. Expected format: "name~version~hash" (e.g., "client-name~client-version~hash")',
|
|
extensions: {
|
|
code: 'INVALID_DOCUMENT_ID',
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
await testServer.stop();
|
|
});
|