Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion frontend/lib/microcosm/build-runs.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { describe, expect, test } from "bun:test";
import { describe, expect, spyOn, test } from "bun:test";

import {
type BuildRunDocuments,
buildTimeline,
} from "@/lib/microcosm/build-monitor";
import {
type HostedRunLoaders,
loadBuildRuns,
refreshHostedRun,
} from "@/lib/microcosm/build-runs";
import {
FAKE_COLLECTOR_URL,
fakeTelemetryCollector,
} from "@/lib/microcosm/test-support/fake-telemetry-collector";
import { secretManager } from "@/lib/server/secret-manager";

const NOW = Date.parse("2026-10-05T12:00:00Z");

Expand Down Expand Up @@ -174,3 +180,50 @@ describe("refreshHostedRun", () => {
).rejects.toThrow("collector unavailable");
});
});

describe("loadBuildRuns from the collector", () => {
test("the build monitor reads only its 60 most recent collector runs", async () => {
// The zz fixture country has no staging repository, so the collector is
// the only hosted source and no Hugging Face request is made.
const collector = fakeTelemetryCollector({
countryCode: "ZZ",
runCount: 250,
});
const originalFetch = globalThis.fetch;
const originalEnvironment = {
MICROCOSM_TELEMETRY_COLLECTOR_URL:
process.env.MICROCOSM_TELEMETRY_COLLECTOR_URL,
MICROCOSM_TELEMETRY_READ_SECRET_VERSION:
process.env.MICROCOSM_TELEMETRY_READ_SECRET_VERSION,
};
const readSecret = spyOn(secretManager, "readVersion").mockResolvedValue(
"secret-read-token",
);
process.env.MICROCOSM_TELEMETRY_COLLECTOR_URL = FAKE_COLLECTOR_URL;
process.env.MICROCOSM_TELEMETRY_READ_SECRET_VERSION =
"projects/test-project/secrets/collector-read/versions/latest";
globalThis.fetch = collector.fetch;
try {
const response = await loadBuildRuns("staging", "zz");

const newestRunIds = collector.runs
.slice(0, 60)
.map((run) => run.run_id);
expect(response.available).toBe(true);
expect(response.problems).toEqual([]);
expect(collector.listRequests).toHaveLength(1);
expect(collector.listRequests[0].searchParams.get("limit")).toBe("60");
expect(collector.documentRequests.toSorted()).toEqual(
newestRunIds.toSorted(),
);
expect(response.runs.map((run) => run.run_id)).toEqual(newestRunIds);
} finally {
globalThis.fetch = originalFetch;
readSecret.mockRestore();
for (const [name, value] of Object.entries(originalEnvironment)) {
if (value == null) delete process.env[name];
else process.env[name] = value;
}
}
});
});
4 changes: 2 additions & 2 deletions frontend/lib/microcosm/build-runs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,11 @@ async function loadHuggingFaceStaging(
}

