fix(collectors): проверять параметры и подтверждения сохранения
This commit is contained in:
1 parent
d453f2aec3
commit
117aa9a21a
5 files changed
+66
-2
No files matched your search
@@ -0,0 +1,24 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { test } from "node:test"
|
||||
import { putTrafficCollectorSettings, putServersApiPingSettings, putUptimeCollectorSettings, putInternetPathSettings } from "./collector-settings"
|
||||
const common = { enabled: false, intervalSec: 300, retentionDays: 14, lastCollectedAt: null, lastDurationMs: 0, lastError: null }
|
||||
const uptime = { ...common, resourcesEnabled: false, pingEnabled: false, speedEnabled: false, probeIntervalSec: 15, speedIntervalSec: 60 }
|
||||
for (const [name, put, path, dto] of [
|
||||
["traffic", putTrafficCollectorSettings, "/api/traffic/settings", common],
|
||||
["REST ping", putServersApiPingSettings, "/api/servers-api-ping/settings", common],
|
||||
["uptime", putUptimeCollectorSettings, "/api/uptime/settings", uptime],
|
||||
["internet", putInternetPathSettings, "/api/internet-path/settings", common],
|
||||
] as const) test(name + ": selective PUT requires exact explicit receipt without retries", async () => {
|
||||
const original = globalThis.fetch; let calls = 0
|
||||
try {
|
||||
globalThis.fetch = async (url, init) => { calls++; assert.equal(new URL(String(url)).pathname, path); assert.equal(init?.method, "PUT"); assert.deepEqual(JSON.parse(String(init.body)), { intervalSec: 300 }); return new Response(JSON.stringify({ ok: true, settings: dto })) }
|
||||
assert.equal((await put("http://fixture.test", { intervalSec: 300 })).intervalSec, 300); assert.equal(calls, 1)
|
||||
for (const raw of [{}, { ok: false, settings: dto }, { ok: true, settings: {} }, { ok: true, settings: { ...dto, intervalSec: 301 } }]) {
|
||||
calls = 0; globalThis.fetch = async () => { calls++; return new Response(JSON.stringify(raw)) }
|
||||
await assert.rejects(() => put("http://fixture.test", { intervalSec: 300 }), /подтвер/); assert.equal(calls, 1)
|
||||
}
|
||||
for (const payload of [{ intervalSec: 0 }, { intervalSec: 30.5 }, { intervalSec: NaN }, {}]) {
|
||||
calls = 0; await assert.rejects(() => put("http://fixture.test", payload)); assert.equal(calls, 0)
|
||||
}
|
||||
} finally { globalThis.fetch = original }
|
||||
})
|
||||
@@ -24,3 +24,31 @@ export function getUptimeCollectorSettings(baseUrl: string, init?: RequestInit)
|
||||
export function getInternetPathSettings(baseUrl: string, init?: RequestInit) {
|
||||
return settings(baseUrl, "/api/internet-path/settings", retained, init)
|
||||
}
|
||||
|
||||
const trafficPatch = z.object({ enabled: z.boolean().optional(), intervalSec: count.min(5).optional(), retentionDays: count.positive().optional() }).strict()
|
||||
const restPatch = z.object({ enabled: z.boolean().optional(), intervalSec: count.min(10).optional() }).strict()
|
||||
const uptimePatch = z.object({ resourcesEnabled: z.boolean().optional(), pingEnabled: z.boolean().optional(), speedEnabled: z.boolean().optional(), intervalSec: count.min(5).optional(), probeIntervalSec: count.min(5).optional(), speedIntervalSec: count.min(10).optional(), retentionDays: count.positive().optional() }).strict()
|
||||
const internetPatch = z.object({ enabled: z.boolean().optional(), intervalSec: count.min(30).optional(), retentionDays: count.positive().optional() }).strict()
|
||||
async function putSettings<T, P extends Record<string, unknown>>(baseUrl: string, path: string, payload: P, input: z.ZodType<P>, output: z.ZodType<T>, init?: RequestInit): Promise<T> {
|
||||
const valid = input.safeParse(payload)
|
||||
if (!valid.success || !Object.keys(valid.data).some(key => valid.data[key] !== undefined)) throw new Error("Укажите допустимые целые значения настроек сборщика")
|
||||
const body = valid.data
|
||||
const raw = await requestJson<unknown>(baseUrl, path, { ...init, method: "PUT", body: JSON.stringify(body), signal: AbortSignal.any([...(init?.signal ? [init.signal] : []), AbortSignal.timeout(30_000)]) })
|
||||
const parsed = z.object({ ok: z.literal(true), settings: output }).safeParse(raw)
|
||||
if (!parsed.success) throw new Error("Сохранение настроек не подтверждено. Обновите данные перед повторной попыткой")
|
||||
const confirmed = parsed.data.settings as Record<string, unknown>
|
||||
if (Object.entries(body).some(([key, value]) => value !== undefined && confirmed[key] !== value)) throw new Error("Сервер подтвердил другие настройки. Обновите данные перед повторной попыткой")
|
||||
return parsed.data.settings
|
||||
}
|
||||
export function putTrafficCollectorSettings(baseUrl: string, payload: z.infer<typeof trafficPatch>, init?: RequestInit) {
|
||||
return putSettings(baseUrl, "/api/traffic/settings", payload, trafficPatch, retained, init)
|
||||
}
|
||||
export function putServersApiPingSettings(baseUrl: string, payload: z.infer<typeof restPatch>, init?: RequestInit) {
|
||||
return putSettings(baseUrl, "/api/servers-api-ping/settings", payload, restPatch, common, init)
|
||||
}
|
||||
export function putUptimeCollectorSettings(baseUrl: string, payload: z.infer<typeof uptimePatch>, init?: RequestInit) {
|
||||
return putSettings(baseUrl, "/api/uptime/settings", payload, uptimePatch, uptime, init)
|
||||
}
|
||||
export function putInternetPathSettings(baseUrl: string, payload: z.infer<typeof internetPatch>, init?: RequestInit) {
|
||||
return putSettings(baseUrl, "/api/internet-path/settings", payload, internetPatch, retained, init)
|
||||
}
|
||||
Reference in new issue
Block a user