wgc
Version:
The official CLI tool to manage the GraphQL Federation Platform Cosmo
215 lines • 9.95 kB
JavaScript
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { Command } from 'commander';
import { createClient, createRouterTransport } from '@connectrpc/connect';
import { PlatformService } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import { dirname } from 'pathe';
import PublishSchema from '../src/commands/subgraph/commands/publish.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const schemaPath = resolve(__dirname, 'fixtures', 'schema.graphql');
function createMockTransport(response) {
return createRouterTransport(({ service }) => {
service(PlatformService, {
publishFederatedSubgraph: () => response,
});
});
}
async function runPublish(response, opts = {}) {
const args = ['publish', 'wg.orders', '--schema', schemaPath];
if (opts.failOnCompositionError) {
args.push('--fail-on-composition-error');
}
if (opts.failOnAdmissionWebhookError) {
args.push('--fail-on-admission-webhook-error');
}
if (opts.suppressWarnings) {
args.push('--suppress-warnings');
}
const client = {
platform: createClient(PlatformService, createMockTransport(response)),
};
const program = new Command();
program.addCommand(PublishSchema({ client }));
await program.parseAsync(args, { from: 'user' });
}
export const mockPlatformTransport = () => createRouterTransport(({ service }) => {
service(PlatformService, {
publishFederatedSubgraph: (ctx) => {
return {
response: {
code: EnumStatusCode.OK,
},
};
},
});
});
describe('Schema Command', () => {
test('Publish Schema', () => {
const client = {
platform: createClient(PlatformService, mockPlatformTransport()),
// @ts-ignore
node: null,
};
const program = new Command();
program.addCommand(PublishSchema({
client,
}));
const command = program.parse(['publish', 'wg.orders', '--schema', schemaPath], {
from: 'user',
});
});
});
describe('truncation warning', () => {
let logSpy;
let stderrSpy;
let exitSpy;
beforeEach(() => {
logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => {
throw new Error('process.exit');
});
});
afterEach(() => {
process.exitCode = undefined;
});
test('shows truncation warning when composition errors exceed displayed count', async () => {
await runPublish({
response: { code: EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED },
compositionErrors: [
{ federatedGraphName: 'graph1', namespace: 'default', message: 'Error 1' },
{ federatedGraphName: 'graph2', namespace: 'default', message: 'Error 2' },
],
compositionWarnings: [],
deploymentErrors: [],
counts: {
compositionErrors: 10,
compositionWarnings: 0,
deploymentErrors: 0,
},
});
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Some results were truncated'));
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('composition errors (2 of 10 shown)'));
});
test('shows truncation warning when composition warnings exceed displayed count', async () => {
await runPublish({
response: { code: EnumStatusCode.OK },
compositionErrors: [],
compositionWarnings: [{ federatedGraphName: 'graph1', namespace: 'default', message: 'Warning 1' }],
deploymentErrors: [],
counts: {
compositionErrors: 0,
compositionWarnings: 5,
deploymentErrors: 0,
},
});
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Some results were truncated'));
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('composition warnings (1 of 5 shown)'));
});
test('shows truncation warning when deployment errors exceed displayed count', async () => {
await runPublish({
response: { code: EnumStatusCode.ERR_DEPLOYMENT_FAILED },
compositionErrors: [],
compositionWarnings: [],
deploymentErrors: [{ federatedGraphName: 'graph1', namespace: 'default', message: 'Deploy Error 1' }],
counts: {
compositionErrors: 0,
compositionWarnings: 0,
deploymentErrors: 3,
},
});
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Some results were truncated'));
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('deployment errors (1 of 3 shown)'));
});
test('does not show truncation warning when counts match displayed items', async () => {
await runPublish({
response: { code: EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED },
compositionErrors: [
{ federatedGraphName: 'graph1', namespace: 'default', message: 'Error 1' },
{ federatedGraphName: 'graph2', namespace: 'default', message: 'Error 2' },
],
compositionWarnings: [],
deploymentErrors: [],
counts: {
compositionErrors: 2,
compositionWarnings: 0,
deploymentErrors: 0,
},
});
const truncationCalls = logSpy.mock.calls.filter(([arg]) => typeof arg === 'string' && arg.includes('truncated'));
expect(truncationCalls).toHaveLength(0);
});
test('does not show truncation warning when counts are not provided', async () => {
await runPublish({
response: { code: EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED },
compositionErrors: [{ federatedGraphName: 'graph1', namespace: 'default', message: 'Error 1' }],
compositionWarnings: [],
deploymentErrors: [],
});
const truncationCalls = logSpy.mock.calls.filter(([arg]) => typeof arg === 'string' && arg.includes('truncated'));
expect(truncationCalls).toHaveLength(0);
});
test('shows truncation warning before program.error when failOnCompositionError is set', async () => {
await expect(runPublish({
response: { code: EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED },
compositionErrors: [{ federatedGraphName: 'graph1', namespace: 'default', message: 'Error 1' }],
compositionWarnings: [],
deploymentErrors: [],
counts: {
compositionErrors: 5,
compositionWarnings: 0,
deploymentErrors: 0,
},
}, { failOnCompositionError: true })).rejects.toThrow('process.exit');
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Some results were truncated'));
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('composition errors (1 of 5 shown)'));
});
test('shows truncation warning before program.error when failOnAdmissionWebhookError is set', async () => {
await expect(runPublish({
response: { code: EnumStatusCode.ERR_DEPLOYMENT_FAILED },
compositionErrors: [],
compositionWarnings: [],
deploymentErrors: [{ federatedGraphName: 'graph1', namespace: 'default', message: 'Deploy Error 1' }],
counts: {
compositionErrors: 0,
compositionWarnings: 0,
deploymentErrors: 8,
},
}, { failOnAdmissionWebhookError: true })).rejects.toThrow('process.exit');
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Some results were truncated'));
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('deployment errors (1 of 8 shown)'));
});
test('shows multiple truncation items when multiple types are truncated', async () => {
await runPublish({
response: { code: EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED },
compositionErrors: [{ federatedGraphName: 'graph1', namespace: 'default', message: 'Error 1' }],
compositionWarnings: [{ federatedGraphName: 'graph1', namespace: 'default', message: 'Warning 1' }],
deploymentErrors: [],
counts: {
compositionErrors: 100,
compositionWarnings: 50,
deploymentErrors: 0,
},
});
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('composition errors (1 of 100 shown)'));
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('composition warnings (1 of 50 shown)'));
});
test('does not show warnings truncation when suppressWarnings is set', async () => {
await runPublish({
response: { code: EnumStatusCode.OK },
compositionErrors: [],
compositionWarnings: [{ federatedGraphName: 'graph1', namespace: 'default', message: 'Warning 1' }],
deploymentErrors: [],
counts: {
compositionErrors: 0,
compositionWarnings: 10,
deploymentErrors: 0,
},
}, { suppressWarnings: true });
const warningTableCalls = logSpy.mock.calls.filter(([arg]) => typeof arg === 'string' && arg.includes('warnings were produced'));
expect(warningTableCalls).toHaveLength(0);
});
});
//# sourceMappingURL=publish-schema.test.js.map