Files
MikrotikManager/shared/api/ipsec-export.test.ts
T

106 lines
5.5 KiB
TypeScript

import assert from "node:assert/strict"
import { test } from "node:test"
import { exportIpsecCertByName, exportIpsecUserCert, type IpsecExistingCertExportOptions } from "./ipsec"
const base = "https://fixture.example.test"
const options: IpsecExistingCertExportOptions = { privateKeyExport: true, caPrivateKeyExport: false, requestKey: "11111111-1111-4111-8111-111111111111" }
const passphrase = " synthetic-passphrase "
const bundle = { user: "Renamed label", filename: "fixture.p12", contentB64: "YQ==", passphrase }
test("existing exports reject missing/invalid explicit intent before fetch", async () => {
const original = globalThis.fetch
let calls = 0
globalThis.fetch = async () => { calls++; throw Error("Unexpected fetch") }
try {
for (const invalid of [undefined, {}, { ...options, privateKeyExport: false }, { ...options, caPrivateKeyExport: undefined }, { ...options, requestKey: "invalid" }, { ...options, requestKey: undefined }, { ...options, extra: true }]) {
const intent = invalid as IpsecExistingCertExportOptions
await assert.rejects(() => exportIpsecUserCert(base, "1", "identity:*1", passphrase, intent))
await assert.rejects(() => exportIpsecCertByName(base, "1", "cert", passphrase, intent))
}
await assert.rejects(() => exportIpsecUserCert(base, "1", "identity:*1", "short", options))
assert.equal(calls, 0)
} finally { globalThis.fetch = original }
})
test("client export preserves native identity selector, raw passphrase, flags and caller UUID", async () => {
const original = globalThis.fetch
let calls = 0
globalThis.fetch = async (url, init) => {
calls++
assert.equal(new URL(String(url)).pathname.endsWith("/identity%3A*1%2F2/cert"), true)
assert.equal(init?.method, "POST")
const headers = new Headers(init?.headers), body = JSON.parse(String(init?.body))
assert.equal(headers.get("Idempotency-Key") === options.requestKey, true)
assert.equal(headers.get("Content-Type"), "application/json")
// Compare as booleans so assertion failures cannot print private material.
assert.equal(body.passphrase === passphrase, true)
assert.equal(body.privateKeyExport, true); assert.equal(body.caPrivateKeyExport, false)
assert.equal(Object.hasOwn(body, "requestKey"), false); assert.equal(Object.hasOwn(body, "name"), false)
return new Response(JSON.stringify(bundle))
}
try {
const result = await exportIpsecUserCert(base, "1", "identity:*1/2", passphrase, options)
assert.equal(result.passphrase === passphrase, true); assert.equal(calls, 1)
} finally { globalThis.fetch = original }
})
test("by-name export supports client/server/foreign/CA targets and explicit CA intent", async () => {
const original = globalThis.fetch
let expectedName = "", expectedCa = false, calls = 0
globalThis.fetch = async (url, init) => {
calls++
assert.equal(new URL(String(url)).pathname, "/api/ipsec/certs/1/export")
const body = JSON.parse(String(init?.body))
assert.equal(body.name === expectedName, true); assert.equal(body.passphrase === passphrase, true)
assert.equal(body.privateKeyExport, true); assert.equal(body.caPrivateKeyExport, expectedCa)
assert.equal(new Headers(init?.headers).get("Idempotency-Key") === options.requestKey, true)
return new Response(JSON.stringify(bundle))
}
try {
for (const [name, ca] of [["client", false], ["server", false], ["foreign", false], ["ca", true]] as const) {
expectedName = name; expectedCa = ca
await exportIpsecCertByName(base, "1", name, passphrase, { ...options, caPrivateKeyExport: ca })
}
assert.equal(calls, 4)
} finally { globalThis.fetch = original }
})
test("failed, unknown and malformed responses never retry or allocate another request UUID", async () => {
const original = globalThis.fetch
try {
for (const mode of ["network", "unknown", "malformed"] as const) {
let calls = 0
globalThis.fetch = async (_url, init) => {
calls++
assert.equal(new Headers(init?.headers).get("Idempotency-Key") === options.requestKey, true)
if (mode === "network") throw Error("Synthetic transport failure")
if (mode === "unknown") return new Response(JSON.stringify({ error: "Результат неизвестен", state: "unknown", needsReview: true }), { status: 502 })
return new Response(JSON.stringify({ ...bundle, contentB64: "invalid!" }))
}
await assert.rejects(() => exportIpsecUserCert(base, "1", "identity:*1", passphrase, options))
assert.equal(calls, 1)
await assert.rejects(() => exportIpsecCertByName(base, "1", "client", passphrase, options))
assert.equal(calls, 2)
}
} finally { globalThis.fetch = original }
})
test("closing an export can abort its one fetch without changing explicit intent", async () => {
const original = globalThis.fetch, controller = new AbortController()
let calls = 0
globalThis.fetch = async (_url, init) => {
calls++
assert.equal(init?.signal === controller.signal, true)
assert.equal(new Headers(init?.headers).get("Idempotency-Key") === options.requestKey, true)
return new Promise<Response>((_resolve, reject) => {
init?.signal?.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true })
})
}
try {
const pending = exportIpsecUserCert(base, "1", "identity:*1", passphrase, options, { signal: controller.signal })
controller.abort()
await assert.rejects(pending, error => error instanceof DOMException && error.name === "AbortError")
assert.equal(calls, 1)
} finally { globalThis.fetch = original }
})