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" ) // setupMessageEdgeRouter creates a test router for message handler edge-case tests. func setupMessageEdgeRouter(handler *MessageHandler) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() router.Use(gin.Recovery(), mockAuthMiddleware()) router.POST("/api/v1/accounts/:account_id/conversations/:conversation_id/messages", handler.Create) router.GET("/api/v1/accounts/:account_id/conversations/:conversation_id/messages", handler.List) router.GET("/api/v1/accounts/:account_id/messages/search", handler.Search) router.PATCH("/api/v1/accounts/:account_id/conversations/:conversation_id/messages/:id", handler.Update) router.DELETE("/api/v1/accounts/:account_id/conversations/:conversation_id/messages/:id", handler.Delete) return router } func newMessageEdgeHandler() *MessageHandler { return NewMessageHandler(&service.MessageService{}) } // =========================== // Create Message edge cases // =========================== func TestMessageCreate_EmptyBody(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", nil) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON on empty body returns EOF → 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 TestMessageCreate_InvalidJSON(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", bytes.NewBufferString(`{broken json`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestMessageCreate_MissingRequiredFields(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) // content is required (validate:"required,min=1"); message_type is required (validate:"required,oneof=...") w := httptest.NewRecorder() body := `{"conversation_id":1}` req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON succeeds, but service.Create rejects via validation → nil service panics → 500 assert.NotEqual(t, http.StatusOK, w.Code) assert.NotEqual(t, http.StatusCreated, w.Code) } func TestMessageCreate_InvalidMessageType(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) // message_type must be oneof=outgoing incoming activity template private_note; "invalid" is not valid w := httptest.NewRecorder() body := `{"conversation_id":1,"content":"hello","message_type":"invalid_type"}` req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON succeeds, but service rejects via validation → nil service panics → 500 assert.NotEqual(t, http.StatusCreated, w.Code) } func TestMessageCreate_InvalidContentType(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) // content_type must be oneof=text input_text input_email input_phone select card private_note; "video" is not valid w := httptest.NewRecorder() body := `{"conversation_id":1,"content":"hello","message_type":"outgoing","content_type":"video"}` req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.NotEqual(t, http.StatusCreated, w.Code) } func TestMessageCreate_InvalidAccountID(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/conversations/1/messages", bytes.NewBufferString(`{"content":"hi","message_type":"outgoing","conversation_id":1}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestMessageCreate_InvalidConversationID(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) // conversation_id in URL is not a uint w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/xyz/messages", bytes.NewBufferString(`{"content":"hi","message_type":"outgoing"}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestMessageCreate_WrongMethod(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) // Create is POST-only; GET should hit List route (different behavior) // Let's test DELETE on a POST-only sub-route → 404 for messages/:id with DELETE is valid // Let's test PATCH on the Create route → 404 w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/conversations/1/messages", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // =========================== // Search Message edge cases // =========================== func TestMessageSearch_InvalidAccountID(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/messages/search?q=test", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestMessageSearch_MissingQueryParam(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) // q is required for search w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/1/messages/search", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestMessageSearch_WrongMethod(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) // Search is GET-only; POST should return 404 w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/messages/search?q=test", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // =========================== // Update Message edge cases // =========================== func TestMessageUpdate_EmptyBody(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/conversations/1/messages/1", nil) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestMessageUpdate_InvalidJSON(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/conversations/1/messages/1", bytes.NewBufferString(`{broken`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestMessageUpdate_InvalidAccountID(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/notvalid/conversations/1/messages/1", bytes.NewBufferString(`{"content":"updated"}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestMessageUpdate_InvalidMessageID(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/conversations/1/messages/abc", bytes.NewBufferString(`{"content":"updated"}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // =========================== // Delete Message edge cases // =========================== func TestMessageDelete_InvalidAccountID(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/xyz/conversations/1/messages/1", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestMessageDelete_InvalidMessageID(t *testing.T) { handler := newMessageEdgeHandler() router := setupMessageEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/1/conversations/1/messages/abc", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) }