mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-09-08 14:39:10 +00:00
* Implement `/query-regression` command handling and admission workflow - Add `query-regression-slash.py` script for processing `/query-regression` commands in PR comments, validating case arguments, and checking permissions. - Update `checks.yml` to include tests for the new slash command functionality. - Modify `query-regression-comment.yml` to trigger on the new `Query Regression Command` workflow. - Create `query-regression-slash.yml` to handle the dispatched command, validate allowlist and permissions, and initiate the regression workflow. - Enhance `query-regression.yml` to support additional inputs for PR admission and SHA verification. - Introduce `slash-command-dispatch.yml` to parse and dispatch commands from PR comments. - Document the new command admission process in `AGENTS.md` and `README.md`. - Add unit tests in `test_query_regression_slash.py` to cover command parsing and admission logic. * refactor: enhance query-regression command handling with comment validation and identity checks * feat: implement admission identity handling for query regression workflows * refactor: update PR admission logic in query regression workflow * refactor: update token usage in slash command dispatch and README for clarity * test: add cases for handling re-run failed jobs and stale runner artifacts * refactor: improve repository metadata handling in query regression scripts * chore: enable overwrite for artifact uploads to handle re-run failed jobs * chore: enable overwrite for query regression admission uploads * feat: enhance query-regression admission with HMAC signing and verification - Introduced HMAC signing for admission markers in query-regression workflows to ensure integrity and authenticity. - Updated `query-regression-comment.test.cjs` to include tests for signing and verifying admission markers. - Modified `query-regression-slash.py` to handle admission marker signing and verification, including checks for dispatch sender and head SHA consistency. - Enhanced workflows to securely manage admission markers and HMAC secrets, ensuring they are not exposed to untrusted contexts. - Improved documentation to clarify the admission process and the role of HMAC in securing the workflow. * test: add case to find newly posted marker among newer comments * test: add case to verify multiline output handling in write_outputs function
1150 lines
42 KiB
JavaScript
1150 lines
42 KiB
JavaScript
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
|
|
const handler = require('./query-regression-comment.cjs');
|
|
const {
|
|
collectReportRows,
|
|
renderSummaryTable,
|
|
admissionMac,
|
|
verifyAdmissionMac,
|
|
formatAdmissionMarker,
|
|
parseAdmissionMarker,
|
|
} = handler._test;
|
|
|
|
const HMAC_SECRET = 'test-admission-hmac';
|
|
|
|
function report(name, measurements, thresholds = []) {
|
|
return {
|
|
case: { name },
|
|
status: 'ok',
|
|
targets: [
|
|
{ measurements: measurements.base },
|
|
{ measurements: measurements.candidate },
|
|
],
|
|
thresholds,
|
|
};
|
|
}
|
|
|
|
function markerComment(identity, { id = 1, secret = HMAC_SECRET } = {}) {
|
|
const payload = { ...identity, mac: admissionMac(secret, identity) };
|
|
return { id, body: formatAdmissionMarker(payload) };
|
|
}
|
|
|
|
function githubApi({ identity, pull, comments }) {
|
|
return {
|
|
rest: {
|
|
issues: {
|
|
listComments: async () => ({ data: comments ?? [markerComment(identity)] }),
|
|
},
|
|
pulls: {
|
|
get: async () => ({
|
|
data: pull ?? {
|
|
state: 'open',
|
|
base: { repo: { full_name: identity.base_repo } },
|
|
head: { repo: { full_name: identity.head_repo }, sha: identity.head_sha },
|
|
},
|
|
}),
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
test('keeps the default export callable and exposes only the test seam', () => {
|
|
assert.equal(typeof handler, 'function');
|
|
assert.equal(handler.constructor.name, 'AsyncFunction');
|
|
assert.deepEqual(Object.keys(handler._test).sort(), [
|
|
'admissionMac',
|
|
'collectReportRows',
|
|
'formatAdmissionMarker',
|
|
'parseAdmissionMarker',
|
|
'renderSummaryTable',
|
|
'verifyAdmissionMac',
|
|
]);
|
|
});
|
|
|
|
test('renders every case in one summary table without per-case separators', () => {
|
|
const rows = [
|
|
...collectReportRows(report('first', {
|
|
base: [{ name: 'q1', latency_ms_median: 10 }],
|
|
candidate: [{ name: 'q1', latency_ms_median: 11 }],
|
|
}), '/reports/first/query-regression-report.json'),
|
|
...collectReportRows(report('second', {
|
|
base: [{ name: 'q2', latency_ms_median: 20 }],
|
|
candidate: [{ name: 'q2', latency_ms_median: 18 }],
|
|
}), '/reports/second/query-regression-report.json'),
|
|
];
|
|
|
|
const table = renderSummaryTable(rows);
|
|
|
|
assert.equal((table.match(/^\| Case \| Query \| Case status \|/gm) || []).length, 1);
|
|
assert.match(table, /\| first \| q1 \|/);
|
|
assert.match(table, /\| second \| q2 \|/);
|
|
assert.doesNotMatch(table, /^### /m);
|
|
assert.doesNotMatch(table, /^---$/m);
|
|
});
|
|
|
|
test('renders failed reports as N/A rows with their error', () => {
|
|
const rows = collectReportRows({
|
|
case: { name: 'broken' },
|
|
status: 'failed',
|
|
error: 'connection refused',
|
|
}, '/reports/broken/query-regression-report.json');
|
|
|
|
assert.deepEqual(rows, [{
|
|
caseName: 'broken',
|
|
query: 'N/A',
|
|
status: 'failed',
|
|
baseMedian: 'N/A',
|
|
candidateMedian: 'N/A',
|
|
regression: 'N/A',
|
|
threshold: 'error: connection refused',
|
|
}]);
|
|
});
|
|
|
|
test('reports missing targets and empty measurements', () => {
|
|
const missingTargets = collectReportRows({ case: { name: 'missing' }, status: 'failed' }, '/reports/missing/report.json');
|
|
const emptyMeasurements = collectReportRows(report('empty', { base: [], candidate: [] }), '/reports/empty/report.json');
|
|
|
|
assert.equal(missingTargets[0].threshold, 'base/candidate measurements missing');
|
|
assert.equal(emptyMeasurements[0].threshold, 'no query measurements found');
|
|
});
|
|
|
|
test('renders null, array, and primitive reports as invalid report rows', () => {
|
|
for (const invalidReport of [null, [], 'not an object']) {
|
|
assert.deepEqual(
|
|
collectReportRows(invalidReport, '/reports/fallback/query-regression-report.json'),
|
|
[{
|
|
caseName: 'fallback',
|
|
query: 'N/A',
|
|
status: 'missing',
|
|
baseMedian: 'N/A',
|
|
candidateMedian: 'N/A',
|
|
regression: 'N/A',
|
|
threshold: 'invalid report object',
|
|
}]
|
|
);
|
|
}
|
|
});
|
|
|
|
test('rejects null medians and diagnoses missing asymmetric measurements', () => {
|
|
const rows = collectReportRows(report('missing-values', {
|
|
base: [
|
|
{ name: 'null-median', latency_ms_median: null },
|
|
{ name: 'base-only', latency_ms_median: 10 },
|
|
{ name: 'candidate-null', latency_ms_median: 20 },
|
|
],
|
|
candidate: [
|
|
{ name: 'null-median', latency_ms_median: 20 },
|
|
{ name: 'candidate-null', latency_ms_median: null },
|
|
{ name: 'candidate-only', latency_ms_median: 30 },
|
|
],
|
|
}), '/reports/missing-values/query-regression-report.json');
|
|
const byQuery = new Map(rows.map(row => [row.query, row]));
|
|
|
|
assert.equal(byQuery.get('null-median').baseMedian, 'N/A');
|
|
assert.equal(byQuery.get('null-median').candidateMedian, '20.00');
|
|
assert.equal(byQuery.get('null-median').regression, 'N/A');
|
|
assert.equal(byQuery.get('null-median').threshold, 'base measurement missing');
|
|
assert.equal(byQuery.get('base-only').threshold, 'candidate measurement missing');
|
|
assert.equal(byQuery.get('candidate-null').candidateMedian, 'N/A');
|
|
assert.equal(byQuery.get('candidate-null').regression, 'N/A');
|
|
assert.equal(byQuery.get('candidate-null').threshold, 'candidate measurement missing');
|
|
assert.equal(byQuery.get('candidate-only').threshold, 'base measurement missing');
|
|
});
|
|
|
|
test('rejects empty, blank, NaN, and infinite medians', () => {
|
|
const rows = collectReportRows(report('invalid-values', {
|
|
base: [
|
|
{ name: 'empty-base', latency_ms_median: '' },
|
|
{ name: 'blank-base', latency_ms_median: ' ' },
|
|
{ name: 'nan-candidate', latency_ms_median: 10 },
|
|
{ name: 'infinite-candidate', latency_ms_median: 10 },
|
|
],
|
|
candidate: [
|
|
{ name: 'empty-base', latency_ms_median: 10 },
|
|
{ name: 'blank-base', latency_ms_median: 10 },
|
|
{ name: 'nan-candidate', latency_ms_median: Number.NaN },
|
|
{ name: 'infinite-candidate', latency_ms_median: Number.POSITIVE_INFINITY },
|
|
],
|
|
}), '/reports/invalid-values/query-regression-report.json');
|
|
const byQuery = new Map(rows.map(row => [row.query, row]));
|
|
|
|
for (const query of ['empty-base', 'blank-base', 'nan-candidate', 'infinite-candidate']) {
|
|
assert.equal(byQuery.get(query).regression, 'N/A');
|
|
}
|
|
assert.equal(byQuery.get('empty-base').baseMedian, 'N/A');
|
|
assert.equal(byQuery.get('blank-base').baseMedian, 'N/A');
|
|
assert.equal(byQuery.get('nan-candidate').candidateMedian, 'N/A');
|
|
assert.equal(byQuery.get('infinite-candidate').candidateMedian, 'N/A');
|
|
});
|
|
|
|
test('rejects boolean, array, and object median coercions', () => {
|
|
const rows = collectReportRows(report('invalid-types', {
|
|
base: [
|
|
{ name: 'boolean-base', latency_ms_median: true },
|
|
{ name: 'array-base', latency_ms_median: [] },
|
|
{ name: 'object-base', latency_ms_median: {} },
|
|
{ name: 'boolean-candidate', latency_ms_median: 10 },
|
|
{ name: 'array-candidate', latency_ms_median: 10 },
|
|
{ name: 'object-candidate', latency_ms_median: 10 },
|
|
],
|
|
candidate: [
|
|
{ name: 'boolean-base', latency_ms_median: 10 },
|
|
{ name: 'array-base', latency_ms_median: 10 },
|
|
{ name: 'object-base', latency_ms_median: 10 },
|
|
{ name: 'boolean-candidate', latency_ms_median: false },
|
|
{ name: 'array-candidate', latency_ms_median: [] },
|
|
{ name: 'object-candidate', latency_ms_median: {} },
|
|
],
|
|
}), '/reports/invalid-types/query-regression-report.json');
|
|
|
|
for (const row of rows) {
|
|
assert.equal(row.regression, 'N/A');
|
|
}
|
|
assert.equal(rows.find(row => row.query === 'boolean-base').baseMedian, 'N/A');
|
|
assert.equal(rows.find(row => row.query === 'array-base').baseMedian, 'N/A');
|
|
assert.equal(rows.find(row => row.query === 'object-base').baseMedian, 'N/A');
|
|
assert.equal(rows.find(row => row.query === 'boolean-candidate').candidateMedian, 'N/A');
|
|
assert.equal(rows.find(row => row.query === 'array-candidate').candidateMedian, 'N/A');
|
|
assert.equal(rows.find(row => row.query === 'object-candidate').candidateMedian, 'N/A');
|
|
});
|
|
|
|
test('sorts the base and candidate query union and aggregates scoped thresholds', () => {
|
|
const rows = collectReportRows(report('union', {
|
|
base: [{ name: 'z', latency_ms_median: 10 }],
|
|
candidate: [{ name: 'a', latency_ms_median: 20 }],
|
|
}, [
|
|
{ query: 'z', threshold: 'p95', target: 'base', status: 'warn' },
|
|
{ query: 'z', threshold: 'absolute', target: 'candidate', encoding: 'plain', status: 'pass' },
|
|
]), '/reports/union/query-regression-report.json');
|
|
|
|
assert.deepEqual(rows.map(row => row.query), ['a', 'z']);
|
|
assert.equal(rows[0].baseMedian, 'N/A');
|
|
assert.equal(rows[0].candidateMedian, '20.00');
|
|
assert.equal(rows[0].threshold, 'base measurement missing');
|
|
assert.equal(
|
|
rows[1].threshold,
|
|
'candidate measurement missing; p95 [target=base]: warn; absolute [target=candidate, encoding=plain]: pass'
|
|
);
|
|
});
|
|
|
|
test('keeps unscoped and unmatched thresholds in a case-thresholds detail row', () => {
|
|
const rows = collectReportRows(report('thresholds', {
|
|
base: [{ name: 'measured', latency_ms_median: 10 }],
|
|
candidate: [{ name: 'measured', latency_ms_median: 11 }],
|
|
}, [
|
|
{ query: 'measured', threshold: 'query limit', target: 'base', status: 'passed' },
|
|
{ threshold: 'min_files', target: 'base', status: 'passed' },
|
|
{ threshold: 'min_files', target: 'candidate', status: 'failed' },
|
|
{ threshold: 'encoding limit', target: 'candidate', encoding: 'plain', status: 'failed' },
|
|
{
|
|
query: 'not-measured',
|
|
threshold: 'orphaned limit',
|
|
target: 'base',
|
|
encoding: 'json',
|
|
status: 'failed',
|
|
reason: 'measurement unavailable',
|
|
},
|
|
]), '/reports/thresholds/query-regression-report.json');
|
|
|
|
assert.equal(rows.length, 2);
|
|
assert.equal(rows[0].query, 'measured');
|
|
assert.equal(rows[0].threshold, 'query limit [target=base]: passed');
|
|
assert.equal(rows[1].query, 'N/A');
|
|
assert.equal(rows[1].kind, 'case-thresholds');
|
|
assert.equal(
|
|
rows[1].threshold,
|
|
'min_files [target=base]: passed; min_files [target=candidate]: failed; encoding limit [target=candidate, encoding=plain]: failed; unmatched query not-measured: orphaned limit [target=base, encoding=json]: failed (reason: measurement unavailable)'
|
|
);
|
|
});
|
|
|
|
test('keeps unscoped thresholds out of undefined and null query rows', () => {
|
|
const rows = collectReportRows(report('collisions', {
|
|
base: [
|
|
{ name: 'undefined', latency_ms_median: 10 },
|
|
{ name: 'null', latency_ms_median: 10 },
|
|
],
|
|
candidate: [
|
|
{ name: 'undefined', latency_ms_median: 11 },
|
|
{ name: 'null', latency_ms_median: 11 },
|
|
],
|
|
}, [
|
|
{ threshold: 'min_files', target: 'base', status: 'passed' },
|
|
{ query: null, threshold: 'min_files', target: 'candidate', status: 'failed' },
|
|
]), '/reports/collisions/query-regression-report.json');
|
|
const byQuery = new Map(rows.map(row => [row.query, row]));
|
|
|
|
assert.equal(byQuery.get('undefined').threshold, 'N/A');
|
|
assert.equal(byQuery.get('null').threshold, 'N/A');
|
|
assert.equal(rows.filter(row => row.query === 'N/A').length, 1);
|
|
assert.equal(byQuery.get('N/A').kind, 'case-thresholds');
|
|
assert.equal(
|
|
byQuery.get('N/A').threshold,
|
|
'min_files [target=base]: passed; min_files [target=candidate]: failed'
|
|
);
|
|
});
|
|
|
|
test('renders case/storage thresholds in a collapsible details block below the table', () => {
|
|
const rows = [
|
|
...collectReportRows(report('first', {
|
|
base: [{ name: 'q1', latency_ms_median: 10 }],
|
|
candidate: [{ name: 'q1', latency_ms_median: 11 }],
|
|
}, [
|
|
{ query: 'q1', threshold: 'query limit', target: 'base', status: 'passed' },
|
|
{ threshold: 'min_files', target: 'base', status: 'passed' },
|
|
{ threshold: 'min_files', target: 'candidate', status: 'failed' },
|
|
]), '/reports/first/query-regression-report.json'),
|
|
...collectReportRows({
|
|
case: { name: 'broken' },
|
|
status: 'failed',
|
|
error: 'connection refused',
|
|
}, '/reports/broken/query-regression-report.json'),
|
|
];
|
|
|
|
const rendered = renderSummaryTable(rows);
|
|
const detailsIndex = rendered.indexOf('<details>');
|
|
const table = rendered.slice(0, detailsIndex);
|
|
const details = rendered.slice(detailsIndex);
|
|
|
|
// Query rows and abnormal N/A rows stay in the main table.
|
|
assert.match(table, /\| first \| q1 \| ✅ ok \|/);
|
|
assert.match(table, /\| broken \| N\/A \| ❌ failed \|/);
|
|
// Storage thresholds are not main-table rows.
|
|
assert.doesNotMatch(table, /^\| first \| N\/A \|/m);
|
|
assert.doesNotMatch(table, /min_files/);
|
|
// Status column is plain text without code-span backticks.
|
|
assert.doesNotMatch(table, /`/);
|
|
// Details block keeps its tags unescaped and its items '; '-separated.
|
|
assert.ok(details.startsWith('<details><summary>Case / storage thresholds</summary>'));
|
|
assert.ok(details.includes('- first: min_files \\[target=base\\]: passed; min_files \\[target=candidate\\]: failed'));
|
|
assert.ok(details.endsWith('</details>'));
|
|
assert.ok(!rendered.includes('<details>'));
|
|
});
|
|
|
|
test('escapes Markdown table content, including bare carriage returns', () => {
|
|
const table = renderSummaryTable([{
|
|
caseName: 'safe\r| injected |\n<!-- hidden -->@user <tag>',
|
|
query: 'query`|\n@team',
|
|
status: 'failed',
|
|
baseMedian: '1|2',
|
|
candidateMedian: '3\n4',
|
|
regression: '<!-- comment -->`@all',
|
|
threshold: 'x|y\r\n<!-- drop -->`@here <html>',
|
|
}]);
|
|
|
|
assert.equal(table.split('\n').length, 3);
|
|
assert.match(table, /safe \\\| injected \\\| @\u200buser <tag>/);
|
|
assert.match(table, /query`\\\| @\u200bteam/);
|
|
assert.match(table, /1\\\|2/);
|
|
assert.match(table, /3 4/);
|
|
assert.match(table, /`@\u200ball/);
|
|
assert.match(table, /x\\\|y `@\u200bhere <html>/);
|
|
assert.doesNotMatch(table, /hidden|comment|drop|\r/);
|
|
});
|
|
|
|
test('posts a comment-command report without treating workflow_run as the PR head', async () => {
|
|
const originalCwd = process.cwd();
|
|
const originalRunId = process.env.WORKFLOW_RUN_ID;
|
|
const originalRunAttempt = process.env.WORKFLOW_RUN_ATTEMPT;
|
|
const originalHmac = process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'query-regression-comment-'));
|
|
const artifactDir = path.join(temporaryDir, 'query-regression-comment');
|
|
const outputs = new Map();
|
|
|
|
try {
|
|
fs.mkdirSync(artifactDir);
|
|
const metadata = {
|
|
run_id: 202,
|
|
run_attempt: 1,
|
|
base_repo: 'owner/repo',
|
|
pr_number: 42,
|
|
head_sha: 'pr-head-sha',
|
|
head_repo: 'fork/repo',
|
|
built_base_sha: 'base-sha',
|
|
event_base_sha: 'event-base-sha',
|
|
candidate_sha: 'merge-sha',
|
|
base_sha: 'event-base-sha',
|
|
};
|
|
fs.mkdirSync(path.join(temporaryDir, 'query-regression-admission'));
|
|
fs.writeFileSync(
|
|
path.join(temporaryDir, 'query-regression-admission', 'query-regression-admission.json'),
|
|
JSON.stringify(metadata),
|
|
);
|
|
fs.writeFileSync(path.join(artifactDir, 'query-regression-pr.json'), JSON.stringify(metadata));
|
|
process.chdir(temporaryDir);
|
|
process.env.WORKFLOW_RUN_ID = '202';
|
|
process.env.WORKFLOW_RUN_ATTEMPT = '1';
|
|
process.env.QUERY_REGRESSION_ADMISSION_HMAC = HMAC_SECRET;
|
|
|
|
await handler({
|
|
core: {
|
|
info() {},
|
|
warning() {},
|
|
setOutput(name, value) { outputs.set(name, value); },
|
|
},
|
|
context: {
|
|
repo: { owner: 'owner', repo: 'repo' },
|
|
payload: {
|
|
workflow_run: {
|
|
event: 'repository_dispatch',
|
|
head_sha: 'default-branch-sha',
|
|
head_repository: { full_name: 'owner/repo' },
|
|
pull_requests: [],
|
|
},
|
|
},
|
|
},
|
|
github: githubApi({ identity: metadata }),
|
|
});
|
|
|
|
assert.equal(outputs.get('should_post'), 'true');
|
|
assert.equal(outputs.get('pr_number'), '42');
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
if (originalRunId === undefined) delete process.env.WORKFLOW_RUN_ID;
|
|
else process.env.WORKFLOW_RUN_ID = originalRunId;
|
|
if (originalRunAttempt === undefined) delete process.env.WORKFLOW_RUN_ATTEMPT;
|
|
else process.env.WORKFLOW_RUN_ATTEMPT = originalRunAttempt;
|
|
if (originalHmac === undefined) delete process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
else process.env.QUERY_REGRESSION_ADMISSION_HMAC = originalHmac;
|
|
fs.rmSync(temporaryDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('skips reports produced by a pull_request workflow_run', async () => {
|
|
const originalCwd = process.cwd();
|
|
const originalRunId = process.env.WORKFLOW_RUN_ID;
|
|
const originalRunAttempt = process.env.WORKFLOW_RUN_ATTEMPT;
|
|
const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'query-regression-comment-'));
|
|
const artifactDir = path.join(temporaryDir, 'query-regression-comment');
|
|
const outputs = new Map();
|
|
const infos = [];
|
|
|
|
try {
|
|
fs.mkdirSync(artifactDir);
|
|
const metadata = {
|
|
run_id: 303,
|
|
run_attempt: 1,
|
|
base_repo: 'owner/repo',
|
|
pr_number: 42,
|
|
head_sha: 'head-sha',
|
|
head_repo: 'fork/repo',
|
|
built_base_sha: 'base-sha',
|
|
event_base_sha: 'event-base-sha',
|
|
candidate_sha: 'candidate-sha',
|
|
base_sha: 'event-base-sha',
|
|
};
|
|
fs.mkdirSync(path.join(temporaryDir, 'query-regression-admission'));
|
|
fs.writeFileSync(
|
|
path.join(temporaryDir, 'query-regression-admission', 'query-regression-admission.json'),
|
|
JSON.stringify(metadata),
|
|
);
|
|
fs.writeFileSync(path.join(artifactDir, 'query-regression-pr.json'), JSON.stringify(metadata));
|
|
process.chdir(temporaryDir);
|
|
process.env.WORKFLOW_RUN_ID = '303';
|
|
process.env.WORKFLOW_RUN_ATTEMPT = '1';
|
|
|
|
await handler({
|
|
core: {
|
|
info(message) { infos.push(message); },
|
|
warning() {},
|
|
setOutput(name, value) { outputs.set(name, value); },
|
|
},
|
|
context: {
|
|
repo: { owner: 'owner', repo: 'repo' },
|
|
payload: {
|
|
workflow_run: {
|
|
event: 'pull_request',
|
|
head_sha: 'head-sha',
|
|
head_repository: { full_name: 'fork/repo' },
|
|
pull_requests: [{ number: 42 }],
|
|
},
|
|
},
|
|
},
|
|
github: { rest: { pulls: { get: async () => { throw new Error('should not fetch PR'); } } } },
|
|
});
|
|
|
|
assert.equal(outputs.get('should_post'), 'false');
|
|
assert.match(infos.join('\n'), /not repository_dispatch/);
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
if (originalRunId === undefined) delete process.env.WORKFLOW_RUN_ID;
|
|
else process.env.WORKFLOW_RUN_ID = originalRunId;
|
|
if (originalRunAttempt === undefined) delete process.env.WORKFLOW_RUN_ATTEMPT;
|
|
else process.env.WORKFLOW_RUN_ATTEMPT = originalRunAttempt;
|
|
fs.rmSync(temporaryDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('writes the explicit no-report summary without an empty table', async () => {
|
|
const originalCwd = process.cwd();
|
|
const originalRunId = process.env.WORKFLOW_RUN_ID;
|
|
const originalRunAttempt = process.env.WORKFLOW_RUN_ATTEMPT;
|
|
const originalHmac = process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'query-regression-comment-'));
|
|
const artifactDir = path.join(temporaryDir, 'query-regression-comment');
|
|
const outputs = new Map();
|
|
|
|
try {
|
|
fs.mkdirSync(artifactDir);
|
|
const metadata = {
|
|
run_id: 101,
|
|
run_attempt: 1,
|
|
base_repo: 'owner/repo',
|
|
pr_number: 42,
|
|
head_sha: 'head-sha',
|
|
head_repo: 'fork/repo',
|
|
built_base_sha: 'base-sha',
|
|
event_base_sha: 'event-base-sha',
|
|
candidate_sha: 'candidate-sha',
|
|
base_sha: 'event-base-sha',
|
|
};
|
|
fs.mkdirSync(path.join(temporaryDir, 'query-regression-admission'));
|
|
fs.writeFileSync(
|
|
path.join(temporaryDir, 'query-regression-admission', 'query-regression-admission.json'),
|
|
JSON.stringify(metadata),
|
|
);
|
|
fs.writeFileSync(path.join(artifactDir, 'query-regression-pr.json'), JSON.stringify(metadata));
|
|
process.chdir(temporaryDir);
|
|
process.env.WORKFLOW_RUN_ID = '101';
|
|
process.env.WORKFLOW_RUN_ATTEMPT = '1';
|
|
process.env.QUERY_REGRESSION_ADMISSION_HMAC = HMAC_SECRET;
|
|
|
|
await handler({
|
|
core: {
|
|
info() {},
|
|
warning() {},
|
|
setOutput(name, value) { outputs.set(name, value); },
|
|
},
|
|
context: {
|
|
repo: { owner: 'owner', repo: 'repo' },
|
|
payload: {
|
|
workflow_run: {
|
|
event: 'repository_dispatch',
|
|
head_sha: 'default-branch-sha',
|
|
head_repository: { full_name: 'owner/repo' },
|
|
pull_requests: [],
|
|
},
|
|
},
|
|
},
|
|
github: githubApi({ identity: metadata }),
|
|
});
|
|
|
|
const summary = fs.readFileSync(path.join(artifactDir, 'query-regression-summary.md'), 'utf8');
|
|
assert.equal(outputs.get('should_post'), 'true');
|
|
assert.match(summary, /No query-regression JSON reports were found in the artifact\./);
|
|
assert.doesNotMatch(summary, /\| Case \| Query \|/);
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
if (originalRunId === undefined) delete process.env.WORKFLOW_RUN_ID;
|
|
else process.env.WORKFLOW_RUN_ID = originalRunId;
|
|
if (originalRunAttempt === undefined) delete process.env.WORKFLOW_RUN_ATTEMPT;
|
|
else process.env.WORKFLOW_RUN_ATTEMPT = originalRunAttempt;
|
|
if (originalHmac === undefined) delete process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
else process.env.QUERY_REGRESSION_ADMISSION_HMAC = originalHmac;
|
|
fs.rmSync(temporaryDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('skips when the runner artifact forges a different PR number', async () => {
|
|
const originalCwd = process.cwd();
|
|
const originalRunId = process.env.WORKFLOW_RUN_ID;
|
|
const originalRunAttempt = process.env.WORKFLOW_RUN_ATTEMPT;
|
|
const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'query-regression-comment-'));
|
|
const artifactDir = path.join(temporaryDir, 'query-regression-comment');
|
|
const outputs = new Map();
|
|
const infos = [];
|
|
|
|
try {
|
|
fs.mkdirSync(artifactDir);
|
|
const admission = {
|
|
run_id: 404,
|
|
run_attempt: 1,
|
|
base_repo: 'owner/repo',
|
|
pr_number: 42,
|
|
head_sha: 'head-sha',
|
|
head_repo: 'fork/repo',
|
|
candidate_sha: 'merge-sha',
|
|
base_sha: 'base-sha',
|
|
};
|
|
fs.mkdirSync(path.join(temporaryDir, 'query-regression-admission'));
|
|
fs.writeFileSync(
|
|
path.join(temporaryDir, 'query-regression-admission', 'query-regression-admission.json'),
|
|
JSON.stringify(admission),
|
|
);
|
|
fs.writeFileSync(path.join(artifactDir, 'query-regression-pr.json'), JSON.stringify({
|
|
...admission,
|
|
pr_number: 99,
|
|
}));
|
|
process.chdir(temporaryDir);
|
|
process.env.WORKFLOW_RUN_ID = '404';
|
|
process.env.WORKFLOW_RUN_ATTEMPT = '1';
|
|
|
|
await handler({
|
|
core: {
|
|
info(message) { infos.push(message); },
|
|
warning() {},
|
|
setOutput(name, value) { outputs.set(name, value); },
|
|
},
|
|
context: {
|
|
repo: { owner: 'owner', repo: 'repo' },
|
|
payload: {
|
|
workflow_run: {
|
|
event: 'repository_dispatch',
|
|
head_sha: 'default-branch-sha',
|
|
head_repository: { full_name: 'owner/repo' },
|
|
pull_requests: [],
|
|
},
|
|
},
|
|
},
|
|
github: { rest: { pulls: { get: async () => { throw new Error('should not fetch PR'); } } } },
|
|
});
|
|
|
|
assert.equal(outputs.get('should_post'), 'false');
|
|
assert.match(infos.join('\n'), /does not match admission artifact/);
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
if (originalRunId === undefined) delete process.env.WORKFLOW_RUN_ID;
|
|
else process.env.WORKFLOW_RUN_ID = originalRunId;
|
|
if (originalRunAttempt === undefined) delete process.env.WORKFLOW_RUN_ATTEMPT;
|
|
else process.env.WORKFLOW_RUN_ATTEMPT = originalRunAttempt;
|
|
fs.rmSync(temporaryDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('posts after Re-run failed jobs when admission stays on attempt 1', async () => {
|
|
const originalCwd = process.cwd();
|
|
const originalRunId = process.env.WORKFLOW_RUN_ID;
|
|
const originalRunAttempt = process.env.WORKFLOW_RUN_ATTEMPT;
|
|
const originalHmac = process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'query-regression-comment-'));
|
|
const artifactDir = path.join(temporaryDir, 'query-regression-comment');
|
|
const outputs = new Map();
|
|
|
|
try {
|
|
fs.mkdirSync(artifactDir);
|
|
const admission = {
|
|
run_id: 505,
|
|
run_attempt: 1,
|
|
base_repo: 'owner/repo',
|
|
pr_number: 42,
|
|
head_sha: 'pr-head-sha',
|
|
head_repo: 'fork/repo',
|
|
candidate_sha: 'merge-sha',
|
|
base_sha: 'base-sha',
|
|
};
|
|
const metadata = {
|
|
...admission,
|
|
run_attempt: 2,
|
|
built_base_sha: 'base-sha',
|
|
event_base_sha: 'base-sha',
|
|
};
|
|
fs.mkdirSync(path.join(temporaryDir, 'query-regression-admission'));
|
|
fs.writeFileSync(
|
|
path.join(temporaryDir, 'query-regression-admission', 'query-regression-admission.json'),
|
|
JSON.stringify(admission),
|
|
);
|
|
fs.writeFileSync(path.join(artifactDir, 'query-regression-pr.json'), JSON.stringify(metadata));
|
|
process.chdir(temporaryDir);
|
|
process.env.WORKFLOW_RUN_ID = '505';
|
|
process.env.WORKFLOW_RUN_ATTEMPT = '2';
|
|
process.env.QUERY_REGRESSION_ADMISSION_HMAC = HMAC_SECRET;
|
|
|
|
await handler({
|
|
core: {
|
|
info() {},
|
|
warning() {},
|
|
setOutput(name, value) { outputs.set(name, value); },
|
|
},
|
|
context: {
|
|
repo: { owner: 'owner', repo: 'repo' },
|
|
payload: {
|
|
workflow_run: {
|
|
event: 'repository_dispatch',
|
|
head_sha: 'default-branch-sha',
|
|
head_repository: { full_name: 'owner/repo' },
|
|
pull_requests: [],
|
|
},
|
|
},
|
|
},
|
|
github: githubApi({ identity: admission }),
|
|
});
|
|
|
|
assert.equal(outputs.get('should_post'), 'true');
|
|
assert.equal(outputs.get('pr_number'), '42');
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
if (originalRunId === undefined) delete process.env.WORKFLOW_RUN_ID;
|
|
else process.env.WORKFLOW_RUN_ID = originalRunId;
|
|
if (originalRunAttempt === undefined) delete process.env.WORKFLOW_RUN_ATTEMPT;
|
|
else process.env.WORKFLOW_RUN_ATTEMPT = originalRunAttempt;
|
|
if (originalHmac === undefined) delete process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
else process.env.QUERY_REGRESSION_ADMISSION_HMAC = originalHmac;
|
|
fs.rmSync(temporaryDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('skips a stale runner artifact from a previous attempt', async () => {
|
|
const originalCwd = process.cwd();
|
|
const originalRunId = process.env.WORKFLOW_RUN_ID;
|
|
const originalRunAttempt = process.env.WORKFLOW_RUN_ATTEMPT;
|
|
const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'query-regression-comment-'));
|
|
const artifactDir = path.join(temporaryDir, 'query-regression-comment');
|
|
const outputs = new Map();
|
|
const infos = [];
|
|
|
|
try {
|
|
fs.mkdirSync(artifactDir);
|
|
const admission = {
|
|
run_id: 606,
|
|
run_attempt: 1,
|
|
base_repo: 'owner/repo',
|
|
pr_number: 42,
|
|
head_sha: 'pr-head-sha',
|
|
head_repo: 'fork/repo',
|
|
candidate_sha: 'merge-sha',
|
|
base_sha: 'base-sha',
|
|
};
|
|
fs.mkdirSync(path.join(temporaryDir, 'query-regression-admission'));
|
|
fs.writeFileSync(
|
|
path.join(temporaryDir, 'query-regression-admission', 'query-regression-admission.json'),
|
|
JSON.stringify(admission),
|
|
);
|
|
fs.writeFileSync(path.join(artifactDir, 'query-regression-pr.json'), JSON.stringify({
|
|
...admission,
|
|
built_base_sha: 'base-sha',
|
|
event_base_sha: 'base-sha',
|
|
}));
|
|
process.chdir(temporaryDir);
|
|
process.env.WORKFLOW_RUN_ID = '606';
|
|
process.env.WORKFLOW_RUN_ATTEMPT = '2';
|
|
|
|
await handler({
|
|
core: {
|
|
info(message) { infos.push(message); },
|
|
warning() {},
|
|
setOutput(name, value) { outputs.set(name, value); },
|
|
},
|
|
context: {
|
|
repo: { owner: 'owner', repo: 'repo' },
|
|
payload: {
|
|
workflow_run: {
|
|
event: 'repository_dispatch',
|
|
head_sha: 'default-branch-sha',
|
|
head_repository: { full_name: 'owner/repo' },
|
|
pull_requests: [],
|
|
},
|
|
},
|
|
},
|
|
github: { rest: { pulls: { get: async () => { throw new Error('should not fetch PR'); } } } },
|
|
});
|
|
|
|
assert.equal(outputs.get('should_post'), 'false');
|
|
assert.match(infos.join('\n'), /does not match this workflow_run attempt/);
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
if (originalRunId === undefined) delete process.env.WORKFLOW_RUN_ID;
|
|
else process.env.WORKFLOW_RUN_ID = originalRunId;
|
|
if (originalRunAttempt === undefined) delete process.env.WORKFLOW_RUN_ATTEMPT;
|
|
else process.env.WORKFLOW_RUN_ATTEMPT = originalRunAttempt;
|
|
fs.rmSync(temporaryDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('skips when the current PR head repository is missing', async () => {
|
|
const originalCwd = process.cwd();
|
|
const originalRunId = process.env.WORKFLOW_RUN_ID;
|
|
const originalRunAttempt = process.env.WORKFLOW_RUN_ATTEMPT;
|
|
const originalHmac = process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'query-regression-comment-'));
|
|
const artifactDir = path.join(temporaryDir, 'query-regression-comment');
|
|
const outputs = new Map();
|
|
const infos = [];
|
|
|
|
try {
|
|
fs.mkdirSync(artifactDir);
|
|
const metadata = {
|
|
run_id: 707,
|
|
run_attempt: 1,
|
|
base_repo: 'owner/repo',
|
|
pr_number: 42,
|
|
head_sha: 'pr-head-sha',
|
|
head_repo: 'fork/repo',
|
|
built_base_sha: 'base-sha',
|
|
event_base_sha: 'base-sha',
|
|
candidate_sha: 'merge-sha',
|
|
base_sha: 'base-sha',
|
|
};
|
|
fs.mkdirSync(path.join(temporaryDir, 'query-regression-admission'));
|
|
fs.writeFileSync(
|
|
path.join(temporaryDir, 'query-regression-admission', 'query-regression-admission.json'),
|
|
JSON.stringify(metadata),
|
|
);
|
|
fs.writeFileSync(path.join(artifactDir, 'query-regression-pr.json'), JSON.stringify(metadata));
|
|
process.chdir(temporaryDir);
|
|
process.env.WORKFLOW_RUN_ID = '707';
|
|
process.env.WORKFLOW_RUN_ATTEMPT = '1';
|
|
process.env.QUERY_REGRESSION_ADMISSION_HMAC = HMAC_SECRET;
|
|
|
|
await handler({
|
|
core: {
|
|
info(message) { infos.push(message); },
|
|
warning() {},
|
|
setOutput(name, value) { outputs.set(name, value); },
|
|
},
|
|
context: {
|
|
repo: { owner: 'owner', repo: 'repo' },
|
|
payload: {
|
|
workflow_run: {
|
|
event: 'repository_dispatch',
|
|
head_sha: 'default-branch-sha',
|
|
head_repository: { full_name: 'owner/repo' },
|
|
pull_requests: [],
|
|
},
|
|
},
|
|
},
|
|
github: githubApi({
|
|
identity: metadata,
|
|
pull: {
|
|
state: 'open',
|
|
base: { repo: { full_name: 'owner/repo' } },
|
|
head: { repo: null, sha: 'pr-head-sha' },
|
|
},
|
|
}),
|
|
});
|
|
|
|
assert.equal(outputs.get('should_post'), 'false');
|
|
assert.match(infos.join('\n'), /does not match trusted admission/);
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
if (originalRunId === undefined) delete process.env.WORKFLOW_RUN_ID;
|
|
else process.env.WORKFLOW_RUN_ID = originalRunId;
|
|
if (originalRunAttempt === undefined) delete process.env.WORKFLOW_RUN_ATTEMPT;
|
|
else process.env.WORKFLOW_RUN_ATTEMPT = originalRunAttempt;
|
|
if (originalHmac === undefined) delete process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
else process.env.QUERY_REGRESSION_ADMISSION_HMAC = originalHmac;
|
|
fs.rmSync(temporaryDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('signs and verifies admission markers', () => {
|
|
const identity = {
|
|
run_id: 808,
|
|
pr_number: 42,
|
|
head_sha: 'HEADSHA',
|
|
head_repo: 'fork/repo',
|
|
base_repo: 'owner/repo',
|
|
candidate_sha: 'MERGESHA',
|
|
base_sha: 'BASESHA',
|
|
};
|
|
const mac = admissionMac(HMAC_SECRET, identity);
|
|
assert.equal(verifyAdmissionMac(HMAC_SECRET, identity, mac), true);
|
|
assert.equal(verifyAdmissionMac('other', identity, mac), false);
|
|
assert.equal(verifyAdmissionMac(HMAC_SECRET, { ...identity, pr_number: 99 }, mac), false);
|
|
const parsed = parseAdmissionMarker(`noise\n${formatAdmissionMarker({ ...identity, mac })}\n`);
|
|
assert.equal(parsed.pr_number, 42);
|
|
assert.equal(verifyAdmissionMac(HMAC_SECRET, parsed, parsed.mac), true);
|
|
});
|
|
|
|
test('skips when the HMAC secret is unset', async () => {
|
|
const originalCwd = process.cwd();
|
|
const originalRunId = process.env.WORKFLOW_RUN_ID;
|
|
const originalRunAttempt = process.env.WORKFLOW_RUN_ATTEMPT;
|
|
const originalHmac = process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'query-regression-comment-'));
|
|
const artifactDir = path.join(temporaryDir, 'query-regression-comment');
|
|
const outputs = new Map();
|
|
const infos = [];
|
|
|
|
try {
|
|
fs.mkdirSync(artifactDir);
|
|
const metadata = {
|
|
run_id: 808,
|
|
run_attempt: 1,
|
|
base_repo: 'owner/repo',
|
|
pr_number: 42,
|
|
head_sha: 'pr-head-sha',
|
|
head_repo: 'fork/repo',
|
|
candidate_sha: 'merge-sha',
|
|
base_sha: 'base-sha',
|
|
};
|
|
fs.mkdirSync(path.join(temporaryDir, 'query-regression-admission'));
|
|
fs.writeFileSync(
|
|
path.join(temporaryDir, 'query-regression-admission', 'query-regression-admission.json'),
|
|
JSON.stringify(metadata),
|
|
);
|
|
fs.writeFileSync(path.join(artifactDir, 'query-regression-pr.json'), JSON.stringify(metadata));
|
|
process.chdir(temporaryDir);
|
|
process.env.WORKFLOW_RUN_ID = '808';
|
|
process.env.WORKFLOW_RUN_ATTEMPT = '1';
|
|
delete process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
|
|
await handler({
|
|
core: {
|
|
info(message) { infos.push(message); },
|
|
warning() {},
|
|
setOutput(name, value) { outputs.set(name, value); },
|
|
},
|
|
context: {
|
|
repo: { owner: 'owner', repo: 'repo' },
|
|
payload: {
|
|
workflow_run: {
|
|
event: 'repository_dispatch',
|
|
head_sha: 'default-branch-sha',
|
|
head_repository: { full_name: 'owner/repo' },
|
|
pull_requests: [],
|
|
},
|
|
},
|
|
},
|
|
github: githubApi({ identity: metadata }),
|
|
});
|
|
|
|
assert.equal(outputs.get('should_post'), 'false');
|
|
assert.match(infos.join('\n'), /QUERY_REGRESSION_ADMISSION_HMAC is unset/);
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
if (originalRunId === undefined) delete process.env.WORKFLOW_RUN_ID;
|
|
else process.env.WORKFLOW_RUN_ID = originalRunId;
|
|
if (originalRunAttempt === undefined) delete process.env.WORKFLOW_RUN_ATTEMPT;
|
|
else process.env.WORKFLOW_RUN_ATTEMPT = originalRunAttempt;
|
|
if (originalHmac === undefined) delete process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
else process.env.QUERY_REGRESSION_ADMISSION_HMAC = originalHmac;
|
|
fs.rmSync(temporaryDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('skips when the hinted PR has no signed marker for this run', async () => {
|
|
const originalCwd = process.cwd();
|
|
const originalRunId = process.env.WORKFLOW_RUN_ID;
|
|
const originalRunAttempt = process.env.WORKFLOW_RUN_ATTEMPT;
|
|
const originalHmac = process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'query-regression-comment-'));
|
|
const artifactDir = path.join(temporaryDir, 'query-regression-comment');
|
|
const outputs = new Map();
|
|
const infos = [];
|
|
|
|
try {
|
|
fs.mkdirSync(artifactDir);
|
|
const metadata = {
|
|
run_id: 909,
|
|
run_attempt: 1,
|
|
base_repo: 'owner/repo',
|
|
pr_number: 99,
|
|
head_sha: 'pr-head-sha',
|
|
head_repo: 'fork/repo',
|
|
candidate_sha: 'merge-sha',
|
|
base_sha: 'base-sha',
|
|
};
|
|
fs.mkdirSync(path.join(temporaryDir, 'query-regression-admission'));
|
|
fs.writeFileSync(
|
|
path.join(temporaryDir, 'query-regression-admission', 'query-regression-admission.json'),
|
|
JSON.stringify(metadata),
|
|
);
|
|
fs.writeFileSync(path.join(artifactDir, 'query-regression-pr.json'), JSON.stringify(metadata));
|
|
process.chdir(temporaryDir);
|
|
process.env.WORKFLOW_RUN_ID = '909';
|
|
process.env.WORKFLOW_RUN_ATTEMPT = '1';
|
|
process.env.QUERY_REGRESSION_ADMISSION_HMAC = HMAC_SECRET;
|
|
|
|
await handler({
|
|
core: {
|
|
info(message) { infos.push(message); },
|
|
warning() {},
|
|
setOutput(name, value) { outputs.set(name, value); },
|
|
},
|
|
context: {
|
|
repo: { owner: 'owner', repo: 'repo' },
|
|
payload: {
|
|
workflow_run: {
|
|
event: 'repository_dispatch',
|
|
head_sha: 'default-branch-sha',
|
|
head_repository: { full_name: 'owner/repo' },
|
|
pull_requests: [],
|
|
},
|
|
},
|
|
},
|
|
github: githubApi({ identity: metadata, comments: [{ id: 1, body: 'unrelated' }] }),
|
|
});
|
|
|
|
assert.equal(outputs.get('should_post'), 'false');
|
|
assert.match(infos.join('\n'), /No signed admission marker/);
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
if (originalRunId === undefined) delete process.env.WORKFLOW_RUN_ID;
|
|
else process.env.WORKFLOW_RUN_ID = originalRunId;
|
|
if (originalRunAttempt === undefined) delete process.env.WORKFLOW_RUN_ATTEMPT;
|
|
else process.env.WORKFLOW_RUN_ATTEMPT = originalRunAttempt;
|
|
if (originalHmac === undefined) delete process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
else process.env.QUERY_REGRESSION_ADMISSION_HMAC = originalHmac;
|
|
fs.rmSync(temporaryDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('skips a marker whose HMAC does not match', async () => {
|
|
const originalCwd = process.cwd();
|
|
const originalRunId = process.env.WORKFLOW_RUN_ID;
|
|
const originalRunAttempt = process.env.WORKFLOW_RUN_ATTEMPT;
|
|
const originalHmac = process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'query-regression-comment-'));
|
|
const artifactDir = path.join(temporaryDir, 'query-regression-comment');
|
|
const outputs = new Map();
|
|
const infos = [];
|
|
|
|
try {
|
|
fs.mkdirSync(artifactDir);
|
|
const metadata = {
|
|
run_id: 910,
|
|
run_attempt: 1,
|
|
base_repo: 'owner/repo',
|
|
pr_number: 42,
|
|
head_sha: 'pr-head-sha',
|
|
head_repo: 'fork/repo',
|
|
candidate_sha: 'merge-sha',
|
|
base_sha: 'base-sha',
|
|
};
|
|
fs.mkdirSync(path.join(temporaryDir, 'query-regression-admission'));
|
|
fs.writeFileSync(
|
|
path.join(temporaryDir, 'query-regression-admission', 'query-regression-admission.json'),
|
|
JSON.stringify(metadata),
|
|
);
|
|
fs.writeFileSync(path.join(artifactDir, 'query-regression-pr.json'), JSON.stringify(metadata));
|
|
process.chdir(temporaryDir);
|
|
process.env.WORKFLOW_RUN_ID = '910';
|
|
process.env.WORKFLOW_RUN_ATTEMPT = '1';
|
|
process.env.QUERY_REGRESSION_ADMISSION_HMAC = HMAC_SECRET;
|
|
|
|
await handler({
|
|
core: {
|
|
info(message) { infos.push(message); },
|
|
warning() {},
|
|
setOutput(name, value) { outputs.set(name, value); },
|
|
},
|
|
context: {
|
|
repo: { owner: 'owner', repo: 'repo' },
|
|
payload: {
|
|
workflow_run: {
|
|
event: 'repository_dispatch',
|
|
head_sha: 'default-branch-sha',
|
|
head_repository: { full_name: 'owner/repo' },
|
|
pull_requests: [],
|
|
},
|
|
},
|
|
},
|
|
github: githubApi({
|
|
identity: metadata,
|
|
comments: [markerComment(metadata, { secret: 'forged-secret' })],
|
|
}),
|
|
});
|
|
|
|
assert.equal(outputs.get('should_post'), 'false');
|
|
assert.match(infos.join('\n'), /No signed admission marker/);
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
if (originalRunId === undefined) delete process.env.WORKFLOW_RUN_ID;
|
|
else process.env.WORKFLOW_RUN_ID = originalRunId;
|
|
if (originalRunAttempt === undefined) delete process.env.WORKFLOW_RUN_ATTEMPT;
|
|
else process.env.WORKFLOW_RUN_ATTEMPT = originalRunAttempt;
|
|
if (originalHmac === undefined) delete process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
else process.env.QUERY_REGRESSION_ADMISSION_HMAC = originalHmac;
|
|
fs.rmSync(temporaryDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('finds a newly posted marker after a full page of newer comments', async () => {
|
|
const originalCwd = process.cwd();
|
|
const originalRunId = process.env.WORKFLOW_RUN_ID;
|
|
const originalRunAttempt = process.env.WORKFLOW_RUN_ATTEMPT;
|
|
const originalHmac = process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'query-regression-comment-'));
|
|
const artifactDir = path.join(temporaryDir, 'query-regression-comment');
|
|
const outputs = new Map();
|
|
const pages = [];
|
|
|
|
try {
|
|
fs.mkdirSync(artifactDir);
|
|
const metadata = {
|
|
run_id: 911,
|
|
run_attempt: 1,
|
|
base_repo: 'owner/repo',
|
|
pr_number: 42,
|
|
head_sha: 'pr-head-sha',
|
|
head_repo: 'fork/repo',
|
|
candidate_sha: 'merge-sha',
|
|
base_sha: 'base-sha',
|
|
};
|
|
fs.mkdirSync(path.join(temporaryDir, 'query-regression-admission'));
|
|
fs.writeFileSync(
|
|
path.join(temporaryDir, 'query-regression-admission', 'query-regression-admission.json'),
|
|
JSON.stringify(metadata),
|
|
);
|
|
fs.writeFileSync(path.join(artifactDir, 'query-regression-pr.json'), JSON.stringify(metadata));
|
|
process.chdir(temporaryDir);
|
|
process.env.WORKFLOW_RUN_ID = '911';
|
|
process.env.WORKFLOW_RUN_ATTEMPT = '1';
|
|
process.env.QUERY_REGRESSION_ADMISSION_HMAC = HMAC_SECRET;
|
|
|
|
await handler({
|
|
core: {
|
|
info() {},
|
|
warning() {},
|
|
setOutput(name, value) { outputs.set(name, value); },
|
|
},
|
|
context: {
|
|
repo: { owner: 'owner', repo: 'repo' },
|
|
payload: {
|
|
workflow_run: {
|
|
event: 'repository_dispatch',
|
|
head_sha: 'default-branch-sha',
|
|
head_repository: { full_name: 'owner/repo' },
|
|
pull_requests: [],
|
|
},
|
|
},
|
|
},
|
|
github: {
|
|
rest: {
|
|
issues: {
|
|
listComments: async ({ page, direction }) => {
|
|
pages.push({ page, direction });
|
|
if (page === 1) {
|
|
return {
|
|
data: Array.from({ length: 100 }, (_, index) => ({
|
|
id: 10_000 - index,
|
|
body: 'unrelated',
|
|
})),
|
|
};
|
|
}
|
|
return { data: [markerComment(metadata, { id: 50 })] };
|
|
},
|
|
},
|
|
pulls: {
|
|
get: async () => ({
|
|
data: {
|
|
state: 'open',
|
|
base: { repo: { full_name: 'owner/repo' } },
|
|
head: { repo: { full_name: 'fork/repo' }, sha: 'pr-head-sha' },
|
|
},
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
assert.equal(outputs.get('should_post'), 'true');
|
|
assert.deepEqual(pages, [
|
|
{ page: 1, direction: 'desc' },
|
|
{ page: 2, direction: 'desc' },
|
|
]);
|
|
} finally {
|
|
process.chdir(originalCwd);
|
|
if (originalRunId === undefined) delete process.env.WORKFLOW_RUN_ID;
|
|
else process.env.WORKFLOW_RUN_ID = originalRunId;
|
|
if (originalRunAttempt === undefined) delete process.env.WORKFLOW_RUN_ATTEMPT;
|
|
else process.env.WORKFLOW_RUN_ATTEMPT = originalRunAttempt;
|
|
if (originalHmac === undefined) delete process.env.QUERY_REGRESSION_ADMISSION_HMAC;
|
|
else process.env.QUERY_REGRESSION_ADMISSION_HMAC = originalHmac;
|
|
fs.rmSync(temporaryDir, { recursive: true, force: true });
|
|
}
|
|
});
|