Skip to content
Closed
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
36 changes: 35 additions & 1 deletion tests/collect-launch-metrics.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@ function routes({
];
}

function collect(routeList) {
function collect(routeList, env = {}) {
return runScriptInSandbox({
script: 'collect-launch-metrics.mjs',
// The script writes straight into data/, which only exists in the sandbox
// if something is written there first.
fixtures: { 'data/.keep': '' },
routes: routeList,
outputs: [OUTPUT],
env,
});
}

Expand Down Expand Up @@ -367,3 +368,36 @@ test('an unparseable Link rel="next" target is refused', () => {
/Ignoring unparseable Link rel="next" target: http:\/\/\[v1\.unparseable/,
);
});

test('paginated collection requests carry the token when GH_TOKEN is set', () => {
const result = collect(routes(), { GH_TOKEN: 'fixture-token' });

readBaseline(result);
const paginated = result.requests.filter(
(request) =>
request.url.includes(CONTRIBUTORS_ROUTE) ||
request.url.includes(ISSUES_ROUTE),
);
assert.ok(paginated.length > 0, 'expected paginated collection requests');
for (const request of paginated) {
assert.ok(
String(request.headers.Authorization).includes('fixture-token'),
`missing credential on ${request.url}`,
);
}
});

test('paginated collection requests are unauthenticated without GH_TOKEN', () => {
const result = collect(routes());

readBaseline(result);
const paginated = result.requests.filter(
(request) =>
request.url.includes(CONTRIBUTORS_ROUTE) ||
request.url.includes(ISSUES_ROUTE),
);
assert.ok(paginated.length > 0, 'expected paginated collection requests');
for (const request of paginated) {
assert.equal('Authorization' in request.headers, false, request.url);
}
});
53 changes: 53 additions & 0 deletions tests/collect-metrics.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -505,3 +505,56 @@ test('collect-metrics refuses an unparseable Link rel=next target', () => {
/Ignoring unparseable Link rel="next" target: http:\/\/\[v1\.unparseable/,
);
});

test('collect-metrics tolerates a landscape.yml with no landscape key', () => {
const data = metricsFrom(run({ landscape: 'other: {}\n' }));

const byId = Object.fromEntries(
data.metrics.map((entry) => [entry.id, entry]),
);
assert.equal(byId['cncf-projects'].value, 0);
assert.deepEqual(data.breakdowns.projectCategories.values, []);
assert.deepEqual(data.breakdowns.projectMaturity.values, []);
});

test('collect-metrics skips a subcategory that declares no items', () => {
const data = metricsFrom(
run({
landscape: `landscape:
- name: Orchestration
subcategories:
- name: Empty subcategory
- name: Scheduling
items:
- name: Kubernetes
project: graduated
`,
}),
);

const byId = Object.fromEntries(
data.metrics.map((entry) => [entry.id, entry]),
);
assert.equal(byId['cncf-projects'].value, 1);
assert.deepEqual(data.breakdowns.projectCategories.values, [
{ name: 'Orchestration', value: 1 },
]);
});

test('collect-metrics files projects from an unnamed category under "Other"', () => {
const data = metricsFrom(
run({
landscape: `landscape:
- subcategories:
- name: Scheduling
items:
- name: Kubernetes
project: graduated
`,
}),
);

assert.deepEqual(data.breakdowns.projectCategories.values, [
{ name: 'Other', value: 1 },
]);
});
17 changes: 17 additions & 0 deletions tests/collect-radar-reports.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,20 @@ test('fails the run when the first page errors', () => {
);
assert.equal(badRequest.outputs[OUTPUT], null);
});

test('an existing catalog without a radarReports key is treated as empty', () => {
const result = collect({
routes: [
{ match: POSTS_ROUTE, body: [post()] },
{ match: TYPES_ROUTE, body: [radarTypeTerm()] },
],
fixtures: {
[OUTPUT]: JSON.stringify({ source: 'cncf.io reports' }, null, 2) + '\n',
},
});

const catalog = readCatalog(result);
assert.equal(catalog.radarReports.length, 1);
assert.equal(catalog.radarReports[0].slug, 'radar-one');
assert.equal(catalog.radarReports[0].summary, PLACEHOLDER_SUMMARY);
});
21 changes: 19 additions & 2 deletions tests/helpers-script-sandbox.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@ const repoRoot = new URL('..', import.meta.url).pathname;
// before the script under test is evaluated, so network-dependent scripts run
// offline and deterministically. Routes match by substring against the URL and
// are consulted in declaration order, so more specific routes come first.
// Every call is appended to ENDUSERS_FETCH_LOG as one JSON object per line so
// the caller can assert on what the script actually sent, not only on what it
// did with the reply.
const FETCH_STUB = `
import { appendFileSync } from 'node:fs';
const routes = JSON.parse(process.env.ENDUSERS_FETCH_ROUTES ?? '[]');
globalThis.fetch = async (input) => {
globalThis.fetch = async (input, init) => {
const url = typeof input === 'string' ? input : input.url;
appendFileSync(
process.env.ENDUSERS_FETCH_LOG,
JSON.stringify({ url, headers: init?.headers ?? {} }) + '\\n',
);
const route = routes.find((candidate) => url.includes(candidate.match));
if (!route) throw new Error('unstubbed request: ' + url);
if (route.networkError) throw new Error(route.networkError);
Expand Down Expand Up @@ -111,7 +119,8 @@ exit 0
* @param {string[]} [options.outputs] repo-relative paths to read back afterwards
* @param {Record<string, string>} [options.env] extra environment variables
* @returns {{status: number, stdout: string, stderr: string,
* outputs: Record<string, string | null>}}
* outputs: Record<string, string | null>,
* requests: Array<{url: string, headers: Record<string, string>}>}}
*/
export function runScriptInSandbox({
script,
Expand Down Expand Up @@ -154,6 +163,9 @@ export function runScriptInSandbox({
const stubPath = join(work, 'fetch-stub.mjs');
writeFileSync(stubPath, FETCH_STUB);

const fetchLog = join(work, 'fetch-log.jsonl');
writeFileSync(fetchLog, '');

const result = spawnSync(
'node',
['--import', stubPath, join(work, 'scripts', script)],
Expand All @@ -167,6 +179,7 @@ export function runScriptInSandbox({
// whether or not the developer or CI job exports a token.
GH_TOKEN: '',
ENDUSERS_FETCH_ROUTES: JSON.stringify(routes),
ENDUSERS_FETCH_LOG: fetchLog,
ENDUSERS_GIT_FIXTURES: fixtureRepos,
ENDUSERS_REAL_GIT: realGitPath(),
...repoDates,
Expand All @@ -188,6 +201,10 @@ export function runScriptInSandbox({
stdout: result.stdout ?? '',
stderr: result.stderr ?? '',
outputs: collected,
requests: readFileSync(fetchLog, 'utf8')
.split('\n')
.filter(Boolean)
.map((line) => JSON.parse(line)),
};
} finally {
rmSync(work, { recursive: true, force: true });
Expand Down