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