返回 DeepSeek-Reasonix
stale-report-sweep.test.mjs
根目录 / scripts / stale-report-sweep.test.mjs
1 import assert from "node:assert/strict";
2 import { test } from "node:test";
3 import {
4 ASK_MARKER,
5 CUTOFF_MINOR,
6 WINDOW_DAYS,
7 highestMentionedVersion,
8 isStaleVersion,
9 parseReportedVersion,
10 releasedVersions,
11 renderAsk,
12 renderClose,
13 shouldAsk,
14 shouldClose,
15 } from "./stale-report-sweep.mjs";
16
17 const NOW = "2026-07-30T00:00:00Z";
18 const daysBefore = (n) => new Date(Date.parse(NOW) - n * 86400000).toISOString();
19 const ask = (createdAt) => ({ body: `${ASK_MARKER}\nstill reproducing?`, createdAt, authorAssociation: "OWNER" });
20
21 test("the reported version comes from the form field, not from prose", () => {
22 const body = "### Version line\n\nv2\n\n### Exact version\n\n1.8.1\n\n### What happened?\n\nsaw 1.17.9 mentioned in a log";
23 assert.deepEqual(parseReportedVersion(body), { major: 1, minor: 8, patch: 1, raw: "1.8.1" });
24 assert.equal(parseReportedVersion("### What happened?\n\n1.2.3"), null);
25 });
26
27 test("staleness is a version cutoff, and an unparsable version is never stale", () => {
28 assert.equal(isStaleVersion(parseReportedVersion("### Exact version\n\n1.8.1")), true);
29 assert.equal(isStaleVersion(parseReportedVersion("### Exact version\n\n1.17.21")), false);
30 assert.equal(isStaleVersion(parseReportedVersion("### Exact version\n\n1.10.0")), false);
31 assert.equal(isStaleVersion(null), false);
32 });
33
34 test("a version that was never released is a typo, not an old release", () => {
35 const released = releasedVersions(["desktop-v1.7.0", "desktop-v1.17.18", "desktop-v1.18.0", "npm-v1.17.18"]);
36 const typo = parseReportedVersion("### Exact version\n\n1.7.18"); // dropped digit from 1.17.18
37 const real = parseReportedVersion("### Exact version\n\n1.7.0");
38 assert.equal(isStaleVersion(typo, CUTOFF_MINOR, released), false);
39 assert.equal(isStaleVersion(real, CUTOFF_MINOR, released), true);
40 // without the tag list there is nothing to catch it, which is why main passes one
41 assert.equal(isStaleVersion(typo), true);
42 });
43
44 test("a version named elsewhere in the body outranks a wrong form field", () => {
45 const released = releasedVersions(["desktop-v1.0.0", "desktop-v1.17.13", "desktop-v1.18.0"]);
46 // real shape: the form field kept its default while the true build is in prose
47 const body = "### Exact version\n\n1.0.0\n\n### Steps to reproduce\n\nwin64 vscode, 软件版本 v1.17.13";
48 assert.equal(parseReportedVersion(body).raw, "1.0.0");
49 assert.equal(highestMentionedVersion(body, released).raw, "1.17.13");
50 assert.equal(isStaleVersion(highestMentionedVersion(body, released), CUTOFF_MINOR, released), false);
51 });
52
53 test("unreleased numbers in the body are ignored, so Node versions cannot age a report", () => {
54 const released = releasedVersions(["desktop-v1.8.1", "desktop-v1.18.0"]);
55 const body = "### Exact version\n\n1.8.1\n\n**Node**: v26.3.1\n**Terminal size**: 65.0.0";
56 assert.equal(highestMentionedVersion(body, released).raw, "1.8.1");
57 assert.equal(highestMentionedVersion("no versions here", released), null);
58 });
59
60 test("released versions are collected across every tag series", () => {
61 const released = releasedVersions(["v1.18.0", "desktop-v1.17.21", "npm-v1.17.21", "not-a-tag", "desktop-v1.7.0"]);
62 assert.deepEqual([...released].sort(), ["1.17.21", "1.18.0", "1.7.0"]);
63 });
64
65 test("only defect reports are swept — a feature request cannot answer the question", () => {
66 const base = { version: { major: 1, minor: 2, patch: 0, raw: "1.2.0" }, comments: [] };
67 assert.equal(shouldAsk({ ...base, labels: ["enhancement", "desktop"] }), false);
68 assert.equal(shouldAsk({ ...base, labels: [] }), false);
69 assert.equal(shouldAsk({ ...base, labels: ["bug", "enhancement"] }), true);
70 });
71
72 test("severity labels are never swept, however old the report", () => {
73 const base = { version: { major: 1, minor: 2, patch: 0, raw: "1.2.0" }, comments: [] };
74 for (const label of ["data-loss", "security", "crash"]) {
75 assert.equal(shouldAsk({ ...base, labels: ["bug", label] }), false, label);
76 }
77 assert.equal(shouldAsk({ ...base, labels: ["bug", "windows"] }), true);
78 });
79
80 test("a maintainer reply takes the report out of the sweep", () => {
81 const base = { version: { major: 1, minor: 2, patch: 0, raw: "1.2.0" }, labels: ["bug"] };
82 const reporter = { body: "me too", createdAt: daysBefore(40), authorAssociation: "NONE" };
83 const maintainer = { body: "looking", createdAt: daysBefore(40), authorAssociation: "COLLABORATOR" };
84 assert.equal(shouldAsk({ ...base, comments: [reporter] }), true);
85 assert.equal(shouldAsk({ ...base, comments: [maintainer] }), false);
86 });
87
88 test("the same issue is never asked twice", () => {
89 const issue = {
90 version: { major: 1, minor: 2, patch: 0, raw: "1.2.0" },
91 labels: ["bug"],
92 comments: [ask(daysBefore(3))],
93 };
94 assert.equal(shouldAsk(issue), false);
95 });
96
97 test("closing requires an ask that nobody answered and a window that elapsed", () => {
98 const labels = ["bug"];
99 assert.equal(shouldClose({ labels, comments: [] }, { now: NOW }), false, "never asked");
100 assert.equal(
101 shouldClose({ labels, comments: [ask(daysBefore(WINDOW_DAYS - 1))] }, { now: NOW }),
102 false,
103 "window not elapsed",
104 );
105 assert.equal(shouldClose({ labels, comments: [ask(daysBefore(WINDOW_DAYS))] }, { now: NOW }), true);
106 });
107
108 test("any reply after the ask cancels the close, whoever wrote it", () => {
109 const labels = ["bug"];
110 const asked = ask(daysBefore(60));
111 for (const association of ["NONE", "CONTRIBUTOR", "COLLABORATOR"]) {
112 const reply = { body: "still broken", createdAt: daysBefore(1), authorAssociation: association };
113 assert.equal(shouldClose({ labels, comments: [asked, reply] }, { now: NOW }), false, association);
114 }
115 // a comment predating the ask is not an answer to it
116 const older = { body: "me too", createdAt: daysBefore(90), authorAssociation: "NONE" };
117 assert.equal(shouldClose({ labels, comments: [older, asked] }, { now: NOW }), true);
118 });
119
120 test("a severity label added after the ask still blocks the close", () => {
121 const comments = [ask(daysBefore(60))];
122 assert.equal(shouldClose({ labels: ["bug"], comments }, { now: NOW }), true);
123 assert.equal(shouldClose({ labels: ["bug", "data-loss"], comments }, { now: NOW }), false);
124 });
125
126 test("the ask states both versions and the deadline; the close invites reopening", () => {
127 const body = renderAsk({ version: "1.8.1", current: "desktop-v1.18.0" });
128 assert.ok(body.startsWith(ASK_MARKER));
129 assert.match(body, /1\.8\.1/);
130 assert.match(body, /desktop-v1\.18\.0/);
131 assert.match(body, new RegExp(`${WINDOW_DAYS} days`));
132 const closed = renderClose({});
133 assert.match(closed, /reopens/);
134 assert.match(closed, new RegExp(`${WINDOW_DAYS} days`));
135 // stale is a bookkeeping state; claiming a fix we never verified would be a lie
136 assert.doesNotMatch(closed, /\bfixed\b|\bresolved\b/i);
137 });
138
138 lines Plain Text