package v1 import ( "bytes" "context" "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) r.POST("/api/v1/accounts/:account_id/inboxes/:inbox_id/enable_whatsapp_calling", handler.EnableWhatsAppCalling) r.POST("/api/v1/accounts/:account_id/inboxes/:inbox_id/disable_whatsapp_calling", handler.DisableWhatsAppCalling) return r } func newNilInboxHandler() *InboxHandler { return NewInboxHandler(&service.InboxService{}) } type fakeInboxHandlerWhatsAppService struct{} func (f *fakeInboxHandlerWhatsAppService) FetchMessageTemplates(context.Context, *channelmodel.ChannelWhatsApp) ([]interface{}, error) { return nil, nil } func (f *fakeInboxHandlerWhatsAppService) FetchHealthStatus(context.Context, *channelmodel.ChannelWhatsApp) (map[string]interface{}, error) { return map[string]interface{}{}, nil } func (f *fakeInboxHandlerWhatsAppService) SetupWebhook(context.Context, *channelmodel.ChannelWhatsApp, string) error { return nil } func (f *fakeInboxHandlerWhatsAppService) SetupWebhookFields(context.Context, *channelmodel.ChannelWhatsApp, string, []string) error { return nil } func (f *fakeInboxHandlerWhatsAppService) UpdateCallingStatus(context.Context, *channelmodel.ChannelWhatsApp, string) error { return nil } // 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") } // ======================================== // WhatsApp calling — param and response parity tests // ======================================== func TestInboxWhatsAppCalling_BadParams(t *testing.T) { handler := newNilInboxHandler() router := setupInboxMemberActionRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/inboxes/5/enable_whatsapp_calling", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, parseJSONError(w.Body.Bytes()), "invalid account id") w = httptest.NewRecorder() req, _ = http.NewRequest("POST", "/api/v1/accounts/1/inboxes/xyz/disable_whatsapp_calling", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, parseJSONError(w.Body.Bytes()), "invalid inbox id") } func TestInboxWhatsAppCalling_EnableDisableReturnEmptyOK(t *testing.T) { db, err := gorm.Open(sqlite.Open("file:inbox_whatsapp_calling_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{})) account := &model.Account{Name: "Calling", Locale: "en", Active: true, FeatureFlags: `{"channel_voice":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) svc := service.NewInboxService(repository.NewInboxRepo(db), nil, nil, nil, nil, &fakeInboxHandlerWhatsAppService{}, whatsappchannel.NewRepository(db)) handler := NewInboxHandler(svc) router := setupInboxMemberActionRouter(handler) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/1/inboxes/1/enable_whatsapp_calling", nil) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) assert.Empty(t, w.Body.String()) w = httptest.NewRecorder() req, _ = http.NewRequest("POST", "/api/v1/accounts/1/inboxes/1/disable_whatsapp_calling", nil) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) assert.Empty(t, w.Body.String()) }