Files
gitmost/packages/editor-ext/src/lib/utils.spec.ts
claude code agent 227 7c48bab1f2 test: add unit tests for 10 candidates from issue #139
Adds co-located unit tests for ten targets (client → vitest *.test.ts(x),
server → jest *.spec.ts), plus minimal behavior-preserving extractions/exports
where the issue required a pure function to test:

- encode-wav: WAV header + PCM16 clamping
- editor-ext embed-provider / utils (sanitizeUrl, isInternalFileUrl) / indent
  (export clampIndent)
- label.dto @Matches regex
- move-page.dto vs generateJitteredKeyBetween parity (bug locked via test.failing)
- new-note-button canCreatePage (extracted to can-create-page.ts)
- history-editor diff (extracted pure computeHistoryDiff into history-diff.ts)
- notification getTypesForTab + repo contract (direct-tab divergence locked via
  test.failing)
- search buildTsQuery (extracted + sanitizes operator inputs so adversarial
  queries no longer risk a to_tsquery 500)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 04:13:44 +03:00

55 lines
2.3 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { sanitizeUrl, isInternalFileUrl } from "./utils";
// Security contract tests for the editor URL helpers (utils.ts).
// `sanitizeUrl` wraps @braintree/sanitize-url and maps its "about:blank" XSS
// sentinel to "" so callers can treat empty as "blocked". `isInternalFileUrl`
// decides whether a URL points at our own file-serving routes (used to skip
// external-link affordances). A regression here is a stored-XSS or SSRF vector.
describe("sanitizeUrl", () => {
it("blocks dangerous schemes (returns empty string)", () => {
expect(sanitizeUrl("javascript:alert(1)")).toBe("");
expect(sanitizeUrl("data:text/html,<script>alert(1)</script>")).toBe("");
expect(sanitizeUrl("vbscript:msgbox(1)")).toBe("");
// case-insensitive + leading whitespace must not bypass the filter
expect(sanitizeUrl(" JaVaScRiPt:alert(1)")).toBe("");
});
it("returns empty string for empty / undefined input", () => {
expect(sanitizeUrl(undefined)).toBe("");
expect(sanitizeUrl("")).toBe("");
});
it("allows safe https, relative file and mailto URLs", () => {
// braintree normalises https URLs (may add a trailing slash); just assert
// the scheme survives and it is not blanked out.
expect(sanitizeUrl("https://example.com/page")).toMatch(/^https:\/\/example\.com\/page/);
expect(sanitizeUrl("/api/files/abc-123")).toBe("/api/files/abc-123");
expect(sanitizeUrl("mailto:user@example.com")).toBe("mailto:user@example.com");
});
});
describe("isInternalFileUrl", () => {
it("is true only for /api/files/ and /files/ prefixes", () => {
expect(isInternalFileUrl("/api/files/abc")).toBe(true);
expect(isInternalFileUrl("/files/abc")).toBe(true);
});
it("trims whitespace before matching the prefix", () => {
expect(isInternalFileUrl(" /api/files/abc")).toBe(true);
expect(isInternalFileUrl("\t/files/abc")).toBe(true);
});
it("is false for external URLs and other paths", () => {
expect(isInternalFileUrl("https://example.com/api/files/abc")).toBe(false);
expect(isInternalFileUrl("/other/files/abc")).toBe(false);
expect(isInternalFileUrl("/apifiles/abc")).toBe(false);
});
it("is false for empty / undefined input", () => {
expect(isInternalFileUrl(undefined)).toBe(false);
expect(isInternalFileUrl("")).toBe(false);
});
});