Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions integration-tests/tests/api/proposals/change-tracking.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { pollFor } from 'testkit/flow';
import { graphql } from 'testkit/gql';
import { ProjectType, ResourceAssignmentModeType } from 'testkit/gql/graphql';
import { execute } from 'testkit/graphql';
import { initSeed } from 'testkit/seed';
import { approveProposal, checkSchema, CreateProposalMutation } from './operations';

describe('Schema Proposals', () => {
test.concurrent(
'approved a schema proposal changes are flagged by schema checks',
async ({ expect }) => {
const { createOrg, ownerToken } = await initSeed().createOwner();
const { createProject, setFeatureFlag } = await createOrg();
await setFeatureFlag('schemaProposals', true);
const { target, createTargetAccessToken } = await createProject(ProjectType.Federation);

const result = await execute({
document: CreateProposalMutation,
variables: {
input: {
target: { byId: target.id },
author: 'Jeff',
title: 'Proposed changes to the schema...',
},
},
authToken: ownerToken,
}).then(r => r.expectNoGraphQLErrors());

const proposalId = result.createSchemaProposal.ok?.schemaProposal.id!;
expect(proposalId).toBeDefined();

const service = 'sample';
const sdl = `
type Query {
example(id: ID!): Example
}

type Example {
id: ID!
name: String!
}
`;
const writeToken = await createTargetAccessToken({
mode: 'readWrite',
});
await writeToken
.publishSchema({
sdl,
service,
url: 'http://localocalhost:4444/example',
})
.then(r => r.expectNoGraphQLErrors());

const modifiedSdl = `
type Query {
example(id: ID!): Example
}

type Example {
id: ID!
name: String!
}

extend type Example {
preview: String
}
`;

// add proposal check
const { schemaCheck: proposedSchema } = await checkSchema({
accessKey: ownerToken,
input: {
sdl: modifiedSdl,
schemaProposalId: proposalId,
service,
target: { byId: target.id },
url: 'http://localocalhost:4444/example',
},
}).then(r => r.expectNoGraphQLErrors());
expect(proposedSchema.__typename).toBe('SchemaCheckSuccess');
expect(
proposedSchema.__typename === 'SchemaCheckSuccess' && proposedSchema.changes?.total,
).toBe(1);

// approve the schema proposal
await approveProposal({
proposalId,
service,
accessKey: ownerToken,
}).then(r => r.expectNoGraphQLErrors());

// run another check that implements the proposal (no schemaProposalId passed as an argument)
const { schemaCheck } = await checkSchema({
accessKey: ownerToken,
input: {
sdl: modifiedSdl,
service: 'example',
target: { byId: target.id },
url: 'http://localocalhost:4444/example',
},
}).then(r => r.expectNoGraphQLErrors());

const checkId =
schemaCheck.__typename === 'SchemaCheckSuccess' && schemaCheck.schemaCheck?.id!;
expect(checkId).toBeDefined();

const ReadCheckQuery = graphql(`
query ReadCheck($targetId: ID!, $checkId: ID!) {
target(reference: { byId: $targetId }) {
schemaCheck(id: $checkId) {
id
schemaChanges {
edges {
node {
schemaProposalChangeDetails {
implementedBy {
id
}
schemaProposal {
id
}
}
}
}
}
}
}
}
`);

// schema check implementation tracking is an async process, so wait for the job to process
const poll = pollFor(async () => {
const { target: t } = await execute({
document: ReadCheckQuery,
variables: {
checkId: String(checkId),
targetId: target.id,
},
token: ownerToken,
}).then(r => r.expectNoGraphQLErrors());
const changeImplementsProposalId =
t?.schemaCheck?.schemaChanges?.edges[0].node.schemaProposalChangeDetails?.schemaProposal
.id;
return changeImplementsProposalId === proposalId;
});
await expect(poll).resolves.toBeUndefined();
},
);
});
129 changes: 129 additions & 0 deletions integration-tests/tests/api/proposals/operations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/** Common useful operations */
import { graphql } from 'testkit/gql';
import {
ApproveProposalMutationVariables,
CheckSchemaMutationVariables,
ReadProposalQueryVariables,
} from 'testkit/gql/graphql';
import { execute } from 'testkit/graphql';

