214 lines
6.1 KiB
Go
214 lines
6.1 KiB
Go
|
|
package server
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"net/http"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/VictorVargas/rony-chat-bot/internal/portfolio"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestConversationRoundTrip(t *testing.T) {
|
||
|
|
url, _ := newTestServer(t)
|
||
|
|
|
||
|
|
// 1) POST /api/chat with a brand-new session — server creates a conv.
|
||
|
|
body := strings.NewReader(`{
|
||
|
|
"messages":[{"role":"user","content":"hola, humano"}],
|
||
|
|
"stream":false
|
||
|
|
}`)
|
||
|
|
resp, err := http.Post(url+"/api/chat", "application/json", body)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
if resp.StatusCode != 200 {
|
||
|
|
t.Fatalf("status = %d, want 200", resp.StatusCode)
|
||
|
|
}
|
||
|
|
var cr ChatResponse
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&cr); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if cr.ConversationID == "" {
|
||
|
|
t.Fatal("response should include a conversation_id")
|
||
|
|
}
|
||
|
|
if cr.Content == "" {
|
||
|
|
t.Error("response content is empty")
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2) GET /api/conversations/:id — should return both user + assistant messages.
|
||
|
|
resp2, err := http.Get(url + "/api/conversations/" + cr.ConversationID)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
defer resp2.Body.Close()
|
||
|
|
if resp2.StatusCode != 200 {
|
||
|
|
t.Fatalf("get conversation status = %d", resp2.StatusCode)
|
||
|
|
}
|
||
|
|
var conv portfolio.Conversation
|
||
|
|
if err := json.NewDecoder(resp2.Body).Decode(&conv); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if conv.ID != cr.ConversationID {
|
||
|
|
t.Errorf("id = %q, want %q", conv.ID, cr.ConversationID)
|
||
|
|
}
|
||
|
|
if len(conv.Messages) < 2 {
|
||
|
|
t.Fatalf("expected >= 2 messages, got %d", len(conv.Messages))
|
||
|
|
}
|
||
|
|
if conv.Messages[0].Role != "user" || conv.Messages[0].Content != "hola, humano" {
|
||
|
|
t.Errorf("first message = %+v", conv.Messages[0])
|
||
|
|
}
|
||
|
|
if conv.Messages[1].Role != "assistant" {
|
||
|
|
t.Errorf("second message role = %q, want assistant", conv.Messages[1].Role)
|
||
|
|
}
|
||
|
|
if conv.Messages[1].Content == "" {
|
||
|
|
t.Error("assistant message content is empty")
|
||
|
|
}
|
||
|
|
// Created + updated timestamps should be set
|
||
|
|
if conv.CreatedAt.IsZero() {
|
||
|
|
t.Error("created_at is zero")
|
||
|
|
}
|
||
|
|
if conv.UpdatedAt.Before(conv.CreatedAt) {
|
||
|
|
t.Errorf("updated_at (%s) < created_at (%s)", conv.UpdatedAt, conv.CreatedAt)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestConversationContinue(t *testing.T) {
|
||
|
|
url, _ := newTestServer(t)
|
||
|
|
|
||
|
|
// First turn: create the conversation.
|
||
|
|
body := strings.NewReader(`{
|
||
|
|
"messages":[{"role":"user","content":"primera"}],
|
||
|
|
"stream":false
|
||
|
|
}`)
|
||
|
|
resp, _ := http.Post(url+"/api/chat", "application/json", body)
|
||
|
|
var cr1 ChatResponse
|
||
|
|
_ = json.NewDecoder(resp.Body).Decode(&cr1)
|
||
|
|
resp.Body.Close()
|
||
|
|
|
||
|
|
// Second turn: pass the same conversation_id and add a new user message.
|
||
|
|
body2 := strings.NewReader(`{
|
||
|
|
"messages":[{"role":"user","content":"primera"},
|
||
|
|
{"role":"assistant","content":"respuesta 1"},
|
||
|
|
{"role":"user","content":"segunda"}],
|
||
|
|
"conversation_id":"` + cr1.ConversationID + `",
|
||
|
|
"stream":false
|
||
|
|
}`)
|
||
|
|
resp2, _ := http.Post(url+"/api/chat", "application/json", body2)
|
||
|
|
var cr2 ChatResponse
|
||
|
|
_ = json.NewDecoder(resp2.Body).Decode(&cr2)
|
||
|
|
resp2.Body.Close()
|
||
|
|
|
||
|
|
if cr2.ConversationID != cr1.ConversationID {
|
||
|
|
t.Errorf("server changed the conversation id: %q → %q", cr1.ConversationID, cr2.ConversationID)
|
||
|
|
}
|
||
|
|
|
||
|
|
// GET should now have 4 messages: user1, assistant1, user2, assistant2.
|
||
|
|
resp3, _ := http.Get(url + "/api/conversations/" + cr1.ConversationID)
|
||
|
|
var conv portfolio.Conversation
|
||
|
|
_ = json.NewDecoder(resp3.Body).Decode(&conv)
|
||
|
|
resp3.Body.Close()
|
||
|
|
|
||
|
|
if len(conv.Messages) != 4 {
|
||
|
|
t.Errorf("expected 4 messages, got %d", len(conv.Messages))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestListConversations(t *testing.T) {
|
||
|
|
url, _ := newTestServer(t)
|
||
|
|
|
||
|
|
// Create two conversations.
|
||
|
|
for _, msg := range []string{"primera conversación", "segunda conversación"} {
|
||
|
|
body := strings.NewReader(`{"messages":[{"role":"user","content":"` + msg + `"}],"stream":false}`)
|
||
|
|
resp, _ := http.Post(url+"/api/chat", "application/json", body)
|
||
|
|
resp.Body.Close()
|
||
|
|
}
|
||
|
|
|
||
|
|
resp, err := http.Get(url + "/api/conversations")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
if resp.StatusCode != 200 {
|
||
|
|
t.Fatalf("status = %d", resp.StatusCode)
|
||
|
|
}
|
||
|
|
var list struct {
|
||
|
|
Count int `json:"count"`
|
||
|
|
Conversations []portfolio.ConversationSummary `json:"conversations"`
|
||
|
|
}
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&list); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if list.Count < 2 {
|
||
|
|
t.Errorf("count = %d, want >= 2", list.Count)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetConversationNotFound(t *testing.T) {
|
||
|
|
url, _ := newTestServer(t)
|
||
|
|
resp, err := http.Get(url + "/api/conversations/nonexistent")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
if resp.StatusCode != 404 {
|
||
|
|
t.Errorf("status = %d, want 404", resp.StatusCode)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDeleteConversation(t *testing.T) {
|
||
|
|
url, _ := newTestServer(t)
|
||
|
|
body := strings.NewReader(`{"messages":[{"role":"user","content":"to be deleted"}],"stream":false}`)
|
||
|
|
resp, _ := http.Post(url+"/api/chat", "application/json", body)
|
||
|
|
var cr ChatResponse
|
||
|
|
_ = json.NewDecoder(resp.Body).Decode(&cr)
|
||
|
|
resp.Body.Close()
|
||
|
|
|
||
|
|
del, err := http.NewRequest("DELETE", url+"/api/conversations/"+cr.ConversationID, nil)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
delResp, err := http.DefaultClient.Do(del)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
delResp.Body.Close()
|
||
|
|
if delResp.StatusCode != 204 {
|
||
|
|
t.Errorf("delete status = %d, want 204", delResp.StatusCode)
|
||
|
|
}
|
||
|
|
|
||
|
|
// GET should now 404.
|
||
|
|
get, _ := http.Get(url + "/api/conversations/" + cr.ConversationID)
|
||
|
|
defer get.Body.Close()
|
||
|
|
if get.StatusCode != 404 {
|
||
|
|
t.Errorf("get after delete status = %d, want 404", get.StatusCode)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestChatStreamingIncludesConversationID(t *testing.T) {
|
||
|
|
url, _ := newTestServer(t)
|
||
|
|
body := strings.NewReader(`{"messages":[{"role":"user","content":"stream test"}],"stream":true}`)
|
||
|
|
resp, err := http.Post(url+"/api/chat", "application/json", body)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
events := parseSSE(t, resp.Body)
|
||
|
|
if len(events) == 0 {
|
||
|
|
t.Fatal("no SSE events received")
|
||
|
|
}
|
||
|
|
if events[0].event != "start" {
|
||
|
|
t.Fatalf("first event = %q, want start", events[0].event)
|
||
|
|
}
|
||
|
|
var start map[string]any
|
||
|
|
if err := json.Unmarshal([]byte(events[0].data), &start); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if start["conversation_id"] == "" || start["conversation_id"] == nil {
|
||
|
|
t.Error("start event missing conversation_id")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// keep the import used
|
||
|
|
var _ = time.Second
|