Files
ARTEX/server/chat_llm_resolve_test.go
BaaRai 8c8ae97400 test(server): join every live test server before its TempDir cleanup
New restores every unfinished task in the shared database and runs its planner, worker and deadline loops on the server context. Tests that passed context.Background() never stopped or joined them, so they kept writing under the manager TempDir after the test returned and the TempDir cleanup failed with directory not empty. Add stopTestServer, which cancels the context, waits for each task to go quiescent, joins its loops, the archive worker and the side-question writer, and defer it right after New in every test that started a live server.
2026-11-14 18:21:52 +01:00

102 lines
3.7 KiB
Go

package server
import (
"context"
"strings"
"testing"
"github.com/Autumn-27/artex/db"
)
// TestChatUnavailableReasonDistinguishesStates pins the operator-facing message
// to the actual configuration state: nothing configured vs. configured-but-not-
// activated must read differently so the user knows what to do.
func TestChatUnavailableReasonDistinguishesStates(t *testing.T) {
m, err := NewManager(t.TempDir(), "")
if err != nil {
t.Skipf("postgres unavailable (%v) — skipping", err)
}
defer m.Close()
td := t.TempDir()
ctx, cancel := context.WithCancel(context.Background())
s := New(ctx, m, td, td, td)
defer stopTestServer(t, s, cancel)
// Start from a clean profile table; other tests in the shared DB may have left rows.
existing, _ := m.pg.ListProfiles()
for _, p := range existing {
if p.IsDefault {
// The active profile cannot be deleted directly; clear the flag first.
_, _ = m.pg.Exec(`UPDATE llm_profiles SET is_default=false WHERE id=$1`, p.ID)
}
_ = m.pg.DeleteProfile(p.ID)
}
if reason := s.chatUnavailableReason(); !strings.Contains(reason, "No LLM configured yet") {
t.Fatalf("no-profile reason=%q, want No LLM configured yet", reason)
}
id, err := m.pg.SaveProfile(&db.LLMProfile{Name: "p1", Format: "anthropic", Model: "claude-x", APIKey: "sk-test"})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
_, _ = m.pg.Exec(`UPDATE llm_profiles SET is_default=false WHERE id=$1`, id)
_ = m.pg.DeleteProfile(id)
})
// Profile exists but is not activated.
if reason := s.chatUnavailableReason(); !strings.Contains(reason, "No active LLM profile") {
t.Fatalf("inactive reason=%q, want No active LLM profile", reason)
}
// Once activated, the reason no longer claims a missing/inactive config.
if err := m.pg.SetActiveProfile(id); err != nil {
t.Fatal(err)
}
if reason := s.chatUnavailableReason(); strings.Contains(reason, "No LLM configured yet") || strings.Contains(reason, "No active LLM profile") {
t.Fatalf("active reason=%q should not report missing/inactive", reason)
}
}
// TestResolveChatAgentHonoursConversationProfile is the regression guard for the
// reported bug: a conversation that picked a valid profile must resolve a chat
// agent even when NO global profile is active, so the send precheck stops
// rejecting it with "LLM not configured".
func TestResolveChatAgentHonoursConversationProfile(t *testing.T) {
m, err := NewManager(t.TempDir(), "")
if err != nil {
t.Skipf("postgres unavailable (%v) — skipping", err)
}
defer m.Close()
td := t.TempDir()
ctx, cancel := context.WithCancel(context.Background())
s := New(ctx, m, td, td, td)
defer stopTestServer(t, s, cancel)
// Force the global fallback to nil so a non-nil result can ONLY come from the
// conversation's own profile — this is exactly the situation the user hit
// (no usable global config) and isolates the fix from the test host's env.
s.cfgMu.Lock()
s.chatAgent = nil
s.cfgMu.Unlock()
id, err := m.pg.SaveProfile(&db.LLMProfile{Name: "conv-pick", Format: "anthropic", Model: "claude-x", APIKey: "sk-test"})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = m.pg.DeleteProfile(id) })
// A conversation that pinned this profile resolves a chat agent despite the
// nil global fallback; without the fix the send precheck rejected it outright.
conv := &db.Conversation{AgentKey: "mainagent", LLMProfileID: &id}
if s.resolveChatAgent(conv) == nil {
t.Fatalf("resolveChatAgent(conversation with valid profile) = nil; global fallback wrongly gates the pick")
}
// With neither a pick nor a global fallback there is genuinely nothing to run.
if s.resolveChatAgent(&db.Conversation{AgentKey: "mainagent"}) != nil {
t.Fatalf("resolveChatAgent with neither pick nor global config should be nil")
}
}