Files
lpra/internal/server/aimodels_test.go
T

111 lines
4.0 KiB
Go

package server
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
)
// The model set before there could be several is the first of the list.
func TestAIModelsFromLegacy(t *testing.T) {
s, _ := newTestServer(t)
_ = s.db.SetConfig(cfgAIType, "openai")
_ = s.db.SetConfig(cfgAIURL, "https://api.example/v1/")
_ = s.db.SetConfig(cfgAIModel, "gpt-x")
_ = s.db.SetConfig(cfgAIKey, "k1")
ms := s.aiModels()
if len(ms) != 1 || ms[0].Type != "openai" || ms[0].URL != "https://api.example/v1" || ms[0].Model != "gpt-x" || ms[0].Key != "k1" || ms[0].ID == "" {
t.Fatalf("models: %+v", ms)
}
if got := s.aiModel("nope"); got.ID != ms[0].ID {
t.Fatalf("an unknown id is not the first model: %+v", got)
}
}
// postAIModels sends the settings form with the given models.
func postAIModels(t *testing.T, s *Server, c *http.Cookie, models [][7]string) {
t.Helper()
f := url.Values{"csrf": {"tok"}}
for _, m := range models {
f.Add("m_id", m[0])
f.Add("m_name", m[1])
f.Add("m_type", m[2])
f.Add("m_url", m[3])
f.Add("m_model", m[4])
f.Add("m_key", m[5])
f.Add("m_short", m[6])
}
req := httptest.NewRequest(http.MethodPost, "/settings/ai", strings.NewReader(f.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.AddCookie(c)
req.AddCookie(&http.Cookie{Name: csrfCookie, Value: "tok"})
w := httptest.NewRecorder()
s.Handler().ServeHTTP(w, req)
if w.Code != http.StatusSeeOther {
t.Fatalf("save: %d %s", w.Code, w.Body)
}
}
// Настройки keep several models with their names; the chat learns the
// names and ids only, never a key.
func TestAIModelsSaveAndList(t *testing.T) {
s, c := newTestServer(t)
postAIModels(t, s, c, [][7]string{
{"", "Qwen", "gradio", "", "", "", ""},
{"", "Claude", "anthropic", "https://api.anthropic.com", "claude-x", "secret-key", "Сонет 4.5 длинно"},
{"", "", "", "", "", "", ""}, // an empty card is dropped
})
ms := s.aiModels()
if len(ms) != 2 || ms[0].Name != "Qwen" || ms[1].Name != "Claude" || ms[1].Key != "secret-key" || ms[0].ID == "" || ms[0].ID == ms[1].ID || ms[1].Short != "Сонет 4.5 дл" {
t.Fatalf("models: %+v", ms)
}
body := get(t, s, c, "/api/panel/ai").Body.String()
if strings.Contains(body, "secret-key") || !strings.Contains(body, `"name":"Claude"`) || !strings.Contains(body, `"short":"Сонет 4.5 дл"`) || !strings.Contains(body, ms[1].ID) {
t.Fatalf("state: %s", body)
}
// Saved again with its id, a model keeps it.
postAIModels(t, s, c, [][7]string{{ms[1].ID, "Claude 2", "anthropic", "https://api.anthropic.com", "claude-x", "secret-key", ""}})
if got := s.aiModels(); len(got) != 1 || got[0].ID != ms[1].ID || got[0].Name != "Claude 2" {
t.Fatalf("after resave: %+v", got)
}
if !strings.Contains(get(t, s, c, "/settings?frag=1").Body.String(), `value="Claude 2"`) {
t.Fatal("the settings page does not show the model's name")
}
}
// The chat asks the model it names.
func TestAIChatUsesChosenModel(t *testing.T) {
s, c := newTestServer(t)
srv := func(word string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "data: {\"choices\":[{\"delta\":{\"content\":\"%s\"}}]}\n\ndata: [DONE]\n\n", word)
}))
}
one, two := srv("first"), srv("second")
defer one.Close()
defer two.Close()
postAIModels(t, s, c, [][7]string{
{"", "One", "openai", one.URL, "m", "", ""},
{"", "Two", "openai", two.URL, "m", "", ""},
})
ms := s.aiModels()
ask := func(model string) string {
b, _ := json.Marshal(map[string]any{"message": "hi", "context": "x", "model": model})
req := httptest.NewRequest(http.MethodPost, "/api/panel/ai/chat", strings.NewReader(string(b)))
req.AddCookie(c)
w := httptest.NewRecorder()
s.Handler().ServeHTTP(w, req)
return w.Body.String()
}
if got := ask(ms[1].ID); !strings.Contains(got, "second") {
t.Fatalf("model Two: %q", got)
}
if got := ask(""); !strings.Contains(got, "first") {
t.Fatalf("no model named - the first: %q", got)
}
}