|
| 1 | +/** |
| 2 | + * The flash cookie's size bound (#3137). A cookie has a hard ceiling and no |
| 3 | + * failure signal: past ~4096 bytes of name=value the browser discards the |
| 4 | + * whole Set-Cookie — nothing in the response, nothing in the console, |
| 5 | + * nothing server-side — and the page after the no-JS redirect is |
| 6 | + * indistinguishable from one where nothing was submitted. The mutation has |
| 7 | + * already COMMITTED by then, so a vanished outcome invites the retry that |
| 8 | + * writes twice. The encoder degrades instead of vanishing: the input echo |
| 9 | + * goes first, then the value is bounded, and `url` plus the error/thrown |
| 10 | + * flags always survive, arriving with `truncated` set. |
| 11 | + * |
| 12 | + * Runs against the built bundles like the other server-function specs. |
| 13 | + */ |
| 14 | +import { describe, expect, it } from "vitest"; |
| 15 | +import { decodeFlashCookie, encodeFlashCookie } from "@solidjs/web/server-functions/server"; |
| 16 | + |
| 17 | +// what the browser stores: the name=value pair, before the attributes |
| 18 | +function pairOf(setCookie: string) { |
| 19 | + return setCookie.slice(0, setCookie.indexOf("; ")); |
| 20 | +} |
| 21 | + |
| 22 | +function roundTrip(setCookie: string) { |
| 23 | + return decodeFlashCookie(pairOf(setCookie)); |
| 24 | +} |
| 25 | + |
| 26 | +describe("the flash cookie stays under the browser's ceiling", () => { |
| 27 | + it("passes a small outcome through whole, untruncated", () => { |
| 28 | + const form = new FormData(); |
| 29 | + form.set("sku", "A-1"); |
| 30 | + const cookie = encodeFlashCookie("/checkout", { receipt: "RCPT-1" }, [form]); |
| 31 | + const submission = roundTrip(cookie)!; |
| 32 | + expect(submission.url).toBe("/checkout"); |
| 33 | + expect(submission.result).toEqual({ receipt: "RCPT-1" }); |
| 34 | + expect(submission.truncated).toBeUndefined(); |
| 35 | + expect((submission.input[0] as FormData).get("sku")).toBe("A-1"); |
| 36 | + }); |
| 37 | + |
| 38 | + it("drops the input echo first, keeping a result that still fits", () => { |
| 39 | + const form = new FormData(); |
| 40 | + form.set("note", "x".repeat(8000)); // the submission is the bulk |
| 41 | + const cookie = encodeFlashCookie("/save", { id: 7, ok: true }, [form]); |
| 42 | + expect(pairOf(cookie).length).toBeLessThanOrEqual(4096); |
| 43 | + const submission = roundTrip(cookie)!; |
| 44 | + expect(submission.result).toEqual({ id: 7, ok: true }); // the answer survives whole |
| 45 | + expect(submission.input).toEqual([]); // the echo paid for it |
| 46 | + expect(submission.truncated).toBe(true); |
| 47 | + }); |
| 48 | + |
| 49 | + it("reduces a structured result past the ceiling to the outcome flag", () => { |
| 50 | + // ~200 rows of the issue's shape — 29 was already past the ceiling |
| 51 | + const rows = Array.from({ length: 200 }, (_, i) => ({ |
| 52 | + id: i, |
| 53 | + sku: `SKU-${i}`, |
| 54 | + name: `Product ${i}`, |
| 55 | + price: 19.99, |
| 56 | + note: "restocked" |
| 57 | + })); |
| 58 | + const cookie = encodeFlashCookie("/bulk-save", rows, []); |
| 59 | + expect(pairOf(cookie).length).toBeLessThanOrEqual(4096); |
| 60 | + const submission = roundTrip(cookie)!; |
| 61 | + // structured JSON has no partial spelling: what survives is THAT it |
| 62 | + // happened and where — the part whose loss causes the double-submit |
| 63 | + expect(submission.url).toBe("/bulk-save"); |
| 64 | + expect(submission.result).toBe(true); |
| 65 | + expect(submission.truncated).toBe(true); |
| 66 | + }); |
| 67 | + |
| 68 | + it("keeps the longest prefix of a string result that fits", () => { |
| 69 | + const cookie = encodeFlashCookie("/report", "line ".repeat(4000), []); |
| 70 | + expect(pairOf(cookie).length).toBeLessThanOrEqual(4096); |
| 71 | + const submission = roundTrip(cookie)!; |
| 72 | + expect(typeof submission.result).toBe("string"); |
| 73 | + expect(submission.result.startsWith("line line ")).toBe(true); |
| 74 | + expect(submission.truncated).toBe(true); |
| 75 | + }); |
| 76 | + |
| 77 | + it("a thrown outcome stays an error with a bounded message", () => { |
| 78 | + const failure = new Error("constraint violated: " + "detail ".repeat(3000)); |
| 79 | + const cookie = encodeFlashCookie("/charge", failure, [], true); |
| 80 | + expect(pairOf(cookie).length).toBeLessThanOrEqual(4096); |
| 81 | + const submission = roundTrip(cookie)!; |
| 82 | + expect(submission.error).toBeInstanceOf(Error); |
| 83 | + expect((submission.error as Error).message.startsWith("constraint violated:")).toBe(true); |
| 84 | + expect(submission.result).toBeUndefined(); |
| 85 | + expect(submission.truncated).toBe(true); |
| 86 | + }); |
| 87 | +}); |
0 commit comments