mirror of
https://github.com/graphql-hive/console
synced 2026-04-27 09:27:17 +00:00
2631 lines
72 KiB
TypeScript
2631 lines
72 KiB
TypeScript
import {
|
|
ProjectType,
|
|
ResourceAssignmentModeType,
|
|
RuleInstanceSeverityLevel,
|
|
} from 'testkit/gql/graphql';
|
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
import { createStorage } from '@hive/storage';
|
|
import { graphql } from '../../../testkit/gql';
|
|
import { execute } from '../../../testkit/graphql';
|
|
import { initSeed } from '../../../testkit/seed';
|
|
import { createPolicy } from '../policy/policy-check.spec';
|
|
|
|
test.concurrent('can check a schema with target:registry:read access', async ({ expect }) => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject } = await createOrg();
|
|
const { createTargetAccessToken } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
const noAccessToken = await createTargetAccessToken({
|
|
mode: 'noAccess',
|
|
});
|
|
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
// Check schema with no read and write rights
|
|
const checkResultErrors = await noAccessToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
foo: String
|
|
}
|
|
`)
|
|
.then(r => r.expectGraphQLErrors());
|
|
expect(checkResultErrors).toHaveLength(1);
|
|
expect(checkResultErrors[0].message).toMatch(
|
|
`No access (reason: "Missing permission for performing 'schemaCheck:create' on resource")`,
|
|
);
|
|
|
|
// Check schema with read rights
|
|
const checkResultValid = await readToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
foo: String
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
expect(checkResultValid.schemaCheck.__typename).toBe('SchemaCheckSuccess');
|
|
});
|
|
|
|
test.concurrent('should match indentation of previous description', async ({ expect }) => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject } = await createOrg();
|
|
const { createTargetAccessToken } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
" ping-ping "
|
|
ping: String
|
|
"pong-pong"
|
|
pong: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
"""
|
|
ping-ping
|
|
"""
|
|
ping: String
|
|
" pong-pong "
|
|
pong: String
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckSuccess') {
|
|
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
|
}
|
|
|
|
expect(check.__typename).toBe('SchemaCheckSuccess');
|
|
expect(check.changes!.total).toBe(0);
|
|
});
|
|
|
|
const SchemaCheckQuery = graphql(/* GraphQL */ `
|
|
query SchemaCheckOnTargetQuery($selector: TargetSelectorInput!, $id: ID!) {
|
|
target(reference: { bySelector: $selector }) {
|
|
schemaCheck(id: $id) {
|
|
__typename
|
|
id
|
|
createdAt
|
|
schemaSDL
|
|
serviceName
|
|
schemaVersion {
|
|
id
|
|
}
|
|
meta {
|
|
commit
|
|
author
|
|
}
|
|
safeSchemaChanges {
|
|
nodes {
|
|
criticality
|
|
criticalityReason
|
|
message
|
|
path
|
|
approval {
|
|
schemaCheckId
|
|
approvedAt
|
|
approvedBy {
|
|
id
|
|
displayName
|
|
}
|
|
}
|
|
}
|
|
}
|
|
breakingSchemaChanges {
|
|
nodes {
|
|
criticality
|
|
criticalityReason
|
|
message
|
|
path
|
|
approval {
|
|
schemaCheckId
|
|
approvedAt
|
|
approvedBy {
|
|
id
|
|
displayName
|
|
}
|
|
}
|
|
}
|
|
}
|
|
schemaPolicyWarnings {
|
|
edges {
|
|
node {
|
|
message
|
|
ruleId
|
|
start {
|
|
line
|
|
column
|
|
}
|
|
end {
|
|
line
|
|
column
|
|
}
|
|
}
|
|
}
|
|
}
|
|
... on SuccessfulSchemaCheck {
|
|
compositeSchemaSDL
|
|
supergraphSDL
|
|
approvalComment
|
|
}
|
|
... on FailedSchemaCheck {
|
|
compositionErrors {
|
|
nodes {
|
|
message
|
|
path
|
|
}
|
|
}
|
|
schemaPolicyErrors {
|
|
edges {
|
|
node {
|
|
message
|
|
ruleId
|
|
start {
|
|
line
|
|
column
|
|
}
|
|
end {
|
|
line
|
|
column
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
|
|
const ApproveFailedSchemaCheckMutation = graphql(/* GraphQL */ `
|
|
mutation ApproveFailedSchemaCheck($input: ApproveFailedSchemaCheckInput!) {
|
|
approveFailedSchemaCheck(input: $input) {
|
|
ok {
|
|
schemaCheck {
|
|
__typename
|
|
... on SuccessfulSchemaCheck {
|
|
isApproved
|
|
approvalComment
|
|
approvedBy {
|
|
__typename
|
|
}
|
|
}
|
|
}
|
|
}
|
|
error {
|
|
message
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
|
|
test.concurrent(
|
|
'successful check without previously published schema is persisted',
|
|
async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
pong: String
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckSuccess') {
|
|
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const schemaCheck = await execute({
|
|
document: SchemaCheckQuery,
|
|
variables: {
|
|
selector: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
},
|
|
id: schemaCheckId,
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(schemaCheck).toMatchObject({
|
|
target: {
|
|
schemaCheck: {
|
|
__typename: 'SuccessfulSchemaCheck',
|
|
id: schemaCheckId,
|
|
createdAt: expect.any(String),
|
|
serviceName: null,
|
|
schemaVersion: null,
|
|
},
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'successful check with previously published schema is persisted',
|
|
async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
pong: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
pong: String
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckSuccess') {
|
|
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const schemaCheck = await execute({
|
|
document: SchemaCheckQuery,
|
|
variables: {
|
|
selector: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
},
|
|
id: schemaCheckId,
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(schemaCheck).toMatchObject({
|
|
target: {
|
|
schemaCheck: {
|
|
__typename: 'SuccessfulSchemaCheck',
|
|
id: schemaCheckId,
|
|
createdAt: expect.any(String),
|
|
serviceName: null,
|
|
schemaVersion: {
|
|
id: expect.any(String),
|
|
},
|
|
},
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent('failed check due to graphql validation is persisted', async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: Str
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckError, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const schemaCheck = await execute({
|
|
document: SchemaCheckQuery,
|
|
variables: {
|
|
selector: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
},
|
|
id: schemaCheckId,
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(schemaCheck).toMatchObject({
|
|
target: {
|
|
schemaCheck: {
|
|
__typename: 'FailedSchemaCheck',
|
|
id: schemaCheckId,
|
|
createdAt: expect.any(String),
|
|
serviceName: null,
|
|
schemaVersion: null,
|
|
compositionErrors: {
|
|
nodes: [
|
|
{
|
|
message: 'Unknown type "Str".',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
test.concurrent('failed check due to breaking change is persisted', async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckError, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const schemaCheck = await execute({
|
|
document: SchemaCheckQuery,
|
|
variables: {
|
|
selector: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
},
|
|
id: schemaCheckId,
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(schemaCheck).toMatchObject({
|
|
target: {
|
|
schemaCheck: {
|
|
__typename: 'FailedSchemaCheck',
|
|
id: schemaCheckId,
|
|
createdAt: expect.any(String),
|
|
serviceName: null,
|
|
schemaVersion: {
|
|
id: expect.any(String),
|
|
},
|
|
compositionErrors: null,
|
|
breakingSchemaChanges: {
|
|
nodes: [
|
|
{
|
|
message: "Field 'Query.ping' changed type from 'String' to 'Float'",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
test.concurrent('failed check due to policy error is persisted', async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target, setProjectSchemaPolicy } = await createProject(
|
|
ProjectType.Single,
|
|
);
|
|
|
|
await setProjectSchemaPolicy(createPolicy(RuleInstanceSeverityLevel.Error));
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
foo: String
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckError, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const schemaCheck = await execute({
|
|
document: SchemaCheckQuery,
|
|
variables: {
|
|
selector: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
},
|
|
id: schemaCheckId,
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(schemaCheck).toMatchObject({
|
|
target: {
|
|
schemaCheck: {
|
|
__typename: 'FailedSchemaCheck',
|
|
id: schemaCheckId,
|
|
createdAt: expect.any(String),
|
|
serviceName: null,
|
|
schemaVersion: {
|
|
id: expect.any(String),
|
|
},
|
|
compositionErrors: null,
|
|
breakingSchemaChanges: null,
|
|
schemaPolicyErrors: {
|
|
edges: [
|
|
{
|
|
node: {
|
|
end: {
|
|
column: 17,
|
|
line: 2,
|
|
},
|
|
message: 'Description is required for type "Query"',
|
|
ruleId: 'require-description',
|
|
start: {
|
|
column: 12,
|
|
line: 2,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
test.concurrent(
|
|
'successful check with warnings and safe changes is persisted',
|
|
async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target, setProjectSchemaPolicy } =
|
|
await createProject(ProjectType.Single);
|
|
|
|
await setProjectSchemaPolicy(createPolicy(RuleInstanceSeverityLevel.Warning));
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
foo: String
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckSuccess') {
|
|
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const schemaCheck = await execute({
|
|
document: SchemaCheckQuery,
|
|
variables: {
|
|
selector: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
},
|
|
id: schemaCheckId,
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(schemaCheck).toMatchObject({
|
|
target: {
|
|
schemaCheck: {
|
|
__typename: 'SuccessfulSchemaCheck',
|
|
id: schemaCheckId,
|
|
createdAt: expect.any(String),
|
|
serviceName: null,
|
|
schemaVersion: {
|
|
id: expect.any(String),
|
|
},
|
|
schemaPolicyWarnings: {
|
|
edges: [
|
|
{
|
|
node: {
|
|
end: {
|
|
column: expect.any(Number),
|
|
line: expect.any(Number),
|
|
},
|
|
message: 'Description is required for type "Query"',
|
|
ruleId: 'require-description',
|
|
start: {
|
|
column: expect.any(Number),
|
|
line: expect.any(Number),
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
safeSchemaChanges: {
|
|
nodes: [
|
|
{
|
|
message: "Field 'foo' was added to object type 'Query'",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'failed check due to missing service name is not persisted (federation/stitching)',
|
|
async ({ expect }) => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject } = await createOrg();
|
|
const { createTargetAccessToken } = await createProject(ProjectType.Federation);
|
|
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
foo: String
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckError, got ${check.__typename}`);
|
|
}
|
|
|
|
expect(check.schemaCheck).toEqual(null);
|
|
},
|
|
);
|
|
|
|
test.concurrent('metadata is persisted', async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(
|
|
/* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
pong: String
|
|
}
|
|
`,
|
|
undefined,
|
|
{
|
|
author: 'Freddy Gibbs',
|
|
commit: '$oul $old $eparately',
|
|
},
|
|
)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckSuccess') {
|
|
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const schemaCheck = await execute({
|
|
document: SchemaCheckQuery,
|
|
variables: {
|
|
selector: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
},
|
|
id: schemaCheckId,
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(schemaCheck).toMatchObject({
|
|
target: {
|
|
schemaCheck: {
|
|
__typename: 'SuccessfulSchemaCheck',
|
|
id: schemaCheckId,
|
|
createdAt: expect.any(String),
|
|
serviceName: null,
|
|
schemaVersion: null,
|
|
meta: {
|
|
author: 'Freddy Gibbs',
|
|
commit: '$oul $old $eparately',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
test.concurrent(
|
|
'approve failed schema check that has breaking change status to successful and attaches meta information to the breaking change',
|
|
async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckError, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const mutationResult = await execute({
|
|
document: ApproveFailedSchemaCheckMutation,
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
},
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(mutationResult).toEqual({
|
|
approveFailedSchemaCheck: {
|
|
ok: {
|
|
schemaCheck: {
|
|
__typename: 'SuccessfulSchemaCheck',
|
|
isApproved: true,
|
|
approvalComment: null,
|
|
approvedBy: {
|
|
__typename: 'User',
|
|
},
|
|
},
|
|
},
|
|
error: null,
|
|
},
|
|
});
|
|
|
|
const schemaCheck = await execute({
|
|
document: SchemaCheckQuery,
|
|
variables: {
|
|
selector: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
},
|
|
id: schemaCheckId,
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(schemaCheck).toMatchObject({
|
|
target: {
|
|
schemaCheck: {
|
|
__typename: 'SuccessfulSchemaCheck',
|
|
breakingSchemaChanges: {
|
|
nodes: [
|
|
{
|
|
approval: {
|
|
schemaCheckId,
|
|
approvedAt: expect.any(String),
|
|
approvedBy: {
|
|
id: expect.any(String),
|
|
displayName: expect.any(String),
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent('approve failed schema check with a comment', async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckError, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const mutationResult = await execute({
|
|
document: ApproveFailedSchemaCheckMutation,
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
comment: 'This is a comment',
|
|
},
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(mutationResult).toEqual({
|
|
approveFailedSchemaCheck: {
|
|
ok: {
|
|
schemaCheck: {
|
|
__typename: 'SuccessfulSchemaCheck',
|
|
isApproved: true,
|
|
approvalComment: 'This is a comment',
|
|
approvedBy: {
|
|
__typename: 'User',
|
|
},
|
|
},
|
|
},
|
|
error: null,
|
|
},
|
|
});
|
|
|
|
const schemaCheck = await execute({
|
|
document: SchemaCheckQuery,
|
|
variables: {
|
|
selector: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
},
|
|
id: schemaCheckId,
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(schemaCheck).toMatchObject({
|
|
target: {
|
|
schemaCheck: {
|
|
__typename: 'SuccessfulSchemaCheck',
|
|
approvalComment: 'This is a comment',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
test.concurrent(
|
|
'approving a schema check with contextId containing breaking changes allows the changes for subsequent checks with the same contextId',
|
|
async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
const contextId = 'pr-69420';
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(
|
|
/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`,
|
|
undefined,
|
|
undefined,
|
|
contextId,
|
|
)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckError, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const mutationResult = await execute({
|
|
document: ApproveFailedSchemaCheckMutation,
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
},
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(mutationResult).toEqual({
|
|
approveFailedSchemaCheck: {
|
|
ok: {
|
|
schemaCheck: {
|
|
__typename: 'SuccessfulSchemaCheck',
|
|
isApproved: true,
|
|
approvalComment: null,
|
|
approvedBy: {
|
|
__typename: 'User',
|
|
},
|
|
},
|
|
},
|
|
error: null,
|
|
},
|
|
});
|
|
|
|
const secondCheckResult = await readToken
|
|
.checkSchema(
|
|
/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`,
|
|
undefined,
|
|
undefined,
|
|
contextId,
|
|
)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
if (secondCheckResult.schemaCheck.__typename !== 'SchemaCheckSuccess') {
|
|
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
|
}
|
|
|
|
const newSchemaCheckId = secondCheckResult.schemaCheck.schemaCheck?.id;
|
|
|
|
if (newSchemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const newSchemaCheck = await execute({
|
|
document: SchemaCheckQuery,
|
|
variables: {
|
|
selector: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
},
|
|
id: newSchemaCheckId,
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(newSchemaCheck.target?.schemaCheck).toMatchObject({
|
|
id: newSchemaCheckId,
|
|
breakingSchemaChanges: {
|
|
nodes: [
|
|
{
|
|
approval: {
|
|
schemaCheckId,
|
|
approvedAt: expect.any(String),
|
|
approvedBy: {
|
|
id: expect.any(String),
|
|
displayName: expect.any(String),
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'approving a schema check with contextId containing breaking changes does not allow the changes for subsequent checks with a different contextId',
|
|
async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
const contextId = 'pr-69420';
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(
|
|
/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`,
|
|
undefined,
|
|
undefined,
|
|
contextId,
|
|
)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckError, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const mutationResult = await execute({
|
|
document: ApproveFailedSchemaCheckMutation,
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
},
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(mutationResult).toEqual({
|
|
approveFailedSchemaCheck: {
|
|
ok: {
|
|
schemaCheck: {
|
|
__typename: 'SuccessfulSchemaCheck',
|
|
isApproved: true,
|
|
approvalComment: null,
|
|
approvedBy: {
|
|
__typename: 'User',
|
|
},
|
|
},
|
|
},
|
|
error: null,
|
|
},
|
|
});
|
|
|
|
const secondCheckResult = await readToken
|
|
.checkSchema(
|
|
/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`,
|
|
undefined,
|
|
undefined,
|
|
contextId + '|' + contextId,
|
|
)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
if (secondCheckResult.schemaCheck.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
|
}
|
|
|
|
const newSchemaCheckId = secondCheckResult.schemaCheck.schemaCheck?.id;
|
|
|
|
if (newSchemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const newSchemaCheck = await execute({
|
|
document: SchemaCheckQuery,
|
|
variables: {
|
|
selector: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
},
|
|
id: newSchemaCheckId,
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(newSchemaCheck.target?.schemaCheck).toMatchObject({
|
|
id: newSchemaCheckId,
|
|
breakingSchemaChanges: {
|
|
nodes: [
|
|
{
|
|
approval: null,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'can not approve schema check with insufficient permissions granted by default user role',
|
|
async () => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { organization, createProject, inviteAndJoinMember } = await createOrg();
|
|
|
|
// Setup Start: Create a failed schema check
|
|
|
|
const { project, createTargetAccessToken, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
const checkResult = await writeToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
if (checkResult.schemaCheck.__typename !== 'SchemaCheckError') {
|
|
throw new Error('Invalid result: ' + checkResult.schemaCheck.__typename);
|
|
}
|
|
const schemaCheckId = await checkResult.schemaCheck.schemaCheck?.id;
|
|
if (!schemaCheckId) {
|
|
throw new Error('Invalid result: ' + JSON.stringify(checkResult, null, 2));
|
|
}
|
|
|
|
// Setup Done: Create a failed schema check
|
|
|
|
// Create a member with no access to projects
|
|
const { member, assignMemberRole, memberToken } = await inviteAndJoinMember();
|
|
expect(member.role.name).toEqual('Viewer');
|
|
await assignMemberRole({
|
|
roleId: member.role.id,
|
|
userId: member.user.id,
|
|
resources: { mode: ResourceAssignmentModeType.Granular, projects: [] },
|
|
});
|
|
|
|
// Attempt approving the failed schema check
|
|
const errors = await execute({
|
|
document: ApproveFailedSchemaCheckMutation,
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
},
|
|
},
|
|
authToken: memberToken,
|
|
}).then(r => r.expectGraphQLErrors());
|
|
expect(errors).toHaveLength(1);
|
|
expect(errors.at(0)).toMatchObject({
|
|
extensions: {
|
|
code: 'UNAUTHORISED',
|
|
},
|
|
message: `No access (reason: "Missing permission for performing 'schemaCheck:approve' on resource")`,
|
|
path: ['approveFailedSchemaCheck'],
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'can not approve schema check with insufficient permissions granted by member role (no access to project resource)',
|
|
async () => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { organization, createProject, inviteAndJoinMember } = await createOrg();
|
|
|
|
// Setup Start: Create a failed schema check
|
|
|
|
const { project, createTargetAccessToken, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
const checkResult = await writeToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
if (checkResult.schemaCheck.__typename !== 'SchemaCheckError') {
|
|
throw new Error('Invalid result: ' + checkResult.schemaCheck.__typename);
|
|
}
|
|
const schemaCheckId = await checkResult.schemaCheck.schemaCheck?.id;
|
|
if (!schemaCheckId) {
|
|
throw new Error('Invalid result: ' + JSON.stringify(checkResult, null, 2));
|
|
}
|
|
|
|
// Setup Done: Create a failed schema check
|
|
|
|
// Create a member with no access to projects
|
|
const { member, assignMemberRole, createMemberRole, memberToken } = await inviteAndJoinMember();
|
|
const memberRole = await createMemberRole(['schemaCheck:approve', 'project:describe']);
|
|
await assignMemberRole({
|
|
roleId: memberRole.id,
|
|
userId: member.user.id,
|
|
resources: { mode: ResourceAssignmentModeType.Granular, projects: [] },
|
|
});
|
|
|
|
// Attempt approving the failed schema check
|
|
const errors = await execute({
|
|
document: ApproveFailedSchemaCheckMutation,
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
},
|
|
},
|
|
authToken: memberToken,
|
|
}).then(r => r.expectGraphQLErrors());
|
|
expect(errors).toHaveLength(1);
|
|
expect(errors.at(0)).toMatchObject({
|
|
extensions: {
|
|
code: 'UNAUTHORISED',
|
|
},
|
|
message: `No access (reason: "Missing permission for performing 'schemaCheck:approve' on resource")`,
|
|
path: ['approveFailedSchemaCheck'],
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'can not approve schema check with insufficient permissions granted by member role (no access to target resource)',
|
|
async () => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { organization, createProject, inviteAndJoinMember } = await createOrg();
|
|
|
|
// Setup Start: Create a failed schema check
|
|
|
|
const { project, createTargetAccessToken, target, targets } = await createProject(
|
|
ProjectType.Single,
|
|
);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
const checkResult = await writeToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
if (checkResult.schemaCheck.__typename !== 'SchemaCheckError') {
|
|
throw new Error('Invalid result: ' + checkResult.schemaCheck.__typename);
|
|
}
|
|
const schemaCheckId = await checkResult.schemaCheck.schemaCheck?.id;
|
|
if (!schemaCheckId) {
|
|
throw new Error('Invalid result: ' + JSON.stringify(checkResult, null, 2));
|
|
}
|
|
|
|
// Setup Done: Create a failed schema check
|
|
|
|
// Create a member with no access to project targets
|
|
const { member, createMemberRole, assignMemberRole, memberToken } = await inviteAndJoinMember();
|
|
const memberRole = await createMemberRole(['schemaCheck:approve', 'project:describe']);
|
|
await assignMemberRole({
|
|
roleId: memberRole.id,
|
|
userId: member.user.id,
|
|
resources: {
|
|
mode: ResourceAssignmentModeType.Granular,
|
|
projects: [
|
|
{
|
|
projectId: project.id,
|
|
targets: {
|
|
mode: ResourceAssignmentModeType.Granular,
|
|
targets: [],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
// Attempt approving the failed schema check
|
|
const errors = await execute({
|
|
document: ApproveFailedSchemaCheckMutation,
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
},
|
|
},
|
|
authToken: memberToken,
|
|
}).then(r => r.expectGraphQLErrors());
|
|
expect(errors).toHaveLength(1);
|
|
expect(errors.at(0)).toMatchObject({
|
|
extensions: {
|
|
code: 'UNAUTHORISED',
|
|
},
|
|
message: `No access (reason: "Missing permission for performing 'schemaCheck:approve' on resource")`,
|
|
path: ['approveFailedSchemaCheck'],
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'can approve schema with sufficient permissions granted by member role (access to target)',
|
|
async () => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { organization, createProject, inviteAndJoinMember } = await createOrg();
|
|
|
|
// Setup Start: Create a failed schema check
|
|
|
|
const { project, createTargetAccessToken, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
const checkResult = await writeToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
if (checkResult.schemaCheck.__typename !== 'SchemaCheckError') {
|
|
throw new Error('Invalid result: ' + checkResult.schemaCheck.__typename);
|
|
}
|
|
const schemaCheckId = await checkResult.schemaCheck.schemaCheck?.id;
|
|
if (!schemaCheckId) {
|
|
throw new Error('Invalid result: ' + JSON.stringify(checkResult, null, 2));
|
|
}
|
|
|
|
// Setup Done: Create a failed schema check
|
|
|
|
// Create a member with no access to projects
|
|
const { member, createMemberRole, assignMemberRole, memberToken } = await inviteAndJoinMember();
|
|
const memberRole = await createMemberRole(['schemaCheck:approve', 'project:describe']);
|
|
await assignMemberRole({
|
|
roleId: memberRole.id,
|
|
userId: member.user.id,
|
|
resources: {
|
|
mode: ResourceAssignmentModeType.Granular,
|
|
projects: [
|
|
{
|
|
projectId: project.id,
|
|
targets: {
|
|
mode: ResourceAssignmentModeType.Granular,
|
|
targets: [
|
|
{
|
|
targetId: target.id,
|
|
appDeployments: { mode: ResourceAssignmentModeType.All },
|
|
services: { mode: ResourceAssignmentModeType.All },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
// Attempt approving the failed schema check
|
|
const result = await execute({
|
|
document: ApproveFailedSchemaCheckMutation,
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
},
|
|
},
|
|
authToken: memberToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
expect(result.approveFailedSchemaCheck.ok).toMatchObject({
|
|
schemaCheck: {
|
|
__typename: 'SuccessfulSchemaCheck',
|
|
isApproved: true,
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'subsequent schema check with shared contextId that contains new breaking changes that have not been approved fails',
|
|
async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
pong: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
const contextId = 'pr-69420';
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(
|
|
/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`,
|
|
undefined,
|
|
undefined,
|
|
contextId,
|
|
)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckError, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const mutationResult = await execute({
|
|
document: ApproveFailedSchemaCheckMutation,
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
},
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(mutationResult).toEqual({
|
|
approveFailedSchemaCheck: {
|
|
ok: {
|
|
schemaCheck: {
|
|
__typename: 'SuccessfulSchemaCheck',
|
|
isApproved: true,
|
|
approvalComment: null,
|
|
approvedBy: {
|
|
__typename: 'User',
|
|
},
|
|
},
|
|
},
|
|
error: null,
|
|
},
|
|
});
|
|
|
|
const secondCheckResult = await readToken
|
|
.checkSchema(
|
|
/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
pong: Float
|
|
}
|
|
`,
|
|
undefined,
|
|
undefined,
|
|
contextId,
|
|
)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
if (secondCheckResult.schemaCheck.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckSuccess, got ${check.__typename}`);
|
|
}
|
|
|
|
const newSchemaCheckId = secondCheckResult.schemaCheck.schemaCheck?.id;
|
|
|
|
if (newSchemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const newSchemaCheck = await execute({
|
|
document: SchemaCheckQuery,
|
|
variables: {
|
|
selector: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
},
|
|
id: newSchemaCheckId,
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(newSchemaCheck.target?.schemaCheck).toMatchObject({
|
|
id: newSchemaCheckId,
|
|
breakingSchemaChanges: {
|
|
nodes: [
|
|
{
|
|
approval: {
|
|
schemaCheckId,
|
|
approvedAt: expect.any(String),
|
|
approvedBy: {
|
|
id: expect.any(String),
|
|
displayName: expect.any(String),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
approval: null,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'contextId that has more than 300 characters is not allowed',
|
|
async ({ expect }) => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject } = await createOrg();
|
|
const { createTargetAccessToken } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
pong: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
const contextId = '';
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(
|
|
/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`,
|
|
undefined,
|
|
undefined,
|
|
contextId,
|
|
)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(checkResult.schemaCheck).toMatchObject({
|
|
__typename: 'SchemaCheckError',
|
|
errors: {
|
|
nodes: [
|
|
{
|
|
message: 'Context ID must be at least 1 character long.',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent('contextId that has fewer than 1 characters is not allowed', async ({ expect }) => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject } = await createOrg();
|
|
const { createTargetAccessToken } = await createProject(ProjectType.Single);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
// Publish schema with write rights
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
pong: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// Create a token with read rights
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readOnly',
|
|
});
|
|
|
|
const contextId = new Array(201).fill('A').join('');
|
|
|
|
// Check schema with read rights
|
|
const checkResult = await readToken
|
|
.checkSchema(
|
|
/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`,
|
|
undefined,
|
|
undefined,
|
|
contextId,
|
|
)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(checkResult.schemaCheck).toMatchObject({
|
|
__typename: 'SchemaCheckError',
|
|
errors: {
|
|
nodes: [
|
|
{
|
|
message: 'Context ID cannot exceed length of 200 characters.',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|
|
|
|
describe.concurrent(
|
|
'schema check composition skip due to unchanged input schemas when being compared to failed schema version',
|
|
() => {
|
|
test.concurrent('native federation', async () => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject, setFeatureFlag } = await createOrg();
|
|
const { createTargetAccessToken, setNativeFederation } = await createProject(
|
|
ProjectType.Federation,
|
|
);
|
|
await setFeatureFlag('compareToPreviousComposableVersion', true);
|
|
await setNativeFederation(true);
|
|
|
|
const token = await createTargetAccessToken({});
|
|
|
|
// here we use @tag without an argument to trigger a validation/composition error
|
|
const sdl = /* GraphQL */ `
|
|
extend schema
|
|
@link(url: "https://specs.apollo.dev/link/v1.0")
|
|
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@tag"])
|
|
|
|
type Query {
|
|
ping: String
|
|
pong: String
|
|
foo: User @tag
|
|
}
|
|
|
|
type User {
|
|
id: ID!
|
|
}
|
|
`;
|
|
|
|
// Publish schema with write rights
|
|
await token
|
|
.publishSchema({
|
|
sdl,
|
|
service: 'serviceA',
|
|
url: 'http://localhost:4000',
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
const result = await token.checkSchema(sdl, 'serviceA').then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(result.schemaCheck).toMatchObject({
|
|
valid: false,
|
|
__typename: 'SchemaCheckError',
|
|
changes: expect.objectContaining({
|
|
total: 0,
|
|
}),
|
|
errors: expect.objectContaining({
|
|
total: 1,
|
|
}),
|
|
});
|
|
});
|
|
|
|
test.concurrent('legacy fed composition', async () => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject, setFeatureFlag } = await createOrg();
|
|
const { createTargetAccessToken, setNativeFederation } = await createProject(
|
|
ProjectType.Federation,
|
|
);
|
|
await setFeatureFlag('compareToPreviousComposableVersion', false);
|
|
await setNativeFederation(false);
|
|
|
|
const token = await createTargetAccessToken({});
|
|
|
|
// @key(fields:) is invalid - should trigger a composition error
|
|
const sdl = /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
pong: String
|
|
foo: User
|
|
}
|
|
|
|
type User @key(fields: "uuid") {
|
|
id: ID!
|
|
}
|
|
`;
|
|
|
|
// Publish schema with write rights
|
|
await token
|
|
.publishSchema({
|
|
sdl,
|
|
service: 'serviceA',
|
|
url: 'http://localhost:4000',
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
const result = await token.checkSchema(sdl, 'serviceA').then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(result.schemaCheck).toMatchObject({
|
|
valid: false,
|
|
__typename: 'SchemaCheckError',
|
|
changes: expect.objectContaining({
|
|
total: 0,
|
|
}),
|
|
errors: expect.objectContaining({
|
|
total: 1,
|
|
}),
|
|
});
|
|
});
|
|
|
|
test.concurrent(
|
|
'legacy fed composition with compareToPreviousComposableVersion=true',
|
|
async () => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject, setFeatureFlag } = await createOrg();
|
|
const { createTargetAccessToken, setNativeFederation } = await createProject(
|
|
ProjectType.Federation,
|
|
);
|
|
await setFeatureFlag('compareToPreviousComposableVersion', true);
|
|
await setNativeFederation(false);
|
|
|
|
const token = await createTargetAccessToken({});
|
|
|
|
// @key(fields:) is invalid - should trigger a composition error
|
|
const sdl = /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
pong: String
|
|
foo: User
|
|
}
|
|
|
|
type User @key(fields: "uuid") {
|
|
id: ID!
|
|
}
|
|
`;
|
|
|
|
// Publish schema with write rights
|
|
await token
|
|
.publishSchema({
|
|
sdl,
|
|
service: 'serviceA',
|
|
url: 'http://localhost:4000',
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
const result = await token
|
|
.checkSchema(sdl, 'serviceA')
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(result.schemaCheck).toMatchObject({
|
|
valid: false,
|
|
__typename: 'SchemaCheckError',
|
|
changes: expect.objectContaining({
|
|
total: 0,
|
|
}),
|
|
errors: expect.objectContaining({
|
|
total: 1,
|
|
}),
|
|
});
|
|
},
|
|
);
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'checking an invalid schema fails due to validation errors (deprecated non-nullable input field)',
|
|
async () => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject } = await createOrg();
|
|
const { createTargetAccessToken } = await createProject(ProjectType.Single);
|
|
const token = await createTargetAccessToken({});
|
|
|
|
const sdl = /* GraphQL */ `
|
|
type Query {
|
|
a(b: B!): String
|
|
}
|
|
|
|
input B {
|
|
a: String! @deprecated(reason: "This field is deprecated")
|
|
b: String!
|
|
}
|
|
`;
|
|
|
|
const result = await token.checkSchema(sdl).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(result.schemaCheck).toEqual({
|
|
__typename: 'SchemaCheckError',
|
|
changes: {
|
|
nodes: [],
|
|
total: 0,
|
|
},
|
|
errors: {
|
|
nodes: [
|
|
{
|
|
message: 'Required input field B.a cannot be deprecated.',
|
|
},
|
|
],
|
|
total: 1,
|
|
},
|
|
schemaCheck: {
|
|
id: expect.any(String),
|
|
},
|
|
valid: false,
|
|
});
|
|
},
|
|
);
|
|
|
|
function connectionString() {
|
|
const {
|
|
POSTGRES_USER = 'postgres',
|
|
POSTGRES_PASSWORD = 'postgres',
|
|
POSTGRES_HOST = 'localhost',
|
|
POSTGRES_PORT = 5432,
|
|
POSTGRES_DB = 'registry',
|
|
POSTGRES_SSL = null,
|
|
POSTGRES_CONNECTION_STRING = null,
|
|
} = process.env;
|
|
return (
|
|
POSTGRES_CONNECTION_STRING ||
|
|
`postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}${
|
|
POSTGRES_SSL ? '?sslmode=require' : '?sslmode=disable'
|
|
}`
|
|
);
|
|
}
|
|
|
|
test.concurrent(
|
|
'checking a valid schema onto a broken schema succeeds (prior schema has deprecated non-nullable input)',
|
|
async () => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
const token = await createTargetAccessToken({});
|
|
|
|
const brokenSdl = /* GraphQL */ `
|
|
type Query {
|
|
a(b: B!): String
|
|
}
|
|
|
|
input B {
|
|
a: String! @deprecated(reason: "This field is deprecated")
|
|
b: String!
|
|
}
|
|
`;
|
|
|
|
// we need to manually insert a broken schema version into the database
|
|
// as we fixed the issue that allows publishing such a broken version
|
|
|
|
const conn = connectionString();
|
|
const storage = await createStorage(conn, 2);
|
|
await storage.createVersion({
|
|
schema: brokenSdl,
|
|
author: 'Jochen',
|
|
async actionFn() {},
|
|
base_schema: null,
|
|
commit: '123',
|
|
changes: [],
|
|
compositeSchemaSDL: null,
|
|
conditionalBreakingChangeMetadata: null,
|
|
contracts: null,
|
|
coordinatesDiff: null,
|
|
diffSchemaVersionId: null,
|
|
github: null,
|
|
metadata: null,
|
|
logIds: [],
|
|
projectId: project.id,
|
|
service: null,
|
|
organizationId: organization.id,
|
|
previousSchemaVersion: null,
|
|
valid: true,
|
|
schemaCompositionErrors: [],
|
|
supergraphSDL: null,
|
|
tags: null,
|
|
targetId: target.id,
|
|
url: null,
|
|
schemaMetadata: null,
|
|
metadataAttributes: null,
|
|
});
|
|
await storage.destroy();
|
|
|
|
const validSdl = /* GraphQL */ `
|
|
type Query {
|
|
a(b: B!): String
|
|
}
|
|
|
|
input B {
|
|
a: String @deprecated(reason: "This field is deprecated")
|
|
b: String!
|
|
}
|
|
`;
|
|
|
|
const sdl = /* GraphQL */ `
|
|
type Query {
|
|
a(b: B!): String
|
|
}
|
|
|
|
input B {
|
|
a: String @deprecated(reason: "This field is deprecated")
|
|
b: String!
|
|
}
|
|
`;
|
|
|
|
const result = await token.checkSchema(sdl).then(r => r.expectNoGraphQLErrors());
|
|
expect(result.schemaCheck).toEqual({
|
|
__typename: 'SchemaCheckSuccess',
|
|
changes: {
|
|
nodes: [
|
|
{
|
|
criticality: 'Safe',
|
|
message: "Input field 'B.a' changed type from 'String!' to 'String'",
|
|
},
|
|
],
|
|
total: 1,
|
|
},
|
|
schemaCheck: {
|
|
id: expect.any(String),
|
|
},
|
|
valid: true,
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent('schema policy errors prevent schema check approval', async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, setProjectSchemaPolicy, project, target } = await createProject(
|
|
ProjectType.Single,
|
|
);
|
|
|
|
await setProjectSchemaPolicy(createPolicy(RuleInstanceSeverityLevel.Error));
|
|
|
|
const writeToken = await createTargetAccessToken({});
|
|
await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
const checkResult = await writeToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckError, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const mutationResult = await execute({
|
|
document: ApproveFailedSchemaCheckMutation,
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
},
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(mutationResult).toEqual({
|
|
approveFailedSchemaCheck: {
|
|
ok: null,
|
|
error: {
|
|
message: 'Schema check has schema policy errors that must be resolved before approval.',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
test.concurrent(
|
|
'schema policy errors prevent schema check approval with safe changes',
|
|
async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, setProjectSchemaPolicy, project, target } =
|
|
await createProject(ProjectType.Single);
|
|
|
|
await setProjectSchemaPolicy(createPolicy(RuleInstanceSeverityLevel.Error));
|
|
|
|
const writeToken = await createTargetAccessToken({});
|
|
await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
const checkResult = await writeToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
pong: String
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
const check = checkResult.schemaCheck;
|
|
|
|
if (check.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckError, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const mutationResult = await execute({
|
|
document: ApproveFailedSchemaCheckMutation,
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
},
|
|
},
|
|
authToken: ownerToken,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(mutationResult).toEqual({
|
|
approveFailedSchemaCheck: {
|
|
ok: null,
|
|
error: {
|
|
message: 'Schema check has schema policy errors that must be resolved before approval.',
|
|
},
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'approve failed schema check with author field using target access token',
|
|
async ({ expect }) => {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
const publishResult = await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
const readToken = await createTargetAccessToken({
|
|
mode: 'readWrite',
|
|
});
|
|
|
|
const checkResult = await readToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: Float
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
const check = checkResult.schemaCheck;
|
|
if (check.__typename !== 'SchemaCheckError') {
|
|
throw new Error(`Expected SchemaCheckError, got ${check.__typename}`);
|
|
}
|
|
|
|
const schemaCheckId = check.schemaCheck?.id;
|
|
if (schemaCheckId == null) {
|
|
throw new Error('Missing schema check id.');
|
|
}
|
|
|
|
const mutationResult = await execute({
|
|
document: graphql(/* GraphQL */ `
|
|
mutation ApproveFailedSchemaCheckWithAuthor($input: ApproveFailedSchemaCheckInput!) {
|
|
approveFailedSchemaCheck(input: $input) {
|
|
ok {
|
|
schemaCheck {
|
|
__typename
|
|
... on SuccessfulSchemaCheck {
|
|
isApproved
|
|
approvalComment
|
|
cliApprovalMetadata {
|
|
displayName
|
|
email
|
|
}
|
|
}
|
|
}
|
|
}
|
|
error {
|
|
message
|
|
}
|
|
}
|
|
}
|
|
`),
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
comment: 'Check force approved automatically via CLI --forceSafe flag',
|
|
author: 'John Doe <john@example.com>',
|
|
},
|
|
},
|
|
authToken: readToken.secret,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(mutationResult.approveFailedSchemaCheck.ok).not.toBeNull();
|
|
expect(mutationResult.approveFailedSchemaCheck.error).toBeNull();
|
|
|
|
const approvedCheck = mutationResult.approveFailedSchemaCheck.ok?.schemaCheck;
|
|
expect(approvedCheck?.__typename).toBe('SuccessfulSchemaCheck');
|
|
|
|
if (approvedCheck?.__typename === 'SuccessfulSchemaCheck') {
|
|
expect(approvedCheck.isApproved).toBe(true);
|
|
|
|
expect(approvedCheck.cliApprovalMetadata).toMatchObject({
|
|
displayName: 'John Doe',
|
|
email: 'john@example.com',
|
|
});
|
|
}
|
|
|
|
const schemaCheckQueryResult = await execute({
|
|
document: graphql(/* GraphQL */ `
|
|
query GetSchemaCheckWithApproval(
|
|
$organizationSlug: String!
|
|
$projectSlug: String!
|
|
$targetSlug: String!
|
|
$schemaCheckId: ID!
|
|
) {
|
|
target(
|
|
reference: {
|
|
bySelector: {
|
|
organizationSlug: $organizationSlug
|
|
projectSlug: $projectSlug
|
|
targetSlug: $targetSlug
|
|
}
|
|
}
|
|
) {
|
|
schemaCheck(id: $schemaCheckId) {
|
|
__typename
|
|
... on SuccessfulSchemaCheck {
|
|
id
|
|
isApproved
|
|
cliApprovalMetadata {
|
|
displayName
|
|
email
|
|
}
|
|
breakingSchemaChanges {
|
|
nodes {
|
|
message
|
|
approval {
|
|
cliApprovalMetadata {
|
|
displayName
|
|
email
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`),
|
|
variables: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId,
|
|
},
|
|
authToken: readToken.secret,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
const queriedCheck = schemaCheckQueryResult.target?.schemaCheck;
|
|
expect(queriedCheck?.__typename).toBe('SuccessfulSchemaCheck');
|
|
|
|
if (queriedCheck?.__typename === 'SuccessfulSchemaCheck') {
|
|
expect(queriedCheck.isApproved).toBe(true);
|
|
|
|
const breakingChanges = queriedCheck.breakingSchemaChanges?.nodes ?? [];
|
|
expect(breakingChanges.length).toBeGreaterThan(0);
|
|
|
|
for (const change of breakingChanges) {
|
|
expect(change.approval?.cliApprovalMetadata).toMatchObject({
|
|
displayName: 'John Doe',
|
|
email: 'john@example.com',
|
|
});
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'approve failed schema check handles different author formats',
|
|
async ({ expect }) => {
|
|
const testCases = [
|
|
{
|
|
author: 'john@example.com',
|
|
expected: { displayName: 'john@example.com', email: 'john@example.com' },
|
|
description: 'email only',
|
|
},
|
|
{
|
|
author: 'John Doe',
|
|
expected: { displayName: 'John Doe', email: '<no email provided>' },
|
|
description: 'name only',
|
|
},
|
|
{
|
|
author: 'John Doe <john@example.com>',
|
|
expected: { displayName: 'John Doe', email: 'john@example.com' },
|
|
description: 'git standard format',
|
|
},
|
|
];
|
|
|
|
for (const testCase of testCases) {
|
|
const { createOrg } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, target } = await createProject(ProjectType.Single);
|
|
|
|
const writeToken = await createTargetAccessToken({
|
|
mode: 'readWrite',
|
|
});
|
|
|
|
await writeToken
|
|
.publishSchema({
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
email: String
|
|
}
|
|
`,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
const checkResult = await writeToken
|
|
.checkSchema(/* GraphQL */ `
|
|
type Query {
|
|
ping: String
|
|
}
|
|
`)
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(checkResult.schemaCheck.__typename).toBe('SchemaCheckError');
|
|
if (checkResult.schemaCheck.__typename !== 'SchemaCheckError') {
|
|
throw new Error('Expected SchemaCheckError');
|
|
}
|
|
|
|
const schemaCheckId = checkResult.schemaCheck.schemaCheck?.id;
|
|
expect(schemaCheckId).toBeDefined();
|
|
|
|
const mutationResult = await execute({
|
|
document: graphql(/* GraphQL */ `
|
|
mutation ApproveFailedSchemaCheckWithDifferentAuthorFormats(
|
|
$input: ApproveFailedSchemaCheckInput!
|
|
) {
|
|
approveFailedSchemaCheck(input: $input) {
|
|
ok {
|
|
schemaCheck {
|
|
__typename
|
|
... on SuccessfulSchemaCheck {
|
|
isApproved
|
|
approvalComment
|
|
cliApprovalMetadata {
|
|
displayName
|
|
email
|
|
}
|
|
}
|
|
}
|
|
}
|
|
error {
|
|
message
|
|
}
|
|
}
|
|
}
|
|
`),
|
|
variables: {
|
|
input: {
|
|
organizationSlug: organization.slug,
|
|
projectSlug: project.slug,
|
|
targetSlug: target.slug,
|
|
schemaCheckId: schemaCheckId!,
|
|
comment: `Testing ${testCase.description}`,
|
|
author: testCase.author,
|
|
},
|
|
},
|
|
authToken: writeToken.secret,
|
|
}).then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(mutationResult.approveFailedSchemaCheck.ok).not.toBeNull();
|
|
|
|
const approvedCheck = mutationResult.approveFailedSchemaCheck.ok?.schemaCheck;
|
|
if (approvedCheck?.__typename === 'SuccessfulSchemaCheck') {
|
|
expect(approvedCheck.cliApprovalMetadata?.displayName).toBe(testCase.expected.displayName);
|
|
expect(approvedCheck.cliApprovalMetadata?.email).toBe(testCase.expected.email);
|
|
}
|
|
}
|
|
},
|
|
);
|