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" ) // setupInboxEdgeRouter creates a test router for inbox handler edge-case tests. func setupInboxEdgeRouter(handler *InboxHandler) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() router.Use(gin.Recovery(), mockAuthMiddleware()) router.GET("/api/v1/accounts/:inbox_id/inboxes", handler.List) router.GET("/api/v1/accounts/:inbox_id/inboxes/:inbox_id", handler.Get) router.POST("/api/v1/accounts/:inbox_id/inboxes", handler.Create) router.PUT("/api/v1/accounts/:inbox_id/inboxes/:inbox_id", handler.Update) router.DELETE("/api/v1/accounts/:inbox_id/inboxes/:inbox_id", handler.Delete) return router } func newInboxEdgeHandler() *InboxHandler { return NewInboxHandler(&service.InboxService{}) } // =========================== // Create Inbox edge cases // =========================== func TestInboxCreate_EmptyBody(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/inboxes", nil) 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 TestInboxCreate_InvalidJSON(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/inboxes", bytes.NewBufferString(`{broken json`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestInboxCreate_InvalidAccountID(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/inboxes", bytes.NewBufferString(`{"name":"Test","channel_type":"web_widget"}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestInboxCreate_MissingRequiredFields(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) // name is required (validate:"required,min=2") and channel_type is required w := httptest.NewRecorder() body := `{"enabled":true}` req, _ := http.NewRequest("POST", "/api/v1/accounts/1/inboxes", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON succeeds, but nil service panics → 500 from Recovery assert.NotEqual(t, http.StatusCreated, w.Code) } func TestInboxCreate_InvalidChannelType(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) // channel_type must be oneof=web_widget telegram facebook instagram whatsapp email api tiktok line twilio_sms // "invalid_channel" is not a valid type w := httptest.NewRecorder() body := `{"name":"Test Inbox","channel_type":"invalid_channel"}` req, _ := http.NewRequest("POST", "/api/v1/accounts/1/inboxes", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON succeeds, but service.Create rejects invalid channel_type → nil service panics → 500 assert.NotEqual(t, http.StatusCreated, w.Code) } func TestInboxCreate_NameTooShort(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) // name has validate:"required,min=2"; "A" is too short w := httptest.NewRecorder() body := `{"name":"A","channel_type":"web_widget"}` req, _ := http.NewRequest("POST", "/api/v1/accounts/1/inboxes", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON succeeds, but service.Create rejects short name → nil service panics → 500 assert.NotEqual(t, http.StatusCreated, w.Code) } func TestInboxCreate_WrongMethod(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) // Create is POST-only; DELETE on /inboxes (no :inbox_id) → 404 w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/1/inboxes", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // =========================== // Update Inbox edge cases // =========================== func TestInboxUpdate_EmptyBody(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/api/v1/accounts/1/inboxes/1", nil) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestInboxUpdate_InvalidJSON(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/api/v1/accounts/1/inboxes/1", bytes.NewBufferString(`{invalid`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestInboxUpdate_InvalidAccountID(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/api/v1/accounts/abc/inboxes/1", bytes.NewBufferString(`{"name":"Updated"}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestInboxUpdate_InvalidInboxID(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/api/v1/accounts/1/inboxes/notanumber", bytes.NewBufferString(`{"name":"Updated"}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestInboxUpdate_NameTooShort(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) // name has validate:"omitempty,min=2"; providing a 1-char name should fail w := httptest.NewRecorder() body := `{"name":"X"}` req, _ := http.NewRequest("PUT", "/api/v1/accounts/1/inboxes/1", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON succeeds, but service.Update rejects via validation → nil service panics → 500 assert.NotEqual(t, http.StatusOK, w.Code) } func TestInboxUpdate_WrongMethod(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) // Update is PUT-only; PATCH should return 404 w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/inboxes/1", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // =========================== // Get Inbox edge cases // =========================== func TestInboxGet_InvalidAccountID(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/inboxes/1", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestInboxGet_InvalidInboxID(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/1/inboxes/xyz", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestInboxGet_WrongMethod(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) // Get is GET-only; DELETE should match Delete route, not 404 // Let's test POST on Get route → 404 (no POST route for specific inbox) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/inboxes/1", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // =========================== // Delete Inbox edge cases // =========================== func TestInboxDelete_InvalidAccountID(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/notanid/inboxes/1", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestInboxDelete_InvalidInboxID(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/1/inboxes/abc", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestInboxDelete_WrongMethod(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) // Delete is DELETE-only; POST should return 404 w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/inboxes/1", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // =========================== // List Inbox edge cases // =========================== func TestInboxList_InvalidAccountID(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/inboxes", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestInboxList_WrongMethod(t *testing.T) { handler := newInboxEdgeHandler() router := setupInboxEdgeRouter(handler) // List is GET-only; POST matches Create route (different behavior) // Let's test PATCH → 404 w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/inboxes", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) }