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

363 lines
12 KiB
Go

package v1
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"regexp"
"strings"
"testing"
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
"github.com/gochat/gochat/internal/service"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type AgentHandlerTestSuite struct {
suite.Suite
db *gorm.DB
handler *AgentHandler
account *model.Account
user *model.User
}
func (s *AgentHandlerTestSuite) 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)
s.Require().NoError(db.AutoMigrate(&model.Account{}, &model.User{}, &model.AccountUser{}))
s.db = db
agentRepo := repository.NewAgentRepo(db)
svc := service.NewAgentService(agentRepo, db)
s.handler = NewAgentHandler(svc)
s.account = &model.Account{Name: "test-agent-account"}
s.Require().NoError(db.Create(s.account).Error)
s.user = &model.User{
Name: "Inviter Admin",
Email: "inviter@test.com",
Provider: "email",
Active: true,
}
s.Require().NoError(db.Create(s.user).Error)
}
func (s *AgentHandlerTestSuite) SetupTest() {
s.db.Exec("DELETE FROM account_users")
// Don't delete users — we need the inviter user to persist
// Only delete agent users (not the inviter)
s.db.Exec("DELETE FROM users WHERE id != ?", s.user.ID)
}
func (s *AgentHandlerTestSuite) TearDownSuite() {
if s.db != nil {
sqlDB, _ := s.db.DB()
sqlDB.Close()
}
}
func TestAgentHandlerSuite(t *testing.T) {
suite.Run(t, new(AgentHandlerTestSuite))
}
func (s *AgentHandlerTestSuite) makeRequest(method, path string, body interface{}, accountID uint, userID uint) (*httptest.ResponseRecorder, *gin.Context) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
var bodyBytes []byte
if body != nil {
// Chatwoot params.require(:agent) → wrap body under "agent" key for POST/PUT/PATCH
// Exception: bulk_create does NOT use params.require
needsWrap := (method == "POST" || method == "PUT" || method == "PATCH") && !strings.Contains(path, "bulk_create")
if needsWrap {
wrapped := map[string]interface{}{"agent": body}
bodyBytes, _ = json.Marshal(wrapped)
} else {
bodyBytes, _ = json.Marshal(body)
}
}
c.Request = httptest.NewRequest(method, path, bytes.NewReader(bodyBytes))
if body != nil {
c.Request.Header.Set("Content-Type", "application/json")
}
// Set account_id and id params (both refer to account ID in GoChat routes)
params := gin.Params{
{Key: "account_id", Value: fmt.Sprintf("%d", accountID)},
{Key: "id", Value: fmt.Sprintf("%d", accountID)},
}
// Extract agent_id from path like /api/v1/accounts/1/agents/123
// Match pattern: /agents/<number> or /agents/<number>/...
re := regexp.MustCompile(`/agents/(\d+)`)
matches := re.FindStringSubmatch(path)
if len(matches) > 1 {
params = append(params, gin.Param{Key: "agent_id", Value: matches[1]})
}
c.Params = params
c.Set("account_id", accountID)
c.Set("user_id", userID)
return w, c
}
func (s *AgentHandlerTestSuite) TestListEmpty() {
w, c := s.makeRequest("GET", "/api/v1/accounts/1/agents", nil, s.account.ID, s.user.ID)
s.handler.List(c)
assert.Equal(s.T(), http.StatusOK, w.Code)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
assert.True(s.T(), resp["success"].(bool))
data := resp["data"].([]interface{})
assert.Equal(s.T(), 0, len(data))
}
func (s *AgentHandlerTestSuite) TestCreateAgent() {
req := service.CreateAgentRequest{
Email: "agent1@test.com",
Name: "Agent One",
Role: "agent",
}
w, c := s.makeRequest("POST", "/api/v1/accounts/1/agents", req, s.account.ID, s.user.ID)
s.handler.Create(c)
assert.Equal(s.T(), http.StatusCreated, w.Code)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
assert.True(s.T(), resp["success"].(bool))
data := resp["data"].(map[string]interface{})
assert.Equal(s.T(), "agent1@test.com", data["email"])
assert.Equal(s.T(), "Agent One", data["name"])
}
func (s *AgentHandlerTestSuite) TestCreateAgentDuplicate() {
// Create first agent
req := service.CreateAgentRequest{
Email: "agent2@test.com",
Name: "Agent Two",
Role: "agent",
}
w, c := s.makeRequest("POST", "/api/v1/accounts/1/agents", req, s.account.ID, s.user.ID)
s.handler.Create(c)
assert.Equal(s.T(), http.StatusCreated, w.Code)
// Try creating again — should conflict
w2, c2 := s.makeRequest("POST", "/api/v1/accounts/1/agents", req, s.account.ID, s.user.ID)
s.handler.Create(c2)
assert.Equal(s.T(), http.StatusConflict, w2.Code)
}
func (s *AgentHandlerTestSuite) TestCreateAgentValidation() {
req := service.CreateAgentRequest{
Email: "", // required
Name: "", // required
}
w, c := s.makeRequest("POST", "/api/v1/accounts/1/agents", req, s.account.ID, s.user.ID)
s.handler.Create(c)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func (s *AgentHandlerTestSuite) TestGetAgent() {
// Create an agent first
req := service.CreateAgentRequest{
Email: "agent3@test.com",
Name: "Agent Three",
Role: "agent",
}
w, c := s.makeRequest("POST", "/api/v1/accounts/1/agents", req, s.account.ID, s.user.ID)
s.handler.Create(c)
assert.Equal(s.T(), http.StatusCreated, w.Code)
var createResp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &createResp)
data := createResp["data"].(map[string]interface{})
agentID := uint(data["id"].(float64))
// Get the agent
w2, c2 := s.makeRequest("GET", fmt.Sprintf("/api/v1/accounts/1/agents/%d", agentID), nil, s.account.ID, s.user.ID)
c2.Params = append(c2.Params, gin.Param{Key: "id", Value: fmt.Sprintf("%d", agentID)})
s.handler.Get(c2)
assert.Equal(s.T(), http.StatusOK, w2.Code)
var getResp map[string]interface{}
json.Unmarshal(w2.Body.Bytes(), &getResp)
assert.True(s.T(), getResp["success"].(bool))
getData := getResp["data"].(map[string]interface{})
assert.Equal(s.T(), "agent3@test.com", getData["email"])
}
func (s *AgentHandlerTestSuite) TestGetAgentNotFound() {
w, c := s.makeRequest("GET", "/api/v1/accounts/1/agents/9999", nil, s.account.ID, s.user.ID)
s.handler.Get(c)
assert.Equal(s.T(), http.StatusNotFound, w.Code)
}
func (s *AgentHandlerTestSuite) TestUpdateAgent() {
// Create an agent
req := service.CreateAgentRequest{
Email: "agent4@test.com",
Name: "Agent Four",
Role: "agent",
}
w, c := s.makeRequest("POST", "/api/v1/accounts/1/agents", req, s.account.ID, s.user.ID)
s.handler.Create(c)
assert.Equal(s.T(), http.StatusCreated, w.Code)
var createResp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &createResp)
data := createResp["data"].(map[string]interface{})
agentID := uint(data["id"].(float64))
// Update the agent
updateReq := service.UpdateAgentRequest{
Name: "Updated Name",
Role: "administrator",
Availability: "online",
}
w2, c2 := s.makeRequest("PUT", fmt.Sprintf("/api/v1/accounts/1/agents/%d", agentID), updateReq, s.account.ID, s.user.ID)
c2.Params = append(c2.Params, gin.Param{Key: "id", Value: fmt.Sprintf("%d", agentID)})
s.handler.Update(c2)
assert.Equal(s.T(), http.StatusOK, w2.Code)
var updateResp map[string]interface{}
json.Unmarshal(w2.Body.Bytes(), &updateResp)
assert.True(s.T(), updateResp["success"].(bool))
updateData := updateResp["data"].(map[string]interface{})
assert.Equal(s.T(), "Updated Name", updateData["name"])
assert.Equal(s.T(), "administrator", updateData["role"])
assert.Equal(s.T(), "online", updateData["availability"])
}
func (s *AgentHandlerTestSuite) TestDeleteAgent() {
// Create an agent
req := service.CreateAgentRequest{
Email: "agent5@test.com",
Name: "Agent Five",
Role: "agent",
}
w, c := s.makeRequest("POST", "/api/v1/accounts/1/agents", req, s.account.ID, s.user.ID)
s.handler.Create(c)
assert.Equal(s.T(), http.StatusCreated, w.Code)
var createResp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &createResp)
data := createResp["data"].(map[string]interface{})
agentID := uint(data["id"].(float64))
// Delete the agent
w2, c2 := s.makeRequest("DELETE", fmt.Sprintf("/api/v1/accounts/1/agents/%d", agentID), nil, s.account.ID, s.user.ID)
c2.Params = append(c2.Params, gin.Param{Key: "id", Value: fmt.Sprintf("%d", agentID)})
s.handler.Delete(c2)
assert.Equal(s.T(), http.StatusOK, w2.Code)
// Verify agent is gone
w3, c3 := s.makeRequest("GET", fmt.Sprintf("/api/v1/accounts/1/agents/%d", agentID), nil, s.account.ID, s.user.ID)
c3.Params = append(c3.Params, gin.Param{Key: "id", Value: fmt.Sprintf("%d", agentID)})
s.handler.Get(c3)
assert.Equal(s.T(), http.StatusNotFound, w3.Code)
}
func (s *AgentHandlerTestSuite) TestBulkCreate() {
req := service.BulkCreateAgentRequest{
Emails: []string{"bulk1@test.com", "bulk2@test.com", "bulk3@test.com"},
}
w, c := s.makeRequest("POST", "/api/v1/accounts/1/agents/bulk_create", req, s.account.ID, s.user.ID)
s.handler.BulkCreate(c)
assert.Equal(s.T(), http.StatusOK, w.Code)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
assert.True(s.T(), resp["success"].(bool))
data := resp["data"].([]interface{})
assert.Equal(s.T(), 3, len(data))
// Verify emails
for i, agent := range data {
agentMap := agent.(map[string]interface{})
expectedEmail := fmt.Sprintf("bulk%d@test.com", i+1)
assert.Equal(s.T(), expectedEmail, agentMap["email"])
}
}
func (s *AgentHandlerTestSuite) TestBulkCreateValidation() {
req := service.BulkCreateAgentRequest{
Emails: []string{}, // min=1
}
w, c := s.makeRequest("POST", "/api/v1/accounts/1/agents/bulk_create", req, s.account.ID, s.user.ID)
s.handler.BulkCreate(c)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func (s *AgentHandlerTestSuite) TestBulkCreateSkipsDuplicates() {
// Pre-create one agent
preReq := service.CreateAgentRequest{
Email: "existing@test.com",
Name: "Existing",
Role: "agent",
}
w, c := s.makeRequest("POST", "/api/v1/accounts/1/agents", preReq, s.account.ID, s.user.ID)
s.handler.Create(c)
assert.Equal(s.T(), http.StatusCreated, w.Code)
// Bulk create with a duplicate + a new one
req := service.BulkCreateAgentRequest{
Emails: []string{"existing@test.com", "new@test.com"},
}
w2, c2 := s.makeRequest("POST", "/api/v1/accounts/1/agents/bulk_create", req, s.account.ID, s.user.ID)
s.handler.BulkCreate(c2)
assert.Equal(s.T(), http.StatusOK, w2.Code)
var resp map[string]interface{}
json.Unmarshal(w2.Body.Bytes(), &resp)
data := resp["data"].([]interface{})
// Only the new one should be in results (existing skipped silently)
assert.Equal(s.T(), 1, len(data))
newAgent := data[0].(map[string]interface{})
assert.Equal(s.T(), "new@test.com", newAgent["email"])
}
func (s *AgentHandlerTestSuite) TestListAfterCreate() {
// Create two agents
for i := 1; i <= 2; i++ {
req := service.CreateAgentRequest{
Email: fmt.Sprintf("list%d@test.com", i),
Name: fmt.Sprintf("List Agent %d", i),
Role: "agent",
}
w, c := s.makeRequest("POST", "/api/v1/accounts/1/agents", req, s.account.ID, s.user.ID)
s.handler.Create(c)
assert.Equal(s.T(), http.StatusCreated, w.Code)
}
// List agents
w, c := s.makeRequest("GET", "/api/v1/accounts/1/agents", nil, s.account.ID, s.user.ID)
s.handler.List(c)
assert.Equal(s.T(), http.StatusOK, w.Code)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
data := resp["data"].([]interface{})
assert.Equal(s.T(), 2, len(data))
}
func (s *AgentHandlerTestSuite) TestListNoAccountID() {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest("GET", "/api/v1/accounts/agents", nil)
// No account_id set
s.handler.List(c)
assert.Equal(s.T(), http.StatusUnauthorized, w.Code)
}