Files
miti99bot/internal/modules/twentyq/parser_test.go
T
tiennm99 ce99633e25 feat(modules): port new command modules + update registry
- Add new modules: doantu, semantle, twentyq, ai (Gemini integration)
- Update module registry with new command registration
- Update tests and documentation for module system
- Update README with new module references
2026-05-10 02:29:58 +07:00

123 lines
3.3 KiB
Go

package twentyq
import "testing"
func TestParseJSON_Plain(t *testing.T) {
got := parseJSON(`{"is_guess": true, "answer": "yes", "hint": "ok"}`)
if got == nil {
t.Fatal("parseJSON returned nil")
}
if got["is_guess"] != true || got["answer"] != "yes" {
t.Errorf("got %+v", got)
}
}
func TestParseJSON_WithFences(t *testing.T) {
in := "```json\n{\"is_guess\": false, \"answer\": \"no\", \"hint\": \"x\"}\n```"
got := parseJSON(in)
if got == nil {
t.Fatalf("parseJSON returned nil for fenced input")
}
if got["answer"] != "no" {
t.Errorf("got %+v", got)
}
}
func TestParseJSON_PreNoise(t *testing.T) {
in := "Sure! Here's the json: {\"is_guess\": false, \"answer\": \"yes\", \"hint\": \"clue\"}"
got := parseJSON(in)
if got == nil || got["answer"] != "yes" {
t.Errorf("parseJSON pre-noise: got %+v", got)
}
}
func TestParseJSON_Malformed(t *testing.T) {
if got := parseJSON("not json at all"); got != nil {
t.Errorf("malformed input returned %+v, want nil", got)
}
if got := parseJSON(""); got != nil {
t.Errorf("empty input returned %+v, want nil", got)
}
if got := parseJSON("{ unclosed"); got != nil {
t.Errorf("unclosed brace returned %+v", got)
}
}
func TestNormalizeJudgement_Defaults(t *testing.T) {
j := normalizeJudgement(nil)
if j.Answer != "no" || j.IsGuess || j.Hint == "" {
t.Errorf("nil payload: got %+v", j)
}
// "YES" lowercased; missing hint → default; bad is_guess type → false.
j = normalizeJudgement(map[string]any{
"is_guess": "true", // wrong type intentionally
"answer": "YES",
"hint": " ",
})
if j.IsGuess {
t.Errorf("string is_guess should not coerce to true: %+v", j)
}
if j.Answer != "yes" {
t.Errorf("answer YES: got %q, want yes", j.Answer)
}
if j.Hint == "" {
t.Errorf("blank hint should fall back to default")
}
}
func TestRedactSecret(t *testing.T) {
cases := []struct {
hint, target, want string
}{
{"this is a guitar", "guitar", "this is a (redacted)"},
{"GUITAR is a thing", "guitar", "(redacted) is a thing"},
{"guitarist works here", "guitar", "guitarist works here"}, // word boundary
{"clean hint", "guitar", "clean hint"},
{"empty target", "", "empty target"},
}
for _, c := range cases {
got := redactSecret(c.hint, c.target)
if got != c.want {
t.Errorf("redactSecret(%q,%q) = %q, want %q", c.hint, c.target, got, c.want)
}
}
}
func TestParseRoundStart(t *testing.T) {
cat, hint, ok := parseRoundStart(map[string]any{
"category": " instrument ",
"initialHint": " a clue ",
})
if !ok || cat != "instrument" || hint != "a clue" {
t.Errorf("got cat=%q hint=%q ok=%v", cat, hint, ok)
}
if _, _, ok := parseRoundStart(nil); ok {
t.Errorf("nil should not parse")
}
if _, _, ok := parseRoundStart(map[string]any{"category": ""}); ok {
t.Errorf("empty category should not parse")
}
}
func TestValidateQuestion(t *testing.T) {
cases := []struct {
raw string
wantOK bool
}{
{"is it big?", true},
{" is it round ?", true},
{" ab", false}, // too short
{string(make([]byte, 250)), false}, // too long
{"What color is it?", false}, // open-ended
{"why is it heavy?", false}, // open-ended
{"", false},
}
for _, c := range cases {
v := validateQuestion(c.raw)
if v.OK != c.wantOK {
t.Errorf("validate(%q) ok=%v, want %v (reason=%s)", c.raw, v.OK, c.wantOK, v.Reason)
}
}
}