package v1 import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/suite" "gorm.io/driver/sqlite" "gorm.io/gorm" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" "github.com/gochat/gochat/internal/service" ) type EmailChannelMigrationHandlerTestSuite struct { suite.Suite db *gorm.DB handler *EmailChannelMigrationHandler router *gin.Engine account *model.Account } func (s *EmailChannelMigrationHandlerTestSuite) SetupSuite() { s.db, _ = gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) s.db.AutoMigrate(&model.EmailChannelMigration{}, &model.Account{}, &model.Inbox{}, &model.User{}) repo := repository.NewEmailChannelMigrationRepo(s.db) svc := service.NewEmailChannelMigrationService(repo) s.handler = NewEmailChannelMigrationHandler(svc) gin.SetMode(gin.TestMode) r := gin.New() // Middleware to inject account_id into context r.Use(func(c *gin.Context) { c.Set("account_id", uint(1)) c.Next() }) accountGroup := r.Group("/api/v1/accounts/:account_id") migrationGroup := accountGroup.Group("/email_channel_migrations") migrationGroup.POST("", s.handler.Create) migrationGroup.GET("", s.handler.List) s.router = r // Create test account s.account = &model.Account{Name: "TestAccount"} s.db.Create(s.account) } func (s *EmailChannelMigrationHandlerTestSuite) TearDownSuite() { s.db.Exec("DELETE FROM email_channel_migrations") s.db.Exec("DELETE FROM accounts") } func (s *EmailChannelMigrationHandlerTestSuite) SetupTest() { s.db.Exec("DELETE FROM email_channel_migrations") } func TestEmailChannelMigrationHandlerTestSuite(t *testing.T) { suite.Run(t, new(EmailChannelMigrationHandlerTestSuite)) } // --- Create tests --- func (s *EmailChannelMigrationHandlerTestSuite) TestCreate_Success() { inbox1 := &model.Inbox{Name: "Source", ChannelType: string(model.InboxChannelTypeEmail), AccountID: s.account.ID} inbox2 := &model.Inbox{Name: "Target", ChannelType: string(model.InboxChannelTypeEmail), AccountID: s.account.ID} s.db.Create(inbox1) s.db.Create(inbox2) body := fmt.Sprintf(`{"inbox_id":%d,"target_inbox_id":%d}`, inbox1.ID, inbox2.ID) w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/email_channel_migrations", s.account.ID), 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("pending", data["migration_status"]) } func (s *EmailChannelMigrationHandlerTestSuite) TestCreate_InvalidJSON() { w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/email_channel_migrations", s.account.ID), bytes.NewBufferString(`{invalid`)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) s.Equal(http.StatusBadRequest, w.Code) } func (s *EmailChannelMigrationHandlerTestSuite) TestCreate_MissingFields() { body := `{}` w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/email_channel_migrations", s.account.ID), bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") s.router.ServeHTTP(w, req) // ShouldBindJSON with binding:"required" → missing required uint fields → validation error → 400 s.Equal(http.StatusBadRequest, w.Code) } func (s *EmailChannelMigrationHandlerTestSuite) TestCreate_InvalidAccountID() { // Router without any account_id source → getAccountID returns 0 → 400 // getAccountID tries: 1) URL param :account_id, 2) X-Account-ID header, 3) c.Get("account_id") // Use "abc" in URL so parseUintParam fails, no header, no context → 0 → 400 r := gin.New() r.POST("/api/v1/accounts/:account_id/email_channel_migrations", s.handler.Create) body := `{"inbox_id":1,"target_inbox_id":2}` w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, "/api/v1/accounts/abc/email_channel_migrations", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") r.ServeHTTP(w, req) s.Equal(http.StatusBadRequest, w.Code) } // --- List tests --- func (s *EmailChannelMigrationHandlerTestSuite) TestList_Success() { s.db.Create(&model.EmailChannelMigration{AccountID: s.account.ID, InboxID: 1, TargetInboxID: 2, MigrationStatus: "pending"}) s.db.Create(&model.EmailChannelMigration{AccountID: s.account.ID, InboxID: 3, TargetInboxID: 4, MigrationStatus: "completed"}) w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/email_channel_migrations", s.account.ID), nil) s.router.ServeHTTP(w, req) s.Equal(http.StatusOK, w.Code) var resp map[string]interface{} s.NoError(json.Unmarshal(w.Body.Bytes(), &resp)) data := resp["data"].([]interface{}) s.Len(data, 2) } func (s *EmailChannelMigrationHandlerTestSuite) TestList_Empty() { w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/email_channel_migrations", s.account.ID), nil) s.router.ServeHTTP(w, req) s.Equal(http.StatusOK, w.Code) var resp map[string]interface{} s.NoError(json.Unmarshal(w.Body.Bytes(), &resp)) data := resp["data"].([]interface{}) s.Len(data, 0) } func (s *EmailChannelMigrationHandlerTestSuite) TestList_OnlyAccountMigrations() { otherAccount := &model.Account{Name: "OtherAccount"} s.db.Create(otherAccount) s.db.Create(&model.EmailChannelMigration{AccountID: s.account.ID, InboxID: 1, TargetInboxID: 2, MigrationStatus: "pending"}) s.db.Create(&model.EmailChannelMigration{AccountID: otherAccount.ID, InboxID: 5, TargetInboxID: 6, MigrationStatus: "pending"}) w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/email_channel_migrations", s.account.ID), nil) s.router.ServeHTTP(w, req) s.Equal(http.StatusOK, w.Code) var resp map[string]interface{} s.NoError(json.Unmarshal(w.Body.Bytes(), &resp)) data := resp["data"].([]interface{}) s.Len(data, 1) }