* fix(captain): restore inbox takeover and KB citations * fix(captain): harden grounded citations and smoke seed --------- Co-authored-by: Rogee <rogee@ipao.vip>
101 lines
4.2 KiB
Go
101 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestAcceptanceFixtureContract(t *testing.T) {
|
|
server := httptest.NewServer(newFixtureServer(log.New(io.Discard, "", 0)))
|
|
t.Cleanup(server.Close)
|
|
|
|
t.Run("knowledge", func(t *testing.T) {
|
|
resp, err := http.Get(server.URL + "/knowledge")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
body, _ := io.ReadAll(resp.Body)
|
|
if resp.StatusCode != http.StatusOK || !bytes.Contains(body, []byte("KBASE-LOCAL-2026")) {
|
|
t.Fatalf("knowledge status=%d marker=%t", resp.StatusCode, bytes.Contains(body, []byte("KBASE-LOCAL-2026")))
|
|
}
|
|
})
|
|
|
|
t.Run("chat retry and faq", func(t *testing.T) {
|
|
fail := `{"model":"gpt-5.6-luna","messages":[{"role":"user","content":"[fixture:fail]"}]}`
|
|
if status := post(t, server.URL+"/v1/chat/completions", fail, nil); status != http.StatusServiceUnavailable {
|
|
t.Fatalf("failure status=%d", status)
|
|
}
|
|
recovered := `{"model":"gpt-5.6-luna","messages":[{"role":"user","content":"[fixture:fail]"},{"role":"user","content":"recovered"}]}`
|
|
if status := post(t, server.URL+"/v1/chat/completions", recovered, nil); status != http.StatusOK {
|
|
t.Fatalf("recovery status=%d", status)
|
|
}
|
|
retry := `{"model":"gpt-5.6-luna","messages":[{"role":"user","content":"[fixture:retry] one"}]}`
|
|
if status := post(t, server.URL+"/v1/chat/completions", retry, nil); status != http.StatusServiceUnavailable {
|
|
t.Fatalf("first retry status=%d", status)
|
|
}
|
|
if status := post(t, server.URL+"/v1/chat/completions", retry, nil); status != http.StatusOK {
|
|
t.Fatalf("second retry status=%d", status)
|
|
}
|
|
faq := `{"model":"gpt-5.6-luna","messages":[{"role":"system","content":"Extract factual, self-contained FAQs"}]}`
|
|
var response struct {
|
|
Choices []struct {
|
|
Message chatMessage `json:"message"`
|
|
} `json:"choices"`
|
|
}
|
|
if status := post(t, server.URL+"/v1/chat/completions", faq, &response); status != http.StatusOK || len(response.Choices) != 1 || !strings.Contains(response.Choices[0].Message.Content, `"faqs"`) {
|
|
t.Fatalf("faq status=%d choices=%d", status, len(response.Choices))
|
|
}
|
|
grounded := `{"model":"gpt-5.6-luna","messages":[{"role":"system","content":"Knowledge Base Context:\nSource: http://captain-fixture:8080/knowledge"},{"role":"user","content":"warranty?"}]}`
|
|
if status := post(t, server.URL+"/v1/chat/completions", grounded, &response); status != http.StatusOK || !strings.Contains(response.Choices[0].Message.Content, "[[1](http://captain-fixture:8080/knowledge)]") {
|
|
t.Fatalf("grounded status=%d content=%q", status, response.Choices[0].Message.Content)
|
|
}
|
|
})
|
|
|
|
t.Run("skill activation", func(t *testing.T) {
|
|
request := `{"model":"gpt-5.6-luna","messages":[{"role":"system","content":"<available_skills_json>[{\"name\":\"local-skill\"}]</available_skills_json>"},{"role":"user","content":"[fixture:skill]"}],"tools":[{"type":"function","function":{"name":"activate_skill"}}]}`
|
|
var response struct {
|
|
Choices []struct {
|
|
Message chatMessage `json:"message"`
|
|
} `json:"choices"`
|
|
}
|
|
status := post(t, server.URL+"/v1/chat/completions", request, &response)
|
|
if status != http.StatusOK || len(response.Choices) != 1 || len(response.Choices[0].Message.ToolCalls) != 1 || response.Choices[0].Message.ToolCalls[0].Function.Name != "activate_skill" {
|
|
t.Fatalf("status=%d choices=%d", status, len(response.Choices))
|
|
}
|
|
})
|
|
|
|
t.Run("embedding dimensions", func(t *testing.T) {
|
|
var response struct {
|
|
Data []struct {
|
|
Embedding []float64 `json:"embedding"`
|
|
} `json:"data"`
|
|
}
|
|
status := post(t, server.URL+"/v1/embeddings", `{"model":"fixture-embedding","input":["hello"]}`, &response)
|
|
if status != http.StatusOK || len(response.Data) != 1 || len(response.Data[0].Embedding) != 1536 {
|
|
t.Fatalf("status=%d vectors=%d dimensions=%d", status, len(response.Data), len(response.Data[0].Embedding))
|
|
}
|
|
})
|
|
}
|
|
|
|
func post(t *testing.T, url, body string, dst any) int {
|
|
t.Helper()
|
|
resp, err := http.Post(url, "application/json", strings.NewReader(body))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if dst != nil {
|
|
if err := json.NewDecoder(resp.Body).Decode(dst); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return resp.StatusCode
|
|
}
|