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/model" "github.com/gochat/gochat/internal/repository" "github.com/gochat/gochat/internal/service" ) // ======== Team Handler Test Suite ======== type TeamHandlerTestSuite struct { suite.Suite router *gin.Engine handler *TeamHandler db *gorm.DB testUserID uint testAccountID uint } func (s *TeamHandlerTestSuite) SetupSuite() { gin.SetMode(gin.TestMode) db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) s.Require().NoError(err) s.db = db err = db.AutoMigrate( &model.Account{}, &model.User{}, &model.AccountUser{}, &model.Team{}, &model.TeamMember{}, ) s.Require().NoError(err) // Create test account account := &model.Account{Name: "TeamTestOrg", Locale: "en", Active: true} s.Require().NoError(db.Create(account).Error) s.testAccountID = account.ID // Create test user user := &model.User{Name: "TeamTestUser", Email: "team@test.com", Provider: "email"} s.Require().NoError(db.Create(user).Error) s.testUserID = user.ID // Wire up real repo + service + handler teamRepo := repository.NewTeamRepo(db) teamMemberRepo := repository.NewTeamMemberRepo(db) teamService := service.NewTeamService(teamRepo, teamMemberRepo) s.handler = NewTeamHandler(teamService) r := gin.New() // Middleware to inject accountID and userID r.Use(func(c *gin.Context) { c.Set("account_id", s.testAccountID) c.Set("user_id", s.testUserID) c.Next() }) teams := r.Group("/api/v1/accounts/:account_id/teams") { teams.POST("", s.handler.Create) teams.GET("", s.handler.List) teams.GET("/:id", s.handler.Get) teams.PUT("/:id", s.handler.Update) teams.DELETE("/:id", s.handler.Delete) teams.POST("/:id/members", s.handler.AddMembers) teams.DELETE("/:id/members/:user_id", s.handler.RemoveMembers) teams.GET("/:id/members", s.handler.ListMembers) } s.router = r } func (s *TeamHandlerTestSuite) TearDownSuite() { if s.db != nil { sqlDB, _ := s.db.DB() sqlDB.Close() } } // Helper to make authenticated request func (s *TeamHandlerTestSuite) makeRequest(method, path string, body interface{}) *httptest.ResponseRecorder { var reqBody *bytes.Reader if body != nil { jsonData, _ := json.Marshal(body) reqBody = bytes.NewReader(jsonData) } else { reqBody = bytes.NewReader(nil) } req, _ := http.NewRequest(method, path, reqBody) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() s.router.ServeHTTP(w, req) return w } func (s *TeamHandlerTestSuite) TestCreate() { body := map[string]interface{}{ "name": "TestTeam", "description": "A test team", "allow_auto_assignment": true, } w := s.makeRequest("POST", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccountID), 10)+"/teams", body) assert.Equal(s.T(), http.StatusCreated, w.Code) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) data := resp["data"].(map[string]interface{}) assert.Equal(s.T(), "TestTeam", data["name"]) } func (s *TeamHandlerTestSuite) TestList() { // Create team via service first team := &model.Team{AccountID: s.testAccountID, Name: "ListTestTeam"} s.Require().NoError(s.db.Create(team).Error) w := s.makeRequest("GET", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccountID), 10)+"/teams?page=1&page_size=10", nil) assert.Equal(s.T(), http.StatusOK, w.Code) } func (s *TeamHandlerTestSuite) TestGet() { team := &model.Team{AccountID: s.testAccountID, Name: "GetTestTeam"} s.Require().NoError(s.db.Create(team).Error) w := s.makeRequest("GET", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccountID), 10)+"/teams/"+strconv.FormatUint(uint64(team.ID), 10), nil) assert.Equal(s.T(), http.StatusOK, w.Code) } func (s *TeamHandlerTestSuite) TestUpdate() { team := &model.Team{AccountID: s.testAccountID, Name: "BeforeUpdate"} s.Require().NoError(s.db.Create(team).Error) body := map[string]interface{}{ "name": "AfterUpdate", "description": "Updated description", } w := s.makeRequest("PUT", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccountID), 10)+"/teams/"+strconv.FormatUint(uint64(team.ID), 10), body) assert.Equal(s.T(), http.StatusOK, w.Code) } func (s *TeamHandlerTestSuite) TestDelete() { team := &model.Team{AccountID: s.testAccountID, Name: "DeleteTeam"} s.Require().NoError(s.db.Create(team).Error) w := s.makeRequest("DELETE", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccountID), 10)+"/teams/"+strconv.FormatUint(uint64(team.ID), 10), nil) assert.Equal(s.T(), http.StatusNoContent, w.Code) } func (s *TeamHandlerTestSuite) TestAddMembers() { team := &model.Team{AccountID: s.testAccountID, Name: "MembersTeam"} s.Require().NoError(s.db.Create(team).Error) user := &model.User{Name: "NewMember", Email: "member@test.com", Provider: "email"} s.Require().NoError(s.db.Create(user).Error) body := map[string]interface{}{ "user_ids": []uint{user.ID}, } w := s.makeRequest("POST", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccountID), 10)+"/teams/"+strconv.FormatUint(uint64(team.ID), 10)+"/members", body) assert.Equal(s.T(), http.StatusOK, w.Code) } func (s *TeamHandlerTestSuite) TestRemoveMembers() { team := &model.Team{AccountID: s.testAccountID, Name: "RemoveTeam"} s.Require().NoError(s.db.Create(team).Error) user := &model.User{Name: "RemoveMember", Email: "remove@test.com", Provider: "email"} s.Require().NoError(s.db.Create(user).Error) member := &model.TeamMember{TeamID: team.ID, UserID: user.ID, AvailabilityStatus: "offline"} s.Require().NoError(s.db.Create(member).Error) w := s.makeRequest("DELETE", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccountID), 10)+"/teams/"+strconv.FormatUint(uint64(team.ID), 10)+"/members/"+strconv.FormatUint(uint64(user.ID), 10), nil) assert.Equal(s.T(), http.StatusNoContent, w.Code) } func (s *TeamHandlerTestSuite) TestListMembers() { team := &model.Team{AccountID: s.testAccountID, Name: "ListMembersTeam"} s.Require().NoError(s.db.Create(team).Error) for i := 0; i < 3; i++ { user := &model.User{Name: "Member" + strconv.Itoa(i), Email: "lm" + strconv.Itoa(i) + "@test.com", Provider: "email"} s.Require().NoError(s.db.Create(user).Error) member := &model.TeamMember{TeamID: team.ID, UserID: user.ID, AvailabilityStatus: "offline"} s.Require().NoError(s.db.Create(member).Error) } w := s.makeRequest("GET", "/api/v1/accounts/"+strconv.FormatUint(uint64(s.testAccountID), 10)+"/teams/"+strconv.FormatUint(uint64(team.ID), 10)+"/members?page=1&page_size=10", nil) assert.Equal(s.T(), http.StatusOK, w.Code) } func TestTeamHandlerSuite(t *testing.T) { suite.Run(t, new(TeamHandlerTestSuite)) } // ======== Profile Handler Test Suite ======== type ProfileHandlerTestSuite struct { suite.Suite router *gin.Engine handler *ProfileHandler db *gorm.DB testUserID uint } func (s *ProfileHandlerTestSuite) SetupSuite() { gin.SetMode(gin.TestMode) db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) s.Require().NoError(err) s.db = db err = db.AutoMigrate( &model.Account{}, &model.User{}, &model.AccountUser{}, ) s.Require().NoError(err) user := &model.User{Name: "ProfileTestUser", Email: "profile@test.com", Provider: "email"} s.Require().NoError(db.Create(user).Error) s.testUserID = user.ID userRepo := repository.NewUserRepo(db) profileService := service.NewProfileService(userRepo) s.handler = NewProfileHandler(profileService) r := gin.New() r.Use(func(c *gin.Context) { c.Set("user_id", s.testUserID) c.Set("account_id", uint(1)) c.Next() }) profiles := r.Group("/api/v1/profile") { profiles.GET("", s.handler.Get) profiles.PUT("", s.handler.Update) profiles.PUT("/avatar", s.handler.UpdateAvatar) } s.router = r } func (s *ProfileHandlerTestSuite) TearDownSuite() { if s.db != nil { sqlDB, _ := s.db.DB() sqlDB.Close() } } func (s *ProfileHandlerTestSuite) makeRequest(method, path string, body interface{}) *httptest.ResponseRecorder { var reqBody *bytes.Reader if body != nil { jsonData, _ := json.Marshal(body) reqBody = bytes.NewReader(jsonData) } else { reqBody = bytes.NewReader(nil) } req, _ := http.NewRequest(method, path, reqBody) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() s.router.ServeHTTP(w, req) return w } func (s *ProfileHandlerTestSuite) TestGetProfile() { w := s.makeRequest("GET", "/api/v1/profile", nil) assert.Equal(s.T(), http.StatusOK, w.Code) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) data := resp["data"].(map[string]interface{}) assert.Equal(s.T(), "ProfileTestUser", data["name"]) } func (s *ProfileHandlerTestSuite) TestUpdateProfile() { body := map[string]interface{}{ "name": "UpdatedProfileUser", "email": "updated@test.com", } w := s.makeRequest("PUT", "/api/v1/profile", body) assert.Equal(s.T(), http.StatusOK, w.Code) } func (s *ProfileHandlerTestSuite) TestUpdateAvatar() { body := map[string]interface{}{ "avatar_url": "https://example.com/new-avatar.png", } w := s.makeRequest("PUT", "/api/v1/profile/avatar", body) assert.Equal(s.T(), http.StatusOK, w.Code) } func TestProfileHandlerSuite(t *testing.T) { suite.Run(t, new(ProfileHandlerTestSuite)) }