package v1 import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" whatsappchannel "github.com/gochat/gochat/internal/channel/whatsapp" "github.com/gochat/gochat/internal/model" channelmodel "github.com/gochat/gochat/internal/model/channel" "github.com/gochat/gochat/internal/repository" "github.com/gochat/gochat/internal/service" "github.com/gochat/gochat/internal/worker" ) // setupInboxMemberActionRouter creates a test router with inbox member-action routes. // We use nil InboxService since we only test param validation (bad IDs). // Body/service-level logic is covered in service tests with real DB. func setupInboxMemberActionRouter(handler *InboxHandler) *gin.Engine { gin.SetMode(gin.TestMode) r := gin.New() r.POST("/api/v1/accounts/:account_id/inboxes/:inbox_id/set_agent_bot", handler.SetAgentBot) r.GET("/api/v1/accounts/:account_id/inboxes/:inbox_id/health", handler.Health) r.POST("/api/v1/accounts/:account_id/inboxes/:inbox_id/sync_templates", handler.SyncTemplates) r.POST("/api/v1/accounts/:account_id/inboxes/:inbox_id/register_webhook", handler.RegisterWebhook) return r } func newNilInboxHandler() *InboxHandler { return NewInboxHandler(&service.InboxService{}) } // parseJSONResponse extracts the "error" key from a JSON response body. func parseJSONError(body []byte) string { var resp map[string]interface{} json.Unmarshal(body, &resp) if v, ok := resp["error"]; ok { return v.(string) } return "" } // ======================================== // SetAgentBot — param validation tests // ======================================== func TestInboxSetAgentBot_BadAccountID(t *testing.T) { handler := newNilInboxHandler() router := setupInboxMemberActionRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/inboxes/5/set_agent_bot", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, parseJSONError(w.Body.Bytes()), "invalid account id") } func TestInboxSetAgentBot_BadInboxID(t *testing.T) { handler := newNilInboxHandler() router := setupInboxMemberActionRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/inboxes/xyz/set_agent_bot", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, parseJSONError(w.Body.Bytes()), "invalid inbox id") } // ======================================== // Health — param validation tests // ======================================== func TestInboxHealth_BadAccountID(t *testing.T) { handler := newNilInboxHandler() router := setupInboxMemberActionRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/inboxes/5/health", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, parseJSONError(w.Body.Bytes()), "invalid account id") } func TestInboxHealth_BadInboxID(t *testing.T) { handler := newNilInboxHandler() router := setupInboxMemberActionRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/1/inboxes/xyz/health", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, parseJSONError(w.Body.Bytes()), "invalid inbox id") } // ======================================== // SyncTemplates — param validation tests // ======================================== func TestInboxSyncTemplates_BadAccountID(t *testing.T) { handler := newNilInboxHandler() router := setupInboxMemberActionRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/inboxes/5/sync_templates", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, parseJSONError(w.Body.Bytes()), "invalid account id") } func TestInboxSyncTemplates_BadInboxID(t *testing.T) { handler := newNilInboxHandler() router := setupInboxMemberActionRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/inboxes/xyz/sync_templates", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, parseJSONError(w.Body.Bytes()), "invalid inbox id") } func TestInboxSyncTemplates_ChatwootQueuedResponse(t *testing.T) { db, err := gorm.Open(sqlite.Open("file:inbox_sync_templates_handler?mode=memory&cache=shared"), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}) require.NoError(t, err) t.Cleanup(func() { sqlDB, dbErr := db.DB() if dbErr == nil { _ = sqlDB.Close() } }) require.NoError(t, db.AutoMigrate(&model.Account{}, &model.Inbox{}, &channelmodel.ChannelWhatsApp{}, &model.BackgroundJob{})) account := &model.Account{Name: "Sync Templates", Locale: "en", Active: true} require.NoError(t, db.Create(account).Error) inbox := &model.Inbox{AccountID: account.ID, Name: "WhatsApp", ChannelType: "whatsapp", ChannelID: 1} require.NoError(t, db.Create(inbox).Error) channel := &channelmodel.ChannelWhatsApp{AccountID: account.ID, InboxID: inbox.ID, PhoneNumber: "+1555010000", PhoneNumberID: "phone-1", BusinessAccountID: "waba-1", AccessToken: "token", Provider: "whatsapp_cloud"} require.NoError(t, db.Create(channel).Error) wp := worker.NewWorkerPool(db) svc := service.NewInboxService(repository.NewInboxRepo(db), nil, nil, nil, nil, nil, whatsappchannel.NewRepository(db)) svc.SetWorkerPool(wp) handler := NewInboxHandler(svc) router := setupInboxMemberActionRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/inboxes/1/sync_templates", bytes.NewReader(nil)) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) var body map[string]any require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body)) assert.Equal(t, service.InboxTemplateSyncInitiatedMessage, body["message"]) assert.NotContains(t, body, "templates") } // ======================================== // RegisterWebhook — param validation tests // ======================================== func TestInboxRegisterWebhook_BadAccountID(t *testing.T) { handler := newNilInboxHandler() router := setupInboxMemberActionRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/inboxes/5/register_webhook", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, parseJSONError(w.Body.Bytes()), "invalid account id") } func TestInboxRegisterWebhook_BadInboxID(t *testing.T) { handler := newNilInboxHandler() router := setupInboxMemberActionRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/inboxes/xyz/register_webhook", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, parseJSONError(w.Body.Bytes()), "invalid inbox id") }