console/integration-tests/tests/api/schema/contracts-check.spec.ts

1363 lines
40 KiB
TypeScript

import { ProjectType } from 'testkit/gql/graphql';
import { graphql } from '../../../testkit/gql';
import { execute } from '../../../testkit/graphql';
import { initSeed } from '../../../testkit/seed';
const CreateContractMutation = graphql(`
mutation CreateContractMutation1($input: CreateContractInput!) {
createContract(input: $input) {
ok {
createdContract {
id
target {
id
}
includeTags
excludeTags
createdAt
}
}
error {
message
details {
target
contractName
includeTags
excludeTags
}
}
}
}
`);
test.concurrent('schema check with successful contract checks', async ({ expect }) => {
const { createOrg, ownerToken } = await initSeed().createOwner();
const { createProject, setFeatureFlag } = await createOrg();
const { createTargetAccessToken, target, setNativeFederation } = await createProject(
ProjectType.Federation,
);
await setFeatureFlag('compareToPreviousComposableVersion', true);
await setNativeFederation(true);
// Create a token with write rights
const writeToken = await createTargetAccessToken({});
// Publish schema with write rights
const publishResult = await writeToken
.publishSchema({
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 {
hello: String
helloHidden: String @tag(name: "toyota")
}
`,
service: 'hello',
url: 'http://hello.com',
})
.then(r => r.expectNoGraphQLErrors());
// Schema publish should be successful
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
const createContractResult = await execute({
document: CreateContractMutation,
variables: {
input: {
target: { byId: target.id },
contractName: 'my-contract',
removeUnreachableTypesFromPublicApiSchema: true,
excludeTags: ['toyota'],
},
},
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
expect(createContractResult.createContract.error).toBeNull();
// Check schema with no read and write rights
const checkResult = await writeToken
.checkSchema(
/* 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 {
meh: String!
mehHidden: String @tag(name: "toyota")
}
`,
'meh',
)
.then(r => r.expectNoGraphQLErrors());
expect(checkResult.schemaCheck.__typename).toBe('SchemaCheckSuccess');
});
test.concurrent('schema check with failing contract composition', async ({ expect }) => {
const { createOrg, ownerToken } = await initSeed().createOwner();
const { createProject, setFeatureFlag } = await createOrg();
const { createTargetAccessToken, target, setNativeFederation } = await createProject(
ProjectType.Federation,
);
await setFeatureFlag('compareToPreviousComposableVersion', true);
await setNativeFederation(true);
// Create a token with write rights
const writeToken = await createTargetAccessToken({});
// Publish schema with write rights
const publishResult = await writeToken
.publishSchema({
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 {
hello: String @tag(name: "toyota")
helloHidden: String @tag(name: "toyota")
}
`,
service: 'hello',
url: 'http://hello.com',
})
.then(r => r.expectNoGraphQLErrors());
// Schema publish should be successful
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
const createContractResult = await execute({
document: CreateContractMutation,
variables: {
input: {
target: { byId: target.id },
contractName: 'my-contract',
removeUnreachableTypesFromPublicApiSchema: true,
excludeTags: ['toyota'],
},
},
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
expect(createContractResult.createContract.error).toBeNull();
// Check schema with no read and write rights
const checkResult = await writeToken
.checkSchema(
/* 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 {
meh: String! @tag(name: "toyota")
mehHidden: String @tag(name: "toyota")
}
`,
'meh',
)
.then(r => r.expectNoGraphQLErrors());
expect(checkResult.schemaCheck.__typename).toBe('SchemaCheckError');
if (checkResult.schemaCheck.__typename !== 'SchemaCheckError') {
throw new Error(`Expected SchemaCheckError, got ${checkResult.schemaCheck.__typename}`);
}
expect(checkResult.schemaCheck.errors?.nodes).toMatchInlineSnapshot(`
[
{
message: [my-contract] Type "Query" is in the API schema but all of its fields are @inaccessible.,
},
]
`);
});
test.concurrent(
'schema check with failing contract composition (multiple contracts)',
async ({ expect }) => {
const { createOrg, ownerToken } = await initSeed().createOwner();
const { createProject, setFeatureFlag } = await createOrg();
const { createTargetAccessToken, target, setNativeFederation } = await createProject(
ProjectType.Federation,
);
await setFeatureFlag('compareToPreviousComposableVersion', true);
await setNativeFederation(true);
// Create a token with write rights
const writeToken = await createTargetAccessToken({});
// Publish schema with write rights
const publishResult = await writeToken
.publishSchema({
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 {
hello: String @tag(name: "toyota")
helloHidden: String @tag(name: "toyota")
}
`,
service: 'hello',
url: 'http://hello.com',
})
.then(r => r.expectNoGraphQLErrors());
// Schema publish should be successful
expect(publishResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
let createContractResult = await execute({
document: CreateContractMutation,
variables: {
input: {
target: { byId: target.id },
contractName: 'my-contract',
removeUnreachableTypesFromPublicApiSchema: true,
excludeTags: ['toyota'],
},
},
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
expect(createContractResult.createContract.error).toBeNull();
createContractResult = await execute({
document: CreateContractMutation,
variables: {
input: {
target: { byId: target.id },
contractName: 'my-other-contract',
removeUnreachableTypesFromPublicApiSchema: true,
includeTags: ['fiat'],
},
},
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
expect(createContractResult.createContract.error).toBeNull();
// Check schema with no read and write rights
const checkResult = await writeToken
.checkSchema(
/* 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 {
meh: String! @tag(name: "toyota")
mehHidden: String @tag(name: "toyota")
}
`,
'meh',
)
.then(r => r.expectNoGraphQLErrors());
expect(checkResult.schemaCheck.__typename).toBe('SchemaCheckError');
if (checkResult.schemaCheck.__typename !== 'SchemaCheckError') {
throw new Error(`Expected SchemaCheckError, got ${checkResult.schemaCheck.__typename}`);
}
expect(checkResult.schemaCheck.errors?.nodes).toMatchInlineSnapshot(`
[
{
message: [my-contract] Type "Query" is in the API schema but all of its fields are @inaccessible.,
},
{
message: [my-other-contract] Type "Query" is in the API schema but all of its fields are @inaccessible.,
},
]
`);
},
);
const ApproveFailedSchemaCheckMutation = graphql(/* GraphQL */ `
mutation ApproveFailedSchemaCheckContracts($input: ApproveFailedSchemaCheckInput!) {
approveFailedSchemaCheck(input: $input) {
ok {
schemaCheck {
__typename
... on SuccessfulSchemaCheck {
isApproved
approvedBy {
__typename
}
}
}
}
error {
message
}
}
}
`);
const SchemaCheckQuery = graphql(/* GraphQL */ `
query SchemaCheckContractsQuery($selector: TargetSelectorInput!, $id: ID!) {
target(reference: { bySelector: $selector }) {
schemaCheck(id: $id) {
__typename
id
createdAt
... on FailedSchemaCheck {
compositionErrors {
nodes {
message
path
}
}
canBeApproved
canBeApprovedByViewer
}
safeSchemaChanges {
nodes {
criticality
criticalityReason
message
path
approval {
schemaCheckId
approvedAt
approvedBy {
id
displayName
}
}
}
}
breakingSchemaChanges {
nodes {
criticality
criticalityReason
message
path
approval {
schemaCheckId
approvedAt
approvedBy {
id
displayName
}
}
}
}
contractChecks {
edges {
node {
id
contractName
isSuccess
breakingSchemaChanges {
nodes {
criticality
criticalityReason
message
path
approval {
schemaCheckId
approvedAt
approvedBy {
id
displayName
}
}
}
}
}
}
}
}
}
}
`);
test.concurrent(
'approve failed schema check that has breaking change in contract check -> updates the status to successful and attaches meta information to the breaking change',
async ({ expect }) => {
const { createOrg, ownerToken } = await initSeed().createOwner();
const { createProject, organization, setFeatureFlag } = await createOrg();
const { createTargetAccessToken, project, target, setNativeFederation } = await createProject(
ProjectType.Federation,
);
await setFeatureFlag('compareToPreviousComposableVersion', true);
await setNativeFederation(true);
// Create a token with write rights
const writeToken = await createTargetAccessToken({});
const service = 'hello';
const serviceUrl = 'http://hello.com';
const contractName = 'my-contract';
const createContractResult = await execute({
document: CreateContractMutation,
variables: {
input: {
target: { byId: target.id },
contractName,
removeUnreachableTypesFromPublicApiSchema: true,
includeTags: ['toyota'],
},
},
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
expect(createContractResult.createContract.error).toBeNull();
// Publish schema with write rights
const publishResult = await writeToken
.publishSchema({
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 {
a: String @tag(name: "toyota")
hello: String @tag(name: "toyota")
helloHidden: String @tag(name: "toyota")
}
`,
service,
url: serviceUrl,
})
.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({});
// Check schema with read rights
const checkResult = await readToken
.checkSchema(
/* 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 {
a: String @tag(name: "toyota")
hello: String @tag(name: "toyota")
helloHidden: String
}
`,
service,
)
.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,
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?.target?.schemaCheck).toMatchObject({
__typename: 'SuccessfulSchemaCheck',
breakingSchemaChanges: null,
contractChecks: {
edges: [
{
node: {
id: expect.any(String),
contractName,
isSuccess: true,
breakingSchemaChanges: {
nodes: [
{
message: "Field 'helloHidden' was removed from object type 'Query'",
approval: {
approvedAt: expect.any(String),
approvedBy: {
id: expect.any(String),
displayName: expect.any(String),
},
schemaCheckId,
},
},
],
},
},
},
],
},
});
},
);
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, setFeatureFlag } = await createOrg();
const { createTargetAccessToken, project, target, setNativeFederation } = await createProject(
ProjectType.Federation,
);
await setFeatureFlag('compareToPreviousComposableVersion', true);
await setNativeFederation(true);
// Create a token with write rights
const writeToken = await createTargetAccessToken({});
const service = 'hello';
const serviceUrl = 'http://hello.com';
const contractName = 'my-contract';
const contextId = 'pr-69420';
const createContractResult = await execute({
document: CreateContractMutation,
variables: {
input: {
target: { byId: target.id },
contractName,
removeUnreachableTypesFromPublicApiSchema: true,
includeTags: ['toyota'],
},
},
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
expect(createContractResult.createContract.error).toBeNull();
// Publish schema with write rights
const publishResult = await writeToken
.publishSchema({
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 {
a: String @tag(name: "toyota")
hello: String @tag(name: "toyota")
helloHidden: String @tag(name: "toyota")
}
`,
service,
url: serviceUrl,
})
.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 */ `
extend schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@tag"])
type Query {
a: String @tag(name: "toyota")
hello: String @tag(name: "toyota")
helloHidden: String
}
`,
service,
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,
approvedBy: {
__typename: 'User',
},
},
},
error: null,
},
});
const secondCheckResult = await readToken
.checkSchema(
/* 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 {
a: String @tag(name: "toyota")
hello: String @tag(name: "toyota")
helloHidden: String
}
`,
service,
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({
__typename: 'SuccessfulSchemaCheck',
breakingSchemaChanges: null,
contractChecks: {
edges: [
{
node: {
id: expect.any(String),
contractName,
isSuccess: true,
breakingSchemaChanges: {
nodes: [
{
message: "Field 'helloHidden' was removed from object type 'Query'",
approval: {
approvedAt: expect.any(String),
approvedBy: {
id: expect.any(String),
displayName: expect.any(String),
},
schemaCheckId,
},
},
],
},
},
},
],
},
});
},
);
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, setFeatureFlag } = await createOrg();
const { createTargetAccessToken, project, target, setNativeFederation } = await createProject(
ProjectType.Federation,
);
await setFeatureFlag('compareToPreviousComposableVersion', true);
await setNativeFederation(true);
// Create a token with write rights
const writeToken = await createTargetAccessToken({});
const service = 'hello';
const serviceUrl = 'http://hello.com';
const contractName = 'my-contract';
const contextId = 'pr-69420';
const createContractResult = await execute({
document: CreateContractMutation,
variables: {
input: {
target: { byId: target.id },
contractName,
removeUnreachableTypesFromPublicApiSchema: true,
includeTags: ['toyota'],
},
},
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
expect(createContractResult.createContract.error).toBeNull();
// Publish schema with write rights
const publishResult = await writeToken
.publishSchema({
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 {
a: String @tag(name: "toyota")
hello: String @tag(name: "toyota")
helloHidden: String @tag(name: "toyota")
}
`,
service,
url: serviceUrl,
})
.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 */ `
extend schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@tag"])
type Query {
a: String @tag(name: "toyota")
hello: String @tag(name: "toyota")
helloHidden: String
}
`,
service,
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,
approvedBy: {
__typename: 'User',
},
},
},
error: null,
},
});
const secondCheckResult = await readToken
.checkSchema(
/* 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 {
a: String @tag(name: "toyota")
hello: String @tag(name: "toyota")
helloHidden: String
}
`,
service,
undefined,
contextId + contextId,
)
.then(r => r.expectNoGraphQLErrors());
if (secondCheckResult.schemaCheck.__typename !== 'SchemaCheckError') {
throw new Error(`Expected SchemaCheckError, 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({
__typename: 'FailedSchemaCheck',
breakingSchemaChanges: null,
contractChecks: {
edges: [
{
node: {
id: expect.any(String),
contractName,
isSuccess: false,
breakingSchemaChanges: {
nodes: [
{
message: "Field 'helloHidden' was removed from object type 'Query'",
approval: null,
},
],
},
},
},
],
},
});
},
);
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, setFeatureFlag } = await createOrg();
const { createTargetAccessToken, project, target, setNativeFederation } = await createProject(
ProjectType.Federation,
);
await setFeatureFlag('compareToPreviousComposableVersion', true);
await setNativeFederation(true);
// Create a token with write rights
const writeToken = await createTargetAccessToken({});
const service = 'hello';
const serviceUrl = 'http://hello.com';
const contractName = 'my-contract';
const contextId = 'pr-69420';
const createContractResult = await execute({
document: CreateContractMutation,
variables: {
input: {
target: { byId: target.id },
contractName,
removeUnreachableTypesFromPublicApiSchema: true,
includeTags: ['toyota'],
},
},
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
expect(createContractResult.createContract.error).toBeNull();
// Publish schema with write rights
const publishResult = await writeToken
.publishSchema({
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 {
a: String @tag(name: "toyota")
hello: String @tag(name: "toyota")
helloHidden: String @tag(name: "toyota")
}
`,
service,
url: serviceUrl,
})
.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 */ `
extend schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@tag"])
type Query {
a: String @tag(name: "toyota")
hello: String @tag(name: "toyota")
helloHidden: String
}
`,
service,
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,
approvedBy: {
__typename: 'User',
},
},
},
error: null,
},
});
const secondCheckResult = await readToken
.checkSchema(
/* 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 {
a: String @tag(name: "toyota")
hello: String
helloHidden: String
}
`,
service,
undefined,
contextId,
)
.then(r => r.expectNoGraphQLErrors());
if (secondCheckResult.schemaCheck.__typename !== 'SchemaCheckError') {
throw new Error(`Expected SchemaCheckError, 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({
__typename: 'FailedSchemaCheck',
breakingSchemaChanges: null,
contractChecks: {
edges: [
{
node: {
id: expect.any(String),
contractName,
isSuccess: false,
breakingSchemaChanges: {
nodes: [
{
message: "Field 'hello' was removed from object type 'Query'",
approval: null,
},
{
message: "Field 'helloHidden' was removed from object type 'Query'",
approval: {
approvedAt: expect.any(String),
approvedBy: {
id: expect.any(String),
displayName: expect.any(String),
},
schemaCheckId,
},
},
],
},
},
},
],
},
});
},
);
test.concurrent(
'schema check that has no composition errors in contract check -> can be approved',
async ({ expect }) => {
const { createOrg, ownerToken } = await initSeed().createOwner();
const { createProject, organization, setFeatureFlag } = await createOrg();
const { createTargetAccessToken, project, target, setNativeFederation } = await createProject(
ProjectType.Federation,
);
await setFeatureFlag('compareToPreviousComposableVersion', true);
await setNativeFederation(true);
// Create a token with write rights
const writeToken = await createTargetAccessToken({});
const service = 'hello';
const serviceUrl = 'http://hello.com';
const contractName = 'my-contract';
const createContractResult = await execute({
document: CreateContractMutation,
variables: {
input: {
target: { byId: target.id },
contractName,
removeUnreachableTypesFromPublicApiSchema: true,
includeTags: ['toyota'],
},
},
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
expect(createContractResult.createContract.error).toBeNull();
// Publish schema with write rights
const publishResult = await writeToken
.publishSchema({
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 {
a: String @tag(name: "toyota")
b: String @tag(name: "toyota")
}
`,
service,
url: serviceUrl,
})
.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 */ `
extend schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@tag"])
type Query {
a: String
b: String @tag(name: "toyota")
}
`,
service,
undefined,
)
.then(r => r.expectNoGraphQLErrors());
if (checkResult.schemaCheck.__typename !== 'SchemaCheckError') {
throw new Error(`Expected SchemaCheckError, got ${checkResult.schemaCheck.__typename}`);
}
const schemaCheckId = checkResult.schemaCheck.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?.target?.schemaCheck).toMatchObject({
__typename: 'FailedSchemaCheck',
id: expect.any(String),
canBeApproved: true,
canBeApprovedByViewer: true,
});
},
);
test.concurrent(
'schema check that has composition errors in contract check -> can not be approved',
async ({ expect }) => {
const { createOrg, ownerToken } = await initSeed().createOwner();
const { createProject, organization, setFeatureFlag } = await createOrg();
const { createTargetAccessToken, project, target, setNativeFederation } = await createProject(
ProjectType.Federation,
);
await setFeatureFlag('compareToPreviousComposableVersion', true);
await setNativeFederation(true);
// Create a token with write rights
const writeToken = await createTargetAccessToken({});
const service = 'hello';
const serviceUrl = 'http://hello.com';
const contractName = 'my-contract';
const createContractResult = await execute({
document: CreateContractMutation,
variables: {
input: {
target: { byId: target.id },
contractName,
removeUnreachableTypesFromPublicApiSchema: true,
includeTags: ['toyota'],
},
},
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
expect(createContractResult.createContract.error).toBeNull();
// Publish schema with write rights
const publishResult = await writeToken
.publishSchema({
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 {
a: String @tag(name: "toyota")
}
`,
service,
url: serviceUrl,
})
.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 */ `
extend schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@tag"])
type Query {
a: String
}
`,
service,
undefined,
)
.then(r => r.expectNoGraphQLErrors());
if (checkResult.schemaCheck.__typename !== 'SchemaCheckError') {
throw new Error(`Expected SchemaCheckError, got ${checkResult.schemaCheck.__typename}`);
}
const schemaCheckId = checkResult.schemaCheck.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?.target?.schemaCheck).toMatchObject({
__typename: 'FailedSchemaCheck',
id: expect.any(String),
canBeApproved: false,
canBeApprovedByViewer: false,
});
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 composition errors.',
},
},
});
},
);