237 lines
8.4 KiB
JavaScript
237 lines
8.4 KiB
JavaScript
import { describe, expect, test } from "bun:test";
|
|
import {
|
|
createReleaseTimingReport,
|
|
renderReleaseTimingMarkdown,
|
|
summarizeReleaseAttempts,
|
|
} from "./report-release-timings.mjs";
|
|
|
|
const step = (name, start, seconds) => ({
|
|
name,
|
|
conclusion: "success",
|
|
started_at: start,
|
|
completed_at: new Date(Date.parse(start) + seconds * 1000).toISOString(),
|
|
});
|
|
|
|
const job = (name, start, seconds, steps = []) => ({
|
|
name,
|
|
conclusion: "success",
|
|
started_at: start,
|
|
completed_at: new Date(Date.parse(start) + seconds * 1000).toISOString(),
|
|
html_url: `https://example.test/jobs/${encodeURIComponent(name)}`,
|
|
steps,
|
|
});
|
|
|
|
const payload = (name, start, seconds, jobs) => ({
|
|
run: {
|
|
name,
|
|
conclusion: "success",
|
|
created_at: start,
|
|
run_started_at: start,
|
|
updated_at: new Date(Date.parse(start) + seconds * 1000).toISOString(),
|
|
html_url: `https://example.test/runs/${encodeURIComponent(name)}`,
|
|
},
|
|
jobs,
|
|
});
|
|
|
|
describe("release timing report", () => {
|
|
test("combines Cloudflare, both registries, and rebuilt clients", () => {
|
|
const start = "2026-08-25T00:00:00Z";
|
|
const report = createReleaseTimingReport({
|
|
release: { tag: "v1.41.0", sha: "abc", publishedAt: start },
|
|
desktop: payload("Desktop", start, 600, [
|
|
job("macOS arm64", start, 480, [
|
|
step("Package desktop installer", start, 180),
|
|
]),
|
|
job("macOS x64", start, 600, [
|
|
step("Package desktop installer", start, 300),
|
|
]),
|
|
job("Windows x64 unsigned Preview", start, 420, [
|
|
step("Package unsigned Windows installer", start, 240),
|
|
]),
|
|
job("Linux x64 AppImage Preview", start, 360, [
|
|
step("Package Linux AppImage", start, 120),
|
|
]),
|
|
]),
|
|
desktopMode: "rebuild",
|
|
mobile: payload("Mobile", start, 540, [
|
|
job("Build Android packages", start, 540, [
|
|
step("Build signed release APK for GitHub Release", start, 420),
|
|
]),
|
|
]),
|
|
mobileMode: "rebuild",
|
|
docker: payload("Docker", start, 300, [
|
|
job("Publish official multi-platform image", start, 300, [
|
|
step("Build and publish image", start, 240),
|
|
]),
|
|
]),
|
|
store: payload("Store", start, 660, [
|
|
job("Deliver Google Play", start, 660, [
|
|
step("Build and verify signed Play bundle", start, 180),
|
|
step("Upload bundle to Google Play", start, 240),
|
|
]),
|
|
]),
|
|
cloudflare: payload("Cloudflare", start, 120, [
|
|
job("Build and deploy Demo Worker", start, 120, [
|
|
step("Deploy Demo Worker", start, 60),
|
|
]),
|
|
]),
|
|
tcrSource: payload("TCR", start, 20, [
|
|
job("Trigger asynchronous Tencent-side image build", start, 20),
|
|
]),
|
|
tcrReadyAt: "2026-08-25T00:10:00Z",
|
|
tcrStatus: "success",
|
|
});
|
|
|
|
expect(report.allEndpointsReady).toBe(true);
|
|
expect(report.allEndpointsReadyDurationMs).toBe(600_000);
|
|
expect(report.rows.map((row) => row.target)).toEqual([
|
|
"Cloudflare Demo Worker",
|
|
"Docker GHCR (amd64 + arm64)",
|
|
"Docker TCR (amd64 + arm64)",
|
|
"macOS arm64",
|
|
"macOS x64",
|
|
"Windows x64 Preview",
|
|
"Linux x64 Preview",
|
|
"Android arm64",
|
|
"Google Play signed APK",
|
|
]);
|
|
const markdown = renderReleaseTimingMarkdown(report);
|
|
expect(markdown).toContain(
|
|
"All post-publication endpoints were observed after **10m 00s**",
|
|
);
|
|
expect(markdown).toContain(
|
|
"| Docker TCR (amd64 + arm64) | Tencent-side build | success | 10m 00s |",
|
|
);
|
|
expect(markdown).toContain(
|
|
"| macOS x64 | rebuild | success | 10m 00s | package + notarize 5m 00s |",
|
|
);
|
|
expect(markdown).toContain(
|
|
"| Windows x64 Preview | rebuild | success | 7m 00s | package 4m 00s |",
|
|
);
|
|
expect(markdown).toContain(
|
|
"| Linux x64 Preview | rebuild | success | 6m 00s | package 2m 00s |",
|
|
);
|
|
expect(markdown).toContain(
|
|
"| Google Play signed APK | build + deliver | success | 11m 00s | AAB build 3m 00s; Play upload 4m 00s |",
|
|
);
|
|
});
|
|
|
|
test("reports reused native assets without pretending they were rebuilt", () => {
|
|
const start = "2026-08-25T00:00:00Z";
|
|
const common = {
|
|
release: { tag: "v1.41.1", sha: "def", publishedAt: start },
|
|
docker: payload("Docker", start, 60, [
|
|
job("Publish official multi-platform image", start, 60),
|
|
]),
|
|
cloudflare: payload("Cloudflare", start, 60, [
|
|
job("Build and deploy Demo Worker", start, 60),
|
|
]),
|
|
tcrSource: payload("TCR", start, 15, [
|
|
job("Trigger asynchronous Tencent-side image build", start, 15),
|
|
]),
|
|
tcrReadyAt: "2026-08-25T00:02:00Z",
|
|
tcrStatus: "success",
|
|
};
|
|
const report = createReleaseTimingReport({
|
|
...common,
|
|
desktop: payload("Desktop", start, 40, [
|
|
job("Reuse desktop release assets", start, 40),
|
|
]),
|
|
desktopMode: "reuse",
|
|
mobile: payload("Mobile", start, 30, [
|
|
job("Reuse Android release APK", start, 30),
|
|
]),
|
|
mobileMode: "reuse",
|
|
});
|
|
|
|
expect(report.rows.at(-2)).toMatchObject({
|
|
target: "macOS arm64 + x64 + Windows x64 + Linux x64",
|
|
mode: "reuse",
|
|
durationMs: 40_000,
|
|
});
|
|
expect(report.rows.at(-1)).toMatchObject({
|
|
target: "Android arm64",
|
|
mode: "reuse",
|
|
durationMs: 30_000,
|
|
});
|
|
});
|
|
|
|
test("does not claim all endpoints are ready when TCR times out", () => {
|
|
const start = "2026-08-25T00:00:00Z";
|
|
const report = createReleaseTimingReport({
|
|
release: { tag: "v1.41.2", sha: "ghi", publishedAt: start },
|
|
desktop: payload("Desktop", start, 40, [
|
|
job("Reuse desktop release assets", start, 40),
|
|
]),
|
|
desktopMode: "reuse",
|
|
mobile: payload("Mobile", start, 30, [
|
|
job("Reuse Android release APK", start, 30),
|
|
]),
|
|
mobileMode: "reuse",
|
|
docker: payload("Docker", start, 60, [
|
|
job("Publish official multi-platform image", start, 60),
|
|
]),
|
|
cloudflare: payload("Cloudflare", start, 60, [
|
|
job("Build and deploy Demo Worker", start, 60),
|
|
]),
|
|
tcrSource: payload("TCR", start, 15, [
|
|
job("Trigger asynchronous Tencent-side image build", start, 15),
|
|
]),
|
|
tcrReadyAt: null,
|
|
tcrStatus: "timeout",
|
|
});
|
|
|
|
expect(report.allEndpointsReady).toBe(false);
|
|
expect(report.allEndpointsReadyDurationMs).toBeNull();
|
|
expect(renderReleaseTimingMarkdown(report)).toContain(
|
|
"At least one post-publication endpoint failed or was not observed",
|
|
);
|
|
});
|
|
|
|
test("reports every Draft attempt and distinguishes Play APK recovery", () => {
|
|
const start = "2026-08-25T00:00:00Z";
|
|
const attempts = [
|
|
payload("Desktop failed", start, 420, []),
|
|
payload("Desktop retry", "2026-08-25T00:07:30Z", 360, []),
|
|
];
|
|
Object.assign(attempts[0].run, { id: 1, head_sha: "old", conclusion: "failure" });
|
|
Object.assign(attempts[1].run, { id: 2, head_sha: "new", conclusion: "success" });
|
|
expect(summarizeReleaseAttempts(attempts)).toEqual({
|
|
workflowRunCount: 2,
|
|
releaseTargetCount: 2,
|
|
failedRunCount: 1,
|
|
endToEndDurationMs: 810_000,
|
|
cumulativeWorkflowDurationMs: 780_000,
|
|
});
|
|
|
|
const common = {
|
|
release: { tag: "v1.41.3", sha: "new", publishedAt: start },
|
|
desktop: payload("Desktop", start, 40, [job("Plan desktop release asset", start, 40)]),
|
|
desktopMode: "rebuild",
|
|
mobile: payload("Mobile", start, 30, [job("Plan Android release asset", start, 30)]),
|
|
mobileMode: "rebuild",
|
|
docker: payload("Docker", start, 60, [job("Publish official multi-platform image", start, 60)]),
|
|
store: payload("Store recovery", start, 120, [
|
|
job("Deliver Google Play", start, 120, [
|
|
step("Download Play-signed universal APK", start, 45),
|
|
]),
|
|
]),
|
|
storeMode: "recover",
|
|
attempts,
|
|
cloudflare: payload("Cloudflare", start, 60, [job("Build and deploy Demo Worker", start, 60)]),
|
|
tcrSource: payload("TCR", start, 15, [job("Trigger asynchronous Tencent-side image build", start, 15)]),
|
|
tcrReadyAt: "2026-08-25T00:02:00Z",
|
|
tcrStatus: "success",
|
|
};
|
|
const report = createReleaseTimingReport(common);
|
|
expect(report.rows.at(-1)).toMatchObject({
|
|
target: "Google Play signed APK",
|
|
mode: "recover",
|
|
detail: "recovered existing Play-signed APK 45s",
|
|
});
|
|
const markdown = renderReleaseTimingMarkdown(report);
|
|
expect(markdown).toContain("across **2 workflow runs** and **2 release target(s)**");
|
|
expect(markdown).toContain("1 run(s) failed or were cancelled");
|
|
});
|
|
});
|