package v1 import ( "encoding/json" "fmt" "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" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" "github.com/gochat/gochat/internal/service" ) func setupNotionIntegrationRouter(t *testing.T) (*gin.Engine, *gorm.DB) { t.Helper() gin.SetMode(gin.TestMode) r := gin.New() r.RedirectTrailingSlash = false db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}) require.NoError(t, err) require.NoError(t, db.AutoMigrate(&model.Account{}, &model.IntegrationHook{})) t.Cleanup(func() { sqlDB, _ := db.DB() _ = sqlDB.Close() }) handler := NewNotionIntegrationHandler(service.NewNotionIntegrationService(repository.NewIntegrationHookRepo(db))) account := r.Group("/api/v1/accounts/:account_id") account.POST("/notion/authorization", handler.Authorization) integrations := account.Group("/integrations") RegisterNotionIntegrationRoutes(integrations, handler) return r, db } // ======================================== // NotionIntegration — param validation tests // ======================================== func TestNotionIntegration_Delete_BadAccountID(t *testing.T) { r, _ := setupNotionIntegrationRouter(t) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/abc/integrations/notion/", nil) r.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) errBody := resp["error"].(map[string]interface{}) assert.Contains(t, errBody["message"], "invalid account_id") } func TestNotionIntegration_Authorization_BadAccountID(t *testing.T) { r, _ := setupNotionIntegrationRouter(t) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/notion/authorization", nil) r.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) errBody := resp["error"].(map[string]interface{}) assert.Contains(t, errBody["message"], "invalid account_id") } func TestNotionIntegration_Authorization_ReturnsChatwootPayload(t *testing.T) { t.Setenv("NOTION_CLIENT_ID", "notion-client") t.Setenv("NOTION_CLIENT_SECRET", "notion-secret") t.Setenv("FRONTEND_URL", "https://app.example.test/") r, db := setupNotionIntegrationRouter(t) account := &model.Account{Name: "Test Account"} require.NoError(t, db.Create(account).Error) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/notion/authorization", account.ID), nil) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, true, resp["success"]) assert.Contains(t, resp["url"], "https://api.notion.com/v1/oauth/authorize") assert.Contains(t, resp["url"], "redirect_uri=https%3A%2F%2Fapp.example.test%2Fnotion%2Fcallback") } func TestNotionIntegration_Delete_NoTrailingSlashReturnsEmptyOK(t *testing.T) { r, db := setupNotionIntegrationRouter(t) account := &model.Account{Name: "Test Account"} require.NoError(t, db.Create(account).Error) hook := &model.IntegrationHook{AccountID: account.ID, AppID: "notion", HookType: model.HookTypeNotion, Status: model.HookStatusActive, AccessToken: "notion-token"} require.NoError(t, db.Create(hook).Error) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", fmt.Sprintf("/api/v1/accounts/%d/integrations/notion", account.ID), nil) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) assert.Empty(t, w.Body.String()) var count int64 db.Model(&model.IntegrationHook{}).Where("account_id = ? AND app_id = ?", account.ID, "notion").Count(&count) assert.Equal(t, int64(0), count) }