33 lines
2.9 KiB
TypeScript
33 lines
2.9 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import { test } from "node:test"
|
|
import { pollBackupJob, backupJobOutcome, waitForBackupPoll } from "./backup-job-poll"
|
|
import type { BackupJobStatusResponse } from "@mmapp/contracts/backups"
|
|
const target = { serverIds: ["1"], notes: "manual" }
|
|
const baseJob: BackupJobStatusResponse = { id: "job", status: "queued", serverIds: ["1"], notes: "manual", total: 1, completed: 0, requestedAt: "2026-10-01T00:00:00Z", created: [], failures: [] }
|
|
const file = { id: "backup", serverId: 1, serverName: "router", filename: "config.rsc", sizeBytes: 0, createdAt: "2026-10-01T00:00:00Z", kind: "manual" as const, storage: "local" as const }
|
|
test("serial queued/running reads complete only on terminal observation", async () => {
|
|
let reads = 0, inFlight = 0, maximum = 0
|
|
const job = await pollBackupJob("base", "job", target, { signal: new AbortController().signal, wait: async () => {}, readJob: async () => { inFlight++; maximum = Math.max(maximum, inFlight); await Promise.resolve(); const status = ++reads < 3 ? "running" : "done"; inFlight--; return { ...baseJob, status, ...(status === "done" ? { completed: 1, created: [file], finishedAt: "2026-10-01T00:01:00Z" } : {}) } } })
|
|
assert.equal(reads, 3); assert.equal(maximum, 1); assert.equal(backupJobOutcome(job).fullSuccess, true)
|
|
})
|
|
test("partial device and upload errors never become full success", () => {
|
|
for (const patch of [{ created: [], failures: [{ serverId: "1", error: "offline" }] }, { created: [{ ...file, uploadError: "S3 unavailable" }], failures: [] }]) {
|
|
assert.equal(backupJobOutcome({ ...baseJob, status: "done", completed: 1, ...patch }).fullSuccess, false)
|
|
}
|
|
})
|
|
test("foreign target is rejected before onJob", async () => {
|
|
let observations = 0
|
|
await assert.rejects(() => pollBackupJob("base", "job", target, { signal: new AbortController().signal, onJob: () => { observations++ }, readJob: async () => ({ ...baseJob, serverIds: ["2"] }) }), /не соответствует/)
|
|
assert.equal(observations, 0)
|
|
})
|
|
test("read failure or exhausted observation budget is not a failed job", async () => {
|
|
await assert.rejects(() => pollBackupJob("base", "job", target, { signal: new AbortController().signal, readJob: async () => { throw new Error("GET unavailable") } }), /GET unavailable/)
|
|
await assert.rejects(() => pollBackupJob("base", "job", target, { signal: new AbortController().signal, attempts: 1, readJob: async () => baseJob }), /Время наблюдения/)
|
|
})
|
|
test("cancellation interrupts delay and suppresses late observation", async () => {
|
|
const abort = new AbortController(), wait = waitForBackupPoll(10_000, abort.signal); abort.abort(); await assert.rejects(wait)
|
|
const late = new AbortController(); let calls = 0
|
|
await assert.rejects(() => pollBackupJob("base", "job", target, { signal: late.signal, onJob: () => { calls++ }, readJob: async () => { late.abort(); return baseJob } }))
|
|
assert.equal(calls, 0)
|
|
})
|