import { assert, assertEquals, assertRejects } from "jsr:@std/assert@^1.0.0"; import { RunnerCoordinator } from "../../../uptime.link/ts_api/classes/runner-coordinator.ts"; import { RunnerFileStore } from "../../../uptime.link/ts_api/classes/runner-file-store.ts"; import { RunnerHttpHandler } from "../../../uptime.link/ts_api/classes/runner-http-handler.ts"; import { UptimeRunner } from "../../../uptimerunner/ts/runner.ts"; import type { IResultSubmitRequest, TCheckJob, } from "../../../uptimerunner/ts/interfaces.ts"; const scenarioName = "uptimerunner-basic"; const runnerId = `scenario-runner-${Date.now().toString(36)}`; const runnerToken = "scenario-token"; const main = async () => { const targetServer = await startServer(() => new Response("healthy")); const tcpServer = startTcpServer(); const resultSubmissions: IResultSubmitRequest[] = []; const checkQueue: TCheckJob[] = [ { id: "local-http-health", type: "http", url: targetServer.url, expectedStatusCodes: [200], expectedBodyIncludes: "healthy", metadata: { scenario: scenarioName, }, }, { id: "local-tcp-health", type: "tcp", host: "127.0.0.1", port: tcpServer.port, }, { id: "manual-assumption", type: "assumption", assumedStatus: "ok", message: "manual assumption is healthy", }, ]; const coordinator = new RunnerCoordinator({ runners: [{ runnerId, token: runnerToken, labels: ["scenario:basic"] }], }); for (const check of checkQueue) { coordinator.enqueueCheck(check); } const runnerHttpHandler = new RunnerHttpHandler({ coordinator }); const coordinatorServer = await startServer(async (request) => { if ( request.method === "POST" && new URL(request.url).pathname === "/api/runner/v1/results" ) { resultSubmissions.push(await request.clone().json()); } return await runnerHttpHandler.handleRequest(request); }); try { const runner = new UptimeRunner({ instanceUrl: coordinatorServer.url, runnerId, token: runnerToken, pollIntervalMs: 1000, }); console.log(`[${scenarioName}] Running single runner iteration`); const result = await runner.runOnce(); assertEquals(result.checks.length, 3); assertEquals(result.results.length, 3); assertEquals(result.results[0].checkId, "local-http-health"); assertEquals(result.results[0].status, "ok"); assertEquals(result.results[1].checkId, "local-tcp-health"); assertEquals(result.results[1].status, "ok"); assertEquals(result.results[2].checkId, "manual-assumption"); assertEquals(result.results[2].status, "ok"); assertEquals(resultSubmissions.length, 1); assertEquals(resultSubmissions[0].runnerId, runnerId); assertEquals(resultSubmissions[0].results[0].status, "ok"); assert(resultSubmissions[0].results[0].responseTime !== undefined); assertEquals(coordinator.listResults().length, 3); assertEquals(coordinator.getQueueLength(), 0); await assertSnapshotPersistence(coordinator); const emptyResult = await runner.runOnce(); assertEquals(emptyResult.checks.length, 0); assertEquals(emptyResult.results.length, 0); assertEquals(resultSubmissions.length, 1); const unauthorizedRunner = new UptimeRunner({ instanceUrl: coordinatorServer.url, runnerId, token: "wrong-token", pollIntervalMs: 1000, }); await assertRejects(() => unauthorizedRunner.runOnce(), Error, "401"); console.log(`[${scenarioName}] Passed`); } finally { await coordinatorServer.server.shutdown(); await targetServer.server.shutdown(); tcpServer.close(); } }; async function startServer( handlerArg: Deno.ServeHandler, ): Promise<{ server: Deno.HttpServer; url: string }> { let resolveListening: (addr: Deno.NetAddr) => void; const listening = new Promise((resolve) => { resolveListening = resolve; }); const server = Deno.serve({ hostname: "127.0.0.1", port: 0, onListen: (addr) => resolveListening(addr), }, handlerArg); const addr = await listening; return { server, url: `http://${addr.hostname}:${addr.port}`, }; } function startTcpServer(): { port: number; close: () => void } { const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 }); const port = (listener.addr as Deno.NetAddr).port; let closed = false; const acceptLoop = async () => { while (!closed) { try { const connection = await listener.accept(); connection.close(); } catch (error) { if (!closed) { throw error; } } } }; acceptLoop(); return { port, close: () => { closed = true; listener.close(); }, }; } async function assertSnapshotPersistence( coordinatorArg: RunnerCoordinator, ): Promise { const snapshotPath = await Deno.makeTempFile(); const store = new RunnerFileStore({ readTextFile: async () => { try { return await Deno.readTextFile(snapshotPath); } catch (error) { if (error instanceof Deno.errors.NotFound) { return undefined; } throw error; } }, writeTextFile: async (contentArg) => { await Deno.writeTextFile(snapshotPath, contentArg); }, }); try { await store.save(coordinatorArg.getSnapshot()); const snapshot = await store.load(); assert(snapshot); const restoredCoordinator = new RunnerCoordinator({ snapshot }); assertEquals(restoredCoordinator.listResults().length, 3); assertEquals(restoredCoordinator.listRunners().length, 1); } finally { await Deno.remove(snapshotPath).catch(() => null); } } if (import.meta.main) { try { await main(); } catch (error) { console.error( `[${scenarioName}] Failed: ${ error instanceof Error ? error.stack : String(error) }`, ); Deno.exit(1); } }