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 [][6]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]) } 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, [][6]string{ {"", "Qwen", "gradio", "", "", ""}, {"", "Claude", "anthropic", "https://api.anthropic.com", "claude-x", "secret-key"}, {"", "", "", "", "", ""}, // 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 { 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, ms[1].ID) { t.Fatalf("state: %s", body) } // Saved again with its id, a model keeps it. postAIModels(t, s, c, [][6]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, [][6]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) } }