package v1 import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "regexp" "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 { 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/ or /agents//... 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 data []interface{} assert.NoError(s.T(), json.Unmarshal(w.Body.Bytes(), &data)) assert.Equal(s.T(), 0, len(data)) } func (s *AgentHandlerTestSuite) TestCreateAgent() { customRoleID := uint(7) req := service.CreateAgentRequest{ Email: "agent1@test.com", Name: "Agent One", Role: "agent", CustomRoleID: &customRoleID, } 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.StatusOK, w.Code) var data map[string]interface{} json.Unmarshal(w.Body.Bytes(), &data) assert.Equal(s.T(), "agent1@test.com", data["email"]) assert.Equal(s.T(), "Agent One", data["name"]) assert.Equal(s.T(), "offline", data["availability_status"]) assert.Equal(s.T(), float64(7), data["custom_role_id"]) assert.Contains(s.T(), data, "confirmed") } 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.StatusOK, 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.StatusOK, w.Code) var data map[string]interface{} json.Unmarshal(w.Body.Bytes(), &data) 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 getData map[string]interface{} json.Unmarshal(w2.Body.Bytes(), &getData) 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.StatusOK, w.Code) var data map[string]interface{} json.Unmarshal(w.Body.Bytes(), &data) 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 updateData map[string]interface{} json.Unmarshal(w2.Body.Bytes(), &updateData) assert.Equal(s.T(), "Updated Name", updateData["name"]) assert.Equal(s.T(), "administrator", updateData["role"]) assert.Equal(s.T(), "online", updateData["availability_status"]) } 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.StatusOK, w.Code) var data map[string]interface{} json.Unmarshal(w.Body.Bytes(), &data) 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) assert.Empty(s.T(), w.Body.String()) } 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.StatusOK, 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) assert.Empty(s.T(), w2.Body.String()) } 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.StatusOK, 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 data []interface{} json.Unmarshal(w.Body.Bytes(), &data) 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) }