Skip to content

Commit

Permalink
Native Federation v2 support
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilkisiela committed Sep 16, 2023
1 parent ac6d63f commit 9aa7ff7
Show file tree
Hide file tree
Showing 30 changed files with 718 additions and 390 deletions.
3 changes: 2 additions & 1 deletion integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@trpc/server": "10.31.0",
"@types/dockerode": "3.3.19",
"@types/ioredis": "4.28.10",
"@vitest/ui": "0.34.3",
"@whatwg-node/fetch": "0.9.9",
"bcryptjs": "2.4.3",
"date-fns": "2.30.0",
Expand All @@ -33,7 +34,7 @@
"slonik": "30.4.4",
"strip-ansi": "7.1.0",
"tslib": "2.5.3",
"vitest": "0.31.1",
"vitest": "0.34.3",
"zod": "3.21.4"
}
}
9 changes: 4 additions & 5 deletions integration-tests/testkit/registry-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ export async function prepareProject(
const { createOrg } = await initSeed().createOwner();
const { organization, createProject, setFeatureFlag, setOrganizationSchemaPolicy } =
await createOrg();
const { project, createToken, target, targets, setProjectSchemaPolicy } = await createProject(
projectType,
{
const { project, createToken, target, targets, setProjectSchemaPolicy, setNativeFederation } =
await createProject(projectType, {
useLegacyRegistryModels: model === RegistryModel.Legacy,
},
);
});

// Create a token with write rights
const { secret: readwriteToken } = await createToken({
Expand Down Expand Up @@ -46,5 +44,6 @@ export async function prepareProject(
},
},
setFeatureFlag,
setNativeFederation,
};
}
7 changes: 7 additions & 0 deletions integration-tests/testkit/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ export function initSeed() {
project,
targets,
target,
async setNativeFederation(enabled: boolean) {
const pool = await poolPromise;

await pool.query(sql`
UPDATE public.projects SET native_federation = ${enabled} WHERE id = ${project.id}
`);
},
async setProjectSchemaPolicy(policy: SchemaPolicyInput) {
const result = await execute({
document: UpdateSchemaPolicyForProject,
Expand Down
109 changes: 68 additions & 41 deletions integration-tests/tests/models/federation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
import { ProjectType, TargetAccessScope } from '@app/gql/graphql';
import { normalizeCliOutput } from '../../../scripts/serializers/cli-output';
import { createCLI, schemaPublish } from '../../testkit/cli';
import { prepareProject } from '../../testkit/registry-models';
import { initSeed } from '../../testkit/seed';

const cases = [
['default' as const, [] as [string, boolean][]],
['default' as const, [] as [string, boolean][], false],
[
'compareToPreviousComposableVersion' as const,
[['compareToPreviousComposableVersion', true]] as [string, boolean][],
false,
],
] as Array<['default' | 'compareToPreviousComposableVersion', Array<[string, boolean]>]>;
[
'nativeFederation' as const,
[['compareToPreviousComposableVersion', true]] as [string, boolean][],
true,
],
] as Array<
[
'default' | 'compareToPreviousComposableVersion' | 'nativeFederation',
Array<[string, boolean]>,
boolean,
]
>;

describe('publish', () => {
describe.each(cases)('%s', (caseName, ffs) => {
describe.concurrent.each(cases)('%s', (caseName, ffs, nativeFederation) => {
test.concurrent('accepted: composable', async () => {
const { publish } = await prepare(ffs);
const { publish } = await prepare(ffs, nativeFederation);
await publish({
sdl: `type Query { topProductName: String }`,
serviceName: 'products',
Expand All @@ -24,7 +37,7 @@ describe('publish', () => {
});

test.concurrent('accepted: composable, breaking changes', async () => {
const { publish } = await prepare(ffs);
const { publish } = await prepare(ffs, nativeFederation);
await publish({
sdl: /* GraphQL */ `
type Query {
Expand All @@ -51,7 +64,7 @@ describe('publish', () => {
test.concurrent(
`${caseName === 'default' ? 'rejected' : 'accepted'}: not composable (graphql errors)`,
async () => {
const { publish } = await prepare(ffs);
const { publish } = await prepare(ffs, nativeFederation);

// non-composable
await publish({
Expand All @@ -68,7 +81,7 @@ describe('publish', () => {
);

test.concurrent('accepted: composable, previous version was not', async () => {
const { publish } = await prepare(ffs);
const { publish } = await prepare(ffs, nativeFederation);

// non-composable
await publish({
Expand Down Expand Up @@ -104,7 +117,7 @@ describe('publish', () => {
});

test.concurrent('accepted: composable, no changes', async () => {
const { publish } = await prepare(ffs);
const { publish } = await prepare(ffs, nativeFederation);

// composable
await publish({
Expand Down Expand Up @@ -132,7 +145,7 @@ describe('publish', () => {
});

test.concurrent('accepted: composable, new url', async () => {
const { publish } = await prepare(ffs);
const { publish } = await prepare(ffs, nativeFederation);

// composable
await publish({
Expand Down Expand Up @@ -160,7 +173,7 @@ describe('publish', () => {
});

test.concurrent('rejected: missing service name', async () => {
const { publish } = await prepare(ffs);
const { publish } = await prepare(ffs, nativeFederation);

// composable
await publish({
Expand All @@ -175,7 +188,7 @@ describe('publish', () => {
});

test.concurrent('rejected: missing service url', async () => {
const { publish } = await prepare(ffs);
const { publish } = await prepare(ffs, nativeFederation);

// composable
await publish({
Expand All @@ -190,15 +203,15 @@ describe('publish', () => {
});

test.concurrent('CLI output', async ({ expect }) => {
const { publish } = await prepare(ffs);
const { publish } = await prepare(ffs, nativeFederation);

const service = {
serviceName: 'products',
serviceUrl: 'http://products:3000/graphql',
};

await expect(
publish({
let output = normalizeCliOutput(
(await publish({
sdl: /* GraphQL */ `
type Query {
topProduct: Product
Expand All @@ -211,14 +224,18 @@ describe('publish', () => {
`,
...service,
expect: 'latest-composable',
}),
).resolves.toMatchInlineSnapshot(`
v Published initial schema.
i Available at http://localhost:8080/$organization/$project/production
`);

await expect(
publish({
})) ?? '',
);

expect(output).toEqual(expect.stringContaining(`v Published initial schema.`));
expect(output).toEqual(
expect.stringContaining(
`i Available at http://localhost:8080/$organization/$project/production`,
),
);

output = normalizeCliOutput(
(await publish({
sdl: /* GraphQL */ `
type Query {
topProduct: Product
Expand All @@ -232,19 +249,23 @@ describe('publish', () => {
`,
...service,
expect: 'latest-composable',
}),
).resolves.toMatchInlineSnapshot(`
v Schema published
i Available at http://localhost:8080/$organization/$project/production/history/$version
`);
})) ?? '',
);

expect(output).toEqual(expect.stringContaining(`v Schema published`));
expect(output).toEqual(
expect.stringContaining(
`i Available at http://localhost:8080/$organization/$project/production/history/$version`,
),
);
});
});
});

describe('check', () => {
describe.each(cases)('%s', (_, ffs) => {
describe.concurrent.each(cases)('%s', (_, ffs, nativeFederation) => {
test.concurrent('accepted: composable, no breaking changes', async () => {
const { publish, check } = await prepare(ffs);
const { publish, check } = await prepare(ffs, nativeFederation);

await publish({
sdl: /* GraphQL */ `
Expand Down Expand Up @@ -272,7 +293,7 @@ describe('check', () => {
});

test.concurrent('accepted: composable, previous version was not', async () => {
const { publish, check } = await prepare(ffs);
const { publish, check } = await prepare(ffs, nativeFederation);

await publish({
sdl: /* GraphQL */ `
Expand Down Expand Up @@ -306,7 +327,7 @@ describe('check', () => {
});

test.concurrent('accepted: no changes', async () => {
const { publish, check } = await prepare(ffs);
const { publish, check } = await prepare(ffs, nativeFederation);

await publish({
sdl: /* GraphQL */ `
Expand All @@ -331,7 +352,7 @@ describe('check', () => {
});

test.concurrent('rejected: missing service name', async () => {
const { check } = await prepare(ffs);
const { check } = await prepare(ffs, nativeFederation);

const message = await check({
sdl: /* GraphQL */ `
Expand All @@ -346,7 +367,7 @@ describe('check', () => {
});

test.concurrent('rejected: composable, breaking changes', async () => {
const { publish, check } = await prepare(ffs);
const { publish, check } = await prepare(ffs, nativeFederation);

await publish({
sdl: /* GraphQL */ `
Expand All @@ -373,7 +394,7 @@ describe('check', () => {
});

test.concurrent('rejected: not composable, no breaking changes', async () => {
const { publish, check } = await prepare(ffs);
const { publish, check } = await prepare(ffs, nativeFederation);

await publish({
sdl: /* GraphQL */ `
Expand Down Expand Up @@ -401,7 +422,7 @@ describe('check', () => {
});

test.concurrent('rejected: not composable, breaking changes', async () => {
const { publish, check } = await prepare(ffs);
const { publish, check } = await prepare(ffs, nativeFederation);

await publish({
sdl: /* GraphQL */ `
Expand Down Expand Up @@ -441,9 +462,9 @@ describe('check', () => {
});

describe('delete', () => {
describe.each(cases)('%s', (_, ffs) => {
describe.concurrent.each(cases)('%s', (_, ffs, nativeFederation) => {
test.concurrent('accepted: composable before and after', async () => {
const cli = await prepare(ffs);
const cli = await prepare(ffs, nativeFederation);

await cli.publish({
sdl: /* GraphQL */ `
Expand Down Expand Up @@ -486,7 +507,7 @@ describe('delete', () => {
});

test.concurrent('rejected: unknown service', async () => {
const cli = await prepare(ffs);
const cli = await prepare(ffs, nativeFederation);

await cli.publish({
sdl: /* GraphQL */ `
Expand Down Expand Up @@ -515,7 +536,7 @@ describe('delete', () => {
});

describe('other', () => {
describe.each(cases)('%s', (_, ffs) => {
describe.concurrent.each(cases)('%s', (_, ffs) => {
test.concurrent('service url should be available in supergraph', async () => {
const { createOrg } = await initSeed().createOwner();
const { inviteAndJoinMember, createProject } = await createOrg();
Expand Down Expand Up @@ -718,12 +739,18 @@ describe('other', () => {
});
});

async function prepare(featureFlags: Array<[string, boolean]> = []) {
const { tokens, setFeatureFlag } = await prepareProject(ProjectType.Federation);
async function prepare(featureFlags: Array<[string, boolean]> = [], nativeFederation = false) {
const { tokens, setFeatureFlag, setNativeFederation } = await prepareProject(
ProjectType.Federation,
);

for await (const [name, enabled] of featureFlags) {
await setFeatureFlag(name, enabled);
}

if (nativeFederation === true) {
await setNativeFederation(true);
}

return createCLI(tokens.registry);
}
Loading

0 comments on commit 9aa7ff7

Please sign in to comment.