package v1 import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/gochat/gochat/internal/service" ) // setupConversationEdgeRouter creates a test router for conversation handler edge-case tests. func setupConversationEdgeRouter(handler *ConversationHandler) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() router.Use(gin.Recovery(), mockAuthMiddleware()) router.POST("/api/v1/accounts/:account_id/conversations", handler.Create) router.POST("/api/v1/accounts/:account_id/conversations/filter", handler.Filter) router.PATCH("/api/v1/accounts/:account_id/conversations/:id", handler.Update) router.GET("/api/v1/accounts/:account_id/conversations", handler.List) router.GET("/api/v1/accounts/:account_id/conversations/:id", handler.Get) router.DELETE("/api/v1/accounts/:account_id/conversations/:id", handler.Delete) return router } func newConversationEdgeHandler() *ConversationHandler { return NewConversationHandler(&service.ConversationService{}, &service.MessageService{}) } // =========================== // Create Conversation edge cases // =========================== func TestConversationCreate_EmptyBody(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations", nil) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON on nil body returns EOF error → 400 assert.Equal(t, http.StatusBadRequest, w.Code) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) assert.Contains(t, resp, "error") } func TestConversationCreate_InvalidJSON(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations", bytes.NewBufferString(`{invalid json`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) assert.Contains(t, resp, "error") } func TestConversationCreate_MissingRequiredFields(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) // inbox_id and contact_id are required (validate:"required") w := httptest.NewRecorder() body := `{"status":"open"}` req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON succeeds (no syntax error), but the zero-service will fail when calling Create // with validation error. Since service is nil, it panics → Recovery returns 500. // But the handler should at least not crash. We just check it doesn't return 200. assert.NotEqual(t, http.StatusOK, w.Code) } func TestConversationCreate_InvalidAccountID(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/conversations", bytes.NewBufferString(`{"inbox_id":1,"contact_id":1}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestConversationCreate_OutOfRangeStatus(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) // status must be oneof=open resolved pending snoozed; "invalid_status" is out of range w := httptest.NewRecorder() body := `{"inbox_id":1,"contact_id":1,"status":"invalid_status"}` req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON succeeds (JSON syntax is valid), but service validation should reject // With nil service, handler calls service.Create which panics → 500 from Recovery assert.NotEqual(t, http.StatusOK, w.Code) } // =========================== // Filter Conversation edge cases // =========================== func TestConversationFilter_EmptyBody(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/filter", nil) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestConversationFilter_InvalidJSON(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/filter", bytes.NewBufferString(`{broken`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestConversationFilter_InvalidAccountID(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/notanumber/conversations/filter", bytes.NewBufferString(`{"status":"open"}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestConversationFilter_OutOfRangeParams(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) // status must be oneof=open resolved pending snoozed; "closed" is invalid w := httptest.NewRecorder() body := `{"status":"closed"}` req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/filter", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON succeeds, but service.Filter will fail with nil db → 500 from Recovery assert.NotEqual(t, http.StatusOK, w.Code) } // =========================== // Update Conversation edge cases // =========================== func TestConversationUpdate_EmptyBody(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/conversations/1", nil) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestConversationUpdate_InvalidJSON(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/conversations/1", bytes.NewBufferString(`{"status":`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestConversationUpdate_InvalidAccountID(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/abc/conversations/1", bytes.NewBufferString(`{"status":"open"}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestConversationUpdate_InvalidConversationID(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/conversations/xyz", bytes.NewBufferString(`{"status":"open"}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestConversationUpdate_OutOfRangePriority(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) // priority must be oneof=urgent high medium low; "extreme" is invalid w := httptest.NewRecorder() body := `{"priority":"extreme"}` req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/conversations/1", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON succeeds, but service rejects via validation → 500 from nil service assert.NotEqual(t, http.StatusOK, w.Code) } // =========================== // List Conversation edge cases // =========================== func TestConversationList_InvalidAccountID(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/invalid/conversations", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestConversationList_WrongMethod(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) // List is GET-only; sending DELETE should return 404 (no route match) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/1/conversations", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // =========================== // Get Conversation edge cases // =========================== func TestConversationGet_InvalidAccountID(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/conversations/1", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestConversationGet_InvalidConversationID(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/notanumber", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // =========================== // Delete Conversation edge cases // =========================== func TestConversationDelete_WrongMethod(t *testing.T) { handler := newConversationEdgeHandler() router := setupConversationEdgeRouter(handler) // PUT on the DELETE-only route returns 404 (no route match) w := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/api/v1/accounts/1/conversations/1", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) }