mirror of
https://github.com/graphql-hive/console
synced 2026-05-23 00:58:36 +00:00
469 lines
12 KiB
TypeScript
469 lines
12 KiB
TypeScript
import { ProjectType, RegistryModel } from 'testkit/gql/graphql';
|
|
import { createCLI } from '../../testkit/cli';
|
|
import { prepareProject } from '../../testkit/registry-models';
|
|
import { initSeed } from '../../testkit/seed';
|
|
|
|
describe('publish', () => {
|
|
test.concurrent('accepted: composable', async () => {
|
|
const { publish } = await prepare();
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProductName: String
|
|
}
|
|
`,
|
|
expect: 'latest-composable',
|
|
});
|
|
});
|
|
|
|
test.concurrent('rejected: not composable (initial)', async () => {
|
|
const { publish } = await prepare();
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProductName: Product
|
|
}
|
|
`,
|
|
expect: 'rejected',
|
|
});
|
|
});
|
|
|
|
test.concurrent('partially accepted: not composable (force)', async () => {
|
|
const { publish } = await prepare();
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProductName: Product
|
|
}
|
|
`,
|
|
legacy_force: true,
|
|
expect: 'latest',
|
|
});
|
|
});
|
|
|
|
test.concurrent('rejected: composable, breaking changes', async () => {
|
|
const { publish } = await prepare();
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProductName: String
|
|
}
|
|
`,
|
|
expect: 'latest-composable',
|
|
});
|
|
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
nooooo: String
|
|
}
|
|
`,
|
|
expect: 'rejected',
|
|
});
|
|
});
|
|
|
|
test.concurrent('accepted: composable, breaking changes (acceptBreakingChanges)', async () => {
|
|
const { publish } = await prepare();
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProductName: String
|
|
}
|
|
`,
|
|
expect: 'latest-composable',
|
|
});
|
|
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
nooooo: String
|
|
}
|
|
`,
|
|
legacy_acceptBreakingChanges: true,
|
|
expect: 'latest-composable',
|
|
});
|
|
});
|
|
|
|
test.concurrent('partially accepted: composable, breaking changes (force)', async () => {
|
|
const { publish } = await prepare();
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProductName: String
|
|
}
|
|
`,
|
|
expect: 'latest-composable',
|
|
});
|
|
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
nooooo: String
|
|
}
|
|
`,
|
|
legacy_force: true,
|
|
expect: 'latest',
|
|
});
|
|
});
|
|
|
|
test.concurrent('accepted (ignored): composable, no changes', async () => {
|
|
const { publish } = await prepare();
|
|
|
|
// composable
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProduct: String
|
|
}
|
|
`,
|
|
expect: 'latest-composable',
|
|
});
|
|
|
|
// composable but no changes
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProduct: String
|
|
}
|
|
`,
|
|
expect: 'ignored',
|
|
});
|
|
});
|
|
|
|
test.concurrent('CLI output', async ({ expect }) => {
|
|
const { publish } = await prepare();
|
|
|
|
await expect(
|
|
publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProduct: Product
|
|
}
|
|
|
|
type Product {
|
|
id: ID!
|
|
name: String!
|
|
}
|
|
`,
|
|
expect: 'latest-composable',
|
|
}),
|
|
).resolves.toMatchInlineSnapshot(`
|
|
v Published initial schema.
|
|
i Available at $appUrl/$organization/$project/$target
|
|
`);
|
|
|
|
await expect(
|
|
publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProduct: Product
|
|
}
|
|
|
|
type Product {
|
|
id: ID!
|
|
name: String!
|
|
price: Int!
|
|
}
|
|
`,
|
|
expect: 'latest-composable',
|
|
}),
|
|
).resolves.toMatchInlineSnapshot(`
|
|
i Detected 1 change
|
|
Safe changes:
|
|
- Field price was added to object type Product
|
|
v Schema published
|
|
i Available at $appUrl/$organization/$project/$target/history/$version
|
|
`);
|
|
});
|
|
});
|
|
|
|
describe('check', () => {
|
|
test.concurrent('accepted: composable, no breaking changes', async () => {
|
|
const { publish, check } = await prepare();
|
|
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProduct: String
|
|
}
|
|
`,
|
|
expect: 'latest-composable',
|
|
});
|
|
|
|
const message = await check({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProduct: String
|
|
topProductName: String
|
|
}
|
|
`,
|
|
expect: 'approved',
|
|
});
|
|
|
|
expect(message).toMatch('topProductName');
|
|
});
|
|
|
|
test.concurrent('accepted: no changes', async () => {
|
|
const { publish, check } = await prepare();
|
|
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProduct: String
|
|
}
|
|
`,
|
|
expect: 'latest-composable',
|
|
});
|
|
|
|
await check({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProduct: String
|
|
}
|
|
`,
|
|
expect: 'approved',
|
|
});
|
|
});
|
|
|
|
test.concurrent('rejected: composable, breaking changes', async () => {
|
|
const { publish, check } = await prepare();
|
|
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProduct: String
|
|
}
|
|
`,
|
|
expect: 'latest-composable',
|
|
});
|
|
|
|
const message = await check({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProductName: String
|
|
}
|
|
`,
|
|
expect: 'rejected',
|
|
});
|
|
|
|
expect(message).toMatch('removed');
|
|
});
|
|
|
|
test.concurrent('rejected: not composable, no breaking changes', async () => {
|
|
const { publish, check } = await prepare();
|
|
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProduct: String
|
|
}
|
|
`,
|
|
expect: 'latest-composable',
|
|
});
|
|
|
|
const message = await check({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProduct: String
|
|
topProductName: Strin
|
|
}
|
|
`,
|
|
expect: 'rejected',
|
|
});
|
|
|
|
expect(message).toMatch('Strin');
|
|
});
|
|
|
|
test.concurrent('rejected: not composable, breaking changes', async () => {
|
|
const { publish, check } = await prepare();
|
|
|
|
await publish({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
topProduct: Product
|
|
}
|
|
type Product {
|
|
id: ID!
|
|
name: String
|
|
}
|
|
`,
|
|
expect: 'latest-composable',
|
|
});
|
|
|
|
const message = await check({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
product(id: ID!): Product
|
|
}
|
|
type Product {
|
|
id: ID!
|
|
name: Str
|
|
}
|
|
`,
|
|
expect: 'rejected',
|
|
});
|
|
|
|
expect(message).toMatch('Str');
|
|
});
|
|
});
|
|
|
|
describe('delete', () => {
|
|
test.concurrent('not supported', async () => {
|
|
const cli = await prepare();
|
|
|
|
await cli.delete({
|
|
serviceName: 'test',
|
|
expect: 'rejected',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('other', () => {
|
|
test.concurrent('marking versions as valid', async () => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject } = await createOrg();
|
|
const { createTargetAccessToken, fetchVersions, updateSchemaVersionStatus } =
|
|
await createProject(ProjectType.Single, {
|
|
useLegacyRegistryModels: true,
|
|
});
|
|
const { publishSchema, fetchLatestValidSchema } = await createTargetAccessToken({});
|
|
|
|
// Initial schema
|
|
let result = await publishSchema({
|
|
author: 'Kamil',
|
|
commit: 'c0',
|
|
sdl: `type Query { ping: String }`,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(result.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Second version with a forced breaking change
|
|
result = await publishSchema({
|
|
author: 'Kamil',
|
|
commit: 'c1',
|
|
sdl: `type Query { pong: String }`,
|
|
force: true,
|
|
metadata: JSON.stringify({ c1: true }),
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
// third version with another forced breaking change
|
|
result = await publishSchema({
|
|
author: 'Kamil',
|
|
commit: 'c2',
|
|
sdl: `type Query { tennis: String }`,
|
|
force: true,
|
|
metadata: JSON.stringify({ c2: true }),
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
const versions = await fetchVersions(3);
|
|
|
|
expect(versions).toHaveLength(3);
|
|
|
|
// the initial version should be the latest valid version
|
|
let latestValidSchemaResult = await fetchLatestValidSchema();
|
|
expect(latestValidSchemaResult.latestValidVersion?.schemas.total).toEqual(1);
|
|
expect(latestValidSchemaResult.latestValidVersion?.schemas.nodes[0]).toEqual(
|
|
expect.objectContaining({
|
|
commit: 'c0',
|
|
}),
|
|
);
|
|
|
|
const versionId = (commit: string) =>
|
|
versions.find(node => 'commit' in node.log && node.log.commit === commit)!.id;
|
|
|
|
// marking the third version as valid should promote it to be the latest valid version
|
|
let versionStatusUpdateResult = await updateSchemaVersionStatus(versionId('c2'), true);
|
|
|
|
expect(versionStatusUpdateResult.updateSchemaVersionStatus.id).toEqual(versionId('c2'));
|
|
|
|
latestValidSchemaResult = await fetchLatestValidSchema();
|
|
expect(latestValidSchemaResult.latestValidVersion?.id).toEqual(versionId('c2'));
|
|
|
|
// marking the second (not the most recent) version as valid should NOT promote it to be the latest valid version
|
|
versionStatusUpdateResult = await updateSchemaVersionStatus(versionId('c1'), true);
|
|
|
|
latestValidSchemaResult = await fetchLatestValidSchema();
|
|
expect(latestValidSchemaResult.latestValidVersion?.id).toEqual(versionId('c2'));
|
|
});
|
|
|
|
test.concurrent(
|
|
'marking only the most recent version as valid result in an update of CDN',
|
|
async () => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject } = await createOrg();
|
|
const { createTargetAccessToken, createCdnAccess, fetchVersions, updateSchemaVersionStatus } =
|
|
await createProject(ProjectType.Single, {
|
|
useLegacyRegistryModels: true,
|
|
});
|
|
const { publishSchema } = await createTargetAccessToken({});
|
|
const { fetchSchemaFromCDN, fetchMetadataFromCDN } = await createCdnAccess();
|
|
|
|
// Initial schema
|
|
let result = await publishSchema({
|
|
author: 'Kamil',
|
|
commit: 'c0',
|
|
sdl: `type Query { ping: String }`,
|
|
metadata: JSON.stringify({ c0: 1 }),
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(result.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Second version with a forced breaking change
|
|
result = await publishSchema({
|
|
author: 'Kamil',
|
|
commit: 'c1',
|
|
sdl: `type Query { pong: String }`,
|
|
force: true,
|
|
metadata: JSON.stringify({ c1: 1 }),
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
// third version with another forced breaking change
|
|
result = await publishSchema({
|
|
author: 'Kamil',
|
|
commit: 'c2',
|
|
sdl: `type Query { tennis: String }`,
|
|
force: true,
|
|
metadata: JSON.stringify({ c2: 1 }),
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
// the initial version should available on CDN
|
|
let cdnResult = await fetchSchemaFromCDN();
|
|
expect(cdnResult.body).toContain('ping');
|
|
|
|
let cdnMetadataResult = await fetchMetadataFromCDN();
|
|
expect(cdnMetadataResult.status).toEqual(200);
|
|
expect(cdnMetadataResult.body).toEqual({ c0: 1 });
|
|
|
|
const versions = await fetchVersions(3);
|
|
|
|
const versionId = (commit: string) =>
|
|
versions.find(node => 'commit' in node.log && node.log.commit === commit)!.id;
|
|
|
|
// marking the third version as valid should promote it to be the latest valid version and publish it to CDN
|
|
await updateSchemaVersionStatus(versionId('c2'), true);
|
|
|
|
cdnResult = await fetchSchemaFromCDN();
|
|
expect(cdnResult.body).toContain('tennis');
|
|
|
|
cdnMetadataResult = await fetchMetadataFromCDN();
|
|
expect(cdnMetadataResult.status).toEqual(200);
|
|
expect(cdnMetadataResult.body).toEqual({ c2: 1 });
|
|
|
|
// marking the second (not the most recent) version as valid should NOT promote it to be the latest valid version
|
|
await updateSchemaVersionStatus(versionId('c1'), true);
|
|
|
|
cdnResult = await fetchSchemaFromCDN();
|
|
expect(cdnResult.body).toContain('tennis');
|
|
|
|
cdnMetadataResult = await fetchMetadataFromCDN();
|
|
expect(cdnMetadataResult.status).toEqual(200);
|
|
expect(cdnMetadataResult.body).toEqual({ c2: 1 });
|
|
},
|
|
);
|
|
});
|
|
|
|
async function prepare() {
|
|
const { tokens } = await prepareProject(ProjectType.Single, RegistryModel.Legacy);
|
|
|
|
return createCLI(tokens.registry);
|
|
}
|