Files
gochat/internal/handler/api/v1/automation_rule_handler_edge_test.go
T
2026-06-04 15:44:48 +08:00

321 lines
12 KiB
Go

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)
}