import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; import { createHash, createHmac } from "node:crypto"; import { existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; import { createServer } from "node:http"; import { tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; import { createGithubSyncHandler, createDurableInboxStore, createDurableSyncController, createSyncCoordinator, readFailedDeliveryRecoveryStatus, run, runResult, syncRepository, } from "./platform-infra-gitea-github-sync-server.mjs"; const zeroDelay = async () => {}; test("primary status exposes a bounded redacted failed-delivery recovery ledger", async () => { const root = mkdtempSync(join(tmpdir(), "gitea-hook-recovery-status-")); const statePath = join(root, "ledger.json"); try { const records = Object.fromEntries(Array.from({ length: 55 }, (_, index) => [`record-${index}`, { repository: "pikasTech/agentrun", hookId: 77, deliveryId: `guid-${index}`, apiDeliveryId: String(1000 + index), state: index % 2 === 0 ? "attempted" : "pending", attempts: index % 3, cooldownMs: 300_000, nextAt: "2026-07-12T07:11:40Z", updatedAt: `2026-07-12T07:${String(index).padStart(2, "0")}:00Z`, lastStatusCode: 502, secret: "must-not-leak", url: "https://must-not-leak.invalid/hook", }])); writeFileSync(statePath, JSON.stringify({ version: 1, kind: "GiteaGithubHookRecoveryLedger", records })); const status = await readFailedDeliveryRecoveryStatus(statePath); assert.equal(status.ready, true); assert.equal(status.entries.length, 50); assert.equal(status.counts.attempted + status.counts.pending, 55); assert.equal(JSON.stringify(status).includes("must-not-leak"), false); writeFileSync(statePath, JSON.stringify({ version: 1, kind: "GiteaGithubHookRecoveryLedger", records: { corrupt: { ...records["record-0"], state: "unknown-hidden-state" } }, })); const invalid = await readFailedDeliveryRecoveryStatus(statePath); assert.equal(invalid.ready, false); assert.equal(invalid.state, "invalid-or-unreadable"); assert.equal(invalid.errorType, "hook-recovery-ledger-record-invalid"); assert.deepEqual(invalid.entries, []); } finally { rmSync(root, { recursive: true, force: true }); } }); test("explicit entrypoint starts through Kubernetes ConfigMap symlinks", () => { const root = mkdtempSync(join(tmpdir(), "gitea-sync-main-")); try { const dataDirectory = join(root, "..data"); const serverSource = join(dataDirectory, "server.mjs"); const entrypointSource = join(dataDirectory, "entrypoint.mjs"); const marker = join(root, "started"); mkdirSync(dataDirectory); writeFileSync(serverSource, `import { writeFileSync } from "node:fs";\nexport function startServer() { writeFileSync(${JSON.stringify(marker)}, "started\\n"); }\n`, { flag: "wx" }); writeFileSync(entrypointSource, 'import { startServer } from "./server.mjs";\nstartServer();\n', { flag: "wx" }); symlinkSync(join("..data", "server.mjs"), join(root, "server.mjs")); symlinkSync(join("..data", "entrypoint.mjs"), join(root, "entrypoint.mjs")); execFileSync(process.execPath, [join(root, "entrypoint.mjs")], { stdio: "pipe" }); assert.equal(existsSync(marker), true); } finally { rmSync(root, { recursive: true, force: true }); } }); test("HTTP 202 waits for durable acceptance but does not claim refs are committed", async () => { const repo = fakeRepo("one"); const logs = []; let release; const gate = new Promise((resolve) => { release = resolve; }); let mode = "accept"; const controller = { readiness: () => ({ ready: true }), status: async () => ({ ready: true, counts: {}, deliveries: [] }), async accept() { if (mode === "accept") { await gate; return { state: "accepted", duplicate: false }; } throw new Error("Authorization: Basic c2VjcmV0"); }, }; const server = createServer(createGithubSyncHandler({ path: "/hook", responseBudgetMs: 1000, maxBodyBytes: 4096, webhookSecret: "test-secret", repos: [repo], controller, log: (event) => logs.push(event), })); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const port = server.address().port; try { const pending = postWebhook(port, repo, "http-success", "a".repeat(40), "test-secret"); const beforeCommit = await Promise.race([pending.then(() => "responded"), new Promise((resolve) => setTimeout(() => resolve("waiting"), 30))]); assert.equal(beforeCommit, "waiting"); release(); const accepted = await pending; assert.equal(accepted.status, 202); assert.deepEqual(await accepted.json(), { ok: true, event: "push", deliveryId: "http-success", repository: repo.upstream.repository, ref: "refs/heads/main", repo: repo.key, requestedCommit: "a".repeat(40), state: "accepted", disposition: "durable-inbox-accepted", refsCommitted: false, valuesPrinted: false, }); mode = "throw"; const unexpected = await postWebhook(port, repo, "http-throw", "d".repeat(40), "test-secret"); assert.equal(unexpected.status, 503); assert.equal((await unexpected.json()).error, "internal-sync-error"); const rejected = logs.at(-1); assert.deepEqual( [rejected.deliveryId, rejected.repository, rejected.ref, rejected.requestedCommit], ["http-throw", repo.upstream.repository, "refs/heads/main", "d".repeat(40)], ); assert.match(rejected.error, //); } finally { server.close(); } }); test("duplicate HTTP 202 reports the durable accepted, processing, failed or committed state truthfully", async () => { const repo = fakeRepo("duplicate-state"); const committedResult = { sourceCommit: "c".repeat(40), authorityCommit: "c".repeat(40), snapshotRef: `refs/snapshots/source/${"c".repeat(40)}`, disposition: "atomic-source-authority-committed", }; let durable = { state: "accepted", duplicate: true, result: null }; const controller = { readiness: () => ({ ready: true }), status: async () => ({ ready: true, counts: {}, deliveries: [] }), async accept() { return durable; }, }; const server = createServer(createGithubSyncHandler({ path: "/hook", responseBudgetMs: 1000, maxBodyBytes: 4096, webhookSecret: "test-secret", repos: [repo], controller, log: () => {}, })); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const port = server.address().port; try { for (const state of ["accepted", "processing", "failed", "committed"]) { durable = { state, duplicate: true, result: state === "committed" ? committedResult : null }; const response = await postWebhook(port, repo, `duplicate-${state}`, "c".repeat(40), "test-secret"); assert.equal(response.status, 202); const body = await response.json(); assert.equal(body.state, state); assert.equal(body.disposition, "duplicate-durable-inbox-record"); assert.equal(body.refsCommitted, state === "committed"); if (state === "committed") assert.deepEqual(body.result, committedResult); else assert.equal(Object.hasOwn(body, "result"), false); } } finally { server.close(); } }); test("durable acceptance response budget returns typed 503 while a late write continues and remains idempotent", async () => { const root = mkdtempSync(join(tmpdir(), "unidesk-gitea-acceptance-budget-")); const repo = fakeRepo("acceptance-budget"); let releaseFirstAccept; const firstAcceptGate = new Promise((resolve) => { releaseFirstAccept = resolve; }); let releaseSync; const syncGate = new Promise((resolve) => { releaseSync = resolve; }); const logs = []; const durableController = createDurableSyncController(durableControllerOptions(root, [repo], { sync: async (_repo, delivery) => { await syncGate; return { ok: true, sourceCommit: delivery.requestedCommit, authorityCommit: delivery.requestedCommit, snapshotRef: `refs/snapshots/source/${delivery.requestedCommit}`, disposition: "atomic-source-authority-committed", }; }, })); let acceptCalls = 0; const delayedController = { readiness: () => durableController.readiness(), status: () => durableController.status(), async accept(repoValue, delivery) { acceptCalls += 1; if (acceptCalls === 1) await firstAcceptGate; return await durableController.accept(repoValue, delivery); }, }; const server = createServer(createGithubSyncHandler({ path: "/hook", responseBudgetMs: 40, maxBodyBytes: 4096, webhookSecret: "test-secret", repos: [repo], controller: delayedController, log: (event) => logs.push(event), })); try { await durableController.start(); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const port = server.address().port; const commit = "7".repeat(40); const startedAt = performance.now(); const timedOut = await postWebhook(port, repo, "late-durable-write", commit, "test-secret", { marker: "late" }); const elapsedMs = performance.now() - startedAt; assert.equal(timedOut.status, 503); assert.equal((await timedOut.json()).error, "durable-acceptance-response-deadline-exhausted"); assert.ok(elapsedMs < 200, `durable acceptance deadline responded after ${elapsedMs}ms`); assert.equal((await durableController.status()).deliveries.length, 0); releaseFirstAccept(); await waitFor(async () => (await durableController.status()).deliveries.length === 1); const duplicate = await postWebhook(port, repo, "late-durable-write", commit, "test-secret", { marker: "late" }); assert.equal(duplicate.status, 202); const duplicateBody = await duplicate.json(); assert.equal(duplicateBody.disposition, "duplicate-durable-inbox-record"); assert.equal((await durableController.status()).deliveries.length, 1); assert.equal(logs.some((event) => event.event === "github-to-gitea-delivery-accepted-after-response-deadline"), true); releaseSync(); await waitFor(async () => (await durableController.status()).counts.committed === 1); const committedDuplicate = await postWebhook(port, repo, "late-durable-write", commit, "test-secret", { marker: "late" }); const committedBody = await committedDuplicate.json(); assert.equal(committedBody.state, "committed"); assert.equal(committedBody.refsCommitted, true); assert.equal(committedBody.result.sourceCommit, commit); } finally { server.close(); await durableController.stop(); rmSync(root, { recursive: true, force: true }); } }); test("durable inbox fsync acceptance is body-sensitive, fast, and later becomes committed", async () => { const root = mkdtempSync(join(tmpdir(), "unidesk-gitea-inbox-http-")); const repo = fakeRepo("durable"); let releaseSync; const syncGate = new Promise((resolve) => { releaseSync = resolve; }); let syncStarted = false; const controller = createDurableSyncController(durableControllerOptions(root, [repo], { sync: async (_repo, delivery) => { syncStarted = true; await syncGate; return { ok: true, sourceCommit: delivery.requestedCommit, authorityCommit: delivery.requestedCommit, snapshotRef: `refs/snapshots/source/${delivery.requestedCommit}`, disposition: "atomic-source-authority-committed", }; }, })); const server = createServer(createGithubSyncHandler({ path: "/hook", responseBudgetMs: 1000, maxBodyBytes: 4096, webhookSecret: "test-secret", repos: [repo], controller, log: () => {}, })); try { await controller.start(); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const port = server.address().port; const commit = "a".repeat(40); const startedAt = performance.now(); const accepted = await postWebhook(port, repo, "durable-delivery", commit, "test-secret", { marker: "one" }); const elapsedMs = performance.now() - startedAt; assert.equal(accepted.status, 202); assert.ok(elapsedMs < 300, `fsync acceptance took ${elapsedMs}ms`); assert.equal((await accepted.json()).refsCommitted, false); await waitFor(() => syncStarted); const duplicate = await postWebhook(port, repo, "durable-delivery", commit, "test-secret", { marker: "one" }); assert.equal(duplicate.status, 202); assert.match((await duplicate.json()).disposition, /duplicate-durable-inbox-record/); const bodyConflict = await postWebhook(port, repo, "durable-delivery", commit, "test-secret", { marker: "two" }); assert.equal(bodyConflict.status, 409); releaseSync(); await waitFor(async () => (await controller.status()).deliveries.some((record) => record.deliveryId === "durable-delivery" && record.state === "committed")); const committed = (await controller.status()).deliveries.find((record) => record.deliveryId === "durable-delivery"); assert.equal(committed.result.sourceCommit, commit); assert.equal(committed.result.authorityCommit, commit); assert.equal(committed.result.snapshotRef, `refs/snapshots/source/${commit}`); } finally { server.close(); await controller.stop(); rmSync(root, { recursive: true, force: true }); } }); test("startup rejects corrupt journal records and recovers a crash-left processing record", async () => { const corruptRoot = mkdtempSync(join(tmpdir(), "unidesk-gitea-inbox-corrupt-")); const corruptInbox = join(corruptRoot, "inbox"); const journalCreatedAt = Date.parse("2026-07-11T07:00:00.000Z"); const corruptStore = createDurableInboxStore({ path: corruptInbox, maxBytes: 1024 * 1024, now: () => journalCreatedAt }); await corruptStore.initialize(); assert.equal(corruptStore.journalMetadata().createdAt, new Date(journalCreatedAt).toISOString()); const restartedStore = createDurableInboxStore({ path: corruptInbox, maxBytes: 1024 * 1024, now: () => journalCreatedAt + 60_000 }); await restartedStore.initialize(); assert.equal(restartedStore.journalMetadata().createdAt, new Date(journalCreatedAt).toISOString()); writeFileSync(join(corruptInbox, `${"a".repeat(64)}.json`), JSON.stringify({ version: 1, deliveryId: "corrupt", fingerprint: "b".repeat(64), state: "accepted" })); const corruptController = createDurableSyncController(durableControllerOptions(corruptRoot, [fakeRepo("corrupt")])); try { await corruptController.start(); assert.equal(corruptController.readiness().ready, false); assert.equal(corruptController.readiness().errorType, "durable-inbox-record-invalid"); } finally { await corruptController.stop(); rmSync(corruptRoot, { recursive: true, force: true }); } const recoveryRoot = mkdtempSync(join(tmpdir(), "unidesk-gitea-inbox-recovery-")); const repo = fakeRepo("recovery"); const store = createDurableInboxStore({ path: join(recoveryRoot, "inbox"), maxBytes: 1024 * 1024 }); try { await store.initialize(); const delivery = fakeDelivery("processing-before-crash", "b".repeat(40), repo); await store.accept(repo, delivery); await assert.rejects( () => store.update(delivery.deliveryId, (record) => ({ ...record, acceptedSequence: record.acceptedSequence + 1 })), /invalid-record-update/, ); await store.update(delivery.deliveryId, (record) => ({ ...record, state: "processing", processingAt: new Date().toISOString(), updatedAt: new Date().toISOString(), totalAttempts: 1, cycleAttempt: 1, })); const controller = createDurableSyncController(durableControllerOptions(recoveryRoot, [repo], { sync: async () => ({ ok: true, sourceCommit: delivery.requestedCommit, authorityCommit: delivery.requestedCommit, snapshotRef: `refs/snapshots/source/${delivery.requestedCommit}`, disposition: "atomic-source-authority-committed", }), })); await controller.start(); await waitFor(async () => (await controller.status()).deliveries.some((record) => record.deliveryId === delivery.deliveryId && record.state === "committed")); await controller.stop(); } finally { rmSync(recoveryRoot, { recursive: true, force: true }); } }); test("unavailable or full durable inbox is not ready and fails closed with 503", async () => { const root = mkdtempSync(join(tmpdir(), "unidesk-gitea-inbox-unavailable-")); const blockingFile = join(root, "not-a-directory"); writeFileSync(blockingFile, "blocked"); const repo = fakeRepo("capacity"); const unavailable = createDurableSyncController(durableControllerOptions(root, [repo], { inbox: { path: join(blockingFile, "inbox"), maxBytes: 1024 }, })); await unavailable.start(); assert.equal(unavailable.readiness().ready, false); await assert.rejects(() => unavailable.accept(repo, fakeDelivery("unavailable", "a".repeat(40), repo)), /durable-inbox/); await unavailable.stop(); const full = createDurableSyncController(durableControllerOptions(root, [repo], { inbox: { path: join(root, "full-inbox"), maxBytes: 1 }, })); try { await full.start(); await assert.rejects(() => full.accept(repo, fakeDelivery("full", "b".repeat(40), repo)), /capacity-exhausted/); assert.equal(full.readiness().ready, false); assert.equal(full.readiness().errorType, "durable-inbox-capacity-exhausted"); } finally { await full.stop(); rmSync(root, { recursive: true, force: true }); } }); test("retention keeps each repository latest committed watermark and status keeps every repo latest record", async () => { const root = mkdtempSync(join(tmpdir(), "unidesk-gitea-inbox-retention-")); const store = createDurableInboxStore({ path: join(root, "inbox"), maxBytes: 4 * 1024 * 1024 }); try { await store.initialize(); const old = new Date(Date.now() - 10_000).toISOString(); for (let index = 0; index < 105; index += 1) { const repo = fakeRepo(index < 2 ? "quiet" : `noise-${index}`); const delivery = { ...fakeDelivery(`retained-${index}`, index.toString(16).padStart(40, "0"), repo), receivedAt: old }; await store.accept(repo, delivery); await store.update(delivery.deliveryId, (record) => ({ ...record, state: "committed", updatedAt: old, committedAt: old, nextAttemptAt: null, result: { sourceCommit: delivery.requestedCommit, authorityCommit: delivery.requestedCommit, snapshotRef: `refs/snapshots/source/${delivery.requestedCommit}`, disposition: "atomic-source-authority-committed", }, })); } const removed = await store.cleanupCommitted(Date.now()); assert.equal(removed, 1); const retained = await store.list(); assert.equal(retained.filter((record) => record.repoKey === "quiet").length, 1); assert.equal(retained.find((record) => record.repoKey === "quiet").deliveryId, "retained-1"); const quietRepo = fakeRepo("quiet"); const failedDelivery = fakeDelivery("quiet-failed", "f".repeat(40), quietRepo); await store.accept(quietRepo, failedDelivery); await store.update(failedDelivery.deliveryId, (record) => ({ ...record, state: "failed", updatedAt: new Date().toISOString(), failedAt: new Date().toISOString(), nextAttemptAt: "2099-01-01T00:00:00.000Z", lastError: { errorType: "test-failure", retryable: true, terminal: true }, })); const controller = createDurableSyncController(durableControllerOptions(root, [...retained.map((record) => fakeRepo(record.repoKey)), quietRepo], { store })); await controller.start(); const status = await controller.status(); assert.ok(status.deliveries.some((record) => record.repo === "quiet" && record.deliveryId === "retained-1")); assert.ok(status.deliveries.some((record) => record.repo === "quiet" && record.deliveryId === "quiet-failed")); await controller.stop(); } finally { rmSync(root, { recursive: true, force: true }); } }); test("same-millisecond records are listed and scheduled by immutable acceptedSequence", async () => { const root = mkdtempSync(join(tmpdir(), "unidesk-gitea-same-millisecond-order-")); const repo = fakeRepo("same-millisecond"); const store = createDurableInboxStore({ path: join(root, "inbox"), maxBytes: 1024 * 1024 }); const receivedAt = "2026-07-11T08:00:00.000Z"; const first = { ...fakeDelivery("same-ms-first", "1".repeat(40), repo), receivedAt }; const second = { ...fakeDelivery("same-ms-second", "2".repeat(40), repo), receivedAt }; const executionOrder = []; let controller; try { await store.initialize(); await store.accept(repo, first); await store.accept(repo, second); const listed = await store.list(); assert.deepEqual(listed.map((record) => [record.deliveryId, record.acceptedSequence]), [ [first.deliveryId, 1], [second.deliveryId, 2], ]); controller = createDurableSyncController(durableControllerOptions(root, [repo], { sync: async (_repo, delivery) => { executionOrder.push(delivery.deliveryId); return { ok: true, sourceCommit: delivery.requestedCommit, authorityCommit: delivery.requestedCommit, snapshotRef: `refs/snapshots/source/${delivery.requestedCommit}`, disposition: "atomic-source-authority-committed", }; }, })); await controller.start(); await waitFor(async () => (await controller.status()).counts.committed === 2); assert.deepEqual(executionOrder, [first.deliveryId, second.deliveryId]); } finally { if (controller) await controller.stop(); rmSync(root, { recursive: true, force: true }); } }); test("concurrent duplicate delivery shares one promise and payload conflict fails closed", async () => { let release; const gate = new Promise((resolve) => { release = resolve; }); let calls = 0; const coordinator = createSyncCoordinator({ responseBudgetMs: 1000, retryMaxAttempts: 2, retryInitialDelayMs: 1, retryMaxDelayMs: 1, sleep: zeroDelay, log: () => {}, sync: async (_repo, delivery) => { calls += 1; await gate; return { ok: true, sourceCommit: delivery.requestedCommit, authorityCommit: delivery.requestedCommit, snapshotRef: `refs/snapshots/${delivery.requestedCommit}`, disposition: "atomic-source-authority-committed" }; }, }); const repo = fakeRepo("one"); const delivery = fakeDelivery("delivery-1", "a".repeat(40), repo); const first = coordinator.execute(repo, delivery); const duplicate = coordinator.execute(repo, { ...delivery }); assert.throws( () => coordinator.execute(repo, { ...delivery, requestedCommit: "b".repeat(40) }), /github-delivery-id-payload-conflict/, ); release(); const [firstResult, duplicateResult] = await Promise.all([first, duplicate]); assert.equal(calls, 1); assert.equal(firstResult.ok, true); assert.equal(duplicateResult.duplicate, true); }); test("different repositories are isolated while each repository stays serialized", async () => { let releaseOne; const gateOne = new Promise((resolve) => { releaseOne = resolve; }); const order = []; const coordinator = createSyncCoordinator({ responseBudgetMs: 1000, retryMaxAttempts: 1, retryInitialDelayMs: 1, retryMaxDelayMs: 1, sleep: zeroDelay, log: () => {}, sync: async (repo, delivery) => { order.push(`start:${repo.key}:${delivery.deliveryId}`); if (repo.key === "one") await gateOne; order.push(`end:${repo.key}:${delivery.deliveryId}`); return { ok: true, sourceCommit: delivery.requestedCommit, authorityCommit: delivery.requestedCommit, snapshotRef: `refs/snapshots/${delivery.requestedCommit}`, disposition: "atomic-source-authority-committed" }; }, }); const repoOne = fakeRepo("one"); const repoTwo = fakeRepo("two"); const oneA = coordinator.execute(repoOne, fakeDelivery("one-a", "a".repeat(40), repoOne)); const oneB = coordinator.execute(repoOne, fakeDelivery("one-b", "b".repeat(40), repoOne)); const two = coordinator.execute(repoTwo, fakeDelivery("two-a", "c".repeat(40), repoTwo)); await two; assert.deepEqual(order.slice(0, 3), ["start:one:one-a", "start:two:two-a", "end:two:two-a"]); releaseOne(); await Promise.all([oneA, oneB]); assert.ok(order.indexOf("start:one:one-b") > order.indexOf("end:one:one-a")); }); test("retry exhaustion is terminal and observable", async () => { const logs = []; let calls = 0; const coordinator = createSyncCoordinator({ responseBudgetMs: 1000, retryMaxAttempts: 3, retryInitialDelayMs: 1, retryMaxDelayMs: 1, sleep: zeroDelay, log: (event) => logs.push(event), sync: async () => { calls += 1; return { ok: false, retryable: true, errorType: "git-source-sync-failed" }; }, }); const repo = fakeRepo("one"); const result = await coordinator.execute(repo, fakeDelivery("delivery-fail", "a".repeat(40), repo)); assert.equal(result.ok, false); assert.equal(calls, 3); assert.deepEqual(logs.filter((event) => event.event === "github-to-gitea-sync").map((event) => [event.syncAttempt, event.willRetry, event.terminal]), [ [1, true, false], [2, true, false], [3, false, true], ]); }); test("durable worker automatically starts a new cycle after terminal retry exhaustion", async () => { const root = mkdtempSync(join(tmpdir(), "unidesk-gitea-terminal-retry-")); const repo = fakeRepo("terminal-retry"); const logs = []; let calls = 0; const controller = createDurableSyncController(durableControllerOptions(root, [repo], { log: (event) => logs.push(event), workerRetry: { maxAttempts: 2, initialDelayMs: 1, maxDelayMs: 1, terminalRetryDelayMs: 5, scanIntervalMs: 1, }, sync: async (_repo, delivery) => { calls += 1; if (calls < 3) return { ok: false, retryable: true, errorType: "transient-source-sync-failed" }; return { ok: true, sourceCommit: delivery.requestedCommit, authorityCommit: delivery.requestedCommit, snapshotRef: `refs/snapshots/source/${delivery.requestedCommit}`, disposition: "atomic-source-authority-committed", }; }, })); try { await controller.start(); const delivery = fakeDelivery("terminal-retry-delivery", "a".repeat(40), repo); await controller.accept(repo, delivery); await waitFor(async () => (await controller.status()).deliveries.some((record) => record.deliveryId === delivery.deliveryId && record.state === "committed")); const committed = (await controller.status()).deliveries.find((record) => record.deliveryId === delivery.deliveryId); assert.equal(calls, 3); assert.equal(committed.totalAttempts, 3); assert.equal(logs.some((event) => event.event === "github-to-gitea-delivery-failed" && event.terminal === true && event.automaticRetryScheduled === true), true); } finally { await controller.stop(); rmSync(root, { recursive: true, force: true }); } }); test("unexpected executor rejection is durably failed and automatically retries without restart", async () => { const root = mkdtempSync(join(tmpdir(), "unidesk-gitea-executor-rejection-")); const repo = fakeRepo("executor-rejection"); const logs = []; let calls = 0; const controller = createDurableSyncController(durableControllerOptions(root, [repo], { log: (event) => logs.push(event), workerRetry: { initialDelayMs: 1, maxDelayMs: 1, scanIntervalMs: 1 }, executor: { async execute(_repo, delivery) { calls += 1; if (calls === 1) throw new Error("unexpected executor rejection"); return { ok: true, sourceCommit: delivery.requestedCommit, authorityCommit: delivery.requestedCommit, snapshotRef: `refs/snapshots/source/${delivery.requestedCommit}`, disposition: "atomic-source-authority-committed", }; }, }, })); try { await controller.start(); const delivery = fakeDelivery("executor-rejection-delivery", "8".repeat(40), repo); await controller.accept(repo, delivery); await waitFor(async () => (await controller.status()).counts.committed === 1); const committed = (await controller.status()).deliveries[0]; assert.equal(calls, 2); assert.equal(committed.state, "committed"); assert.equal(committed.totalAttempts, 2); assert.equal(logs.some((event) => event.event === "github-to-gitea-delivery-failed" && event.errorType === "source-sync-executor-threw" && event.automaticRetryScheduled === true), true); } finally { await controller.stop(); rmSync(root, { recursive: true, force: true }); } }); test("undefined and incomplete successful executor results are durably failed before retry", async () => { const root = mkdtempSync(join(tmpdir(), "unidesk-gitea-malformed-executor-result-")); const repo = fakeRepo("malformed-executor-result"); const logs = []; let calls = 0; const controller = createDurableSyncController(durableControllerOptions(root, [repo], { log: (event) => logs.push(event), workerRetry: { maxAttempts: 3, initialDelayMs: 1, maxDelayMs: 1, scanIntervalMs: 1 }, executor: { async execute(_repo, delivery) { calls += 1; if (calls === 1) return undefined; if (calls === 2) return { ok: true }; return { ok: true, sourceCommit: delivery.requestedCommit, authorityCommit: delivery.requestedCommit, snapshotRef: `refs/snapshots/source/${delivery.requestedCommit}`, disposition: "atomic-source-authority-committed", }; }, }, })); try { await controller.start(); const delivery = fakeDelivery("malformed-executor-result-delivery", "9".repeat(40), repo); await controller.accept(repo, delivery); await waitFor(async () => (await controller.status()).counts.committed === 1); const committed = (await controller.status()).deliveries[0]; assert.equal(calls, 3); assert.equal(committed.totalAttempts, 3); assert.equal(logs.filter((event) => event.event === "github-to-gitea-delivery-failed" && event.errorType === "source-sync-executor-invalid-result").length, 2); } finally { await controller.stop(); rmSync(root, { recursive: true, force: true }); } }); test("slow sync cannot return success after the absolute response deadline", async () => { const coordinator = createSyncCoordinator({ responseBudgetMs: 20, retryMaxAttempts: 1, retryInitialDelayMs: 1, retryMaxDelayMs: 1, sleep: zeroDelay, log: () => {}, sync: async (_repo, delivery) => { await new Promise((resolve) => setTimeout(resolve, 35)); return { ok: true, sourceCommit: delivery.requestedCommit, authorityCommit: delivery.requestedCommit, snapshotRef: `refs/snapshots/source/${delivery.requestedCommit}`, disposition: "atomic-source-authority-committed" }; }, }); const repo = fakeRepo("deadline"); const result = await coordinator.execute(repo, fakeDelivery("slow", "e".repeat(40), repo)); assert.equal(result.ok, false); assert.equal(result.retryable, true); assert.equal(result.errorType, "response-deadline-exhausted"); assert.equal(result.committedBeforeDeadlineUnknown, true); }); test("git subprocesses fail closed before launch when the shared deadline is exhausted", async () => { let commands = 0; const repo = { ...fakeRepo("expired"), upstream: { ...fakeRepo("expired").upstream, cloneUrl: "/does/not/matter" }, gitea: { readUrl: "file:///does/not/matter" }, }; const result = await syncRepository(repo, fakeDelivery("expired", "f".repeat(40), repo), { deadlineAt: 10, now: () => 10, runResult: () => { commands += 1; return { status: 0, stdout: "", stderr: "" }; }, githubAuthHeader: "", giteaAuthHeader: "", giteaBaseUrl: "file://", }); assert.equal(result.ok, false); assert.equal(result.retryable, true); assert.equal(commands, 0); assert.match(result.error, /response deadline exhausted/); }); test("async process runner handles spawn errors and kills the whole subprocess group at deadline", async () => { const missing = await runResult(["unidesk-command-that-does-not-exist"], 100); assert.equal(missing.status, null); assert.match(missing.stderr, /ENOENT|Executable not found/u); const root = mkdtempSync(join(tmpdir(), "unidesk-gitea-process-tree-")); const marker = join(root, "child-survived"); try { const timedOut = await runResult(["sh", "-c", '(sleep 0.25; : > "$1") & wait', "sh", marker], 40); assert.equal(timedOut.status, null); assert.match(timedOut.stderr, /deadline exceeded/); await new Promise((resolve) => setTimeout(resolve, 350)); assert.equal(existsSync(marker), false); } finally { rmSync(root, { recursive: true, force: true }); } }); test("controller shutdown aborts the active process tree and the durable record resumes after restart", async () => { const root = mkdtempSync(join(tmpdir(), "unidesk-gitea-controller-shutdown-")); const startedMarker = join(root, "worker-started"); const survivorMarker = join(root, "child-survived"); const repo = fakeRepo("shutdown"); const delivery = fakeDelivery("shutdown-delivery", "c".repeat(40), repo); const controller = createDurableSyncController(durableControllerOptions(root, [repo], { shutdownGraceMs: 500, sync: async (_repo, _delivery, context) => { const result = await runResult([ "sh", "-c", ': > "$1"; (sleep 0.25; : > "$2") & wait', "sh", startedMarker, survivorMarker, ], 10_000, context.signal); return { ok: false, retryable: true, errorType: result.stderr.includes("aborted") ? "worker-aborted" : "worker-exited" }; }, })); try { await controller.start(); await controller.accept(repo, delivery); await waitFor(() => existsSync(startedMarker)); await controller.stop(); await new Promise((resolve) => setTimeout(resolve, 320)); assert.equal(existsSync(survivorMarker), false); const resumed = createDurableSyncController(durableControllerOptions(root, [repo], { sync: async () => ({ ok: true, sourceCommit: delivery.requestedCommit, authorityCommit: delivery.requestedCommit, snapshotRef: `refs/snapshots/source/${delivery.requestedCommit}`, disposition: "atomic-source-authority-committed", }), })); await resumed.start(); await waitFor(async () => (await resumed.status()).deliveries.some((record) => record.deliveryId === delivery.deliveryId && record.state === "committed")); await resumed.stop(); } finally { rmSync(root, { recursive: true, force: true }); } }); test("atomic mirror handles partial refs, immutable snapshots, out-of-order delivery and lease race", async () => { const fixture = gitFixture(); try { const { repo, runtime, commits, gitea } = fixture; const first = await syncRepository(repo, fakeDelivery("a", commits.a, repo), runtime); assert.equal(first.ok, true); assert.equal(remoteRef(gitea, "refs/heads/main"), commits.a); assert.equal(remoteRef(gitea, `refs/snapshots/source/${commits.a}`), commits.a); git("--git-dir", gitea, "update-ref", `refs/snapshots/source/${commits.a}`, "-d"); const partial = await syncRepository(repo, fakeDelivery("a-partial", commits.a, repo), runtime); assert.equal(partial.ok, true); assert.equal(remoteRef(gitea, `refs/snapshots/source/${commits.a}`), commits.a); const forward = await syncRepository(repo, fakeDelivery("b", commits.b, repo), runtime); assert.equal(forward.ok, true); assert.equal(remoteRef(gitea, "refs/heads/main"), commits.b); assert.equal(remoteRef(gitea, `refs/snapshots/source/${commits.b}`), commits.b); const old = await syncRepository(repo, fakeDelivery("a-late", commits.a, repo), runtime); assert.equal(old.ok, true); assert.equal(old.disposition, "superseded-with-immutable-snapshot"); assert.equal(remoteRef(gitea, "refs/heads/main"), commits.b); const sameSnapshot = await syncRepository(repo, fakeDelivery("b-again", commits.b, repo), runtime); assert.equal(sameSnapshot.ok, true); assert.equal(remoteRef(gitea, "refs/heads/main"), commits.b); git("--git-dir", gitea, "update-ref", `refs/snapshots/source/${commits.c}`, commits.a); const conflict = await syncRepository(repo, fakeDelivery("c-conflict", commits.c, repo), runtime); assert.equal(conflict.ok, false); assert.equal(conflict.errorType, "immutable-snapshot-conflict"); assert.equal(remoteRef(gitea, "refs/heads/main"), commits.b); git("--git-dir", gitea, "update-ref", `refs/snapshots/source/${commits.c}`, "-d"); git("--git-dir", gitea, "fetch", "-q", fixture.source, `${commits.d}:refs/hidden/race-d`); let raced = false; const racingRuntime = { ...runtime, async runResult(args) { if (!raced && args.includes("push") && args.includes("--atomic")) { raced = true; git("--git-dir", gitea, "update-ref", "refs/heads/main", commits.d); } return await runResult(args); }, }; const leaseRace = await syncRepository(repo, fakeDelivery("c-race", commits.c, repo), racingRuntime); assert.equal(leaseRace.ok, false); assert.equal(leaseRace.errorType, "atomic-push-race-or-failure"); assert.equal(remoteRef(gitea, "refs/heads/main"), commits.d); assert.equal(remoteRef(gitea, `refs/snapshots/source/${commits.c}`), null); const retry = await syncRepository(repo, fakeDelivery("c-retry", commits.c, repo), runtime); assert.equal(retry.ok, true); assert.equal(retry.disposition, "superseded-with-immutable-snapshot"); assert.equal(remoteRef(gitea, "refs/heads/main"), commits.d); assert.equal(remoteRef(gitea, `refs/snapshots/source/${commits.c}`), commits.c); const unavailable = await syncRepository(repo, fakeDelivery("force-push-unavailable", `${"0".repeat(39)}1`, repo), runtime); assert.equal(unavailable.ok, false); assert.equal(unavailable.retryable, true); assert.equal(unavailable.errorType, "requested-commit-temporarily-unavailable"); } finally { fixture.cleanup(); } }); test("production async Git runner preserves cross-repository concurrency", async () => { const slow = gitFixture("slow"); const fast = gitFixture("fast"); const marker = join(slow.root, "pre-receive-entered"); writeFileSync(join(slow.gitea, "hooks", "pre-receive"), `#!/bin/sh\n: > "${marker}"\nsleep 1\nexit 0\n`, { mode: 0o755 }); const coordinator = createSyncCoordinator({ responseBudgetMs: 5000, retryMaxAttempts: 1, retryInitialDelayMs: 1, retryMaxDelayMs: 1, log: () => {}, run, runResult, githubAuthHeader: "", giteaAuthHeader: "", giteaBaseUrl: "file://", }); try { const slowResult = coordinator.execute(slow.repo, fakeDelivery("slow-a", slow.commits.a, slow.repo)); await waitFor(() => existsSync(marker)); const fastResult = coordinator.execute(fast.repo, fakeDelivery("fast-a", fast.commits.a, fast.repo)); const first = await Promise.race([ slowResult.then(() => "slow"), fastResult.then(() => "fast"), ]); assert.equal(first, "fast"); assert.equal((await fastResult).ok, true); assert.equal((await slowResult).ok, true); } finally { slow.cleanup(); fast.cleanup(); } }); test("atomic push rejection leaves branch and snapshot unchanged", async () => { const fixture = gitFixture(); try { const { repo, runtime, commits, gitea } = fixture; assert.equal((await syncRepository(repo, fakeDelivery("a", commits.a, repo), runtime)).ok, true); const hook = join(gitea, "hooks", "pre-receive"); writeFileSync(hook, "#!/bin/sh\nexit 1\n", { mode: 0o755 }); const rejected = await syncRepository(repo, fakeDelivery("b", commits.b, repo), runtime); assert.equal(rejected.ok, false); assert.equal(rejected.errorType, "atomic-push-race-or-failure"); assert.equal(remoteRef(gitea, "refs/heads/main"), commits.a); assert.equal(remoteRef(gitea, `refs/snapshots/source/${commits.b}`), null); } finally { fixture.cleanup(); } }); function gitFixture(key = "fixture") { const root = mkdtempSync(join(tmpdir(), "unidesk-gitea-sync-test-")); const work = join(root, "work"); const source = join(root, "source.git"); const gitea = join(root, "gitea.git"); git("init", "-q", "-b", "main", work); git("-C", work, "config", "user.name", "UniDesk Test"); git("-C", work, "config", "user.email", "test@unidesk.local"); const commits = {}; for (const name of ["a", "b", "c", "d"]) { writeFileSync(join(work, "source.txt"), `${name}\n`); git("-C", work, "add", "source.txt"); git("-C", work, "commit", "-q", "-m", name); commits[name] = git("-C", work, "rev-parse", "HEAD").trim(); } git("clone", "-q", "--bare", work, source); git("init", "-q", "--bare", gitea); const repo = { key, upstream: { repository: `pikasTech/${key}`, branch: "main", cloneUrl: source }, gitea: { readUrl: `file://${gitea}` }, snapshot: { prefix: "refs/snapshots/source" }, }; return { root, work, source, gitea, commits, repo, runtime: { run, runResult, githubAuthHeader: "", giteaAuthHeader: "", giteaBaseUrl: "file://", deadlineAt: Date.now() + 60000, now: Date.now, }, cleanup: () => rmSync(root, { recursive: true, force: true }), }; } function fakeRepo(key) { return { key, upstream: { repository: `pikasTech/${key}`, branch: "main" }, snapshot: { prefix: "refs/snapshots/source" }, }; } function fakeDelivery(deliveryId, requestedCommit, repo) { return { deliveryId, repository: repo.upstream.repository, ref: `refs/heads/${repo.upstream.branch}`, requestedCommit, payloadSha: createHash("sha256").update(`${deliveryId}:${requestedCommit}`).digest("hex"), receivedAt: new Date().toISOString(), }; } function remoteRef(repo, ref) { try { return git("--git-dir", repo, "rev-parse", "--verify", ref).trim(); } catch { return null; } } async function postWebhook(port, repo, deliveryId, requestedCommit, secret, extra = {}) { const body = JSON.stringify({ ref: `refs/heads/${repo.upstream.branch}`, after: requestedCommit, repository: { full_name: repo.upstream.repository }, ...extra, }); const signature = `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`; return await fetch(`http://127.0.0.1:${port}/hook`, { method: "POST", headers: { "content-type": "application/json", "x-github-event": "push", "x-github-delivery": deliveryId, "x-hub-signature-256": signature, }, body, }); } function durableControllerOptions(root, repos, overrides = {}) { return { repos, inbox: { path: join(root, "inbox"), maxBytes: 4 * 1024 * 1024, committedRetentionMs: 60_000, cleanupIntervalMs: 60_000, ...overrides.inbox, }, workerRetry: { maxAttempts: 2, initialDelayMs: 10, maxDelayMs: 20, terminalRetryDelayMs: 50, attemptTimeoutMs: 2000, scanIntervalMs: 10, ...overrides.workerRetry, }, shutdownGraceMs: 50, githubAuthHeader: "", giteaAuthHeader: "", giteaBaseUrl: "file://", run, runResult, log: () => {}, ...overrides, inbox: { path: join(root, "inbox"), maxBytes: 4 * 1024 * 1024, committedRetentionMs: 60_000, cleanupIntervalMs: 60_000, ...overrides.inbox, }, workerRetry: { maxAttempts: 2, initialDelayMs: 10, maxDelayMs: 20, terminalRetryDelayMs: 50, attemptTimeoutMs: 2000, scanIntervalMs: 10, ...overrides.workerRetry, }, }; } async function waitFor(predicate, timeoutMs = 3000) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { if (await predicate()) return; await new Promise((resolve) => setTimeout(resolve, 10)); } throw new Error("condition not reached before timeout"); } function git(...args) { return execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }); }