Files
Maksim Totmin 17ff5c591f refactor!: make addrkit a multi-city address normalizer
- remove the events/outages module, ArcGIS adapter, portal config and event statuses
- add a configurable city dictionary/parser and make City part of StreetKey
- seed: read city/city_name, namespace IDs by city, keep structures distinct
- fix reliability: schema v2 with fail-closed load, deterministic ordering,
  input limits, config validation, atomic DB publish, LLM response caps
- add read-only shadow mode comparing the old ES path with addrkit
- breaking: registry.db must be re-seeded; config has no portal section
2026-10-27 14:35:51 +07:00

103 lines
3.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package llm
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.totmin.ru/en2zmax/addrkit/internal/config"
)
// chatServer returns a test server that echoes a fixed content and captures the
// request body it received.
func chatServer(t *testing.T, content string) (*httptest.Server, *chatRequest) {
t.Helper()
var got chatRequest
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewDecoder(r.Body).Decode(&got)
w.Header().Set("Content-Type", "application/json")
resp := map[string]any{"choices": []any{map[string]any{"message": map[string]any{"content": content}}}}
_ = json.NewEncoder(w).Encode(resp)
}))
t.Cleanup(ts.Close)
return ts, &got
}
func TestRewriteUsesConfiguredPrompt(t *testing.T) {
ts, got := chatServer(t, `{"variants":["А"]}`)
r := New(config.LLMConfig{URL: ts.URL, Model: "m", SystemPrompt: "CUSTOM PROMPT"})
out, err := r.Rewrite(context.Background(), "вход")
if err != nil {
t.Fatalf("Rewrite: %v", err)
}
if len(got.Messages) == 0 || got.Messages[0].Content != "CUSTOM PROMPT" {
t.Errorf("system prompt not applied: %+v", got.Messages)
}
if len(out) != 1 || out[0] != "А" {
t.Errorf("variants = %v", out)
}
}
func TestRewriteFallsBackToEmbeddedPrompt(t *testing.T) {
ts, got := chatServer(t, `{"variants":[]}`)
r := New(config.LLMConfig{URL: ts.URL, Model: "m"})
if _, err := r.Rewrite(context.Background(), "x"); err != nil {
t.Fatalf("Rewrite: %v", err)
}
if len(got.Messages) == 0 || !strings.Contains(got.Messages[0].Content, "нормализатор") {
t.Errorf("embedded default prompt not used: %q", got.Messages[0].Content)
}
}
func TestRewriteDisablesThinking(t *testing.T) {
ts, got := chatServer(t, `{"variants":[]}`)
r := New(config.LLMConfig{URL: ts.URL, Model: "m", DisableThinking: true})
if _, err := r.Rewrite(context.Background(), "x"); err != nil {
t.Fatalf("Rewrite: %v", err)
}
if v, ok := got.ChatTemplateKwargs["enable_thinking"]; !ok || v != false {
t.Errorf("enable_thinking not disabled: %+v", got.ChatTemplateKwargs)
}
}
func TestParseVariants(t *testing.T) {
cases := []struct {
name string
content string
want int
}{
{"clean", `{"variants":["a","b"]}`, 2},
{"with thinking", "<think>hmm</think>\n{\"variants\":[\"a\"]}", 1},
{"unterminated thinking", "<think>hmm {\"variants\":[\"a\"]}", 0},
{"prose around", "Here you go: {\"variants\":[\"a\",\"b\",\"c\"]} done", 3},
{"garbage", "no json here", 0},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
if got := parseVariants(tt.content); len(got) != tt.want {
t.Errorf("parseVariants(%q) = %v, want %d items", tt.content, got, tt.want)
}
})
}
}
func TestParseVariantsCapsCountAndLength(t *testing.T) {
long := strings.Repeat("x", 600)
content := `{"variants":["` + long + `","a","b","c","d"]}`
out := parseVariants(content)
if len(out) != 3 {
t.Fatalf("variants = %v, want 3", out)
}
for _, v := range out {
if len([]rune(v)) > 512 {
t.Fatalf("oversized variant accepted: %d runes", len([]rune(v)))
}
}
}