fix(backups): сохранить идентичность фоновой задачи
This commit is contained in:
1 parent
30bd479a6f
commit
4aee2a34f1
13 files changed
+157
-12
No files matched your search
@@ -6,7 +6,7 @@ const base = "https://api.example.test"
|
||||
const item = { id: "backup-1", serverId: 1, serverName: "router", filename: "config.rsc", sizeBytes: 0, createdAt: "2026-10-01T00:00:00Z", kind: "manual", storage: "local" }
|
||||
const schedule = { enabled: false, frequency: "daily", hour: 0, minute: 0, weekDay: 0, monthDay: 1, keepCount: 7, format: "rsc", serverIds: [] }
|
||||
const storage = { provider: "local", s3Endpoint: "", s3Region: "", s3Bucket: "", s3Prefix: "", s3AccessKeyId: "", secretConfigured: false, s3ForcePathStyle: false, keepLocalCopy: false }
|
||||
const job = { id: "job-1", status: "done", total: 2, completed: 2, requestedAt: "2026-10-01T00:00:00Z", finishedAt: "2026-10-01T00:01:00Z", created: [item], failures: [{ serverId: "2", error: "RouterOS unavailable" }] }
|
||||
const job = { id: "job-1", serverIds: ["1", "2"], status: "done", total: 2, completed: 2, requestedAt: "2026-10-01T00:00:00Z", finishedAt: "2026-10-01T00:01:00Z", created: [item], failures: [{ serverId: "2", error: "RouterOS unavailable" }] }
|
||||
function json(payload: unknown, status = 200) { return new Response(JSON.stringify(payload), { status }) }
|
||||
|
||||
test("known empty and zero are preserved; malformed catalogue and duplicates rejected", async () => {
|
||||
@@ -35,7 +35,7 @@ test("settings require a confirmed response and preserve false rather than defau
|
||||
})
|
||||
test("invalid mutations fail before HTTP; create receives job acknowledgment", async () => {
|
||||
const original = globalThis.fetch; let calls = 0, body: unknown
|
||||
globalThis.fetch = async (_url, init) => { calls++; body = JSON.parse(String(init?.body)); return json({ jobId: "job-1", status: "queued", total: 1, completed: 0 }, 202) }
|
||||
globalThis.fetch = async (_url, init) => { calls++; body = JSON.parse(String(init?.body)); return json({ jobId: "job-1", serverIds: ["1"], status: "queued", total: 1, completed: 0 }, 202) }
|
||||
try {
|
||||
await assert.rejects(() => createBackupsAsync(base, { serverIds: [] }))
|
||||
await assert.rejects(() => createBackupsAsync(base, { serverIds: ["1", "1"] }))
|
||||
@@ -44,7 +44,7 @@ test("invalid mutations fail before HTTP; create receives job acknowledgment", a
|
||||
assert.equal(calls, 0)
|
||||
assert.equal((await createBackupsAsync(base, { serverIds: ["1"], notes: "manual" })).jobId, "job-1")
|
||||
assert.deepEqual(body, { serverIds: ["1"], notes: "manual" })
|
||||
globalThis.fetch = async () => json({ jobId: "job-1", status: "queued", total: 2, completed: 0 }, 202); await assert.rejects(() => createBackupsAsync(base, { serverIds: ["1"] }), /всех выбранных/);
|
||||
globalThis.fetch = async () => json({ jobId: "job-1", serverIds: ["1", "2"], status: "queued", total: 2, completed: 0 }, 202); await assert.rejects(() => createBackupsAsync(base, { serverIds: ["1"] }), /выбранные серверы/);
|
||||
globalThis.fetch = async () => json({ created: [item], failures: [] }); await assert.rejects(() => createBackupsAsync(base, { serverIds: ["1"] }))
|
||||
} finally { globalThis.fetch = original }
|
||||
})
|
||||
@@ -90,3 +90,9 @@ test("settings acknowledgment must match submitted fields and confirm a new secr
|
||||
await assert.rejects(() => putBackupStorageSettings(base, { s3SecretAccessKey: "new-test-secret" }), /ключа/)
|
||||
} finally { globalThis.fetch = original }
|
||||
})
|
||||
|
||||
test("backup request ID is sent and a different acknowledgment is rejected", async () => {
|
||||
const original = globalThis.fetch, requestId = "11111111-1111-4111-8111-111111111111"; let body: Record<string, unknown> = {}
|
||||
globalThis.fetch = async (_url, init) => { body = JSON.parse(String(init?.body)); return json({ jobId: "other", serverIds: ["1"], status: "queued", total: 1, completed: 0 }, 202) }
|
||||
try { await assert.rejects(() => createBackupsAsync(base, { requestId, serverIds: ["1"] }), /другому запросу/); assert.equal(body.requestId, requestId) } finally { globalThis.fetch = original }
|
||||
})
|
||||
@@ -25,9 +25,11 @@ function backupJson(baseUrl: string, path: string, options?: RequestInit) {
|
||||
export async function listBackups(baseUrl: string, options?: RequestInit) {
|
||||
return backupsListResponseSchema.parse(await backupJson(baseUrl, "/api/backups", { ...options, method: "GET" }))
|
||||
}
|
||||
export async function createBackupsAsync(baseUrl: string, payload: { serverIds: string[]; notes?: string }, options?: RequestInit) {
|
||||
export async function createBackupsAsync(baseUrl: string, payload: { requestId?: string; serverIds: string[]; notes?: string }, options?: RequestInit) {
|
||||
const body = createBackupRequestSchema.parse(payload)
|
||||
const result = createBackupJobResponseSchema.parse(await backupJson(baseUrl, "/api/backups/create", { ...options, method: "POST", body: JSON.stringify(body) }))
|
||||
if (body.requestId && result.jobId !== body.requestId) throw new Error("Подтверждение относится к другому запросу резервного копирования")
|
||||
if (JSON.stringify(result.serverIds) !== JSON.stringify(body.serverIds.map(String))) throw new Error("Не подтверждены выбранные серверы резервного копирования")
|
||||
if (result.total !== body.serverIds.length) throw new Error("Не подтверждено резервное копирование всех выбранных серверов")
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user