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: 11,
                  line: 1,
                },
                message: 'Description is required for type "Query"',
                ruleId: 'require-description',
                start: {
                  column: 6,
                  line: 1,
                },
              },
            },
          ],
        },
      },
    },
  });
});

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 } = await createOrg();
      const { createTargetAccessToken } = await createProject(ProjectType.Federation);

      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 } = await createOrg();
      const { createTargetAccessToken, setNativeFederation } = await createProject(
        ProjectType.Federation,
      );
      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 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);
      }
    }
  },
);
