import { beforeEach, describe, expect, mock, test } from "bun:test"; import { Database } from "bun:sqlite"; import { applyCombinedSchema } from "../test-schema.test-helper"; import type { RunnerRow } from "./runner-registration"; import { memoryScopes } from "../scoped-db/testing"; class StubEntrypoint {} mock.module("cloudflare:workers", () => ({ env: { PUBLIC_URL: "http://localhost:8787", SECRETS_KEY: "test-key" }, waitUntil: () => undefined, WorkflowEntrypoint: StubEntrypoint, DurableObject: StubEntrypoint, WorkerEntrypoint: StubEntrypoint, })); const db = new Database(":memory:"); applyCombinedSchema(db); const actualDb = await import("../db"); mock.module("../db", () => ({ ...actualDb, first: async (sql: string, ...params: unknown[]) => db.query(sql).get(...(params as never[])) ?? null, all: async (sql: string, ...params: unknown[]) => db.query(sql).all(...(params as never[])), run: async (sql: string, ...params: unknown[]) => ({ meta: { changes: db.query(sql).run(...(params as never[])).changes }, }), })); const scopes = memoryScopes(); mock.module("../scoped-db", () => scopes.module); const repoState = scopes.raw("repo", "repo"); const { claimOnce } = await import("./runner-claim"); function runner(labels: string[]): RunnerRow { return { id: `r-${labels.join("-")}`, name: "r", labels: JSON.stringify(labels), owner_id: "org", repo_id: null, max_jobs: 1, isolation_attested: 0, } as unknown as RunnerRow; } beforeEach(() => { db.exec(`DELETE FROM repos; DELETE FROM accounts;`); repoState.exec(`DELETE FROM workflow_jobs; DELETE FROM workflow_runs;`); db.exec(` INSERT INTO accounts (id, name, kind, created_at) VALUES ('org', 'acme', 'org', 0); INSERT INTO repos (id, owner_id, name, artifacts_name, artifacts_remote, visibility, created_at, updated_at) VALUES ('repo', 'org', 'web', 'a', 'https://a', 'public', 0, 0); `); repoState.exec(` INSERT INTO workflow_runs (id, repo_id, source_repo_id, run_number, workflow_path, workflow_name, event, ref, sha, title, status, created_at) VALUES ('run', 'repo', 'repo', 1, 'ci.yml', 'CI', 'push', 'refs/heads/main', 'abc', 't', 'queued', 0); `); }); describe("runner doorbell", () => { test("a runner whose labels match no queued job still reports queued work, so it never mutes the shared bell", async () => { repoState.exec(`INSERT INTO workflow_jobs (id, run_id, repo_id, job_key, name, runs_on, spec, status, created_at) VALUES ('job', 'run', 'repo', 'build', 'build', '["self-hosted","gpu"]', '{}', 'queued', 0);`); expect(await claimOnce(runner(["linux"]))).toEqual({ job: null, sawCandidates: true }); }); test("an empty scope lets the bell go quiet", async () => { expect(await claimOnce(runner(["linux"]))).toEqual({ job: null, sawCandidates: false }); }); });