321 lines
12 KiB
Go
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)
|
|
} |