Files
MikrotikManager/shared/api/geoip.test.ts
T

37 lines
3.7 KiB
TypeScript

import assert from "node:assert/strict"
import { test } from "node:test"
import { getGeoipStatus, putGeoipSettings, runGeoipUpdateNow } from "./geoip"
const base = "https://api.example.test"
const status = { ready: false, countryLoaded: false, asnLoaded: false, countryFile: "country.mmdb", asnFile: "asn.mmdb", dir: "geoip", running: false, settings: { enabled: false, updateIntervalSec: 604800, lastCheckAt: null, lastSuccessAt: null, lastError: null, countryBuildAt: null, asnBuildAt: null, updatedAt: "2026-10-01T00:00:00Z" } }
const snapshot = { v: 1, job: "geoip_update", sampledAt: "2026-10-01T00:00:00Z", checked: 2, downloaded: 1, skippedUnchanged: 0, bytes: 0, errors: ["ASN unavailable"] }
const json = (body: unknown) => new Response(JSON.stringify(body))
test("malformed GeoIP status is rejected before UI state; false and nullable metadata stay explicit", async () => {
const original = globalThis.fetch
try {
for (const raw of [{}, null, { ...status, settings: undefined }, { ...status, settings: {} }, { ...status, ready: true }, { ...status, settings: { ...status.settings, updatedAt: "bad" } }]) { globalThis.fetch = async () => json(raw); await assert.rejects(() => getGeoipStatus(base), /Данные GeoIP не подтверждены/) }
globalThis.fetch = async () => json(status); const result = await getGeoipStatus(base); assert.equal(result.ready, false); assert.equal(result.settings.enabled, false); assert.equal(result.settings.lastSuccessAt, null)
} finally { globalThis.fetch = original }
})
test("PUT validates input and confirms sent settings rather than trusting ok", async () => {
const original = globalThis.fetch
try {
let calls = 0; globalThis.fetch = async () => { calls++; return json({}) }; await assert.rejects(() => putGeoipSettings(base, { updateIntervalSec: 0 })); assert.equal(calls, 0)
for (const raw of [{}, { ok: false, status }, { ok: true, status: { ...status, settings: { ...status.settings, enabled: true } } }]) { globalThis.fetch = async () => json(raw); await assert.rejects(() => putGeoipSettings(base, { enabled: false })) }
globalThis.fetch = async () => json({ ok: true, status }); assert.equal((await putGeoipSettings(base, { enabled: false })).status.settings.enabled, false)
} finally { globalThis.fetch = original }
})
test("partial update retains downloaded data and errors; contradictory, negative or empty success is rejected", async () => {
const original = globalThis.fetch
try {
globalThis.fetch = async () => json({ ok: false, snapshot }); const result = await runGeoipUpdateNow(base); assert.equal(result.ok, false); assert.equal(result.snapshot.downloaded, 1); assert.equal(result.snapshot.bytes, 0)
for (const raw of [{}, { ok: true, snapshot }, { ok: false, snapshot: { ...snapshot, checked: -1 } }, { ok: false, snapshot: { ...snapshot, downloaded: 3 } }, { ok: false, snapshot: { ...snapshot, v: 2 } }, { ok: false, snapshot: { ...snapshot, sampledAt: "bad" } }]) { globalThis.fetch = async () => json(raw); await assert.rejects(() => runGeoipUpdateNow(base)) }
} finally { globalThis.fetch = original }
})
test("mutation has one attempt on lost acknowledgment and carries caller cancellation", async () => {
const original = globalThis.fetch
try {
let calls = 0; globalThis.fetch = async () => { calls++; throw Error("lost receipt") }; await assert.rejects(() => runGeoipUpdateNow(base), /lost receipt/); assert.equal(calls, 1)
const controller = new AbortController(); controller.abort(); globalThis.fetch = async (_url, init) => { assert.equal(init?.signal?.aborted, true); throw controller.signal.reason }; await assert.rejects(() => getGeoipStatus(base, { signal: controller.signal }))
} finally { globalThis.fetch = original }
})