891136c65d
PR #41 and #44 landed these two files unformatted, so the gofmt gate that PR #12 added to `make test` failed as soon as both were on one branch. Struct-tag and comment alignment only, no semantic change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
106 lines
3.3 KiB
Go
106 lines
3.3 KiB
Go
package kiwix
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/kami/maven/internal/llm"
|
|
)
|
|
|
|
// Bad model output must never reach Kiwix. No model needed for this.
|
|
func TestCleanQueryRejectsJunk(t *testing.T) {
|
|
bad := []struct{ name, raw string }{
|
|
{"empty", ""},
|
|
{"blank", " \n "},
|
|
{"russian came back", "почему небо синее"},
|
|
{"mixed russian", "sky синее scattering"},
|
|
{"full sentence", "The sky looks blue because of the scattering of sunlight by air molecules"},
|
|
{"prose with quotes", `Sure! Here is a good search query: "Rayleigh scattering", which explains it.`},
|
|
}
|
|
for _, c := range bad {
|
|
if got, err := CleanQuery(c.raw); err == nil {
|
|
t.Errorf("%s: want rejection, got %q", c.name, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCleanQueryCleans(t *testing.T) {
|
|
ok := []struct{ raw, want string }{
|
|
{"Rayleigh scattering sky", "Rayleigh scattering sky"},
|
|
{" boiled egg cooking \n", "boiled egg cooking"},
|
|
{`"virtual private network"`, "virtual private network"},
|
|
{"solid-state drive", "solid-state drive"},
|
|
{"cat purr.", "cat purr"},
|
|
{"hiccup\nAlso: hiccough", "hiccup"},
|
|
// Question words are filler to a keyword ranker, so they go.
|
|
{"why is the sky blue", "sky blue"},
|
|
{"how much water to drink daily", "water drink daily"},
|
|
{"SSD vs HDD comparison", "SSD HDD comparison"},
|
|
// Nothing but filler: keep it rather than return nothing.
|
|
{"what is it", "what is it"},
|
|
}
|
|
for _, c := range ok {
|
|
got, err := CleanQuery(c.raw)
|
|
if err != nil {
|
|
t.Errorf("%q: %v", c.raw, err)
|
|
continue
|
|
}
|
|
if got != c.want {
|
|
t.Errorf("%q -> %q, want %q", c.raw, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
type fakeCompleter struct {
|
|
out string
|
|
req llm.Req
|
|
}
|
|
|
|
func (f *fakeCompleter) Complete(_ context.Context, r llm.Req) (string, error) {
|
|
f.req = r
|
|
return f.out, nil
|
|
}
|
|
|
|
func TestRewriteConstrainsTheCall(t *testing.T) {
|
|
f := &fakeCompleter{out: `{"query":"Rayleigh scattering sky"}`}
|
|
got, err := NewRewriter(f).Rewrite(context.Background(), "почему небо синее?")
|
|
if err != nil {
|
|
t.Fatalf("rewrite: %v", err)
|
|
}
|
|
if got != "Rayleigh scattering sky" {
|
|
t.Errorf("query = %q", got)
|
|
}
|
|
if f.req.Grammar == "" {
|
|
t.Error("no grammar sent")
|
|
}
|
|
if f.req.MaxTokens == 0 || f.req.MaxTokens > 32 {
|
|
t.Errorf("max_tokens = %d, want a small cap", f.req.MaxTokens)
|
|
}
|
|
}
|
|
|
|
func TestRewriteRejectsBadModelOutput(t *testing.T) {
|
|
bad := []string{
|
|
`{"query":"почему небо синее"}`, // never translated
|
|
`{"query":""}`, // empty
|
|
`{"query":"the sky is blue because sunlight is scattered by air"}`, // an answer
|
|
// Note: a SHORT English prose fragment ("Let me analyze this request")
|
|
// is under the word cap and cannot be caught here. The grammar is what
|
|
// stops that one.
|
|
}
|
|
for _, out := range bad {
|
|
f := &fakeCompleter{out: out}
|
|
if got, err := NewRewriter(f).Rewrite(context.Background(), "почему небо синее?"); err == nil {
|
|
t.Errorf("%s: want rejection, got %q", out, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A reply that is not the JSON shape but is still usable keywords should pass.
|
|
func TestRewriteFallsBackToPlainText(t *testing.T) {
|
|
f := &fakeCompleter{out: "Rayleigh scattering sky"}
|
|
got, err := NewRewriter(f).Rewrite(context.Background(), "почему небо синее?")
|
|
if err != nil || got != "Rayleigh scattering sky" {
|
|
t.Errorf("got %q, %v", got, err)
|
|
}
|
|
}
|