mirror of
https://github.com/graphql-hive/console
synced 2026-04-28 01:47:17 +00:00
1148 lines
27 KiB
TypeScript
1148 lines
27 KiB
TypeScript
import { buildSchema, parse } from 'graphql';
|
|
import nock from 'nock';
|
|
import { Logger, MemoryLogWriter } from '@graphql-hive/logger';
|
|
import { createHive } from '../src/client/client';
|
|
import { atLeastOnceSampler } from '../src/client/samplers';
|
|
import type { Report } from '../src/client/usage';
|
|
import { version } from '../src/version';
|
|
import {
|
|
createHiveTestingLogger,
|
|
fastFetchError,
|
|
normalizeLogMessage,
|
|
waitFor,
|
|
} from './test-utils';
|
|
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
'graphql-client-name': 'Hive Client',
|
|
'graphql-client-version': version,
|
|
};
|
|
|
|
const schema = buildSchema(/* GraphQL */ `
|
|
type Query {
|
|
project(selector: ProjectSelectorInput!): Project
|
|
projectsByType(type: ProjectType!): [Project!]!
|
|
projects(filter: FilterInput): [Project!]!
|
|
}
|
|
|
|
type Mutation {
|
|
deleteProject(selector: ProjectSelectorInput!): DeleteProjectPayload!
|
|
}
|
|
|
|
input ProjectSelectorInput {
|
|
organization: ID!
|
|
project: ID!
|
|
}
|
|
|
|
input FilterInput {
|
|
type: ProjectType
|
|
pagination: PaginationInput
|
|
}
|
|
|
|
input PaginationInput {
|
|
limit: Int
|
|
offset: Int
|
|
}
|
|
|
|
type ProjectSelector {
|
|
organization: ID!
|
|
project: ID!
|
|
}
|
|
|
|
type DeleteProjectPayload {
|
|
selector: ProjectSelector!
|
|
deletedProject: Project!
|
|
}
|
|
|
|
type Project {
|
|
id: ID!
|
|
cleanId: ID!
|
|
name: String!
|
|
type: ProjectType!
|
|
buildUrl: String
|
|
validationUrl: String
|
|
}
|
|
|
|
enum ProjectType {
|
|
FEDERATION
|
|
STITCHING
|
|
SINGLE
|
|
CUSTOM
|
|
}
|
|
`);
|
|
|
|
const op = parse(/* GraphQL */ `
|
|
mutation deleteProject($selector: ProjectSelectorInput!) {
|
|
deleteProject(selector: $selector) {
|
|
selector {
|
|
organization
|
|
project
|
|
}
|
|
deletedProject {
|
|
...ProjectFields
|
|
}
|
|
}
|
|
}
|
|
|
|
fragment ProjectFields on Project {
|
|
id
|
|
cleanId
|
|
name
|
|
type
|
|
}
|
|
`);
|
|
|
|
const op2 = parse(/* GraphQL */ `
|
|
query getProject($selector: ProjectSelectorInput!) {
|
|
project(selector: $selector) {
|
|
...ProjectFields
|
|
}
|
|
}
|
|
|
|
fragment ProjectFields on Project {
|
|
id
|
|
cleanId
|
|
name
|
|
type
|
|
}
|
|
`);
|
|
|
|
beforeEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
nock.cleanAll();
|
|
});
|
|
|
|
test('should send data to Hive', async () => {
|
|
const logger = createHiveTestingLogger();
|
|
|
|
const token = 'Token';
|
|
|
|
let report: Report = {
|
|
size: 0,
|
|
map: {},
|
|
operations: [],
|
|
};
|
|
const http = nock('http://localhost')
|
|
.post('/200')
|
|
.matchHeader('Authorization', `Bearer ${token}`)
|
|
.matchHeader('Content-Type', headers['Content-Type'])
|
|
.matchHeader('graphql-client-name', headers['graphql-client-name'])
|
|
.matchHeader('graphql-client-version', headers['graphql-client-version'])
|
|
.once()
|
|
.reply((_, _body) => {
|
|
report = _body as any;
|
|
return [200];
|
|
});
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
debug: true,
|
|
agent: {
|
|
timeout: 500,
|
|
maxRetries: 0,
|
|
sendInterval: 10,
|
|
logger,
|
|
},
|
|
token,
|
|
selfHosting: {
|
|
graphqlEndpoint: 'http://localhost/graphql',
|
|
applicationUrl: 'http://localhost/',
|
|
usageEndpoint: 'http://localhost/200',
|
|
},
|
|
usage: true,
|
|
});
|
|
|
|
const collect = hive.collectUsage();
|
|
|
|
await waitFor(20);
|
|
await collect(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'deleteProject',
|
|
},
|
|
{},
|
|
);
|
|
await hive.dispose();
|
|
await waitFor(40);
|
|
http.done();
|
|
|
|
expect(logger.getLogs()).toMatchInlineSnapshot(`
|
|
[DBG] Disposing
|
|
[DBG] Sending report (queue 1)
|
|
[DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
[DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
|
|
[DBG] Report sent!
|
|
`);
|
|
|
|
// Map
|
|
expect(report.size).toEqual(1);
|
|
expect(Object.keys(report.map)).toHaveLength(1);
|
|
|
|
const key = Object.keys(report.map)[0];
|
|
const record = report.map[key];
|
|
|
|
// operation
|
|
expect(record.operation).toMatch('mutation deleteProject');
|
|
expect(record.operationName).toMatch('deleteProject');
|
|
// fields
|
|
expect(record.fields).toMatchInlineSnapshot(`
|
|
[
|
|
Mutation.deleteProject,
|
|
Mutation.deleteProject.selector,
|
|
DeleteProjectPayload.selector,
|
|
ProjectSelector.organization,
|
|
ProjectSelector.project,
|
|
DeleteProjectPayload.deletedProject,
|
|
Project.id,
|
|
Project.cleanId,
|
|
Project.name,
|
|
Project.type,
|
|
ProjectType.FEDERATION,
|
|
ProjectType.STITCHING,
|
|
ProjectType.SINGLE,
|
|
ProjectType.CUSTOM,
|
|
ProjectSelectorInput.organization,
|
|
ID,
|
|
ProjectSelectorInput.project,
|
|
]
|
|
`);
|
|
|
|
// Operations
|
|
const operations = report.operations;
|
|
expect(operations).toHaveLength(1); // one operation
|
|
if (!operations?.length) {
|
|
throw new Error('Expected operations to be an array');
|
|
}
|
|
|
|
const operation = operations[0];
|
|
|
|
expect(operation.operationMapKey).toEqual(key);
|
|
expect(operation.timestamp).toEqual(expect.any(Number));
|
|
// execution
|
|
expect(operation.execution.duration).toBeGreaterThanOrEqual(18 * 1_000_000); // >=18ms in microseconds
|
|
expect(operation.execution.duration).toBeLessThan(25 * 1_000_000); // <25ms
|
|
expect(operation.execution.errorsTotal).toBe(0);
|
|
expect(operation.execution.ok).toBe(true);
|
|
});
|
|
|
|
test('should send data to Hive (deprecated endpoint)', async () => {
|
|
const logger = createHiveTestingLogger();
|
|
const token = 'Token';
|
|
|
|
let report: Report = {
|
|
size: 0,
|
|
map: {},
|
|
operations: [],
|
|
};
|
|
const http = nock('http://localhost')
|
|
.post('/200')
|
|
.matchHeader('Authorization', `Bearer ${token}`)
|
|
.matchHeader('Content-Type', headers['Content-Type'])
|
|
.matchHeader('graphql-client-name', headers['graphql-client-name'])
|
|
.matchHeader('graphql-client-version', headers['graphql-client-version'])
|
|
.once()
|
|
.reply((_, _body) => {
|
|
report = _body as any;
|
|
return [200];
|
|
});
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
debug: true,
|
|
agent: {
|
|
timeout: 500,
|
|
maxRetries: 0,
|
|
sendInterval: 10,
|
|
logger,
|
|
},
|
|
token,
|
|
usage: {
|
|
endpoint: 'http://localhost/200',
|
|
},
|
|
});
|
|
|
|
const collect = hive.collectUsage();
|
|
|
|
await waitFor(20);
|
|
await collect(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'deleteProject',
|
|
},
|
|
{},
|
|
);
|
|
await hive.dispose();
|
|
await waitFor(50);
|
|
http.done();
|
|
|
|
expect(logger.getLogs()).toMatchInlineSnapshot(`
|
|
[DBG] Disposing
|
|
[DBG] Sending report (queue 1)
|
|
[DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
[DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
|
|
[DBG] Report sent!
|
|
`);
|
|
|
|
// Map
|
|
expect(report.size).toEqual(1);
|
|
expect(Object.keys(report.map)).toHaveLength(1);
|
|
|
|
const key = Object.keys(report.map)[0];
|
|
const record = report.map[key];
|
|
|
|
// operation
|
|
expect(record.operation).toMatch('mutation deleteProject');
|
|
expect(record.operationName).toMatch('deleteProject');
|
|
// fields
|
|
expect(record.fields).toMatchInlineSnapshot(`
|
|
[
|
|
Mutation.deleteProject,
|
|
Mutation.deleteProject.selector,
|
|
DeleteProjectPayload.selector,
|
|
ProjectSelector.organization,
|
|
ProjectSelector.project,
|
|
DeleteProjectPayload.deletedProject,
|
|
Project.id,
|
|
Project.cleanId,
|
|
Project.name,
|
|
Project.type,
|
|
ProjectType.FEDERATION,
|
|
ProjectType.STITCHING,
|
|
ProjectType.SINGLE,
|
|
ProjectType.CUSTOM,
|
|
ProjectSelectorInput.organization,
|
|
ID,
|
|
ProjectSelectorInput.project,
|
|
]
|
|
`);
|
|
|
|
// Operations
|
|
const operations = report.operations;
|
|
expect(operations).toHaveLength(1); // one operation
|
|
if (!operations?.length) {
|
|
throw new Error('Expected operations to be an array');
|
|
}
|
|
|
|
const operation = operations[0];
|
|
|
|
expect(operation.operationMapKey).toEqual(key);
|
|
expect(operation.timestamp).toEqual(expect.any(Number));
|
|
// execution
|
|
expect(operation.execution.duration).toBeGreaterThanOrEqual(18 * 1_000_000); // >=18ms in microseconds
|
|
expect(operation.execution.duration).toBeLessThan(25 * 1_000_000); // <25ms
|
|
expect(operation.execution.errorsTotal).toBe(0);
|
|
expect(operation.execution.ok).toBe(true);
|
|
});
|
|
|
|
test('should not leak the exception', { retry: 3 }, async () => {
|
|
const logger = createHiveTestingLogger();
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
debug: true,
|
|
agent: {
|
|
fetch: fastFetchError,
|
|
timeout: 500,
|
|
maxRetries: 1,
|
|
sendInterval: 10,
|
|
minTimeout: 10,
|
|
logger,
|
|
},
|
|
token: 'Token',
|
|
usage: {
|
|
endpoint: 'http://404.localhost.noop',
|
|
},
|
|
});
|
|
|
|
await hive.collectUsage()(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'deleteProject',
|
|
},
|
|
{},
|
|
);
|
|
|
|
await waitFor(50);
|
|
await hive.dispose();
|
|
|
|
expect(logger.getLogs()).toMatchInlineSnapshot(`
|
|
[DBG] Sending report (queue 1)
|
|
[DBG] POST http://404.localhost.noop (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) Attempt (1/2)
|
|
[DBG] Error: getaddrinfo ENOTFOUND 404.localhost.noop
|
|
[DBG] POST http://404.localhost.noop (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) failed (666ms). getaddrinfo ENOTFOUND 404.localhost.noop
|
|
[DBG] Disposing
|
|
`);
|
|
});
|
|
|
|
test('sendImmediately should not stop the schedule', async () => {
|
|
const logger = createHiveTestingLogger();
|
|
|
|
const token = 'Token';
|
|
|
|
const http = nock('http://localhost')
|
|
.post('/200')
|
|
.matchHeader('authorization', `Bearer ${token}`)
|
|
.matchHeader('Content-Type', headers['Content-Type'])
|
|
.matchHeader('graphql-client-name', headers['graphql-client-name'])
|
|
.matchHeader('graphql-client-version', headers['graphql-client-version'])
|
|
.times(3)
|
|
.reply((_, _body) => {
|
|
return [200];
|
|
});
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
debug: true,
|
|
printTokenInfo: false,
|
|
agent: {
|
|
timeout: 500,
|
|
maxRetries: 0,
|
|
maxSize: 2,
|
|
minTimeout: 10,
|
|
logger,
|
|
sendInterval: 50,
|
|
},
|
|
token,
|
|
usage: {
|
|
endpoint: 'http://localhost/200',
|
|
},
|
|
});
|
|
|
|
const args = {
|
|
schema,
|
|
document: op,
|
|
operationName: 'deleteProject',
|
|
};
|
|
|
|
const collect = hive.collectUsage();
|
|
|
|
await collect(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'deleteProject',
|
|
},
|
|
{},
|
|
);
|
|
await waitFor(120);
|
|
// Because maxSize is 2 and sendInterval is 50ms (+120ms buffer)
|
|
// the scheduled send task should be done by now
|
|
|
|
// since we sent only 1 element, the buffer was not full,
|
|
// so we should not see "Sending immediately"
|
|
|
|
expect(logger.getLogs()).not.toMatch('Sending immediately');
|
|
logger.clear();
|
|
|
|
// Now we will hit the maxSize
|
|
// We run collect two times
|
|
await Promise.all([collect(args, {}), collect(args, {})]);
|
|
await waitFor(1);
|
|
expect(logger.getLogs()).toMatch('Sending immediately');
|
|
expect(logger.getLogs()).toMatch('Sending report (queue 2)');
|
|
logger.clear();
|
|
// Let's check if the scheduled send task is still running
|
|
await collect(args, {});
|
|
await waitFor(60);
|
|
expect(logger.getLogs()).toMatch('Sending report (queue 1)');
|
|
|
|
await hive.dispose();
|
|
http.done();
|
|
});
|
|
|
|
test('should send data to Hive at least once when using atLeastOnceSampler', async () => {
|
|
const logger = createHiveTestingLogger();
|
|
|
|
const token = 'Token';
|
|
|
|
let report: Report = {
|
|
size: 0,
|
|
map: {},
|
|
operations: [],
|
|
};
|
|
const http = nock('http://localhost')
|
|
.post('/200')
|
|
.matchHeader('Authorization', `Bearer ${token}`)
|
|
.matchHeader('Content-Type', headers['Content-Type'])
|
|
.matchHeader('graphql-client-name', headers['graphql-client-name'])
|
|
.matchHeader('graphql-client-version', headers['graphql-client-version'])
|
|
.once()
|
|
.reply((_, _body) => {
|
|
report = _body as any;
|
|
return [200];
|
|
});
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
debug: true,
|
|
printTokenInfo: false,
|
|
agent: {
|
|
timeout: 500,
|
|
sendInterval: 10,
|
|
maxRetries: 0,
|
|
logger,
|
|
},
|
|
token,
|
|
selfHosting: {
|
|
graphqlEndpoint: 'http://localhost/graphql',
|
|
applicationUrl: 'http://localhost/',
|
|
usageEndpoint: 'http://localhost/200',
|
|
},
|
|
usage: {
|
|
sampler: atLeastOnceSampler({
|
|
keyFn(ctx) {
|
|
return ctx.operationName;
|
|
},
|
|
sampler() {
|
|
// only
|
|
return 0;
|
|
},
|
|
}),
|
|
},
|
|
});
|
|
|
|
const collect = hive.collectUsage();
|
|
|
|
await Promise.all([
|
|
collect(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'deleteProject',
|
|
},
|
|
{},
|
|
),
|
|
// different query
|
|
collect(
|
|
{
|
|
schema,
|
|
document: op2,
|
|
operationName: 'getProject',
|
|
},
|
|
{},
|
|
),
|
|
// duplicated call
|
|
collect(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'deleteProject',
|
|
},
|
|
{},
|
|
),
|
|
]);
|
|
await hive.dispose();
|
|
await waitFor(50);
|
|
http.done();
|
|
|
|
expect(logger.getLogs()).toMatchInlineSnapshot(`
|
|
[DBG] Disposing
|
|
[DBG] Sending report (queue 2)
|
|
[DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
[DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
|
|
[DBG] Report sent!
|
|
`);
|
|
|
|
// Map
|
|
expect(report.size).toEqual(2);
|
|
expect(Object.keys(report.map)).toHaveLength(2);
|
|
|
|
const foundRecords: string[] = [];
|
|
for (const key in report.map) {
|
|
const record = report.map[key];
|
|
|
|
foundRecords.push(record.operationName ?? 'anonymous');
|
|
}
|
|
|
|
expect(foundRecords).toContainEqual('deleteProject');
|
|
expect(foundRecords).toContainEqual('getProject');
|
|
|
|
const operations = report.operations;
|
|
expect(operations).toHaveLength(2); // two operations
|
|
});
|
|
|
|
test('should not send excluded operation name data to Hive', async () => {
|
|
const logger = createHiveTestingLogger();
|
|
|
|
const token = 'Token';
|
|
|
|
let report: Report = {
|
|
size: 0,
|
|
map: {},
|
|
operations: [],
|
|
};
|
|
const http = nock('http://localhost')
|
|
.post('/200')
|
|
.once()
|
|
.reply((_, _body) => {
|
|
report = _body as any;
|
|
return [200];
|
|
});
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
debug: true,
|
|
agent: {
|
|
timeout: 500,
|
|
maxRetries: 0,
|
|
sendInterval: 10,
|
|
logger,
|
|
},
|
|
token,
|
|
selfHosting: {
|
|
graphqlEndpoint: 'http://localhost/graphql',
|
|
applicationUrl: 'http://localhost/',
|
|
usageEndpoint: 'http://localhost/200',
|
|
},
|
|
usage: {
|
|
exclude: ['deleteProjectShouldntBeIncluded', new RegExp('ExcludeThis$')],
|
|
},
|
|
});
|
|
|
|
const collect = hive.collectUsage();
|
|
await waitFor(20);
|
|
await Promise.all([
|
|
(collect(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'deleteProjectExcludeThis',
|
|
},
|
|
{},
|
|
),
|
|
collect(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'deleteProjectShouldntBeIncluded',
|
|
},
|
|
{},
|
|
),
|
|
collect(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'deleteProject',
|
|
},
|
|
{},
|
|
),
|
|
collect(
|
|
{
|
|
schema,
|
|
document: op2,
|
|
operationName: 'getProject',
|
|
},
|
|
{},
|
|
)),
|
|
]);
|
|
await hive.dispose();
|
|
await waitFor(50);
|
|
http.done();
|
|
|
|
expect(logger.getLogs()).toMatchInlineSnapshot(`
|
|
[DBG] Disposing
|
|
[DBG] Sending report (queue 2)
|
|
[DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
[DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
|
|
[DBG] Report sent!
|
|
`);
|
|
|
|
// Map
|
|
expect(report.size).toEqual(2);
|
|
expect(Object.keys(report.map)).toHaveLength(2);
|
|
|
|
const key = Object.keys(report.map)[0];
|
|
const record = report.map[key];
|
|
|
|
// operation
|
|
expect(record.operation).toMatch('mutation deleteProject');
|
|
expect(record.operationName).toMatch('deleteProject');
|
|
// fields
|
|
expect(record.fields).toMatchInlineSnapshot(`
|
|
[
|
|
Mutation.deleteProject,
|
|
Mutation.deleteProject.selector,
|
|
DeleteProjectPayload.selector,
|
|
ProjectSelector.organization,
|
|
ProjectSelector.project,
|
|
DeleteProjectPayload.deletedProject,
|
|
Project.id,
|
|
Project.cleanId,
|
|
Project.name,
|
|
Project.type,
|
|
ProjectType.FEDERATION,
|
|
ProjectType.STITCHING,
|
|
ProjectType.SINGLE,
|
|
ProjectType.CUSTOM,
|
|
ProjectSelectorInput.organization,
|
|
ID,
|
|
ProjectSelectorInput.project,
|
|
]
|
|
`);
|
|
|
|
// Operations
|
|
const operations = report.operations;
|
|
expect(operations).toHaveLength(2); // two operations
|
|
if (!operations?.length) {
|
|
throw new Error('Expected operations to be an array');
|
|
}
|
|
|
|
const operation = operations[0];
|
|
|
|
expect(operation.operationMapKey).toEqual(key);
|
|
expect(operation.timestamp).toEqual(expect.any(Number));
|
|
// execution
|
|
expect(operation.execution.duration).toBeGreaterThanOrEqual(18 * 1_000_000); // >=18ms in microseconds
|
|
expect(operation.execution.duration).toBeLessThan(25 * 1_000_000); // <25ms
|
|
expect(operation.execution.errorsTotal).toBe(0);
|
|
expect(operation.execution.ok).toBe(true);
|
|
});
|
|
|
|
test('retry on non-200', async () => {
|
|
const logger = createHiveTestingLogger();
|
|
|
|
const token = 'Token';
|
|
|
|
const fetchSpy = vi.fn(async (_url: RequestInfo | URL, _init?: RequestInit) => {
|
|
return new Response('No no no', { status: 500, statusText: 'Internal server error' });
|
|
});
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
debug: true,
|
|
printTokenInfo: false,
|
|
agent: {
|
|
logger,
|
|
timeout: 10,
|
|
minTimeout: 10,
|
|
sendInterval: 10,
|
|
maxRetries: 1,
|
|
fetch: fetchSpy,
|
|
},
|
|
token,
|
|
usage: {
|
|
endpoint: 'http://localhost/200',
|
|
},
|
|
reporting: false,
|
|
});
|
|
|
|
const collect = hive.collectUsage();
|
|
|
|
await collect(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'asd',
|
|
},
|
|
{},
|
|
);
|
|
|
|
await waitFor(50);
|
|
await hive.dispose();
|
|
|
|
expect(logger.getLogs()).toMatchInlineSnapshot(`
|
|
[DBG] Sending report (queue 1)
|
|
[DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) Attempt (1/2)
|
|
[DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) failed with status 500 (666ms): No no no
|
|
[DBG] Disposing
|
|
`);
|
|
});
|
|
|
|
test('constructs URL with usage.target (hvo1/)', async ({ expect }) => {
|
|
const logger = createHiveTestingLogger();
|
|
const token = 'hvo1/brrrrt';
|
|
const dUrl = Promise.withResolvers<string>();
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
debug: true,
|
|
agent: {
|
|
timeout: 500,
|
|
maxRetries: 0,
|
|
sendInterval: 1,
|
|
maxSize: 1,
|
|
async fetch(url) {
|
|
dUrl.resolve(url.toString());
|
|
return new Response('', {
|
|
status: 200,
|
|
});
|
|
},
|
|
logger,
|
|
},
|
|
token,
|
|
selfHosting: {
|
|
graphqlEndpoint: 'http://localhost:2/graphql',
|
|
applicationUrl: 'http://localhost:1',
|
|
usageEndpoint: 'http://localhost',
|
|
},
|
|
usage: {
|
|
target: 'the-guild/graphql-hive/staging',
|
|
},
|
|
});
|
|
|
|
await hive.collectUsage()(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'asd',
|
|
},
|
|
{},
|
|
);
|
|
|
|
const url = await dUrl.promise;
|
|
expect(url).toEqual('http://localhost/the-guild/graphql-hive/staging');
|
|
await hive.dispose();
|
|
});
|
|
|
|
test('constructs URL with usage.target (hvp1/)', async ({ expect }) => {
|
|
const logger = createHiveTestingLogger();
|
|
const token = 'hvp1/brrrrt';
|
|
const dUrl = Promise.withResolvers<string>();
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
debug: true,
|
|
agent: {
|
|
timeout: 500,
|
|
maxRetries: 0,
|
|
sendInterval: 1,
|
|
maxSize: 1,
|
|
async fetch(url) {
|
|
dUrl.resolve(url.toString());
|
|
return new Response('', {
|
|
status: 200,
|
|
});
|
|
},
|
|
logger,
|
|
},
|
|
token,
|
|
selfHosting: {
|
|
graphqlEndpoint: 'http://localhost:2/graphql',
|
|
applicationUrl: 'http://localhost:1',
|
|
usageEndpoint: 'http://localhost',
|
|
},
|
|
usage: {
|
|
target: 'the-guild/graphql-hive/staging',
|
|
},
|
|
});
|
|
|
|
await hive.collectUsage()(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'asd',
|
|
},
|
|
{},
|
|
);
|
|
|
|
const url = await dUrl.promise;
|
|
expect(url).toEqual('http://localhost/the-guild/graphql-hive/staging');
|
|
await hive.dispose();
|
|
});
|
|
|
|
test('constructs URL with usage.target (hvu1/)', async ({ expect }) => {
|
|
const logger = createHiveTestingLogger();
|
|
const token = 'hvu1/brrrrt';
|
|
const dUrl = Promise.withResolvers<string>();
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
debug: true,
|
|
agent: {
|
|
timeout: 500,
|
|
maxRetries: 0,
|
|
sendInterval: 1,
|
|
maxSize: 1,
|
|
async fetch(url) {
|
|
dUrl.resolve(url.toString());
|
|
return new Response('', {
|
|
status: 200,
|
|
});
|
|
},
|
|
logger,
|
|
},
|
|
token,
|
|
selfHosting: {
|
|
graphqlEndpoint: 'http://localhost:2/graphql',
|
|
applicationUrl: 'http://localhost:1',
|
|
usageEndpoint: 'http://localhost',
|
|
},
|
|
usage: {
|
|
target: 'the-guild/graphql-hive/staging',
|
|
},
|
|
});
|
|
|
|
await hive.collectUsage()(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'asd',
|
|
},
|
|
{},
|
|
);
|
|
|
|
const url = await dUrl.promise;
|
|
expect(url).toEqual('http://localhost/the-guild/graphql-hive/staging');
|
|
await hive.dispose();
|
|
});
|
|
|
|
test('no debug property -> logger.debug is invoked', async ({ expect }) => {
|
|
const logger = createHiveTestingLogger();
|
|
const token = 'hvu1/brrrrt';
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
agent: {
|
|
timeout: 500,
|
|
maxRetries: 0,
|
|
sendInterval: 1,
|
|
maxSize: 1,
|
|
async fetch() {
|
|
return new Response('', {
|
|
status: 200,
|
|
});
|
|
},
|
|
logger,
|
|
},
|
|
token,
|
|
selfHosting: {
|
|
graphqlEndpoint: 'http://localhost:2/graphql',
|
|
applicationUrl: 'http://localhost:1',
|
|
usageEndpoint: 'http://localhost',
|
|
},
|
|
usage: {
|
|
target: 'the-guild/graphql-hive/staging',
|
|
},
|
|
});
|
|
|
|
await hive.collectUsage()(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'asd',
|
|
},
|
|
{},
|
|
);
|
|
|
|
await hive.dispose();
|
|
expect(logger.getLogs()).toMatchInlineSnapshot(`
|
|
[DBG] Disposing
|
|
[DBG] Sending immediately
|
|
[DBG] Sending report (queue 1)
|
|
[DBG] POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
[DBG] POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
|
|
[DBG] Report sent!
|
|
`);
|
|
});
|
|
|
|
test('debug: false -> logger.debug is not invoked', async ({ expect }) => {
|
|
const logger = createHiveTestingLogger();
|
|
const token = 'hvu1/brrrrt';
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
debug: false,
|
|
agent: {
|
|
timeout: 500,
|
|
maxRetries: 0,
|
|
sendInterval: 1,
|
|
maxSize: 1,
|
|
async fetch() {
|
|
return new Response('', {
|
|
status: 200,
|
|
});
|
|
},
|
|
logger,
|
|
},
|
|
token,
|
|
selfHosting: {
|
|
graphqlEndpoint: 'http://localhost:2/graphql',
|
|
applicationUrl: 'http://localhost:1',
|
|
usageEndpoint: 'http://localhost',
|
|
},
|
|
usage: {
|
|
target: 'the-guild/graphql-hive/staging',
|
|
},
|
|
});
|
|
|
|
await hive.collectUsage()(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'asd',
|
|
},
|
|
{},
|
|
);
|
|
|
|
await hive.dispose();
|
|
expect(logger.getLogs()).toMatchInlineSnapshot(`
|
|
[DBG] Disposing
|
|
[DBG] Sending immediately
|
|
[DBG] Sending report (queue 1)
|
|
[DBG] POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
[DBG] POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
|
|
[DBG] Report sent!
|
|
`);
|
|
});
|
|
|
|
test('debug: true and missing logger.debug method -> logger.info is invoked (to cover legacy logger implementation)', async ({
|
|
expect,
|
|
}) => {
|
|
const logger = createHiveTestingLogger();
|
|
// @ts-expect-error we remove this property to emulate logger without it
|
|
logger.debug = undefined;
|
|
const token = 'hvu1/brrrrt';
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
debug: true,
|
|
agent: {
|
|
timeout: 500,
|
|
maxRetries: 0,
|
|
sendInterval: 1,
|
|
maxSize: 1,
|
|
async fetch() {
|
|
return new Response('', {
|
|
status: 200,
|
|
});
|
|
},
|
|
logger,
|
|
},
|
|
token,
|
|
selfHosting: {
|
|
graphqlEndpoint: 'http://localhost:2/graphql',
|
|
applicationUrl: 'http://localhost:1',
|
|
usageEndpoint: 'http://localhost',
|
|
},
|
|
usage: {
|
|
target: 'the-guild/graphql-hive/staging',
|
|
},
|
|
});
|
|
|
|
await hive.collectUsage()(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'asd',
|
|
},
|
|
{},
|
|
);
|
|
|
|
await hive.dispose();
|
|
expect(logger.getLogs()).toMatchInlineSnapshot(`
|
|
[INF] Disposing
|
|
[INF] Sending immediately
|
|
[INF] Sending report (queue 1)
|
|
[INF] POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
[INF] POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
|
|
[INF] Report sent!
|
|
`);
|
|
});
|
|
|
|
test('new logger option', async () => {
|
|
const logWriter = new MemoryLogWriter();
|
|
const logger = new Logger({
|
|
writers: [
|
|
{
|
|
write(level, attrs, msg) {
|
|
if (msg) {
|
|
msg = normalizeLogMessage(msg);
|
|
}
|
|
|
|
logWriter.write(level, attrs, msg);
|
|
},
|
|
},
|
|
],
|
|
level: 'debug',
|
|
});
|
|
|
|
const token = 'hvu1/brrrrt';
|
|
|
|
const hive = createHive({
|
|
enabled: true,
|
|
logger,
|
|
agent: {
|
|
timeout: 500,
|
|
maxRetries: 0,
|
|
sendInterval: 1,
|
|
maxSize: 1,
|
|
async fetch() {
|
|
return new Response('', {
|
|
status: 200,
|
|
});
|
|
},
|
|
},
|
|
token,
|
|
selfHosting: {
|
|
graphqlEndpoint: 'http://localhost:2/graphql',
|
|
applicationUrl: 'http://localhost:1',
|
|
usageEndpoint: 'http://localhost',
|
|
},
|
|
usage: {
|
|
target: 'the-guild/graphql-hive/staging',
|
|
},
|
|
});
|
|
|
|
await hive.collectUsage()(
|
|
{
|
|
schema,
|
|
document: op,
|
|
operationName: 'asd',
|
|
},
|
|
{},
|
|
);
|
|
|
|
await hive.dispose();
|
|
expect(logWriter.logs).toMatchInlineSnapshot(`
|
|
[
|
|
{
|
|
attrs: {
|
|
module: hive-agent,
|
|
},
|
|
level: debug,
|
|
msg: Disposing,
|
|
},
|
|
{
|
|
attrs: {
|
|
module: hive-agent,
|
|
},
|
|
level: debug,
|
|
msg: Sending immediately,
|
|
},
|
|
{
|
|
attrs: {
|
|
module: hive-agent,
|
|
},
|
|
level: debug,
|
|
msg: Sending report (queue 1),
|
|
},
|
|
{
|
|
attrs: {
|
|
module: hive-agent,
|
|
},
|
|
level: debug,
|
|
msg: POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
|
|
},
|
|
{
|
|
attrs: {
|
|
module: hive-agent,
|
|
},
|
|
level: debug,
|
|
msg: POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).,
|
|
},
|
|
{
|
|
attrs: {
|
|
module: hive-agent,
|
|
},
|
|
level: debug,
|
|
msg: Report sent!,
|
|
},
|
|
]
|
|
`);
|
|
});
|