import { describe, it, expect, beforeAll, beforeEach } from "vitest"; import { Database } from "bun:sqlite"; import { sanitizeCommunityContent } from "~/server/lib/sanitize"; import { requireClubMembership, resolveClubIdFromPost, type NessaConn } from "./nessa-community-authz"; /** * Tests for p8-012: sanitize community post/comment content on write. * * Content model: plain text — no HTML is stored. The iOS client renders * with SwiftUI `Text()` (not a WebView), so there is no render-time XSS * surface. Sanitization on write is defense-in-depth against a future HTML * render path. * * The router's `social.createPost` and `social.addComment` run * `sanitizeCommunityContent(input.content)` before the INSERT. These tests * verify the sanitizer directly and then exercise the full write path * against an in-memory SQLite DB wrapped to match the libsql contract. */ // --------------------------------------------------------------------------- // Sanitizer unit tests // --------------------------------------------------------------------------- describe("sanitizeCommunityContent", () => { it("strips hello") ).toBe("alert(1)hello"); }); it("strips safe" ) ).toBe("alert('xss') safe"); }); it("strips event handlers", () => { expect( sanitizeCommunityContent( 'check this out' ) ).toBe("check this out"); }); it("strips ", () => { expect( sanitizeCommunityContent('text') ).toBe("text"); }); it("strips ", () => { expect( sanitizeCommunityContent('body') ).toBe("body"); }); it("strips ", () => { expect( sanitizeCommunityContent( 'text' ) ).toBe("text"); }); it("strips ", () => { expect( sanitizeCommunityContent( 'click here now' ) ).toBe("click here now"); }); it("strips text' ) ).toBe("body{background:red}text"); }); it("strips tags", () => { expect( sanitizeCommunityContent( 'text' ) ).toBe("text"); }); it("strips self-closing tags", () => { expect( sanitizeCommunityContent('text
more
end') ).toBe("textmoreend"); }); it("handles HTML entities — no round-trip through future HTML renderer", () => { // `<script>` decoded to `bold

end' ) ).toBe("alert(1)boldend"); }); it("handles unclosed tags", () => { expect(sanitizeCommunityContent("
text")).toBe("text"); expect(sanitizeCommunityContent("text
")).toBe("text"); }); it("strips , , ", () => { expect( sanitizeCommunityContent( 'text' ) ).toBe("text"); expect( sanitizeCommunityContent('text') ).toBe("text"); expect( sanitizeCommunityContent('text') ).toBe("text"); }); it("strips data: URI tags — remaining text is harmless as plain text", () => { // The tag and its attribute are stripped; the inner text remains. // As plain text, the leftover characters are not executable. const result = sanitizeCommunityContent( 'link' ); expect(result).not.toContain("' ) ).toBe("alert(1)"); }); it("strips
/ /