Skip to content

Commit 63afc43

Browse files
test(e2e): check scoped integration suites declare the builds they load
A scoped integration suite only reruns when one of its declared builds changes, so a fixture that starts loading a new SDK would silently stop triggering reruns. The new harness test derives each scoped suite's packages from its long-running apps, the app configs its tagged tests use, and the harness, and fails when turbo.json disagrees. CI now runs the integration harness tests.
1 parent 1d18230 commit 63afc43

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,10 @@ jobs:
383383
if: matrix.node-version == '24.15.0'
384384
run: pnpm vitest run --root scripts verify-attach verify-flaky
385385

386+
- name: Run integration harness tests
387+
if: matrix.node-version == '24.15.0'
388+
run: pnpm vitest run --root integration
389+
386390
- name: Upload Turbo Summary
387391
uses: actions/upload-artifact@v4.6.2
388392
if: ${{ env.TURBO_SUMMARIZE == 'true' }}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { readdirSync, readFileSync } from 'node:fs';
2+
import { join, resolve } from 'node:path';
3+
4+
import { beforeAll, describe, expect, it, vi } from 'vitest';
5+
6+
import type { ApplicationConfig } from '../../models/applicationConfig';
7+
import type { appConfigs as AppConfigs } from '..';
8+
9+
vi.mock('../instanceKeys', () => {
10+
class AnyInstanceKeys extends Map<string, { pk: string; sk: string }> {
11+
get(key: string) {
12+
return super.get(key) ?? { pk: 'pk_test_Y2xlcmsuZXhhbXBsZS5jb20k', sk: 'sk_test_placeholder' };
13+
}
14+
}
15+
return { instanceKeys: new AnyInstanceKeys() };
16+
});
17+
18+
vi.mock('../../models/longRunningApplication', () => ({
19+
longRunningApplication: (params: { id: string; config: ApplicationConfig }) => params,
20+
}));
21+
22+
const repoRoot = resolve(import.meta.dirname, '../../..');
23+
const testsDir = resolve(repoRoot, 'integration/tests');
24+
25+
const turbo = JSON.parse(readFileSync(join(repoRoot, 'turbo.json'), 'utf8'));
26+
const rootPackage = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8'));
27+
28+
const SERVED_BY_GLOBAL_SETUP = ['@clerk/clerk-js', '@clerk/ui'];
29+
const harnessPackages = Object.entries<string>(rootPackage.devDependencies)
30+
.filter(([name, version]) => name.startsWith('@clerk/') && version.startsWith('workspace:'))
31+
.map(([name]) => name);
32+
33+
const scopedSuites = Object.entries<{ dependsOn?: string[]; inputs: string[] }>(turbo.tasks)
34+
.filter(([id, task]) => id.startsWith('//#test:integration:') && !task.inputs.some(i => i.startsWith('packages/')))
35+
.map(([id, task]) => ({ suite: id.replace('//#test:integration:', ''), dependsOn: task.dependsOn ?? [] }));
36+
37+
const listTestFiles = (dir: string): string[] =>
38+
readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
39+
const path = join(dir, entry.name);
40+
if (entry.isDirectory()) {
41+
return listTestFiles(path);
42+
}
43+
return /\.test\.tsx?$/.test(entry.name) ? [path] : [];
44+
});
45+
46+
const parseScript = (suite: string) => {
47+
const script: string | undefined = rootPackage.scripts[`test:integration:${suite}`];
48+
return {
49+
script,
50+
appIds: script?.match(/E2E_APP_ID=(\S+)/)?.[1].split(','),
51+
tag: script?.match(/--grep (@[\w:-]+)/)?.[1],
52+
};
53+
};
54+
55+
let appConfigs: typeof AppConfigs;
56+
57+
beforeAll(async () => {
58+
vi.stubEnv('CLERK_PLATFORM_API_KEY', '');
59+
vi.stubEnv('E2E_STAGING', '');
60+
({ appConfigs } = await import('..'));
61+
});
62+
63+
const packagesLoadedBy = (suite: string) => {
64+
const { appIds, tag } = parseScript(suite);
65+
const longRunning = (
66+
appConfigs.longRunningApps.getByPattern(appIds!) as unknown as { config: ApplicationConfig }[]
67+
).map(app => app.config);
68+
69+
const tagPattern = new RegExp(`${tag}(?![\\w-])`);
70+
const referenced = listTestFiles(testsDir)
71+
.map(file => readFileSync(file, 'utf8'))
72+
.filter(source => tagPattern.test(source))
73+
.flatMap(source => [...source.matchAll(/appConfigs\.(\w+)\.(\w+)/g)])
74+
.filter(([, group]) => group !== 'envs' && group !== 'longRunningApps')
75+
.map(
76+
([, group, name]) => (appConfigs as unknown as Record<string, Record<string, ApplicationConfig>>)[group][name],
77+
);
78+
79+
return [...longRunning, ...referenced].flatMap(config => config.pkglabDependencies);
80+
};
81+
82+
describe('scoped integration suites declare the builds they load', () => {
83+
it('finds at least one scoped suite', () => {
84+
expect(scopedSuites.map(s => s.suite)).toContain('express');
85+
});
86+
87+
it('reads the harness packages from the root manifest', () => {
88+
expect(harnessPackages).toEqual(expect.arrayContaining(['@clerk/backend', '@clerk/shared', '@clerk/testing']));
89+
});
90+
91+
describe.each(scopedSuites)('$suite', ({ suite, dependsOn }) => {
92+
it('selects its apps with E2E_APP_ID and a --grep tag', () => {
93+
const { script, appIds, tag } = parseScript(suite);
94+
expect(script, `package.json has no test:integration:${suite} script`).toBeDefined();
95+
expect(appIds, `test:integration:${suite} must set E2E_APP_ID to be scoped`).toBeDefined();
96+
expect(tag, `test:integration:${suite} must pass --grep @tag to be scoped`).toBeDefined();
97+
});
98+
99+
it('depends on exactly the builds of the packages its fixtures and harness load', () => {
100+
const expected = [...new Set([...harnessPackages, ...SERVED_BY_GLOBAL_SETUP, ...packagesLoadedBy(suite)])]
101+
.map(name => `${name}#build`)
102+
.sort();
103+
expect([...dependsOn].sort()).toEqual(expected);
104+
});
105+
});
106+
});

0 commit comments

Comments
 (0)