Changeset 0.7.2 (#40)
This commit is contained in:
@@ -1,17 +1,16 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gin.SetMode(gin.TestMode)
|
||||
}
|
||||
"git.gobha.me/xcaliber/chat-switchboard/database"
|
||||
)
|
||||
|
||||
// ── Channel Request Validation ─────────────────
|
||||
|
||||
@@ -60,7 +59,7 @@ func TestUpdateChannelEmptyBody(t *testing.T) {
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Without a DB connection, UpdateChannel will fail at ownership check.
|
||||
// Integration tests with a real DB would validate the "no fields" path.
|
||||
// Integration tests with a real DB validate the "no fields" path.
|
||||
// Here we just confirm it doesn't return 400 for valid JSON.
|
||||
h.UpdateChannel(c)
|
||||
|
||||
@@ -122,19 +121,151 @@ func TestCreateMessageMissingContent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Integration: Message CRUD with Real DB ──────
|
||||
|
||||
func TestCreateMessageValidRoles(t *testing.T) {
|
||||
// Valid roles should pass validation (not return 400).
|
||||
// They'll fail at ownership check (no DB), which is expected.
|
||||
t.Skip("requires database connection for ownership check")
|
||||
database.RequireTestDB(t)
|
||||
database.TruncateAll(t)
|
||||
|
||||
userID := database.SeedTestUser(t, "roletester", "role@test.com")
|
||||
channelID := database.SeedTestChannel(t, userID, "Role Test")
|
||||
|
||||
h := NewMessageHandler()
|
||||
|
||||
for _, role := range []string{"user", "assistant", "system"} {
|
||||
t.Run(role, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("user_id", userID)
|
||||
c.Params = gin.Params{{Key: "id", Value: channelID}}
|
||||
c.Request = httptest.NewRequest("POST", "/api/v1/channels/"+channelID+"/messages",
|
||||
strings.NewReader(`{"role":"`+role+`","content":"hello from `+role+`"}`))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
h.CreateMessage(c)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("role=%s: expected 201, got %d: %s", role, w.Code, w.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ── Regenerate ───────────────────────────────
|
||||
func TestChannelCRUDIntegration(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
database.TruncateAll(t)
|
||||
|
||||
func TestRegenerateRequiresDB(t *testing.T) {
|
||||
// Regenerate is now a full handler (creates sibling assistant message
|
||||
// via tree-aware streaming). It calls userOwnsChannel → database.DB,
|
||||
// so it cannot run without a live database connection.
|
||||
t.Skip("requires database connection for ownership check")
|
||||
userID := database.SeedTestUser(t, "cruduser", "crud@test.com")
|
||||
|
||||
h := NewChannelHandler()
|
||||
r := gin.New()
|
||||
r.Use(func(c *gin.Context) { c.Set("user_id", userID); c.Next() })
|
||||
r.POST("/channels", h.CreateChannel)
|
||||
r.GET("/channels", h.ListChannels)
|
||||
r.GET("/channels/:id", h.GetChannel)
|
||||
r.PUT("/channels/:id", h.UpdateChannel)
|
||||
r.DELETE("/channels/:id", h.DeleteChannel)
|
||||
|
||||
// Create
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/channels",
|
||||
strings.NewReader(`{"title":"Integration Test Channel"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("Create: expected 201, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var created map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &created)
|
||||
channelID := created["id"].(string)
|
||||
|
||||
// Get
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/channels/"+channelID, nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Get: expected 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
// List
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/channels", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("List: expected 200, got %d", w.Code)
|
||||
}
|
||||
var listResp map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &listResp)
|
||||
if listResp["total"].(float64) < 1 {
|
||||
t.Error("List: expected at least 1 channel")
|
||||
}
|
||||
|
||||
// Update
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("PUT", "/channels/"+channelID,
|
||||
strings.NewReader(`{"title":"Updated Title"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Update: expected 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Delete
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/channels/"+channelID, nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Delete: expected 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
// Verify gone
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/channels/"+channelID, nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("Get after delete: expected 404, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegeneratePassesOwnershipCheck(t *testing.T) {
|
||||
database.RequireTestDB(t)
|
||||
database.TruncateAll(t)
|
||||
|
||||
userID := database.SeedTestUser(t, "regenuser", "regen@test.com")
|
||||
channelID := database.SeedTestChannel(t, userID, "Regen Test")
|
||||
|
||||
// Seed an assistant message to regenerate
|
||||
var msgID string
|
||||
err := database.DB.QueryRow(`
|
||||
INSERT INTO messages (channel_id, role, content, participant_type, participant_id)
|
||||
VALUES ($1, 'assistant', 'original response', 'model', 'test-model')
|
||||
RETURNING id
|
||||
`, channelID).Scan(&msgID)
|
||||
if err != nil {
|
||||
t.Fatalf("seed message: %v", err)
|
||||
}
|
||||
|
||||
h := NewMessageHandler()
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("user_id", userID)
|
||||
c.Params = gin.Params{
|
||||
{Key: "id", Value: channelID},
|
||||
{Key: "msgId", Value: msgID},
|
||||
}
|
||||
c.Request = httptest.NewRequest("POST",
|
||||
"/api/v1/channels/"+channelID+"/messages/"+msgID+"/regenerate",
|
||||
strings.NewReader(`{}`))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
h.Regenerate(c)
|
||||
|
||||
// Should NOT be 404 — ownership check passed. Will be 400 or 500
|
||||
// because no API config is set up, which is expected.
|
||||
if w.Code == http.StatusNotFound {
|
||||
t.Errorf("Expected to pass ownership check, got 404: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pagination Helpers ──────────────────────
|
||||
|
||||
Reference in New Issue
Block a user