flybrain/packages/feed/tests/fixtures/chat-cases.json
acamilo 660c3cf00d
Some checks failed
ci / node 22 (test + typecheck) (push) Has been cancelled
ci / rust stable (cargo test --workspace --release) (push) Has been cancelled
ci / infra/tests/lint.sh (push) Has been cancelled
ci / playwright apps/stage (allowed to fail) (push) Has been cancelled
flybrain v0.4.0: public tree (history retained privately)
2026-09-21 15:09:46 +00:00

291 lines
8.2 KiB
JSON

{
"$comment": [
"Shared sanitizer cases for BOTH implementations of the chat text rules:",
" - packages/feed/src/chat.ts (tests/chat.test.ts)",
" - services/flysim/.../src/chat.rs (tests/chat.rs)",
"Neither suite has hostile cases of its own that the other lacks, so the two sanitizers",
"cannot drift: a rule changed in one language fails here in both. `expected` is the cleaned",
"line, or null when the line must be refused, in which case `reason` is the rejection reason",
"(also the label of flysim's fly_chat_rejected_total{reason} metric).",
"A string field may instead be { \"repeat\": { \"unit\": \"a\", \"count\": 5000 } }, which both",
"loaders expand to `unit` repeated `count` times \u2014 long spam cases stay readable."
],
"maxTextLength": 200,
"cases": [
{
"name": "an ordinary chat line survives unchanged",
"input": "go left! the ledge is right there",
"expected": "go left! the ledge is right there"
},
{
"name": "leading, trailing and repeated spaces collapse",
"input": " the fly is stuck ",
"expected": "the fly is stuck"
},
{
"name": "tabs and newlines become single spaces rather than rejecting the line",
"input": "hi\tthere\nfriend\r\nagain",
"expected": "hi there friend again"
},
{
"name": "non-ASCII letters are allowed",
"input": "\u03a9 \u96e8\u5bae \u043f\u0440\u0438\u0432\u0435\u0442",
"expected": "\u03a9 \u96e8\u5bae \u043f\u0440\u0438\u0432\u0435\u0442"
},
{
"name": "NFC normalization: a combining acute folds into the precomposed letter",
"input": "nai\u0308ve",
"expected": "na\u00efve"
},
{
"name": "NBSP and ideographic space fold to an ordinary space",
"input": "hello\u00a0world\u3000again",
"expected": "hello world again"
},
{
"name": "allowed punctuation passes through",
"input": "a_b [ok] {x} 50% #1 a+b c=d e@f; wow!? \"quote\" 'tick' (yes) ~nice~ ,-./:",
"expected": "a_b [ok] {x} 50% #1 a+b c=d e@f; wow!? \"quote\" 'tick' (yes) ~nice~ ,-./:"
},
{
"name": "digits and a decimal number are not mistaken for a host name",
"input": "route 1 took 3.14 minutes, e.g. this one",
"expected": "route 1 took 3.14 minutes, e.g. this one"
},
{
"name": "a bridge template reply passes (these are posted with bot: true)",
"input": "Sugar from fly_fan_42! The fly gets a brief PAM reward pulse.",
"expected": "Sugar from fly_fan_42! The fly gets a brief PAM reward pulse."
},
{
"name": "exactly the length cap is accepted",
"input": {
"repeat": {
"unit": "a",
"count": 200
}
},
"expected": {
"repeat": {
"unit": "a",
"count": 200
}
}
},
{
"name": "one code point over the cap is refused",
"input": {
"repeat": {
"unit": "a",
"count": 201
}
},
"expected": null,
"reason": "too_long"
},
{
"name": "5000-character spam is refused rather than truncated",
"input": {
"repeat": {
"unit": "spam ",
"count": 1000
}
},
"expected": null,
"reason": "too_long"
},
{
"name": "a NUL byte is a control character",
"input": "hi\u0000there",
"expected": null,
"reason": "control"
},
{
"name": "a bell is a control character",
"input": "wake up\u0007",
"expected": null,
"reason": "control"
},
{
"name": "an ANSI escape sequence is a control character",
"input": "\u001b[31mred\u001b[0m",
"expected": null,
"reason": "control"
},
{
"name": "a zero-width space is not a space",
"input": "he\u200bllo",
"expected": null,
"reason": "charset"
},
{
"name": "a zero-width joiner is refused",
"input": "a\u200db",
"expected": null,
"reason": "charset"
},
{
"name": "a right-to-left override is refused",
"input": "\u202egnippot ma I",
"expected": null,
"reason": "charset"
},
{
"name": "a left-to-right mark is refused",
"input": "hi\u200ethere",
"expected": null,
"reason": "charset"
},
{
"name": "a byte order mark is refused (it is not White_Space)",
"input": "\ufeffhello",
"expected": null,
"reason": "charset"
},
{
"name": "combining marks (zalgo) are refused",
"input": "h\u0301\u0301\u0301\u0301i",
"expected": null,
"reason": "charset"
},
{
"name": "emoji are refused for now",
"input": "nice fly \ud83e\udeb0",
"expected": null,
"reason": "charset"
},
{
"name": "markup characters are refused",
"input": "<script>alert(1)</script>",
"expected": null,
"reason": "charset"
},
{
"name": "backticks are refused",
"input": "`rm -rf /`",
"expected": null,
"reason": "charset"
},
{
"name": "pipes are refused",
"input": "cat x | sh",
"expected": null,
"reason": "charset"
},
{
"name": "a dollar sign is refused (shell-shaped, and no price belongs on screen)",
"input": "only $5",
"expected": null,
"reason": "charset"
},
{
"name": "an empty message is refused",
"input": "",
"expected": null,
"reason": "empty"
},
{
"name": "whitespace-only is refused",
"input": " \t \u00a0 ",
"expected": null,
"reason": "empty"
},
{
"name": "a scheme is a URL",
"input": "watch this https://evil.example/pwn",
"expected": null,
"reason": "url"
},
{
"name": "a scheme-relative host is a URL",
"input": "go to //evil.test now",
"expected": null,
"reason": "url"
},
{
"name": "www. anywhere is a URL",
"input": "see www.evil.tv for more",
"expected": null,
"reason": "url"
},
{
"name": "a TLD-like token is a URL",
"input": "join discord.gg/abcd",
"expected": null,
"reason": "url"
},
{
"name": "a two-letter TLD is a URL",
"input": "bit.ly",
"expected": null,
"reason": "url"
},
{
"name": "case does not hide a URL",
"input": "HTTPS://EVIL.EXAMPLE",
"expected": null,
"reason": "url"
},
{
"name": "the URL rule is deliberately over-eager: a dotted word is refused",
"input": "MR.MIME used splash",
"expected": null,
"reason": "url"
},
{
"name": "control characters are checked before the character set",
"input": "\u0000\ud83e\udeb0",
"expected": null,
"reason": "control"
},
{
"name": "the character set is checked before the length cap",
"input": {
"repeat": {
"unit": "\ud83e\udeb0",
"count": 500
}
},
"expected": null,
"reason": "charset"
},
{
"name": "the length cap is checked before the URL rule",
"input": {
"repeat": {
"unit": "bit.ly ",
"count": 100
}
},
"expected": null,
"reason": "too_long"
},
{
"name": "typographic punctuation a phone keyboard produces is allowed",
"input": "it\u2019s stuck \u2014 again\u2026 \u201cright there\u201d",
"expected": "it\u2019s stuck \u2014 again\u2026 \u201cright there\u201d"
},
{
"name": "a bridge template reply with an em dash passes",
"input": "No hand-written strategy, no lookahead search \u2014 just the fly.",
"expected": "No hand-written strategy, no lookahead search \u2014 just the fly."
},
{
"name": "CJK stops and brackets are allowed",
"input": "\u5de6\u3078\u884c\u3051\u3001\u4eca\u3059\u3050\uff01",
"expected": "\u5de6\u3078\u884c\u3051\u3001\u4eca\u3059\u3050\uff01"
},
{
"name": "a mathematical symbol is not punctuation",
"input": "5 \u00d7 5",
"expected": null,
"reason": "charset"
},
{
"name": "a currency symbol is refused like the ASCII dollar",
"input": "only \u20ac5",
"expected": null,
"reason": "charset"
}
]
}