mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 14:37:17 +00:00
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Kamil Kisiela <kamil.kisiela@gmail.com>
628 lines
18 KiB
TypeScript
628 lines
18 KiB
TypeScript
import { TargetAccessScope, ProjectType } from '@app/gql/graphql';
|
|
import { createHash } from 'node:crypto';
|
|
import { schemaPublish, schemaCheck } from '../../testkit/cli';
|
|
import { authenticate } from '../../testkit/auth';
|
|
import {
|
|
createOrganization,
|
|
joinOrganization,
|
|
createProject,
|
|
createToken,
|
|
fetchSupergraphFromCDN,
|
|
inviteToOrganization,
|
|
} from '../../testkit/flow';
|
|
|
|
test('can publish and check a schema with target:registry:read access', async () => {
|
|
const { access_token: owner_access_token } = await authenticate('main');
|
|
const orgResult = await createOrganization(
|
|
{
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
|
|
|
|
const invitationResult = await inviteToOrganization(
|
|
{
|
|
email: 'some@email.com',
|
|
organization: org.cleanId,
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
|
|
expect(inviteCode).toBeDefined();
|
|
|
|
// Join
|
|
const { access_token: member_access_token } = await authenticate('extra');
|
|
await joinOrganization(inviteCode!, member_access_token);
|
|
|
|
const projectResult = await createProject(
|
|
{
|
|
organization: org.cleanId,
|
|
type: ProjectType.Single,
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const project = projectResult.body.data!.createProject.ok!.createdProject;
|
|
const target = projectResult.body.data!.createProject.ok!.createdTargets[0];
|
|
|
|
// Create a token with write rights
|
|
const writeTokenResult = await createToken(
|
|
{
|
|
name: 'test',
|
|
organization: org.cleanId,
|
|
project: project.cleanId,
|
|
target: target.cleanId,
|
|
organizationScopes: [],
|
|
projectScopes: [],
|
|
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
|
},
|
|
owner_access_token,
|
|
);
|
|
expect(writeTokenResult.body.errors).not.toBeDefined();
|
|
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
|
|
|
|
await schemaPublish([
|
|
'--token',
|
|
writeToken,
|
|
'--author',
|
|
'Kamil',
|
|
'--commit',
|
|
'abc123',
|
|
'fixtures/init-schema.graphql',
|
|
]);
|
|
|
|
await schemaCheck(['--token', writeToken, 'fixtures/nonbreaking-schema.graphql']);
|
|
|
|
await expect(
|
|
schemaCheck(['--token', writeToken, 'fixtures/breaking-schema.graphql']),
|
|
).rejects.toThrowError(/breaking/);
|
|
});
|
|
|
|
test('publishing a breaking change results in invalid state', async () => {
|
|
const { access_token: owner_access_token } = await authenticate('main');
|
|
const orgResult = await createOrganization(
|
|
{
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
|
|
|
|
const invitationResult = await inviteToOrganization(
|
|
{
|
|
email: 'some@email.com',
|
|
organization: org.cleanId,
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const inviteCode = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
|
|
expect(inviteCode).toBeDefined();
|
|
|
|
// Join
|
|
const { access_token: member_access_token } = await authenticate('extra');
|
|
await joinOrganization(inviteCode!, member_access_token);
|
|
|
|
const projectResult = await createProject(
|
|
{
|
|
organization: org.cleanId,
|
|
type: ProjectType.Single,
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const project = projectResult.body.data!.createProject.ok!.createdProject;
|
|
const target = projectResult.body.data!.createProject.ok!.createdTargets[0];
|
|
|
|
// Create a token with write rights
|
|
const writeTokenResult = await createToken(
|
|
{
|
|
name: 'test',
|
|
organization: org.cleanId,
|
|
project: project.cleanId,
|
|
target: target.cleanId,
|
|
organizationScopes: [],
|
|
projectScopes: [],
|
|
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
|
},
|
|
owner_access_token,
|
|
);
|
|
expect(writeTokenResult.body.errors).not.toBeDefined();
|
|
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
|
|
|
|
await schemaPublish([
|
|
'--token',
|
|
writeToken,
|
|
'--author',
|
|
'Kamil',
|
|
'--commit',
|
|
'abc123',
|
|
'fixtures/init-schema.graphql',
|
|
]);
|
|
|
|
await expect(
|
|
schemaPublish(['--token', writeToken, 'fixtures/breaking-schema.graphql']),
|
|
).rejects.toThrowError(/breaking/);
|
|
});
|
|
|
|
test('publishing invalid schema SDL provides meaningful feedback for the user.', async () => {
|
|
const { access_token: owner_access_token } = await authenticate('main');
|
|
const orgResult = await createOrganization(
|
|
{
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
|
|
|
|
const invitationResult = await inviteToOrganization(
|
|
{
|
|
email: 'some@email.com',
|
|
organization: org.cleanId,
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
|
|
expect(code).toBeDefined();
|
|
|
|
// Join
|
|
const { access_token: member_access_token } = await authenticate('extra');
|
|
await joinOrganization(code!, member_access_token);
|
|
|
|
const projectResult = await createProject(
|
|
{
|
|
organization: org.cleanId,
|
|
type: ProjectType.Single,
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const project = projectResult.body.data!.createProject.ok!.createdProject;
|
|
const target = projectResult.body.data!.createProject.ok!.createdTargets[0];
|
|
|
|
// Create a token with write rights
|
|
const writeTokenResult = await createToken(
|
|
{
|
|
name: 'test',
|
|
organization: org.cleanId,
|
|
project: project.cleanId,
|
|
target: target.cleanId,
|
|
organizationScopes: [],
|
|
projectScopes: [],
|
|
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
|
},
|
|
owner_access_token,
|
|
);
|
|
expect(writeTokenResult.body.errors).not.toBeDefined();
|
|
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
|
|
|
|
const allocatedError = new Error('Should have thrown.');
|
|
try {
|
|
await schemaPublish([
|
|
'--token',
|
|
writeToken,
|
|
'--author',
|
|
'Kamil',
|
|
'--commit',
|
|
'abc123',
|
|
'fixtures/init-invalid-schema.graphql',
|
|
]);
|
|
throw allocatedError;
|
|
} catch (err) {
|
|
if (err === allocatedError) {
|
|
throw err;
|
|
}
|
|
expect(String(err)).toMatch(`The SDL is not valid at line 1, column 1:`);
|
|
expect(String(err)).toMatch(`Syntax Error: Unexpected Name "iliketurtles"`);
|
|
}
|
|
});
|
|
|
|
test('service url should be available in supergraph', async () => {
|
|
const { access_token: owner_access_token } = await authenticate('main');
|
|
const orgResult = await createOrganization(
|
|
{
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
|
|
|
|
const invitationResult = await inviteToOrganization(
|
|
{
|
|
email: 'some@email.com',
|
|
organization: org.cleanId,
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
|
|
expect(code).toBeDefined();
|
|
|
|
// Join
|
|
const { access_token: member_access_token } = await authenticate('extra');
|
|
await joinOrganization(code!, member_access_token);
|
|
|
|
const projectResult = await createProject(
|
|
{
|
|
organization: org.cleanId,
|
|
type: ProjectType.Federation,
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const project = projectResult.body.data!.createProject.ok!.createdProject;
|
|
const target = projectResult.body.data!.createProject.ok!.createdTargets[0];
|
|
|
|
// Create a token with write rights
|
|
const writeTokenResult = await createToken(
|
|
{
|
|
name: 'test',
|
|
organization: org.cleanId,
|
|
project: project.cleanId,
|
|
target: target.cleanId,
|
|
organizationScopes: [],
|
|
projectScopes: [],
|
|
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
|
},
|
|
owner_access_token,
|
|
);
|
|
expect(writeTokenResult.body.errors).not.toBeDefined();
|
|
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
|
|
|
|
await schemaPublish([
|
|
'--token',
|
|
writeToken,
|
|
'--author',
|
|
'Kamil',
|
|
'--commit',
|
|
'abc123',
|
|
'--service',
|
|
'users',
|
|
'--url',
|
|
'https://api.com/users-subgraph',
|
|
'fixtures/federation-init.graphql',
|
|
]);
|
|
|
|
const supergraph = await fetchSupergraphFromCDN(
|
|
{
|
|
organization: org.cleanId,
|
|
project: project.cleanId,
|
|
target: target.cleanId,
|
|
},
|
|
writeToken,
|
|
);
|
|
|
|
expect(supergraph.body).toMatch('(name: "users" url: "https://api.com/users-subgraph")');
|
|
});
|
|
|
|
test('service url should be required in Federation', async () => {
|
|
const { access_token: owner_access_token } = await authenticate('main');
|
|
const orgResult = await createOrganization(
|
|
{
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
|
|
|
|
const invitationResult = await inviteToOrganization(
|
|
{
|
|
email: 'some@email.com',
|
|
organization: org.cleanId,
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
|
|
expect(code).toBeDefined();
|
|
|
|
// Join
|
|
const { access_token: member_access_token } = await authenticate('extra');
|
|
await joinOrganization(code!, member_access_token);
|
|
|
|
const projectResult = await createProject(
|
|
{
|
|
organization: org.cleanId,
|
|
type: ProjectType.Federation,
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const project = projectResult.body.data!.createProject.ok!.createdProject;
|
|
const target = projectResult.body.data!.createProject.ok!.createdTargets[0];
|
|
|
|
// Create a token with write rights
|
|
const writeTokenResult = await createToken(
|
|
{
|
|
name: 'test',
|
|
organization: org.cleanId,
|
|
project: project.cleanId,
|
|
target: target.cleanId,
|
|
organizationScopes: [],
|
|
projectScopes: [],
|
|
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
|
},
|
|
owner_access_token,
|
|
);
|
|
expect(writeTokenResult.body.errors).not.toBeDefined();
|
|
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
|
|
|
|
await expect(
|
|
schemaPublish([
|
|
'--token',
|
|
writeToken,
|
|
'--author',
|
|
'Kamil',
|
|
'--commit',
|
|
'abc123',
|
|
'--service',
|
|
'users',
|
|
'fixtures/federation-init.graphql',
|
|
]),
|
|
).rejects.toThrowError(/url/);
|
|
});
|
|
|
|
test('schema:publish should print a link to the website', async () => {
|
|
const { access_token: owner_access_token } = await authenticate('main');
|
|
const orgResult = await createOrganization(
|
|
{
|
|
name: 'bar',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
|
|
|
|
const invitationResult = await inviteToOrganization(
|
|
{
|
|
email: 'some@email.com',
|
|
organization: org.cleanId,
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
|
|
expect(code).toBeDefined();
|
|
|
|
// Join
|
|
const { access_token: member_access_token } = await authenticate('extra');
|
|
await joinOrganization(code!, member_access_token);
|
|
|
|
const projectResult = await createProject(
|
|
{
|
|
organization: org.cleanId,
|
|
type: ProjectType.Single,
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const project = projectResult.body.data!.createProject.ok!.createdProject;
|
|
const targets = projectResult.body.data!.createProject.ok!.createdTargets;
|
|
const target = targets.find(t => t.name === 'development')!;
|
|
|
|
// Create a token with write rights
|
|
const writeTokenResult = await createToken(
|
|
{
|
|
name: 'test',
|
|
organization: org.cleanId,
|
|
project: project.cleanId,
|
|
target: target.cleanId,
|
|
organizationScopes: [],
|
|
projectScopes: [],
|
|
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
|
},
|
|
owner_access_token,
|
|
);
|
|
expect(writeTokenResult.body.errors).not.toBeDefined();
|
|
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
|
|
|
|
await expect(
|
|
schemaPublish(['--token', writeToken, 'fixtures/init-schema.graphql']),
|
|
).resolves.toMatch('Available at https://app.graphql-hive.com/bar/foo/development');
|
|
|
|
await expect(
|
|
schemaPublish(['--token', writeToken, 'fixtures/nonbreaking-schema.graphql']),
|
|
).resolves.toMatch('Available at https://app.graphql-hive.com/bar/foo/development/history/');
|
|
});
|
|
|
|
test('schema:check should notify user when registry is empty', async () => {
|
|
const { access_token: owner_access_token } = await authenticate('main');
|
|
const orgResult = await createOrganization(
|
|
{
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
|
|
|
|
const invitationResult = await inviteToOrganization(
|
|
{
|
|
email: 'some@email.com',
|
|
organization: org.cleanId,
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
|
|
expect(code).toBeDefined();
|
|
|
|
// Join
|
|
const { access_token: member_access_token } = await authenticate('extra');
|
|
await joinOrganization(code!, member_access_token);
|
|
|
|
const projectResult = await createProject(
|
|
{
|
|
organization: org.cleanId,
|
|
type: ProjectType.Single,
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const project = projectResult.body.data!.createProject.ok!.createdProject;
|
|
const target = projectResult.body.data!.createProject.ok!.createdTargets[0];
|
|
|
|
// Create a token with write rights
|
|
const writeTokenResult = await createToken(
|
|
{
|
|
name: 'test',
|
|
organization: org.cleanId,
|
|
project: project.cleanId,
|
|
target: target.cleanId,
|
|
organizationScopes: [],
|
|
projectScopes: [],
|
|
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
|
},
|
|
owner_access_token,
|
|
);
|
|
expect(writeTokenResult.body.errors).not.toBeDefined();
|
|
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
|
|
|
|
await expect(
|
|
schemaCheck(['--token', writeToken, 'fixtures/init-schema.graphql']),
|
|
).resolves.toMatch('empty');
|
|
});
|
|
|
|
test('schema:check should throw on corrupted schema', async () => {
|
|
const { access_token: owner_access_token } = await authenticate('main');
|
|
const orgResult = await createOrganization(
|
|
{
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
|
|
|
|
const invitationResult = await inviteToOrganization(
|
|
{
|
|
email: 'some@email.com',
|
|
organization: org.cleanId,
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const code = invitationResult.body.data?.inviteToOrganizationByEmail.ok?.code;
|
|
expect(code).toBeDefined();
|
|
|
|
// Join
|
|
const { access_token: member_access_token } = await authenticate('extra');
|
|
await joinOrganization(code!, member_access_token);
|
|
|
|
const projectResult = await createProject(
|
|
{
|
|
organization: org.cleanId,
|
|
type: ProjectType.Single,
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const project = projectResult.body.data!.createProject.ok!.createdProject;
|
|
const target = projectResult.body.data!.createProject.ok!.createdTargets[0];
|
|
|
|
// Create a token with write rights
|
|
const writeTokenResult = await createToken(
|
|
{
|
|
name: 'test',
|
|
organization: org.cleanId,
|
|
project: project.cleanId,
|
|
target: target.cleanId,
|
|
organizationScopes: [],
|
|
projectScopes: [],
|
|
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
|
},
|
|
owner_access_token,
|
|
);
|
|
expect(writeTokenResult.body.errors).not.toBeDefined();
|
|
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
|
|
|
|
const output = schemaCheck(['--token', writeToken, 'fixtures/missing-type.graphql']);
|
|
|
|
await expect(output).rejects.toThrowError('Unknown type');
|
|
});
|
|
|
|
test('schema:publish should see Invalid Token error when token is invalid', async () => {
|
|
const { access_token: owner_access_token } = await authenticate('main');
|
|
const orgResult = await createOrganization(
|
|
{
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
|
|
|
|
await createProject(
|
|
{
|
|
organization: org.cleanId,
|
|
type: ProjectType.Single,
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const token = createHash('md5').update('nope').digest('hex').substring(0, 31);
|
|
|
|
const output = schemaPublish(['--token', token, 'fixtures/init-schema.graphql']);
|
|
|
|
await expect(output).rejects.toThrowError('Invalid token provided');
|
|
});
|
|
|
|
test('schema:publish should support experimental_acceptBreakingChanges flag', async () => {
|
|
const { access_token: owner_access_token } = await authenticate('main');
|
|
const orgResult = await createOrganization(
|
|
{
|
|
name: 'bar',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
const org = orgResult.body.data!.createOrganization.ok!.createdOrganizationPayload.organization;
|
|
const projectResult = await createProject(
|
|
{
|
|
organization: org.cleanId,
|
|
type: ProjectType.Single,
|
|
name: 'foo',
|
|
},
|
|
owner_access_token,
|
|
);
|
|
|
|
const project = projectResult.body.data!.createProject.ok!.createdProject;
|
|
const targets = projectResult.body.data!.createProject.ok!.createdTargets;
|
|
const target = targets.find(t => t.name === 'development')!;
|
|
|
|
// Create a token with write rights
|
|
const writeTokenResult = await createToken(
|
|
{
|
|
name: 'test',
|
|
organization: org.cleanId,
|
|
project: project.cleanId,
|
|
target: target.cleanId,
|
|
organizationScopes: [],
|
|
projectScopes: [],
|
|
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
|
|
},
|
|
owner_access_token,
|
|
);
|
|
expect(writeTokenResult.body.errors).not.toBeDefined();
|
|
const writeToken = writeTokenResult.body.data!.createToken.ok!.secret;
|
|
|
|
await expect(
|
|
schemaPublish(['--token', writeToken, 'fixtures/init-schema.graphql']),
|
|
).resolves.toMatch('Available at https://app.graphql-hive.com/bar/foo/development');
|
|
|
|
await expect(
|
|
schemaPublish([
|
|
'--token',
|
|
writeToken,
|
|
'--experimental_acceptBreakingChanges',
|
|
'fixtures/breaking-schema.graphql',
|
|
]),
|
|
).resolves.toMatch('Available at https://app.graphql-hive.com/bar/foo/development/history/');
|
|
});
|