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/automation" ) // setupAutomationRuleEdgeRouter creates a test router for automation rule handler edge-case tests. func setupAutomationRuleEdgeRouter(handler *AutomationRuleHandler) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() router.Use(gin.Recovery(), mockAuthMiddleware()) router.GET("/api/v1/accounts/:account_id/automation_rules", handler.List) router.GET("/api/v1/accounts/:account_id/automation_rules/:automation_id", handler.Get) router.POST("/api/v1/accounts/:account_id/automation_rules", handler.Create) router.PUT("/api/v1/accounts/:account_id/automation_rules/:automation_id", handler.Update) router.DELETE("/api/v1/accounts/:account_id/automation_rules/:automation_id", handler.Delete) router.POST("/api/v1/accounts/:account_id/automation_rules/:automation_id/clone", handler.Clone) router.POST("/api/v1/accounts/:account_id/automation_rules/:automation_id/toggle_active", handler.ToggleActive) return router } func newAutomationRuleEdgeHandler() *AutomationRuleHandler { return NewAutomationRuleHandler(&automation.AutomationRuleService{}) } // =========================== // Create AutomationRule edge cases // =========================== func TestAutomationRuleCreate_EmptyBody(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/automation_rules", nil) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) assert.Contains(t, resp, "error") } func TestAutomationRuleCreate_InvalidJSON(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/automation_rules", bytes.NewBufferString(`{bad json`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleCreate_InvalidAccountID(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/notanumber/automation_rules", bytes.NewBufferString(`{"name":"test rule","event_name":"message_created","conditions":[],"actions":[],"active":true}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleCreate_MissingRequiredFields(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) // name and event_name are required (gorm:"not null") — sending empty strings w := httptest.NewRecorder() body := `{"name":"","event_name":"","conditions":[],"actions":[],"active":true}` req, _ := http.NewRequest("POST", "/api/v1/accounts/1/automation_rules", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON succeeds (valid JSON), but nil service panics → 500 from Recovery assert.NotEqual(t, http.StatusCreated, w.Code) } func TestAutomationRuleCreate_WrongMethod(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) // Create is POST-only; DELETE should return 404 w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/1/automation_rules", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // =========================== // Update AutomationRule edge cases // =========================== func TestAutomationRuleUpdate_EmptyBody(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/api/v1/accounts/1/automation_rules/1", nil) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleUpdate_InvalidJSON(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/api/v1/accounts/1/automation_rules/1", bytes.NewBufferString(`{broken json`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleUpdate_InvalidAccountID(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/api/v1/accounts/abc/automation_rules/1", bytes.NewBufferString(`{"name":"updated","event_name":"message_created","active":true}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleUpdate_InvalidRuleID(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/api/v1/accounts/1/automation_rules/xyz", bytes.NewBufferString(`{"name":"updated","event_name":"message_created","active":true}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleUpdate_WrongMethod(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) // Update is PUT-only; POST on the same path triggers Create (different route) // Let's test PATCH instead → 404 (no PATCH route registered) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/automation_rules/1", bytes.NewBufferString(`{"name":"updated"}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // =========================== // Clone AutomationRule edge cases // =========================== func TestAutomationRuleClone_InvalidAccountID(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) // Clone route uses parseUintParam on "id" but not "account_id" // However, the Clone handler only parses :id, not :account_id // So account_id won't be validated by Clone. Let's test invalid :id instead. w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/automation_rules/abc/clone", nil) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleClone_WrongMethod(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) // Clone is POST-only; GET should return 404 w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/1/automation_rules/1/clone", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // =========================== // ToggleActive AutomationRule edge cases // =========================== func TestAutomationRuleToggleActive_EmptyBody(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/automation_rules/1/toggle_active", nil) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleToggleActive_InvalidJSON(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/automation_rules/1/toggle_active", bytes.NewBufferString(`{bad json`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleToggleActive_InvalidRuleID(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/automation_rules/abc/toggle_active", bytes.NewBufferString(`{"active":true}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleToggleActive_MissingActiveField(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) // ToggleActive requires "active" field; sending empty JSON object w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/automation_rules/1/toggle_active", bytes.NewBufferString(`{}`)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // ShouldBindJSON succeeds (valid JSON), but active field defaults to false (zero value) // The handler checks err from ShouldBindJSON, but doesn't require explicit active field // It just uses the boolean. So this goes to nil service which panics → 500 assert.NotEqual(t, http.StatusOK, w.Code) } // =========================== // Get/List/Delete AutomationRule edge cases // =========================== func TestAutomationRuleGet_InvalidAccountID(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/automation_rules/1", nil) router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleGet_InvalidRuleID(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/1/automation_rules/xyz", nil) router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleList_InvalidAccountID(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/automation_rules", nil) router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleDelete_InvalidRuleID(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/1/automation_rules/notanid", nil) router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusBadRequest || w.Code == http.StatusUnprocessableEntity) } func TestAutomationRuleDelete_WrongMethod(t *testing.T) { handler := newAutomationRuleEdgeHandler() router := setupAutomationRuleEdgeRouter(handler) // Delete is DELETE-only; POST on the same :id path triggers Clone route (different route) // Let's test PUT on Delete route → 404 is not correct because PUT matches Update route // Let's test PATCH instead → 404 w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/automation_rules/1", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) }