Adds unit tests for five pieces of security-critical code that previously
had zero coverage, closing the warning-level findings of the ai-chat
multi-aspect review (docs/backlog/ai-chat-review-followups.md, priority 1).
- crypto/secret-box.spec.ts (NEW): AES-256-GCM round-trip; non-determinism
(two encrypts of the same input yield different blobs, both decrypt);
tampered authTag / ciphertext bytes throw with the 'APP_SECRET may have
changed' message; wrong APP_SECRET throws the same. Guards the only
at-rest protection of provider API keys.
- ai-chat/external-mcp/ssrf-guard.spec.ts (NEW): isIpAllowed blocks every
forbidden class (loopback, link-local incl. metadata 169.254.169.254,
private, CGNAT, ULA, unspecified, IPv4-mapped IPv6, unparseable) and
allows a public IP; isUrlAllowed rejects bad scheme / invalid URL,
blocks IP-literal private, and (with a mocked dns.lookup) blocks
DNS-rebinding to a private address and an unresolvable host.
- ai-chat/ai-chat.service.spec.ts (extended): assistantParts now covered
- paired tool call -> output-available (compacted), unpaired call ->
output-error with 'Tool call did not complete.' (regression guard for
the MissingToolResultsError fix), broken calls skipped, step text and
fallback text paths. Requires exporting assistantParts + StepLike (the
only production change here, two export keywords).
- ai-chat/tools/ai-chat-tools.service.spec.ts (extended): JSON-string
coercion in patchNode / insertNode / updatePageJson - string parsed to
object, invalid JSON throws the specific message, updatePageJson
distinguishes undefined (title-only) / object / string. Guards the
OpenAI tool-call compatibility fix.
- database/repos/ai-chat/page-embedding.repo.spec.ts (NEW):
searchByEmbedding with empty spaceIds returns [] without touching the
DB (Proxy stub throws on any access). Guards the access-scoping
early-return.
54 new tests, all green. No functional behaviour changed.