mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
293 lines
8.3 KiB
JavaScript
293 lines
8.3 KiB
JavaScript
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
|
|
const triage = require("./issue-version-triage.cjs");
|
|
|
|
function createGithub({
|
|
latestVersion = "6.0.1",
|
|
existingLabels = new Set(),
|
|
existingComments = [],
|
|
issues = [],
|
|
} = {}) {
|
|
const calls = {
|
|
createComment: [],
|
|
createLabel: [],
|
|
getLabel: [],
|
|
getLatestRelease: [],
|
|
paginate: [],
|
|
setLabels: [],
|
|
};
|
|
|
|
const github = {
|
|
rest: {
|
|
issues: {
|
|
async getLabel({ name }) {
|
|
calls.getLabel.push(name);
|
|
if (existingLabels.has(name)) {
|
|
return { data: { name } };
|
|
}
|
|
const error = new Error("Not Found");
|
|
error.status = 404;
|
|
throw error;
|
|
},
|
|
async createLabel(payload) {
|
|
calls.createLabel.push(payload);
|
|
existingLabels.add(payload.name);
|
|
return { data: payload };
|
|
},
|
|
async setLabels(payload) {
|
|
calls.setLabels.push(payload);
|
|
return { data: payload };
|
|
},
|
|
async createComment(payload) {
|
|
calls.createComment.push(payload);
|
|
return { data: payload };
|
|
},
|
|
listForRepo: Symbol("listForRepo"),
|
|
listComments: Symbol("listComments"),
|
|
},
|
|
repos: {
|
|
async getLatestRelease() {
|
|
calls.getLatestRelease.push(true);
|
|
return { data: { tag_name: `v${latestVersion}` } };
|
|
},
|
|
},
|
|
},
|
|
async paginate(endpoint) {
|
|
calls.paginate.push(endpoint);
|
|
return endpoint === github.rest.issues.listForRepo ? issues : existingComments;
|
|
},
|
|
};
|
|
|
|
return { github, calls };
|
|
}
|
|
|
|
function createContext({ action = "opened", issue }) {
|
|
return {
|
|
payload: {
|
|
action,
|
|
issue,
|
|
},
|
|
repo: {
|
|
owner: "rcourtman",
|
|
repo: "Pulse",
|
|
},
|
|
};
|
|
}
|
|
|
|
function createCore() {
|
|
return {
|
|
info() {},
|
|
warning() {},
|
|
};
|
|
}
|
|
|
|
test("syncLabels adds affects and retest labels for older bug reports", async () => {
|
|
const { github, calls } = createGithub({ latestVersion: "6.0.1" });
|
|
const issue = {
|
|
number: 1402,
|
|
title: "Standalone hosts disappear after upgrade",
|
|
body: "## Feedback type\nBug / regression\n\n## Pulse version\n6.0.0-rc.1\n",
|
|
labels: [],
|
|
};
|
|
|
|
await triage.syncLabels({
|
|
github,
|
|
context: createContext({ issue }),
|
|
core: createCore(),
|
|
});
|
|
|
|
assert.equal(calls.setLabels.length, 1);
|
|
assert.deepEqual(calls.setLabels[0].labels, [
|
|
"affects-6.0.0-rc.1",
|
|
"bug",
|
|
"needs-retest-on-latest",
|
|
]);
|
|
});
|
|
|
|
test("syncLabels only adds documentation classification for non-bug v6 feedback", async () => {
|
|
const { github, calls } = createGithub({ latestVersion: "6.0.1" });
|
|
const issue = {
|
|
number: 1415,
|
|
title: "Docs path is wrong",
|
|
body: "## Feedback type\nDocumentation issue\n\n## Pulse version\n6.0.0-rc.1\n",
|
|
labels: [],
|
|
};
|
|
|
|
await triage.syncLabels({
|
|
github,
|
|
context: createContext({ issue }),
|
|
core: createCore(),
|
|
});
|
|
|
|
assert.equal(calls.setLabels.length, 1);
|
|
assert.deepEqual(calls.setLabels[0].labels, ["documentation"]);
|
|
});
|
|
|
|
test("postRetestComment comments once for older non-maintainer bug reports", async () => {
|
|
const { github, calls } = createGithub({ latestVersion: "6.0.1" });
|
|
const issue = {
|
|
number: 1200,
|
|
title: "Upgrade regression",
|
|
body: "## Feedback type\nRegression\n\n## Pulse version\n5.1.9\n",
|
|
labels: [],
|
|
author_association: "NONE",
|
|
};
|
|
|
|
await triage.postRetestComment({
|
|
github,
|
|
context: createContext({ action: "opened", issue }),
|
|
core: createCore(),
|
|
});
|
|
|
|
assert.equal(calls.createComment.length, 1);
|
|
assert.match(
|
|
calls.createComment[0].body,
|
|
/<!-- issue-version-triage:v1 -->/
|
|
);
|
|
assert.ok(
|
|
calls.createComment[0].body.endsWith(`\n\n${triage.internals.TRIAGE_FOOTER}`)
|
|
);
|
|
assert.equal(
|
|
calls.createComment[0].body.split(triage.internals.TRIAGE_FOOTER).length - 1,
|
|
1
|
|
);
|
|
});
|
|
|
|
test("scheduled retest guidance waits five minutes and reads the current issue", async () => {
|
|
const nowMs = Date.parse("2026-08-26T09:06:00Z");
|
|
const issues = [
|
|
{
|
|
number: 1780,
|
|
title: "Agent token scope",
|
|
body: "## Feedback type\nBug / regression\n\n## Pulse version\n6.3.2\n",
|
|
labels: [{ name: "bug" }],
|
|
author_association: "NONE",
|
|
created_at: "2026-08-26T09:01:51Z",
|
|
},
|
|
];
|
|
const { github, calls } = createGithub({ latestVersion: "6.3.2", issues });
|
|
|
|
const result = await triage.postEligibleRetestComments({
|
|
github,
|
|
context: createContext({ issue: null }),
|
|
core: createCore(),
|
|
nowMs,
|
|
});
|
|
|
|
assert.deepEqual(result, { eligibleCount: 0, postedCount: 0 });
|
|
assert.equal(calls.createComment.length, 0);
|
|
|
|
const laterResult = await triage.postEligibleRetestComments({
|
|
github,
|
|
context: createContext({ issue: null }),
|
|
core: createCore(),
|
|
nowMs: Date.parse("2026-08-26T09:07:00Z"),
|
|
});
|
|
|
|
assert.deepEqual(laterResult, { eligibleCount: 1, postedCount: 0 });
|
|
assert.equal(calls.createComment.length, 0);
|
|
});
|
|
|
|
test("scheduled retest guidance posts once after the grace window", async () => {
|
|
const issues = [
|
|
{
|
|
number: 1200,
|
|
title: "Upgrade regression",
|
|
body: "## Feedback type\nRegression\n\n## Pulse version\n5.1.9\n",
|
|
labels: [{ name: "bug" }],
|
|
author_association: "NONE",
|
|
created_at: "2026-08-26T08:55:00Z",
|
|
},
|
|
];
|
|
const { github, calls } = createGithub({ latestVersion: "6.3.2", issues });
|
|
|
|
const result = await triage.postEligibleRetestComments({
|
|
github,
|
|
context: createContext({ issue: null }),
|
|
core: createCore(),
|
|
nowMs: Date.parse("2026-08-26T09:01:00Z"),
|
|
});
|
|
|
|
assert.deepEqual(result, { eligibleCount: 1, postedCount: 1 });
|
|
assert.equal(calls.createComment.length, 1);
|
|
assert.equal(calls.createComment[0].issue_number, 1200);
|
|
assert.ok(
|
|
calls.createComment[0].body.endsWith(`\n\n${triage.internals.TRIAGE_FOOTER}`)
|
|
);
|
|
});
|
|
|
|
test("timeout close comments use the canonical triage footer", () => {
|
|
const { buildTimeoutCloseCommentBody, CLOSE_COMMENT_MARKER, TRIAGE_FOOTER } =
|
|
triage.internals;
|
|
const body = buildTimeoutCloseCommentBody(7);
|
|
|
|
assert.ok(body.startsWith(CLOSE_COMMENT_MARKER));
|
|
assert.ok(body.endsWith(`\n\n${TRIAGE_FOOTER}`));
|
|
assert.equal(body.split(TRIAGE_FOOTER).length - 1, 1);
|
|
assert.doesNotMatch(body, /automated maintainer|supervised by|AI\)/i);
|
|
});
|
|
|
|
test("postRetestComment skips reopened issues", async () => {
|
|
const { github, calls } = createGithub({ latestVersion: "6.0.1" });
|
|
const issue = {
|
|
number: 1471,
|
|
title: "Disk temperature at 0°C",
|
|
body: "## Feedback type\nBug / regression\n\n## Pulse version\n5.1.31\n",
|
|
labels: [],
|
|
author_association: "NONE",
|
|
};
|
|
|
|
await triage.postRetestComment({
|
|
github,
|
|
context: createContext({ action: "reopened", issue }),
|
|
core: createCore(),
|
|
});
|
|
|
|
assert.equal(calls.createComment.length, 0);
|
|
});
|
|
|
|
test("postRetestComment skips maintainer-authored issues", async () => {
|
|
const { github, calls } = createGithub({ latestVersion: "6.0.1" });
|
|
const issue = {
|
|
number: 1300,
|
|
title: "Maintainer split issue on 5.1.9",
|
|
body: "## Feedback type\nBug / regression\n\n## Pulse version\n5.1.9\n",
|
|
labels: [],
|
|
author_association: "OWNER",
|
|
};
|
|
|
|
await triage.postRetestComment({
|
|
github,
|
|
context: createContext({ action: "opened", issue }),
|
|
core: createCore(),
|
|
});
|
|
|
|
assert.equal(calls.createComment.length, 0);
|
|
});
|
|
|
|
test("normalizeVersion accepts a capitalised V prefix", () => {
|
|
const { normalizeVersion } = triage.internals;
|
|
|
|
// Regression: issue #1538 reported "V6.0.4" under the "Pulse version"
|
|
// heading and was wrongly labelled needs-version-info. The heading regexes
|
|
// are case-insensitive and captured "V6.0.4" correctly, but handed it to a
|
|
// case-sensitive normalizeVersion, so every extraction path returned null.
|
|
assert.equal(normalizeVersion("V6.0.4"), "6.0.4");
|
|
assert.equal(normalizeVersion("v6.0.4"), "6.0.4");
|
|
assert.equal(normalizeVersion("6.0.4"), "6.0.4");
|
|
assert.equal(normalizeVersion("V6.1.0-rc.4"), "6.1.0-rc.4");
|
|
|
|
assert.equal(normalizeVersion("V6"), null);
|
|
assert.equal(normalizeVersion("### Pulse version"), null);
|
|
});
|
|
|
|
test("extractPulseVersion reads a capitalised version under its heading", () => {
|
|
const { extractPulseVersion } = triage.internals;
|
|
|
|
assert.equal(
|
|
extractPulseVersion("[Bug]: something broke", "### Pulse version\nV6.0.4\n"),
|
|
"6.0.4"
|
|
);
|
|
});
|