Pulse/.github/scripts/issue-version-triage.test.cjs
courtmanr@gmail.com acfe84d9cf Fix issue version parser dropping capitalised V prefixes
normalizeVersion matched /\bv?(\d+\.\d+\.\d+...)/ without the i flag, so a
reported version written as "V6.0.4" never parsed.

The heading regexes in extractPulseVersion already carry the i flag and did
capture "V6.0.4" correctly, but they hand the captured string to
normalizeVersion, which then returned null. That collapsed every extraction
path -- inline scan, heading match, legacy match, and title fallback -- so the
issue was labelled needs-version-info despite stating its version.

Adding the i flag to normalizeVersion fixes all four paths at once. It is a
no-op on the rest of the pattern, since [0-9A-Za-z.-] already spans both cases.

Seen on #1538, which stated "V6.0.4" under the "Pulse version" heading and was
wrongly flagged as missing version metadata.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 12:49:24 +01:00

209 lines
5.6 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 = [],
} = {}) {
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 };
},
listComments: Symbol("listComments"),
},
repos: {
async getLatestRelease() {
calls.getLatestRelease.push(true);
return { data: { tag_name: `v${latestVersion}` } };
},
},
},
async paginate() {
calls.paginate.push(true);
return 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 -->/
);
});
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"
);
});