mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 11:40:53 +08:00
Without ARTEX_PG_DSN and without a config file, go test ./server/ failed
44 tests with "no database config found" instead of skipping them. The
tests go through NewManager and t.Fatal on its error: 35 through the
shared fixture trafficEvidenceServer (directly, or through
verificationFixture and newNotifyFixture), 2 through ingestServer, and 7
call NewManager themselves. The 7 already carry a skip on m.pg == nil,
but it sits after the t.Fatal and is never reached.
Add skipWithoutDSN, which calls db.DSN() and skips with the message the
db package and assembly_test.go already use ("no database config (%v) —
skipping"), and call it before NewManager at those 9 sites.
Nothing changes when a database is configured: db.DSN() succeeds, the
helper returns, and a NewManager error (for example an unreachable
Postgres) still fails the test.
158 lines
5.3 KiB
Go
158 lines
5.3 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/Autumn-27/artex/agent"
|
|
"github.com/Autumn-27/artex/db"
|
|
)
|
|
|
|
// The llm/test stored-key fallback must send the same header bytes the runtime
|
|
// sends: form keys are trimmed inside ConfigFrom, the DB fallback used to be
|
|
// assigned raw, so a key saved with surrounding whitespace ran fine in
|
|
// conversations but 401'd the connection test.
|
|
func TestLLMTestStoredKeyFallbackIsTrimmed(t *testing.T) {
|
|
skipWithoutDSN(t)
|
|
m, err := NewManager(t.TempDir(), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { m.Close() })
|
|
if m.pg == nil {
|
|
t.Skip("no postgres for this run")
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
s := New(ctx, m, t.TempDir(), t.TempDir(), t.TempDir())
|
|
|
|
var gotKey atomic.Value
|
|
fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotKey.Store(r.Header.Get("x-api-key"))
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"id":"m","type":"message","role":"assistant","model":"m","content":[{"type":"text","text":"OK"}],"stop_reason":"end_turn","usage":{"input_tokens":1,"output_tokens":1}}`))
|
|
}))
|
|
t.Cleanup(fake.Close)
|
|
|
|
pid, err := m.pg.SaveProfile(&db.LLMProfile{
|
|
Name: "ws-key", Format: "anthropic", Model: "claude-x",
|
|
BaseURL: fake.URL, APIKey: "k1 \t",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _, _ = m.pg.Exec("DELETE FROM llm_profiles WHERE id=$1", pid) })
|
|
|
|
// The handler trims the fallback key: the profile was saved through
|
|
// SaveProfile (which now trims), so also prove the storage trim itself.
|
|
stored, err := m.pg.ProfileByID(pid)
|
|
if err != nil || stored == nil {
|
|
t.Fatalf("ProfileByID: %v %v", stored, err)
|
|
}
|
|
if stored.APIKey != "k1" {
|
|
t.Fatalf("SaveProfile must trim surrounding whitespace, got %q", stored.APIKey)
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"provider": "anthropic", "model": "claude-x", "base_url": fake.URL,
|
|
"api_key": "", "profile_id": pid, "streaming": false,
|
|
})
|
|
req := httptest.NewRequest(http.MethodPost, "/api/llm/test", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
s.testLLM(rec, req)
|
|
|
|
if k, _ := gotKey.Load().(string); k != "k1" {
|
|
t.Fatalf("test request must send the trimmed stored key, header was %q", k)
|
|
}
|
|
// The reply body only proves the call completed; the header is the assertion.
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("handler status %d", rec.Code)
|
|
}
|
|
var resp struct {
|
|
OK bool `json:"ok"`
|
|
Error string `json:"error"`
|
|
}
|
|
_ = json.Unmarshal(rec.Body.Bytes(), &resp)
|
|
if !resp.OK {
|
|
t.Logf("note: provider round-trip returned ok=false (%s); header assertion stands", resp.Error)
|
|
}
|
|
}
|
|
|
|
// Sanity: the runtime path and the test path must agree byte-for-byte on the
|
|
// key they would send for the same stored profile.
|
|
func TestLLMTestFallbackMatchesRuntimeNormalization(t *testing.T) {
|
|
const raw = " k1 \n"
|
|
runtime := agent.ConfigFrom("anthropic", "m", "https://x.example", raw, "")
|
|
if runtime.APIKey != "k1" {
|
|
t.Fatalf("ConfigFrom trims: %q", runtime.APIKey)
|
|
}
|
|
}
|
|
|
|
// The stored-profile test must reach the profile's own endpoint: an empty form
|
|
// (the list-view test button) fills provider/model/base_url from the row, so
|
|
// the key lands on the configured host instead of the canonical default one.
|
|
func TestLLMTestStoredProfileUsesProfileEndpoint(t *testing.T) {
|
|
skipWithoutDSN(t)
|
|
m, err := NewManager(t.TempDir(), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { m.Close() })
|
|
if m.pg == nil {
|
|
t.Skip("no postgres for this run")
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
s := New(ctx, m, t.TempDir(), t.TempDir(), t.TempDir())
|
|
|
|
var hits atomic.Int32
|
|
var gotKey atomic.Value
|
|
fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
hits.Add(1)
|
|
gotKey.Store(r.Header.Get("x-api-key"))
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"id":"m","type":"message","role":"assistant","model":"m","content":[{"type":"text","text":"OK"}],"stop_reason":"end_turn","usage":{"input_tokens":1,"output_tokens":1}}`))
|
|
}))
|
|
t.Cleanup(fake.Close)
|
|
|
|
pid, err := m.pg.SaveProfile(&db.LLMProfile{
|
|
Name: "stored-ep", Format: "anthropic", Model: "claude-x",
|
|
BaseURL: fake.URL, APIKey: "k1",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _, _ = m.pg.Exec("DELETE FROM llm_profiles WHERE id=$1", pid) })
|
|
|
|
// Empty form on purpose: no provider, model, base_url, or streaming.
|
|
body, _ := json.Marshal(map[string]any{"profile_id": pid, "api_key": ""})
|
|
req := httptest.NewRequest(http.MethodPost, "/api/llm/test", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
s.testLLM(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("handler status %d", rec.Code)
|
|
}
|
|
if hits.Load() != 1 {
|
|
t.Fatalf("stored-profile test must hit the profile endpoint exactly once, hits=%d", hits.Load())
|
|
}
|
|
if k, _ := gotKey.Load().(string); k != "k1" {
|
|
t.Fatalf("test request must send the stored key, header was %q", k)
|
|
}
|
|
var resp struct {
|
|
OK bool `json:"ok"`
|
|
Error string `json:"error"`
|
|
}
|
|
_ = json.Unmarshal(rec.Body.Bytes(), &resp)
|
|
if !resp.OK {
|
|
t.Fatalf("stored-profile round-trip not ok: %s", resp.Error)
|
|
}
|
|
}
|