async function loadCollector(country: MicrocosmCountry): Promise<LoadedRuns> {
const summaries = await loadCollectorRuns(country);
const summaries = await loadCollectorRuns(country, STAGING_RUN_LIMIT);
const documents: BuildRunDocuments[] = [];
const problems: BuildRunProblem[] = [];
await Promise.all(
summaries.slice(0, STAGING_RUN_LIMIT).map(async (summary) => {
summaries.map(async (summary) => {
const key = `collector:${country}:${summary.run_id}`;
const cached = stagingRunCache.get(key);
const summaryFinal = FINAL_STATUSES.has(summary.status);
Expand Down
223 changes: 223 additions & 0 deletions frontend/lib/microcosm/test-support/fake-telemetry-collector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// Test support: an in-memory telemetry collector behind a fetch stub. It
// serves `GET /v1/runs` the way the collector does (newest first, a `limit`
// between 1 and 200 that defaults to 60, an opaque `before` cursor, and a
// cursor whenever a page is full) and `GET /v1/runs/{run_id}`, and records
// every request so tests can bound what the dashboard asks for.

export const FAKE_COLLECTOR_URL = "https://telemetry.example";

const COLLECTOR_DEFAULT_LIMIT = 60;
const COLLECTOR_MAX_LIMIT = 200;
const NEWEST_REGISTRATION_MS = Date.parse("2026-10-05T10:00:00Z");

// A type alias, not an interface, so it satisfies the client's JSON-object
// summary type.
export type FakeCollectorRunSummary = {
run_id: string;
country_code: string;
pipeline: string;
candidate_id: string | null;
release_id: string | null;
run_kind: string;
status: string;
current_stage: string;
registered_at: string;
started_at: string;
updated_at: string;
ended_at: string | null;
heartbeat_at: string | null;
};

export interface FakeTelemetryCollectorOptions {
countryCode: string;
runCount: number;
// Serve at most this many runs per page, with a cursor, so tests can cross
// page boundaries below the requested limit.
largestPage?: number;
}

export interface FakeTelemetryCollector {
// Every stored run, newest first: the collector's listing order.
runs: FakeCollectorRunSummary[];
listRequests: URL[];
// The `next_before` cursor served for each list request, in order.
servedCursors: (string | null)[];
documentRequests: string[];
fetch: typeof fetch;
}

function summaryForRun(
countryCode: string,
ageIndex: number,
runCount: number,
): FakeCollectorRunSummary {
const registeredAt = new Date(
NEWEST_REGISTRATION_MS - ageIndex * 60_000,
).toISOString();
const finished = ageIndex > 0;
return {
run_id: `run-${String(runCount - ageIndex).padStart(4, "0")}`,
country_code: countryCode,
pipeline: "fiscal-refresh",
candidate_id: null,
release_id: null,
run_kind: "calibration",
status: finished ? "completed" : "running",
current_stage: finished ? "complete" : "target_compilation",
registered_at: registeredAt,
started_at: registeredAt,
updated_at: registeredAt,
ended_at: finished ? registeredAt : null,
heartbeat_at: null,
};
}

function pageCursor(run: FakeCollectorRunSummary): string {
return Buffer.from(JSON.stringify([run.registered_at, run.run_id])).toString(
"base64url",
);
}

function runIdFromCursor(cursor: string): string | null {
try {
const decoded = JSON.parse(Buffer.from(cursor, "base64url").toString());
return Array.isArray(decoded) && typeof decoded[1] === "string"
? decoded[1]
: null;
} catch {
return null;
}
}

function runDocuments(run: FakeCollectorRunSummary) {
return {
run_id: run.run_id,
country_code: run.country_code,
progress: {
schema_version: 2,
run_id: run.run_id,
country_code: run.country_code,
pipeline: { id: run.pipeline, version: "collector-v1" },
started_at: run.started_at,
updated_at: run.updated_at,
status: run.status,
current_stage: run.current_stage,
},
run_manifest: null,
calibration_progress: null,
events: [
{
schema_version: 2,
sequence: 1,
run_id: run.run_id,
event_type: "stage",
stage_id: run.current_stage,
stage: run.current_stage,
status: run.status === "running" ? "started" : "completed",
timestamp: run.updated_at,
time: run.updated_at,
details: {},
},
],
};
}

export function fakeTelemetryCollector({
countryCode,
runCount,
largestPage,
}: FakeTelemetryCollectorOptions): FakeTelemetryCollector {
const runs = Array.from({ length: runCount }, (_, ageIndex) =>
summaryForRun(countryCode, ageIndex, runCount),
);
const listRequests: URL[] = [];
const servedCursors: (string | null)[] = [];
const documentRequests: string[] = [];

function listRuns(url: URL): Response {
listRequests.push(url);
if (url.searchParams.get("country") !== countryCode) {
return Response.json({ runs: [], next_before: null });
}
const limit = Number(
url.searchParams.get("limit") ?? COLLECTOR_DEFAULT_LIMIT,
);
if (!Number.isInteger(limit) || limit < 1 || limit > COLLECTOR_MAX_LIMIT) {
return Response.json({ detail: "Invalid limit." }, { status: 422 });
}
let start = 0;
const cursor = url.searchParams.get("before");
if (cursor != null) {
const cursorRunId = runIdFromCursor(cursor);
const cursorIndex = runs.findIndex((run) => run.run_id === cursorRunId);
if (cursorIndex < 0) {
return Response.json(
{ detail: "Pagination cursor is invalid." },
{ status: 422 },
);
}
start = cursorIndex + 1;
}
const servedSize = Math.min(limit, largestPage ?? limit);
const page = runs.slice(start, start + servedSize);
const nextBefore =
page.length === servedSize ? pageCursor(page[page.length - 1]) : null;
servedCursors.push(nextBefore);
return Response.json({ runs: page, next_before: nextBefore });
}

function loadRun(runId: string): Response {
documentRequests.push(runId);
const run = runs.find((candidate) => candidate.run_id === runId);
if (!run) {
return Response.json({ detail: "Run was not found." }, { status: 404 });
}
return Response.json(runDocuments(run));
}

const collectorFetch = (async (input: RequestInfo | URL) => {
const url = new URL(String(input));
if (url.origin !== FAKE_COLLECTOR_URL) {
throw new Error(`Unexpected request outside the collector: ${url.origin}`);
}
if (url.pathname === "/v1/runs") return listRuns(url);
const documentPath = /^\/v1\/runs\/([^/]+)$/.exec(url.pathname);
if (documentPath) return loadRun(decodeURIComponent(documentPath[1]));
throw new Error(`Unexpected collector path: ${url.pathname}`);
}) as typeof fetch;

return {
runs,
listRequests,
servedCursors,
documentRequests,
fetch: collectorFetch,
};
}

// Every run the collector lists for a country, read the way the dashboard did
// before the listing was bounded: 200-run pages, following every cursor.
export async function entireCollectorHistory(
collector: FakeTelemetryCollector,
countryCode: string,
): Promise<FakeCollectorRunSummary[]> {
const history: FakeCollectorRunSummary[] = [];
let cursor: string | null = null;
do {
const params = new URLSearchParams({
country: countryCode,
limit: String(COLLECTOR_MAX_LIMIT),
});
if (cursor) params.set("before", cursor);
const response = await collector.fetch(
`${FAKE_COLLECTOR_URL}/v1/runs?${params}`,
);
const page = (await response.json()) as {
runs: FakeCollectorRunSummary[];
next_before: string | null;
};
history.push(...page.runs);
cursor = page.next_before;
} while (cursor);
return history;
}
Loading
Loading