mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 14:37:17 +00:00
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
414 lines
13 KiB
TypeScript
414 lines
13 KiB
TypeScript
import { ProjectType } from 'testkit/gql/graphql';
|
|
import { history } from '../../../testkit/external-composition';
|
|
import { updateSchemaComposition } from '../../../testkit/flow';
|
|
import { initSeed } from '../../../testkit/seed';
|
|
import { generateUnique, getServiceHost } from '../../../testkit/utils';
|
|
|
|
test.concurrent('call an external service to compose and validate services', async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, setNativeFederation } = await createProject(
|
|
ProjectType.Federation,
|
|
);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
const usersServiceName = generateUnique();
|
|
const publishUsersResult = await writeToken
|
|
.publishSchema({
|
|
url: 'https://api.com/users',
|
|
sdl: /* GraphQL */ `
|
|
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
|
|
|
|
type Query {
|
|
me: User
|
|
}
|
|
|
|
type User @key(fields: "id") {
|
|
id: ID!
|
|
name: String
|
|
}
|
|
`,
|
|
service: usersServiceName,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
expect(publishUsersResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// expect `users` service to be composed internally
|
|
await expect(history()).resolves.not.toContainEqual(usersServiceName);
|
|
|
|
// we use internal docker network to connect to the external composition service,
|
|
// so we need to use the name and not resolved host
|
|
const dockerAddress = await getServiceHost('external_composition', 3012, false);
|
|
// enable external composition
|
|
const externalCompositionResult = await updateSchemaComposition(
|
|
{
|
|
project: {
|
|
bySelector: {
|
|
projectSlug: project.slug,
|
|
organizationSlug: organization.slug,
|
|
},
|
|
},
|
|
method: {
|
|
external: {
|
|
endpoint: `http://${dockerAddress}/compose`,
|
|
// eslint-disable-next-line no-process-env
|
|
secret: process.env.EXTERNAL_COMPOSITION_SECRET!,
|
|
},
|
|
},
|
|
},
|
|
ownerToken,
|
|
).then(r => r.expectNoGraphQLErrors());
|
|
expect(
|
|
externalCompositionResult.updateSchemaComposition.ok?.updatedProject.externalSchemaComposition
|
|
?.endpoint,
|
|
).toBe(`http://${dockerAddress}/compose`);
|
|
|
|
// set native federation to false to force external composition
|
|
await setNativeFederation(false);
|
|
|
|
const productsServiceName = generateUnique();
|
|
const publishProductsResult = await writeToken
|
|
.publishSchema({
|
|
url: 'https://api.com/products',
|
|
sdl: /* GraphQL */ `
|
|
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
|
|
|
|
type Query {
|
|
products: [Product]
|
|
}
|
|
type Product @key(fields: "id") {
|
|
id: ID!
|
|
name: String
|
|
}
|
|
`,
|
|
service: productsServiceName,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// expect `products` service to be composed externally
|
|
await expect(history()).resolves.toContainEqual(productsServiceName);
|
|
|
|
// Schema publish should be successful
|
|
expect(publishProductsResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
});
|
|
|
|
test.concurrent(
|
|
'an expected error coming from the external composition service should be visible to the user',
|
|
async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, setNativeFederation, fetchVersions } =
|
|
await createProject(ProjectType.Federation);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
const usersServiceName = generateUnique();
|
|
const publishUsersResult = await writeToken
|
|
.publishSchema({
|
|
url: 'https://api.com/users',
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
me: User
|
|
}
|
|
|
|
type User @key(fields: "id") {
|
|
id: ID!
|
|
name: String
|
|
}
|
|
`,
|
|
service: usersServiceName,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishUsersResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// expect `users` service to be composed internally
|
|
await expect(history()).resolves.not.toContainEqual(usersServiceName);
|
|
|
|
// we use internal docker network to connect to the external composition service,
|
|
// so we need to use the name and not resolved host
|
|
const dockerAddress = await getServiceHost('external_composition', 3012, false);
|
|
// enable external composition
|
|
const externalCompositionResult = await updateSchemaComposition(
|
|
{
|
|
project: {
|
|
bySelector: {
|
|
projectSlug: project.slug,
|
|
organizationSlug: organization.slug,
|
|
},
|
|
},
|
|
method: {
|
|
external: {
|
|
endpoint: `http://${dockerAddress}/fail_on_signature`,
|
|
// eslint-disable-next-line no-process-env
|
|
secret: process.env.EXTERNAL_COMPOSITION_SECRET!,
|
|
},
|
|
},
|
|
},
|
|
ownerToken,
|
|
).then(r => r.expectNoGraphQLErrors());
|
|
expect(
|
|
externalCompositionResult.updateSchemaComposition.ok?.updatedProject.externalSchemaComposition
|
|
?.endpoint,
|
|
).toBe(`http://${dockerAddress}/fail_on_signature`);
|
|
|
|
// set native federation to false to force external composition
|
|
await setNativeFederation(false);
|
|
|
|
const productsServiceName = generateUnique();
|
|
const publishProductsResult = await writeToken
|
|
.publishSchema({
|
|
url: 'https://api.com/products',
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
products: [Product]
|
|
}
|
|
type Product @key(fields: "id") {
|
|
id: ID!
|
|
name: String
|
|
}
|
|
`,
|
|
service: productsServiceName,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be unsuccessful and the error coming from the external composition service should be visible
|
|
expect(publishProductsResult.schemaPublish).toEqual(
|
|
expect.objectContaining({
|
|
__typename: 'SchemaPublishError',
|
|
changes: {
|
|
total: 0,
|
|
nodes: [],
|
|
},
|
|
errors: {
|
|
total: 1,
|
|
nodes: [
|
|
{
|
|
message: expect.stringContaining('(ERR_INVALID_SIGNATURE)'), // composition
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
);
|
|
|
|
// ensure no new schema version is created for failed external composition
|
|
const versions = await fetchVersions(20);
|
|
expect(versions.length).toEqual(1);
|
|
},
|
|
);
|
|
|
|
test.concurrent(
|
|
'a network error coming from the external composition service should be visible to the user',
|
|
async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, setNativeFederation, fetchVersions } =
|
|
await createProject(ProjectType.Federation);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
const usersServiceName = generateUnique();
|
|
const publishUsersResult = await writeToken
|
|
.publishSchema({
|
|
url: 'https://api.com/users',
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
me: User
|
|
}
|
|
|
|
type User @key(fields: "id") {
|
|
id: ID!
|
|
name: String
|
|
}
|
|
`,
|
|
service: usersServiceName,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishUsersResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// expect `users` service to be composed internally
|
|
await expect(history()).resolves.not.toContainEqual(usersServiceName);
|
|
|
|
// we use internal docker network to connect to the external composition service,
|
|
// so we need to use the name and not resolved host
|
|
const dockerAddress = await getServiceHost('external_composition', 3012, false);
|
|
// enable external composition
|
|
const externalCompositionResult = await updateSchemaComposition(
|
|
{
|
|
project: {
|
|
bySelector: {
|
|
projectSlug: project.slug,
|
|
organizationSlug: organization.slug,
|
|
},
|
|
},
|
|
method: {
|
|
external: {
|
|
endpoint: `http://${dockerAddress}/non-existing-endpoint`,
|
|
// eslint-disable-next-line no-process-env
|
|
secret: process.env.EXTERNAL_COMPOSITION_SECRET!,
|
|
},
|
|
},
|
|
},
|
|
ownerToken,
|
|
).then(r => r.expectNoGraphQLErrors());
|
|
expect(
|
|
externalCompositionResult.updateSchemaComposition.ok?.updatedProject.externalSchemaComposition
|
|
?.endpoint,
|
|
).toBe(`http://${dockerAddress}/non-existing-endpoint`);
|
|
// set native federation to false to force external composition
|
|
await setNativeFederation(false);
|
|
|
|
const productsServiceName = generateUnique();
|
|
const publishProductsResult = await writeToken
|
|
.publishSchema({
|
|
url: 'https://api.com/products',
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
products: [Product]
|
|
}
|
|
type Product @key(fields: "id") {
|
|
id: ID!
|
|
name: String
|
|
}
|
|
`,
|
|
service: productsServiceName,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be unsuccessful and the error coming from the external composition service should be visible
|
|
expect(publishProductsResult.schemaPublish).toEqual(
|
|
expect.objectContaining({
|
|
__typename: 'SchemaPublishError',
|
|
changes: {
|
|
total: 0,
|
|
nodes: [],
|
|
},
|
|
errors: {
|
|
total: 1,
|
|
nodes: [
|
|
{
|
|
message: expect.stringContaining('404'), // composition
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
);
|
|
|
|
// ensure no new schema version is created for failed external composition
|
|
const versions = await fetchVersions(20);
|
|
expect(versions.length).toEqual(1);
|
|
},
|
|
);
|
|
|
|
test.concurrent('a timeout error should be visible to the user', async ({ expect }) => {
|
|
const { createOrg, ownerToken } = await initSeed().createOwner();
|
|
const { createProject, organization } = await createOrg();
|
|
const { createTargetAccessToken, project, setNativeFederation, fetchVersions } =
|
|
await createProject(ProjectType.Federation);
|
|
|
|
// Create a token with write rights
|
|
const writeToken = await createTargetAccessToken({});
|
|
|
|
const usersServiceName = generateUnique();
|
|
const publishUsersResult = await writeToken
|
|
.publishSchema({
|
|
url: 'https://api.com/users',
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
me: User
|
|
}
|
|
|
|
type User @key(fields: "id") {
|
|
id: ID!
|
|
name: String
|
|
}
|
|
`,
|
|
service: usersServiceName,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be successful
|
|
expect(publishUsersResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
|
|
|
|
// expect `users` service to be composed internally
|
|
await expect(history()).resolves.not.toContainEqual(usersServiceName);
|
|
|
|
// we use internal docker network to connect to the external composition service,
|
|
// so we need to use the name and not resolved host
|
|
const dockerAddress = await getServiceHost('external_composition', 3012, false);
|
|
// enable external composition
|
|
const externalCompositionResult = await updateSchemaComposition(
|
|
{
|
|
project: {
|
|
bySelector: {
|
|
projectSlug: project.slug,
|
|
organizationSlug: organization.slug,
|
|
},
|
|
},
|
|
method: {
|
|
external: {
|
|
endpoint: `http://${dockerAddress}/timeout`,
|
|
// eslint-disable-next-line no-process-env
|
|
secret: process.env.EXTERNAL_COMPOSITION_SECRET!,
|
|
},
|
|
},
|
|
},
|
|
ownerToken,
|
|
).then(r => r.expectNoGraphQLErrors());
|
|
expect(
|
|
externalCompositionResult.updateSchemaComposition.ok?.updatedProject.externalSchemaComposition
|
|
?.endpoint,
|
|
).toBe(`http://${dockerAddress}/timeout`);
|
|
// set native federation to false to force external composition
|
|
await setNativeFederation(false);
|
|
|
|
const productsServiceName = generateUnique();
|
|
const publishProductsResult = await writeToken
|
|
.publishSchema({
|
|
url: 'https://api.com/products',
|
|
sdl: /* GraphQL */ `
|
|
type Query {
|
|
products: [Product]
|
|
}
|
|
type Product @key(fields: "id") {
|
|
id: ID!
|
|
name: String
|
|
}
|
|
`,
|
|
service: productsServiceName,
|
|
})
|
|
.then(r => r.expectNoGraphQLErrors());
|
|
|
|
// Schema publish should be unsuccessful and the timeout error should be visible
|
|
expect(publishProductsResult.schemaPublish).toEqual(
|
|
expect.objectContaining({
|
|
__typename: 'SchemaPublishError',
|
|
changes: {
|
|
total: 0,
|
|
nodes: [],
|
|
},
|
|
errors: {
|
|
total: 1,
|
|
nodes: [
|
|
{
|
|
message: expect.stringMatching(/The schema composition timed out. Please try again./i),
|
|
},
|
|
],
|
|
},
|
|
linkToWebsite: null,
|
|
valid: false,
|
|
}),
|
|
);
|
|
|
|
// ensure no new schema version is created for failed external composition
|
|
const versions = await fetchVersions(20);
|
|
expect(versions.length).toEqual(1);
|
|
});
|