package main import "testing" const botID = "@ai:vojo.chat" func msg(body, formatted string, userIDs []string, withMentions bool) *MessageContent { mc := &MessageContent{Body: body, FormattedBody: formatted} if withMentions { mc.Mentions = &Mentions{UserIDs: userIDs} } return mc } func TestMentionsBot(t *testing.T) { cases := []struct { name string mc *MessageContent replyIsBot bool want bool }{ {"explicit user_ids mention", msg("hi", "", []string{botID}, true), false, true}, {"empty m.mentions {} (F29)", msg("hi ai", "", nil, true), false, false}, {"someone else mentioned", msg("hi", "", []string{"@alice:vojo.chat"}, true), false, false}, {"typed @ai no pill no mentions (F30)", msg("hey @ai what's up", "", nil, false), false, false}, {"pill href in formatted_body", msg("hi", `Vojo AI`, nil, false), false, true}, {"pill href %40 encoded", msg("hi", `Vojo AI`, nil, false), false, true}, {"reply to bot's message", msg("thanks", "", nil, true), true, true}, {"plain message, not a DM", msg("just chatting", "", nil, true), false, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { if got := mentionsBot(c.mc, botID, c.replyIsBot); got != c.want { t.Fatalf("mentionsBot = %v, want %v", got, c.want) } }) } } func TestIsDM(t *testing.T) { cases := []struct { name string joined, invited int known bool want bool }{ {"2 joined", 2, 0, true, true}, {"1 joined + 1 invited (fresh DM)", 1, 1, true, true}, {"2 joined + 1 invited NOT a 1:1 (F3)", 2, 1, true, false}, {"3 joined group", 3, 0, true, false}, {"counts unknown", 2, 0, false, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { m := &roomMeta{joined: c.joined, invited: c.invited, countsKnown: c.known} if got := m.isDM(); got != c.want { t.Fatalf("isDM = %v, want %v", got, c.want) } }) } } func TestStripReplyFallback(t *testing.T) { in := "> <@alice:vojo.chat> secret third-party text\n> more quote\n\n@ai answer me" if got := stripReplyFallback(in); got != "@ai answer me" { t.Fatalf("stripReplyFallback = %q", got) } if got := stripReplyFallback(" plain "); got != "plain" { t.Fatalf("plain trim = %q", got) } } func TestStripBotMention(t *testing.T) { cases := []struct{ in, want string }{ // The headline regression: the full-mxid pill fallback cinny writes must not reach // the search query (it made the grounding provider search for "vojo.chat"). {"@ai:vojo.chat мессенджер макс удалили из эппстора?", "мессенджер макс удалили из эппстора?"}, // Bare "@localpart" fallback some clients write, with trailing address punctuation. {"@ai, какая погода в Москве", "какая погода в Москве"}, // Mention mid-message is still removed (it is never user content). {"скажи @ai:vojo.chat кто выиграл", "скажи кто выиграл"}, // No mention → unchanged (DMs, where the bot isn't addressed by name). {"кто выиграл вчера", "кто выиграл вчера"}, // The product name in a real question must survive (we never strip the display name). {"@ai:vojo.chat что умеет Vojo AI", "что умеет Vojo AI"}, // A longer handle that merely contains the localpart is kept. {"@ai:vojo.chat пинг @aibot", "пинг @aibot"}, } for _, c := range cases { if got := stripBotMention(c.in, botID); got != c.want { t.Errorf("stripBotMention(%q) = %q, want %q", c.in, got, c.want) } } } func TestComputeUSD(t *testing.T) { const model = "grok-test" cfg := &Config{XAIModel: model, Prices: map[string]ModelPrice{ model: {InputPerM: 1.25, CachedPerM: 0.20, OutputPerM: 2.50}, }} u := Usage{PromptTokens: 1_000_000, CachedTokens: 400_000, CompletionTokens: 1_000_000} // nonCached 600k*1.25 + cached 400k*0.20 + out 1M*2.50 = 0.75 + 0.08 + 2.50 got := computeUSD(model, u, cfg) want := 0.75 + 0.08 + 2.50 if diff := got - want; diff > 1e-9 || diff < -1e-9 { t.Fatalf("computeUSD = %v, want %v", got, want) } // An unknown model falls back to the default model's price (never $0, which would // blind the ceiling). if got := computeUSD("unknown-model", u, cfg); got != want { t.Fatalf("unknown-model fallback = %v, want default %v", got, want) } } func TestBuildContextGroupDropsThirdParties(t *testing.T) { history := []bufferedMsg{ {sender: "@alice:vojo.chat", body: "third-party chatter", isBot: false}, {sender: botID, body: "previous bot reply", isBot: true}, {sender: "@bob:vojo.chat", body: "more third-party", isBot: false}, } got := buildContext("SYS", history, false /* group */, "what is 2+2?", 20, 8000) // system first, trigger last, and NO third-party user content in between. if got[0].Role != "system" || got[0].Content != "SYS" { t.Fatalf("first message must be system prompt, got %+v", got[0]) } last := got[len(got)-1] if last.Role != "user" || last.Content != "what is 2+2?" { t.Fatalf("last message must be the trigger, got %+v", last) } for _, m := range got { if m.Content == "third-party chatter" || m.Content == "more third-party" { t.Fatalf("group context leaked third-party content: %+v", got) } } // the bot's own prior reply is kept as an assistant turn foundAssistant := false for _, m := range got { if m.Role == "assistant" && m.Content == "previous bot reply" { foundAssistant = true } } if !foundAssistant { t.Fatalf("group context should keep the bot's own prior reply: %+v", got) } } func TestBuildContextDMIncludesPeer(t *testing.T) { history := []bufferedMsg{ {sender: "@peer:vojo.chat", body: "earlier peer line", isBot: false}, {sender: botID, body: "earlier bot line", isBot: true}, } got := buildContext("SYS", history, true /* DM */, "follow up", 20, 8000) var sawPeer, sawBot bool for _, m := range got { if m.Role == "user" && m.Content == "earlier peer line" { sawPeer = true } if m.Role == "assistant" && m.Content == "earlier bot line" { sawBot = true } } if !sawPeer || !sawBot { t.Fatalf("DM context should include peer + bot history: %+v", got) } }