1import { describe, expect, test } from "vitest";2import {3 decodePeachScript,4 encodePeachScript,5 isPeachScript,6 normalisePeachScript,7} from "./peachScript";8import { IMPORT_LIMITS } from "../shared/import-limits";910describe("peachScript decode size bound (PB-26-010)", () => {11 test("decode rejects input above MAX_JSON_BYTES before allocating", () => {12 13 14 const chunk = String.fromCodePoint(0xe000).repeat(1024);15 const tooLarge = chunk.repeat(16 Math.floor(IMPORT_LIMITS.MAX_JSON_BYTES / 1024) + 1,17 );18 expect(tooLarge.length).toBeGreaterThan(IMPORT_LIMITS.MAX_JSON_BYTES);19 expect(() => decodePeachScript(tooLarge)).toThrow(20 "PEACHSCRIPT_TOO_LARGE",21 );22 });2324 test("decode accepts input at the bound", () => {25 const atLimit = String.fromCodePoint(26 ...Array.from({ length: 64 }, () => 0xe041),27 );28 expect(decodePeachScript(atLimit).length).toBe(64);29 });30});3132describe("peachScript", () => {33 test("byte 0 maps to U+E000", () => {34 const out = encodePeachScript(new Uint8Array([0]));35 expect(out.codePointAt(0)).toBe(0xe000);36 });3738 test("byte 255 maps to U+E0FF", () => {39 const out = encodePeachScript(new Uint8Array([255]));40 expect(out.codePointAt(0)).toBe(0xe0ff);41 });4243 test("round-trip identity for all 256 byte values", () => {44 const bytes = new Uint8Array(256);45 for (let i = 0; i < 256; i++) bytes[i] = i;46 const encoded = encodePeachScript(bytes);47 const decoded = decodePeachScript(encoded);48 expect(decoded).toEqual(bytes);49 });5051 test("empty bytes round-trip to empty string and back", () => {52 expect(encodePeachScript(new Uint8Array())).toBe("");53 expect(decodePeachScript("")).toEqual(new Uint8Array());54 });5556 test("decode throws on character outside PUA range", () => {57 expect(() => decodePeachScript("hello")).toThrow("INVALID_PEACHSCRIPT_CHARACTER");58 expect(() => decodePeachScript(String.fromCodePoint(0xe100))).toThrow(59 "INVALID_PEACHSCRIPT_CHARACTER"60 );61 expect(() => decodePeachScript(String.fromCodePoint(0xdfff))).toThrow(62 "INVALID_PEACHSCRIPT_CHARACTER"63 );64 });6566 test("random-payload round-trip preserves bytes", () => {67 const payload = crypto.getRandomValues(new Uint8Array(2048));68 const decoded = decodePeachScript(encodePeachScript(payload));69 expect(decoded).toEqual(payload);70 });7172 test("normalisePeachScript strips whitespace", () => {73 const encoded = encodePeachScript(new Uint8Array([1, 2, 3, 4]));74 const wrapped = encoded.slice(0, 2) + "\n " + encoded.slice(2);75 expect(normalisePeachScript(wrapped)).toBe(encoded);76 });7778 test("isPeachScript accepts only PUA strings", () => {79 expect(isPeachScript(encodePeachScript(new Uint8Array([1, 2, 3])))).toBe(true);80 expect(isPeachScript("")).toBe(false);81 expect(isPeachScript("hello")).toBe(false);82 const mixed = encodePeachScript(new Uint8Array([1])) + "x";83 expect(isPeachScript(mixed)).toBe(false);84 });85});8687describe("peachScript fuzz corpus", () => {88 89 test("rejects U+E100 (off-by-one out of range)", () => {90 expect(() => decodePeachScript(String.fromCodePoint(0xe100))).toThrow(91 "INVALID_PEACHSCRIPT_CHARACTER"92 );93 });9495 test("rejects U+E200 (far out of range)", () => {96 expect(() => decodePeachScript(String.fromCodePoint(0xe200))).toThrow(97 "INVALID_PEACHSCRIPT_CHARACTER"98 );99 });100101 102 test("rejects lone low surrogate U+DFFF", () => {103 expect(() => decodePeachScript(String.fromCodePoint(0xdfff))).toThrow(104 "INVALID_PEACHSCRIPT_CHARACTER"105 );106 });107108 test("rejects lone high surrogate U+D800", () => {109 expect(() => decodePeachScript(String.fromCodePoint(0xd800))).toThrow(110 "INVALID_PEACHSCRIPT_CHARACTER"111 );112 });113114 115 test("rejects U+1D2E0 (real Mayan numeral — outside codec range)", () => {116 expect(() => decodePeachScript("\u{1D2E0}")).toThrow(117 "INVALID_PEACHSCRIPT_CHARACTER"118 );119 });120121 122 test("rejects BOM (U+FEFF) mid-stream", () => {123 const encoded = encodePeachScript(new Uint8Array([0x41, 0x42]));124 125 const withBOM = encoded[0] + String.fromCodePoint(0xfeff) + encoded[1];126 expect(() => decodePeachScript(withBOM)).toThrow("INVALID_PEACHSCRIPT_CHARACTER");127 });128129 test("rejects RTL mark (U+200F) mid-stream", () => {130 const encoded = encodePeachScript(new Uint8Array([0x41]));131 132 expect(() => decodePeachScript(encoded + String.fromCodePoint(0x200f))).toThrow(133 "INVALID_PEACHSCRIPT_CHARACTER"134 );135 });136137 test("rejects zero-width non-joiner (U+200C)", () => {138 const encoded = encodePeachScript(new Uint8Array([0x41]));139 140 expect(() => decodePeachScript(encoded + String.fromCodePoint(0x200c))).toThrow(141 "INVALID_PEACHSCRIPT_CHARACTER"142 );143 });144145 146 test("rejects space between valid rune characters", () => {147 const encoded = encodePeachScript(new Uint8Array([0x41, 0x42]));148 const withSpace = encoded[0] + " " + encoded[1];149 expect(() => decodePeachScript(withSpace)).toThrow("INVALID_PEACHSCRIPT_CHARACTER");150 });151152 test("rejects newline between valid rune characters", () => {153 const encoded = encodePeachScript(new Uint8Array([0x41, 0x42]));154 const withNewline = encoded[0] + "\n" + encoded[1];155 expect(() => decodePeachScript(withNewline)).toThrow("INVALID_PEACHSCRIPT_CHARACTER");156 });157158 159 test("byte 0x00 round-trips correctly", () => {160 const data = new Uint8Array([0x00]);161 expect(decodePeachScript(encodePeachScript(data))).toEqual(data);162 });163164 test("byte 0xFE round-trips correctly", () => {165 const data = new Uint8Array([0xfe]);166 expect(decodePeachScript(encodePeachScript(data))).toEqual(data);167 });168169 test("byte 0xFF round-trips correctly", () => {170 const data = new Uint8Array([0xff]);171 expect(decodePeachScript(encodePeachScript(data))).toEqual(data);172 });173174 175 test("1 KiB deterministic payload round-trips", () => {176 const data = new Uint8Array(1024);177 for (let i = 0; i < 1024; i++) data[i] = (i * 37 + 13) % 256;178 expect(decodePeachScript(encodePeachScript(data))).toEqual(data);179 });180181 182 183 test("rejects large all-invalid input in well under 50 ms", () => {184 const invalid = String.fromCodePoint(0xe100).repeat(10_000);185 const start = Date.now();186 expect(() => decodePeachScript(invalid)).toThrow("INVALID_PEACHSCRIPT_CHARACTER");187 expect(Date.now() - start).toBeLessThan(50);188 });189190 191 test("rejects left double quotation mark (U+201C) prefix", () => {192 const encoded = encodePeachScript(new Uint8Array([0x41]));193 expect(() => decodePeachScript(String.fromCodePoint(0x201c) + encoded)).toThrow(194 "INVALID_PEACHSCRIPT_CHARACTER"195 );196 });197});