export const CreateProposalMutation = graphql(`
mutation CreateProposal($input: CreateSchemaProposalInput!) {
createSchemaProposal(input: $input) {
ok {
schemaProposal {
id
}
}
error {
message
}
}
}
`);

export const ReadProposalQuery = graphql(`
query ReadProposal($input: SchemaProposalInput!) {
schemaProposal(input: $input) {
title
description
checks(input: { latestPerService: true }) {
edges {
node {
id
}
}
}
}
}
`);

export const CheckSchemaMutation = graphql(`
mutation CheckSchema($input: SchemaCheckInput!) {
schemaCheck(input: $input) {
... on SchemaCheckSuccess {
__typename
valid
changes {
nodes {
message
criticality
}
total
}
schemaCheck {
id
}
}
... on SchemaCheckError {
__typename
valid
changes {
nodes {
message
criticality
}
total
}
errors {
nodes {
message
}
total
}
schemaCheck {
id
}
}
}
}
`);

export const ApproveProposalMutation = graphql(`
mutation ApproveProposal($proposalId: ID!, $service: String! = "") {
reviewSchemaProposal(
input: { schemaProposalId: $proposalId, serviceName: $service, stageTransition: APPROVED }
) {
ok {
__typename
}
error {
message
}
}
}
`);

export function readProposal({
accessKey,
...vars
}: ReadProposalQueryVariables & { accessKey: string }) {
return execute({
document: ReadProposalQuery,
variables: vars,
token: accessKey,
});
}

/** Requires schemaProposal:modify, schemaProposal:describe, project:describe, and schemaCheck:create */
export function checkSchema({
accessKey,
...vars
}: CheckSchemaMutationVariables & { accessKey: string }) {
return execute({
document: CheckSchemaMutation,
variables: vars,
token: accessKey,
});
}

export function approveProposal({
accessKey,
...vars
}: ApproveProposalMutationVariables & { accessKey: string }) {
return execute({
document: ApproveProposalMutation,
variables: vars,
token: accessKey,
});
}
52 changes: 8 additions & 44 deletions integration-tests/tests/api/proposals/read.spec.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,12 @@
import { graphql } from 'testkit/gql';
import { ProjectType, ResourceAssignmentModeType } from 'testkit/gql/graphql';
import { execute } from 'testkit/graphql';
import { initSeed } from 'testkit/seed';

const CreateProposalMutation = graphql(`
mutation CreateProposalMutation($input: CreateSchemaProposalInput!) {
createSchemaProposal(input: $input) {
ok {
schemaProposal {
id
}
}
error {
message
}
}
}
`);

const ReadProposalQuery = graphql(`
query ReadProposalQuery($input: SchemaProposalInput!) {
schemaProposal(input: $input) {
title
description
checks(input: { latestPerService: true }) {
edges {
node {
id
}
}
}
}
}
`);
import { CreateProposalMutation, readProposal, ReadProposalQuery } from './operations';

/**
* Creates a proposal and returns a token with specified permissions
**/
async function setup(input: {
tokenPermissions: string[];
}): Promise<{ accessKey: string; proposalId: string }> {
async function setup(input: { tokenPermissions: string[] }) {
const { createOrg, ownerToken } = await initSeed().createOwner();
const { createProject, createOrganizationAccessToken, setFeatureFlag } = await createOrg();
await setFeatureFlag('schemaProposals', true);
Expand Down Expand Up @@ -68,26 +35,23 @@ async function setup(input: {
ownerToken,
);
const proposalId = result.createSchemaProposal.ok?.schemaProposal.id!;
return { accessKey, proposalId };
return { accessKey, proposalId, target };
}

describe('Schema Proposals', () => {
test.concurrent(
'can read proposal with "schemaProposal:describe" permission',
async ({ expect }) => {
const { accessKey, proposalId } = await setup({
const { accessKey, proposalId, target } = await setup({
tokenPermissions: ['schemaProposal:describe'],
});

{
const proposal = await execute({
document: ReadProposalQuery,
variables: {
input: {
id: proposalId,
},
const proposal = await readProposal({
input: {
id: proposalId,
},
token: accessKey,
accessKey,
}).then(r => r.expectNoGraphQLErrors());

expect(proposal.schemaProposal?.title).toMatchInlineSnapshot(
Expand Down
Loading
Loading