mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
183 lines
8.3 KiB
TypeScript
183 lines
8.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|||
|
|
|
||
|
|
import {
|
||
|
|
cardLaneSilence,
|
||
|
|
renderFallbackNotice,
|
||
|
|
resolvePackagePublication,
|
||
|
|
versionMetadataUrl,
|
||
|
|
} from "../src/notifications/prerelease-fallback.ts";
|
||
|
|
import type { FallbackNoticeInput } from "../src/notifications/prerelease-fallback.ts";
|
||
|
|
|
||
|
|
const ORIGIN_RUN = "https://github.com/nexu-io/open-design/actions/runs/111";
|
||
|
|
const CARD_RUN = "https://github.com/nexu-io/open-design/actions/runs/222";
|
||
|
|
const METADATA = "https://releases.example/prerelease/versions/0.21.1-prerelease.3/metadata.json";
|
||
|
|
|
||
|
|
function notice(overrides: Partial<FallbackNoticeInput> = {}): FallbackNoticeInput {
|
||
|
|
return {
|
||
|
|
silence: "card-not-delivered",
|
||
|
|
channelLabel: "Prerelease",
|
||
|
|
version: "0.21.1-prerelease.3",
|
||
|
|
branch: "release/v0.21.1",
|
||
|
|
commit: "0123456789abcdef0123456789abcdef01234567",
|
||
|
|
publication: "published",
|
||
|
|
metadataUrl: METADATA,
|
||
|
|
pipeline: "finished",
|
||
|
|
originRunUrl: ORIGIN_RUN,
|
||
|
|
cardRunUrl: CARD_RUN,
|
||
|
|
laneSummary: "card job=success · delivered=false",
|
||
|
|
...overrides,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("cardLaneSilence", () => {
|
||
|
|
it("stays quiet when the watcher finished and the card reached the chat", () => {
|
||
|
|
// The whole point of the fallback: on a healthy prerelease it must post
|
||
|
|
// nothing, or the channel gets two messages per release and the alert
|
||
|
|
// stops meaning anything.
|
||
|
|
expect(cardLaneSilence({ stage: "watch", jobResult: "success", deliveredFinalCard: true })).toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("stays quiet when the card workflow was dispatched", () => {
|
||
|
|
expect(cardLaneSilence({ stage: "dispatch", dispatched: true })).toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("alerts when the watcher job finished but no card was ever delivered", () => {
|
||
|
|
// The headline silent path: the application bot's credentials expire or it
|
||
|
|
// is removed from the chat, every send/patch throws, the watcher swallows
|
||
|
|
// the delivery failure by design, and the job still exits 0. A fallback
|
||
|
|
// keyed only on the job result would see green here.
|
||
|
|
expect(cardLaneSilence({ stage: "watch", jobResult: "success", deliveredFinalCard: false })).toBe(
|
||
|
|
"card-not-delivered",
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("alerts when the watcher job did not complete successfully", () => {
|
||
|
|
for (const jobResult of ["failure", "cancelled", "skipped", ""]) {
|
||
|
|
expect(cardLaneSilence({ stage: "watch", jobResult, deliveredFinalCard: false })).toBe(
|
||
|
|
"watcher-not-completed",
|
||
|
|
);
|
||
|
|
// A job that died carries no trustworthy delivery claim either way: the
|
||
|
|
// card is at best frozen mid-release, so it is still an alert.
|
||
|
|
expect(cardLaneSilence({ stage: "watch", jobResult, deliveredFinalCard: true })).toBe(
|
||
|
|
"watcher-not-completed",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it("alerts when the card workflow was never dispatched", () => {
|
||
|
|
expect(cardLaneSilence({ stage: "dispatch", dispatched: false })).toBe("never-dispatched");
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("versionMetadataUrl", () => {
|
||
|
|
it("reproduces the key publish-metadata writes the version metadata to", () => {
|
||
|
|
expect(versionMetadataUrl("https://releases.example", "prerelease", "0.21.1-prerelease.3")).toBe(METADATA);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("tolerates a trailing slash on the public origin", () => {
|
||
|
|
expect(versionMetadataUrl("https://releases.example/", "prerelease", "0.21.1-prerelease.3")).toBe(METADATA);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("returns nothing when the origin or the version is unknown", () => {
|
||
|
|
expect(versionMetadataUrl("", "prerelease", "0.21.1-prerelease.3")).toBe("");
|
||
|
|
expect(versionMetadataUrl("https://releases.example", "prerelease", "")).toBe("");
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("resolvePackagePublication", () => {
|
||
|
|
it("trusts a metadata URL the caller already holds", () => {
|
||
|
|
// release-prerelease.yml's publish job hands over the real output; there is
|
||
|
|
// nothing more authoritative to probe for.
|
||
|
|
expect(resolvePackagePublication({ declaredMetadataUrl: METADATA, probe: "skipped" })).toBe("published");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("reads the probe when the caller holds no URL", () => {
|
||
|
|
expect(resolvePackagePublication({ declaredMetadataUrl: "", probe: "found" })).toBe("published");
|
||
|
|
expect(resolvePackagePublication({ declaredMetadataUrl: "", probe: "missing" })).toBe("absent");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("never claims a verdict it could not check", () => {
|
||
|
|
expect(resolvePackagePublication({ declaredMetadataUrl: "", probe: "error" })).toBe("unknown");
|
||
|
|
expect(resolvePackagePublication({ declaredMetadataUrl: "", probe: "skipped" })).toBe("unknown");
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("renderFallbackNotice", () => {
|
||
|
|
it("names the version and both runs so the reader needs nothing else", () => {
|
||
|
|
const rendered = renderFallbackNotice(notice());
|
||
|
|
expect(rendered.title).toContain("0.21.1-prerelease.3");
|
||
|
|
expect(rendered.body).toContain("0.21.1-prerelease.3");
|
||
|
|
expect(rendered.body).toContain("release/v0.21.1");
|
||
|
|
expect(rendered.body).toContain(ORIGIN_RUN);
|
||
|
|
expect(rendered.body).toContain(CARD_RUN);
|
||
|
|
expect(rendered.runUrl).toBe(ORIGIN_RUN);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("reports a published package with its version metadata URL", () => {
|
||
|
|
const rendered = renderFallbackNotice(notice({ publication: "published" }));
|
||
|
|
expect(rendered.body).toContain("已发布");
|
||
|
|
expect(rendered.body).toContain(METADATA);
|
||
|
|
// The packages shipped; only the notification broke. That is not the same
|
||
|
|
// incident as "nothing was released", and the colour has to say so.
|
||
|
|
expect(rendered.template).toBe("orange");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("separates a package that never shipped from one that has not shipped yet", () => {
|
||
|
|
const finished = renderFallbackNotice(notice({ publication: "absent", pipeline: "finished", metadataUrl: "" }));
|
||
|
|
expect(finished.body).toContain("未发布");
|
||
|
|
expect(finished.body).toContain("已结束");
|
||
|
|
expect(finished.template).toBe("red");
|
||
|
|
|
||
|
|
const running = renderFallbackNotice(notice({ publication: "absent", pipeline: "running", metadataUrl: "" }));
|
||
|
|
expect(running.body).toContain("尚未发布");
|
||
|
|
expect(running.body).toContain("仍在进行");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("does not claim the pipeline finished when it could not find out", () => {
|
||
|
|
// Absent metadata plus an unknown pipeline state: the object is not there,
|
||
|
|
// but nothing licenses "已结束" or "仍在进行".
|
||
|
|
const rendered = renderFallbackNotice(notice({ publication: "absent", pipeline: "unknown" }));
|
||
|
|
expect(rendered.body).toContain("未发布");
|
||
|
|
expect(rendered.body).not.toContain("已结束");
|
||
|
|
expect(rendered.body).not.toContain("仍在进行");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("names where the version metadata will appear when nothing has shipped yet", () => {
|
||
|
|
// The dispatch-side alert fires beside the build, so "not published" is the
|
||
|
|
// expected reading. Handing over the object that would prove publication
|
||
|
|
// turns "go look" into one clickable check.
|
||
|
|
const rendered = renderFallbackNotice(notice({ publication: "absent", pipeline: "running" }));
|
||
|
|
expect(rendered.body).toContain(METADATA);
|
||
|
|
expect(rendered.body).not.toContain("已发布");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("says so plainly when it could not establish whether a package shipped", () => {
|
||
|
|
const rendered = renderFallbackNotice(notice({ publication: "unknown", metadataUrl: "" }));
|
||
|
|
expect(rendered.body).toContain("未知");
|
||
|
|
expect(rendered.template).toBe("red");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("explains which way the card lane went silent", () => {
|
||
|
|
expect(renderFallbackNotice(notice({ silence: "never-dispatched" })).body).toContain("从未");
|
||
|
|
expect(renderFallbackNotice(notice({ silence: "watcher-not-completed" })).body).toContain("未正常结束");
|
||
|
|
expect(renderFallbackNotice(notice({ silence: "card-not-delivered" })).body).toContain("凭证");
|
||
|
|
// The raw per-job results travel with the notice, so the reader can tell a
|
||
|
|
// dispatch that never fired from a watcher that died.
|
||
|
|
expect(renderFallbackNotice(notice()).body).toContain("card job=success · delivered=false");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("stays readable when the pipeline died before a version existed", () => {
|
||
|
|
const rendered = renderFallbackNotice(
|
||
|
|
notice({ version: "", branch: "", commit: "", metadataUrl: "", publication: "absent", pipeline: "finished" }),
|
||
|
|
);
|
||
|
|
expect(rendered.title).not.toContain("undefined");
|
||
|
|
expect(rendered.body).not.toContain("undefined");
|
||
|
|
expect(rendered.body).toContain("未确定");
|
||
|
|
expect(rendered.runUrl).toBe(ORIGIN_RUN);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("falls back to the card run link when the origin run is unknown", () => {
|
||
|
|
expect(renderFallbackNotice(notice({ originRunUrl: "" })).runUrl).toBe(CARD_RUN);
|
||
|
|
});
|
||
|
|
});
|