Files
tiennm99bot/internal/modules/blacklist/normalize_test.go
T
tiennm99 e5125fcd93 feat(blacklist): add per-topic text deny-list with whitelist exceptions
Six public commands let any member of a chat curate two lists of text and
ask whether a given text is blocked: /blacklist_add, /blacklist_del,
/whitelist_add, /whitelist_del, /blacklist_rules and /blacklist_check.

The module is passive. It never reads ordinary chat messages and never
deletes, warns or restricts anyone; the lists stay inert until
/blacklist_check asks about a specific text.

Scope is one forum topic, keyed (Chat.ID, MessageThreadID) and gated on
IsTopicMessage so a reply chain in a plain supergroup does not become its
own unreachable scope. A plain group, a DM and a forum's General topic all
resolve to one chat-wide list.

Matching is substring, after NFKC composition, case folding and whitespace
collapse. Diacritics stay significant, so ma, má and mà are three entries.
A whitelist entry rescues a blacklist match only when it spans that match,
which is what keeps "I met an assassin, dumbass" blocked.

Entry text is percent-encoded before it becomes a storage key, since keys
forbid '/' and cap at 1500 bytes, and is capped at 200 bytes before and
after normalization because NFKC can expand as well as contract.

/blacklist_rules reads each list with Scan, so listing costs one round trip
per list rather than a Get per entry.
2026-09-15 13:37:03 +07:00

58 lines
1.5 KiB
Go

package blacklist
import "testing"
func TestNormalize_ComposesVietnamese(t *testing.T) {
// The same word as a client sending NFC and a client sending NFD. Without
// composition these are different strings and would never match.
nfc := "má" // U+00E1 precomposed
nfd := "má" // "a" + combining acute
gotNFC, ok := Normalize(nfc)
if !ok {
t.Fatal("NFC form rejected")
}
gotNFD, ok := Normalize(nfd)
if !ok {
t.Fatal("NFD form rejected")
}
if gotNFC != gotNFD {
t.Fatalf("NFC %q and NFD %q normalize differently", gotNFC, gotNFD)
}
}
func TestNormalize_FoldsCaseButKeepsDiacritics(t *testing.T) {
upper, _ := Normalize("MÁ")
lower, _ := Normalize("má")
if upper != lower {
t.Fatalf("case not folded: %q vs %q", upper, lower)
}
bare, _ := Normalize("ma")
if bare == lower {
t.Fatalf("diacritic was stripped: %q == %q", bare, lower)
}
}
func TestNormalize_FoldsCompatibilityVariants(t *testing.T) {
full, _ := Normalize("AB") // full-width AB
plain, _ := Normalize("ab")
if full != plain {
t.Fatalf("full-width %q did not fold to %q", full, plain)
}
}
func TestNormalize_CollapsesWhitespace(t *testing.T) {
got, ok := Normalize(" cat \n dog ")
if !ok || got != "cat dog" {
t.Fatalf("Normalize = %q, %v; want \"cat dog\", true", got, ok)
}
}
func TestNormalize_RejectsEmpty(t *testing.T) {
for _, in := range []string{"", " ", "\n\t "} {
if got, ok := Normalize(in); ok {
t.Fatalf("Normalize(%q) = %q, true; want not ok", in, got)
}
}
}