Files
ihasmail-inbuxa/web/src/lib/__tests__/signatureHtml.test.ts
T
jcoffey-dev a2337f6ad8 Use an example address, and the right name, in the test fixtures
Two things, one of them not what it looked like.

An organizer fixture was built from a real, routable address. Every other
fixture in the codebase uses example.org or example.com, and this
repository is public, so that one was a personal address sitting in
public source for no reason -- the test asserts roles and participation
status and never reads either value. It is [email protected] now.

The names were wrong in the other direction. Three fixtures across two
files said "John Ellis", which is not the maintainer's name; it is
John Coffey. Being a name rather than a routable address, it leaked
nothing, but it was simply incorrect, and incorrect in the sort of place
nobody rereads.

The address and the name are separate questions and got separate answers:
the address is fictional because it is an address, and the name is real
because it is right. A message from [email protected] signed John Coffey
is exactly what these tests mean.

Found while checking, at the maintainer's prompting, whether the repo
leaked anything about the host it runs on. It does not -- the nginx and
deploy files here are the generic examples they claim to be, and the real
ones live in a private repository.
2026-08-27 14:15:00 -07:00

83 lines
4.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { buildMarkerSignature, byteLength, compactHtml, markerOf, signatureTooLong, SIGNATURE_LIMIT } from "../signatureHtml";
describe("signature compaction", () => {
it("strips office cruft and non-essential styles but keeps colours and links", () => {
const src = `<!--[if gte mso 9]><xml>x</xml><![endif]--><div class="WordSection1" style="mso-margin-top-alt:auto;line-height:115%;font-family:'Calibri',sans-serif;color:windowtext"><p class="MsoNormal" style="margin:0cm;font-size:11pt"><span lang="EN-US" style="font-size:12pt;color:#1F4E79;mso-fareast-language:EN-US"><b>John Coffey</b></span><o:p></o:p></p><p><span></span></p><a href="https://linuxexpert.org" target="_blank" data-x="1">linuxexpert.org</a><img src="https://x/y.png" width="100" style="mso-foo:bar"></div>`;
const out = compactHtml(src);
expect(out).not.toContain("mso-");
expect(out).not.toContain("class=");
expect(out).not.toContain("<xml");
expect(out).not.toContain("o:p");
expect(out).toContain("color:#1F4E79");
expect(out).toContain("<b>John Coffey</b>");
expect(out).toContain('href="https://linuxexpert.org"');
expect(out).toContain('width="100"');
expect(out.length).toBeLessThan(src.length / 2);
});
it("builds marker signatures within the limit", () => {
const big = `<div>${"<b>x</b>".repeat(1000)}</div>`;
const m = buildMarkerSignature("blob123", big);
expect(m.htmlSignature.length).toBeLessThanOrEqual(SIGNATURE_LIMIT);
expect(m.textSignature.length).toBeLessThanOrEqual(SIGNATURE_LIMIT);
expect(markerOf(m.htmlSignature)).toEqual({ blobId: "blob123", type: "text/html" });
expect(markerOf("<div>plain</div>")).toBeNull();
});
});
/**
* Stalwart's cap is `value.len() < 2048` on a Rust string — 2047 bytes of
* UTF-8. Measuring with JavaScript's `.length` counts UTF-16 units instead,
* which agrees only for ASCII: an accent is one unit and two bytes, CJK three,
* an emoji two units and four. Every check has to weigh the encoded form or a
* signature we judged to fit comes back rejected.
*/
describe("signature size is measured in bytes", () => {
const sigOf = (html: string) => buildMarkerSignature("blob123", html);
it("counts multi-byte characters at their encoded size", () => {
expect(byteLength("hello")).toBe(5);
expect(byteLength("Grüße")).toBe(7); // two 2-byte characters
expect(byteLength("日本語")).toBe(9); // three 3-byte characters
expect(byteLength("🎉")).toBe(4); // one 4-byte character, two UTF-16 units
});
it("spots a signature that fits in characters but not in bytes", () => {
// Comfortably under the limit counted as characters, well over it as bytes.
const cjk = "日".repeat(1200);
expect(cjk.length).toBeLessThan(SIGNATURE_LIMIT);
expect(signatureTooLong(cjk, cjk)).toBe(true);
});
it("keeps a marker signature within the byte limit for non-ASCII text", () => {
for (const filler of ["ü", "日", "🎉", "x"]) {
const m = sigOf(`<div>${filler.repeat(3000)}</div>`);
expect(byteLength(m.htmlSignature), `html for ${filler}`).toBeLessThanOrEqual(SIGNATURE_LIMIT);
expect(byteLength(m.textSignature), `text for ${filler}`).toBeLessThanOrEqual(SIGNATURE_LIMIT);
expect(markerOf(m.htmlSignature)).toEqual({ blobId: "blob123", type: "text/html" });
}
});
it("never truncates through a surrogate pair", () => {
const m = sigOf(`<div>${"🎉".repeat(3000)}</div>`);
// A split pair leaves a lone surrogate, which encodes as U+FFFD.
expect(m.htmlSignature).not.toContain("");
expect(m.textSignature).not.toContain("");
expect(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/.test(m.textSignature)).toBe(false);
});
it("never truncates through an HTML entity", () => {
// Escaping turns each of these into a 5-character entity; cutting the
// rendered string could leave "&am" behind.
const m = sigOf(`<div>${"a &amp; b <c> ".repeat(400)}</div>`);
expect(m.htmlSignature).not.toMatch(/&[a-z]*$/i);
expect(m.htmlSignature.replace(/&(amp|lt|gt|quot|#39);/g, "")).not.toContain("&");
});
it("leaves a signature that already fits completely alone", () => {
const m = sigOf("<div>Grüße, John</div>");
expect(m.textSignature).toBe("Grüße, John");
expect(m.htmlSignature).not.toContain("…");
});
});