567 lines
19 KiB
Go
567 lines
19 KiB
Go
package v1
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/suite"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
|
|
"github.com/gochat/gochat/internal/automation"
|
|
"github.com/gochat/gochat/internal/model"
|
|
)
|
|
|
|
// ========== DBProvider for tests ==========
|
|
|
|
// testBotRuleDBProvider wraps *gorm.DB to satisfy automation.DBProvider.
|
|
type testBotRuleDBProvider struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func (d *testBotRuleDBProvider) DB() *gorm.DB { return d.db }
|
|
|
|
// Verify testBotRuleDBProvider satisfies automation.DBProvider at compile time.
|
|
var _ automation.DBProvider = (*testBotRuleDBProvider)(nil)
|
|
|
|
// ========== Test Suite ==========
|
|
|
|
// BotRuleHandlerTestSuite tests BotRuleHandler endpoints with real SQLite DB,
|
|
// real BotRuleService, and httptest.
|
|
type BotRuleHandlerTestSuite struct {
|
|
suite.Suite
|
|
|
|
db *gorm.DB
|
|
router *gin.Engine
|
|
handler *BotRuleHandler
|
|
svc *automation.BotRuleService
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) SetupSuite() {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{
|
|
Logger: logger.Default.LogMode(logger.Silent),
|
|
})
|
|
s.Require().NoError(err, "failed to open SQLite test database")
|
|
s.Require().NoError(db.AutoMigrate(
|
|
&model.Account{},
|
|
&automation.BotRule{},
|
|
), "failed to auto-migrate models")
|
|
s.db = db
|
|
|
|
dbProvider := &testBotRuleDBProvider{db: db}
|
|
s.svc = automation.NewBotRuleService(dbProvider)
|
|
s.handler = NewBotRuleHandler(s.svc)
|
|
|
|
r := gin.New()
|
|
s.router = r
|
|
|
|
// Register routes matching the handler's expected URL patterns
|
|
accountGroup := r.Group("/api/v1/accounts/:account_id/agent_bots/:agent_bot_id")
|
|
{
|
|
accountGroup.GET("/bot_rules_configs", s.handler.List) // List by account
|
|
accountGroup.GET("/bot_rules", s.handler.ListByBot) // List by bot
|
|
accountGroup.GET("/bot_rules/:rule_id", s.handler.Get) // Get single
|
|
accountGroup.POST("/bot_rules", s.handler.Create) // Create
|
|
accountGroup.PUT("/bot_rules/:rule_id", s.handler.Update) // Update
|
|
accountGroup.DELETE("/bot_rules/:rule_id", s.handler.Delete) // Delete
|
|
accountGroup.PATCH("/bot_rules/:rule_id/status", s.handler.ToggleStatus) // Toggle status
|
|
accountGroup.POST("/bot_rules/:rule_id/clone", s.handler.Clone) // Clone
|
|
}
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TearDownSuite() {
|
|
sqlDB, err := s.db.DB()
|
|
if err == nil {
|
|
sqlDB.Close()
|
|
}
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) SetupTest() {
|
|
s.db.Exec("DELETE FROM bot_rules")
|
|
}
|
|
|
|
// ========== Helper methods ==========
|
|
|
|
// createTestBotRule creates a BotRule directly via DB for test setup.
|
|
func (s *BotRuleHandlerTestSuite) createTestBotRule(accountID, agentBotID uint, name string, eventName automation.BotRuleEventType, status automation.BotRuleStatus) *automation.BotRule {
|
|
rule := &automation.BotRule{
|
|
AccountID: accountID,
|
|
AgentBotID: agentBotID,
|
|
Name: name,
|
|
EventName: eventName,
|
|
Conditions: automation.Conditions{
|
|
{Attribute: "status", FilterOperator: "equal", Values: []string{"open"}, QueryOperator: "and"},
|
|
},
|
|
Actions: automation.Actions{
|
|
{ActionName: "assign_agent", ActionParams: map[string]interface{}{"agent_id": float64(1)}},
|
|
},
|
|
Status: status,
|
|
}
|
|
err := s.db.Create(rule).Error
|
|
s.Require().NoError(err)
|
|
return rule
|
|
}
|
|
|
|
// uid converts a uint ID to string for URL path parameters.
|
|
func (s *BotRuleHandlerTestSuite) uid(id uint) string {
|
|
return strconv.FormatUint(uint64(id), 10)
|
|
}
|
|
|
|
// ========== List (by account) tests ==========
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestList_Empty() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/agent_bots/1/bot_rules_configs", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusOK, w.Code)
|
|
|
|
var body map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
|
|
botRules := body["bot_rules"].([]interface{})
|
|
s.Len(botRules, 0)
|
|
meta := body["meta"].(map[string]interface{})
|
|
s.Equal(float64(0), meta["count"])
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestList_WithRules() {
|
|
s.createTestBotRule(1, 1, "Rule1", automation.BotRuleEventConversationCreated, automation.BotRuleStatusActive)
|
|
s.createTestBotRule(1, 2, "Rule2", automation.BotRuleEventMessageCreated, automation.BotRuleStatusActive)
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/agent_bots/1/bot_rules_configs", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusOK, w.Code)
|
|
|
|
var body map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
|
|
botRules := body["bot_rules"].([]interface{})
|
|
s.Len(botRules, 2)
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestList_InvalidAccountID() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/agent_bots/1/bot_rules_configs", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
|
|
var body map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
|
|
s.False(body["success"].(bool))
|
|
errObj := body["error"].(map[string]interface{})
|
|
s.Equal("BAD_REQUEST", errObj["code"])
|
|
}
|
|
|
|
// ========== ListByBot tests ==========
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestListByBot_Empty() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/agent_bots/1/bot_rules", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusOK, w.Code)
|
|
|
|
var body map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
|
|
botRules := body["bot_rules"].([]interface{})
|
|
s.Len(botRules, 0)
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestListByBot_WithRules() {
|
|
s.createTestBotRule(1, 1, "Rule1", automation.BotRuleEventConversationCreated, automation.BotRuleStatusActive)
|
|
s.createTestBotRule(1, 1, "Rule2", automation.BotRuleEventMessageCreated, automation.BotRuleStatusInactive)
|
|
// This rule belongs to bot 2, should not appear in bot 1 list
|
|
s.createTestBotRule(1, 2, "Rule3", automation.BotRuleEventMessageCreated, automation.BotRuleStatusActive)
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/agent_bots/1/bot_rules", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusOK, w.Code)
|
|
|
|
var body map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
|
|
botRules := body["bot_rules"].([]interface{})
|
|
s.Len(botRules, 2) // Only rules for bot 1
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestListByBot_InvalidAccountID() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/agent_bots/1/bot_rules", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestListByBot_InvalidAgentBotID() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/agent_bots/abc/bot_rules", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
// ========== Get tests ==========
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestGet_Success() {
|
|
rule := s.createTestBotRule(1, 1, "Test Rule", automation.BotRuleEventConversationCreated, automation.BotRuleStatusActive)
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/agent_bots/1/bot_rules/"+s.uid(rule.ID), nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusOK, w.Code)
|
|
|
|
var body map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
|
|
s.True(body["success"].(bool))
|
|
data := body["data"].(map[string]interface{})
|
|
s.Equal("Test Rule", data["name"])
|
|
s.Equal("conversation_created", data["event_name"])
|
|
s.Equal("active", data["status"])
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestGet_NotFound() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/agent_bots/1/bot_rules/999", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusNotFound, w.Code)
|
|
|
|
var body map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
|
|
s.False(body["success"].(bool))
|
|
errObj := body["error"].(map[string]interface{})
|
|
s.Equal("NOT_FOUND", errObj["code"])
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestGet_InvalidID() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/agent_bots/1/bot_rules/abc", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
|
|
var body map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
|
|
errObj := body["error"].(map[string]interface{})
|
|
s.Equal("BAD_REQUEST", errObj["code"])
|
|
}
|
|
|
|
// ========== Create tests ==========
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestCreate_Success() {
|
|
body := `{
|
|
"name": "Auto-respond",
|
|
"description": "Auto respond on conversation created",
|
|
"event_name": "conversation_created",
|
|
"conditions": [{"attribute":"status","filter_operator":"equal","values":["open"],"query_operator":"and"}],
|
|
"actions": [{"action_name":"send_message","action_params":{"content":"Hello!"}}],
|
|
"status": "active"
|
|
}`
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/agent_bots/1/bot_rules", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusCreated, w.Code)
|
|
|
|
var resp map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
|
s.True(resp["success"].(bool))
|
|
data := resp["data"].(map[string]interface{})
|
|
s.Equal("Auto-respond", data["name"])
|
|
s.Equal("conversation_created", data["event_name"])
|
|
s.Equal("active", data["status"])
|
|
s.Equal(float64(1), data["account_id"])
|
|
s.Equal(float64(1), data["agent_bot_id"])
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestCreate_DefaultActiveStatus() {
|
|
// When status is not specified, it defaults to active
|
|
body := `{
|
|
"name": "Rule without status",
|
|
"event_name": "message_created",
|
|
"conditions": [{"attribute":"status","filter_operator":"equal","values":["open"],"query_operator":"and"}],
|
|
"actions": [{"action_name":"send_message","action_params":{"content":"Hi"}}]
|
|
}`
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/agent_bots/1/bot_rules", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusCreated, w.Code)
|
|
|
|
var resp map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
|
data := resp["data"].(map[string]interface{})
|
|
s.Equal("active", data["status"])
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestCreate_InvalidJSON() {
|
|
body := `{invalid json`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/agent_bots/1/bot_rules", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
|
|
var resp map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
|
s.False(resp["success"].(bool))
|
|
errObj := resp["error"].(map[string]interface{})
|
|
s.Equal("VALIDATION_ERROR", errObj["code"])
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestCreate_InvalidAccountID() {
|
|
body := `{"name":"Rule","event_name":"conversation_created","conditions":[],"actions":[]}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/agent_bots/1/bot_rules", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestCreate_InvalidAgentBotID() {
|
|
body := `{"name":"Rule","event_name":"conversation_created","conditions":[],"actions":[]}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/agent_bots/abc/bot_rules", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
// ========== Update tests ==========
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestUpdate_Success() {
|
|
rule := s.createTestBotRule(1, 1, "Original Rule", automation.BotRuleEventConversationCreated, automation.BotRuleStatusActive)
|
|
|
|
body := `{
|
|
"name": "Updated Rule",
|
|
"description": "Updated description",
|
|
"event_name": "message_created",
|
|
"status": "inactive",
|
|
"conditions": [{"attribute":"content","filter_operator":"contains","values":["spam"],"query_operator":"and"}],
|
|
"actions": [{"action_name":"add_label","action_params":{"labels":["spam"]}}]
|
|
}`
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("PUT", "/api/v1/accounts/1/agent_bots/1/bot_rules/"+s.uid(rule.ID), bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusOK, w.Code)
|
|
|
|
var resp map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
|
s.True(resp["success"].(bool))
|
|
data := resp["data"].(map[string]interface{})
|
|
s.Equal("Updated Rule", data["name"])
|
|
s.Equal("Updated description", data["description"])
|
|
s.Equal("message_created", data["event_name"])
|
|
s.Equal("inactive", data["status"])
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestUpdate_NotFound() {
|
|
body := `{"name":"Updated Rule"}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("PUT", "/api/v1/accounts/1/agent_bots/1/bot_rules/999", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusNotFound, w.Code)
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestUpdate_InvalidID() {
|
|
body := `{"name":"Updated Rule"}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("PUT", "/api/v1/accounts/1/agent_bots/1/bot_rules/abc", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestUpdate_InvalidJSON() {
|
|
rule := s.createTestBotRule(1, 1, "Rule", automation.BotRuleEventConversationCreated, automation.BotRuleStatusActive)
|
|
|
|
body := `{invalid json`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("PUT", "/api/v1/accounts/1/agent_bots/1/bot_rules/"+s.uid(rule.ID), bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
// ========== Delete tests ==========
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestDelete_Success() {
|
|
rule := s.createTestBotRule(1, 1, "Rule to delete", automation.BotRuleEventConversationCreated, automation.BotRuleStatusActive)
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/accounts/1/agent_bots/1/bot_rules/"+s.uid(rule.ID), nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusNoContent, w.Code)
|
|
s.Empty(w.Body.Bytes())
|
|
|
|
// Verify the rule is soft-deleted (not found on subsequent GET)
|
|
w2 := httptest.NewRecorder()
|
|
req2, _ := http.NewRequest("GET", "/api/v1/accounts/1/agent_bots/1/bot_rules/"+s.uid(rule.ID), nil)
|
|
s.router.ServeHTTP(w2, req2)
|
|
s.Equal(http.StatusNotFound, w2.Code)
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestDelete_NotFound() {
|
|
// GORM Delete on non-existent ID returns nil → handler returns 204
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/accounts/1/agent_bots/1/bot_rules/999", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusNoContent, w.Code)
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestDelete_InvalidID() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/accounts/1/agent_bots/1/bot_rules/abc", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
// ========== ToggleStatus tests ==========
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestToggleStatus_Success() {
|
|
rule := s.createTestBotRule(1, 1, "Rule to toggle", automation.BotRuleEventConversationCreated, automation.BotRuleStatusActive)
|
|
|
|
body := `{"status": "inactive"}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/agent_bots/1/bot_rules/"+s.uid(rule.ID)+"/status", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusOK, w.Code)
|
|
|
|
var resp map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
|
s.True(resp["success"].(bool))
|
|
data := resp["data"].(map[string]interface{})
|
|
s.Equal(float64(rule.ID), data["id"])
|
|
s.Equal("inactive", data["status"])
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestToggleStatus_InvalidID() {
|
|
body := `{"status": "active"}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/agent_bots/1/bot_rules/abc/status", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestToggleStatus_InvalidJSON() {
|
|
rule := s.createTestBotRule(1, 1, "Rule", automation.BotRuleEventConversationCreated, automation.BotRuleStatusActive)
|
|
|
|
body := `{invalid json`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/agent_bots/1/bot_rules/"+s.uid(rule.ID)+"/status", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestToggleStatus_NotFound() {
|
|
// GORM Update on non-existent ID returns nil (0 rows affected, no error) → handler returns 200
|
|
body := `{"status": "active"}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/agent_bots/1/bot_rules/999/status", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusOK, w.Code)
|
|
}
|
|
|
|
// ========== Clone tests ==========
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestClone_Success() {
|
|
rule := s.createTestBotRule(1, 1, "Original Rule", automation.BotRuleEventConversationCreated, automation.BotRuleStatusActive)
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/agent_bots/1/bot_rules/"+s.uid(rule.ID)+"/clone", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusCreated, w.Code)
|
|
|
|
var resp map[string]interface{}
|
|
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
|
s.True(resp["success"].(bool))
|
|
data := resp["data"].(map[string]interface{})
|
|
s.Equal("Original Rule (copy)", data["name"])
|
|
s.Equal("conversation_created", data["event_name"])
|
|
s.Equal("active", data["status"])
|
|
s.Equal(float64(1), data["account_id"])
|
|
s.Equal(float64(1), data["agent_bot_id"])
|
|
|
|
// Verify the cloned rule has a different ID
|
|
s.NotEqual(float64(rule.ID), data["id"])
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestClone_NotFound() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/agent_bots/1/bot_rules/999/clone", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusNotFound, w.Code)
|
|
}
|
|
|
|
func (s *BotRuleHandlerTestSuite) TestClone_InvalidID() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/agent_bots/1/bot_rules/abc/clone", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
s.Equal(http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
// ========== Nil service guard ==========
|
|
|
|
func TestBotRuleHandler_NilServicePanics(t *testing.T) {
|
|
handler := NewBotRuleHandler(nil)
|
|
assert.Nil(t, handler.svc, "BotRuleHandler with nil service should have nil svc field")
|
|
|
|
// The handler methods will panic if svc is nil because they call h.svc.ListByAccount etc.
|
|
// We verify this by setting up a router and confirming panic behavior.
|
|
r := gin.New()
|
|
r.GET("/api/v1/accounts/:account_id/agent_bots/:agent_bot_id/bot_rules_configs", handler.List)
|
|
|
|
assert.Panics(t, func() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/agent_bots/1/bot_rules_configs", nil)
|
|
r.ServeHTTP(w, req)
|
|
}, "calling List with nil service should panic")
|
|
}
|
|
|
|
// ========== Test runner ==========
|
|
|
|
func TestBotRuleHandlerTestSuite(t *testing.T) {
|
|
suite.Run(t, new(BotRuleHandlerTestSuite))
|
|
}
|