|
14 | 14 |
|
15 | 15 | import { describe, it, expect } from "vitest"; |
16 | 16 |
|
17 | | -/** Mirrors the repair regex from monitor.ts middleware */ |
18 | | -const REPAIR_REGEX = /\\([^"\\\//bfnrtu])/g; |
| 17 | +// ── Mirror `repairJsonEscapes` from monitor.ts ───────────────────────────── |
| 18 | +const VALID_JSON_ESCAPE_CHARS = new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]); |
19 | 19 |
|
20 | | -/** Emulates the two-pass parse logic from the fixed middleware */ |
| 20 | +function repairJsonEscapes(raw: string): string { |
| 21 | + let fixed = ""; |
| 22 | + for (let i = 0; i < raw.length; i += 1) { |
| 23 | + const ch = raw[i]; |
| 24 | + if (ch !== "\\") { |
| 25 | + fixed += ch; |
| 26 | + continue; |
| 27 | + } |
| 28 | + |
| 29 | + let runEnd = i; |
| 30 | + while (raw[runEnd] === "\\") { |
| 31 | + runEnd += 1; |
| 32 | + } |
| 33 | + const runLength = runEnd - i; |
| 34 | + fixed += "\\".repeat(runLength); |
| 35 | + const next = raw[runEnd]; |
| 36 | + if (runLength % 2 === 1 && next !== undefined && !VALID_JSON_ESCAPE_CHARS.has(next)) { |
| 37 | + fixed += "\\"; |
| 38 | + } |
| 39 | + i = runEnd - 1; |
| 40 | + } |
| 41 | + return fixed; |
| 42 | +} |
| 43 | + |
| 44 | +/** Emulate the two-pass middleware. Returns parsed body + which pass was used. */ |
21 | 45 | function twoPassParse(raw: string): { body: unknown; path: "first_pass" | "repaired" } { |
22 | 46 | try { |
23 | 47 | return { body: JSON.parse(raw), path: "first_pass" }; |
24 | 48 | } catch { |
25 | | - const fixed = raw.replace(REPAIR_REGEX, "\\\\$1"); |
| 49 | + const fixed = repairJsonEscapes(raw); |
26 | 50 | return { body: JSON.parse(fixed), path: "repaired" }; // throws if still invalid |
27 | 51 | } |
28 | 52 | } |
@@ -62,6 +86,15 @@ const GOOD_MESSAGE_PAYLOAD = JSON.stringify({ |
62 | 86 | /** Payload with valid JSON escape sequences — must NOT be double-escaped */ |
63 | 87 | const VALID_ESCAPES_PAYLOAD = String.raw`{"text": "line1\nline2\ttab\"quote\\backslash"}`; |
64 | 88 |
|
| 89 | +/** |
| 90 | + * The edge case: a valid \\q sequence (JSON for literal \q) |
| 91 | + * mixed with a separate invalid escape in the same payload. |
| 92 | + */ |
| 93 | +const MIXED_VALID_AND_INVALID_ESCAPES = String.raw`{ |
| 94 | + "path": "C:\\q", |
| 95 | + "team": "Test\Project" |
| 96 | +}`; |
| 97 | + |
65 | 98 | // ──────────────────────────────────────────────────────────── |
66 | 99 | // Tests |
67 | 100 | // ──────────────────────────────────────────────────────────── |
@@ -101,4 +134,12 @@ describe("msteams JSON repair middleware", () => { |
101 | 134 | const garbage = "{ not valid json at all ??? }"; |
102 | 135 | expect(() => twoPassParse(garbage)).toThrow(SyntaxError); |
103 | 136 | }); |
| 137 | + |
| 138 | + it("preserves valid escaped backslashes while repairing another field", () => { |
| 139 | + const { body, path } = twoPassParse(MIXED_VALID_AND_INVALID_ESCAPES); |
| 140 | + expect(path).toBe("repaired"); |
| 141 | + const obj = body as Record<string, string>; |
| 142 | + expect(obj["path"]).toBe("C:\\q"); |
| 143 | + expect(obj["team"]).toBe("Test\\Project"); |
| 144 | + }); |
104 | 145 | }); |
0 commit comments