feat(integrations): align app hook payloads

This commit is contained in:
2026-06-06 06:46:36 +08:00
parent 02ea135e0c
commit d241925b6a
12 changed files with 504 additions and 132 deletions
@@ -53,20 +53,7 @@ func (s *IntegrationHookHandlerSuite) SetupSuite() {
r := gin.New()
r.Use(gin.Recovery(), mockAuthMiddlewareForHook())
g := r.Group("/api/v1/accounts/:account_id/integrations")
apps := g.Group("/apps")
{
apps.GET("/", s.handler.ListApps)
apps.GET("/:id", s.handler.GetApp)
}
hooks := g.Group("/hooks")
{
hooks.GET("/", s.handler.ListHooks)
hooks.GET("/:id", s.handler.GetHook)
hooks.POST("/", s.handler.CreateHook)
hooks.PUT("/:id", s.handler.UpdateHook)
hooks.DELETE("/:id", s.handler.DeleteHook)
hooks.POST("/:id/process_event", s.handler.ProcessHookEvent)
}
RegisterIntegrationHookRoutes(g, s.handler)
s.router = r
}
@@ -105,6 +92,7 @@ func (s *IntegrationHookHandlerSuite) createHook(accountID uint, hookType model.
token := "test_token_" + strconv.Itoa(hookTokenCounter)
hook := &model.IntegrationHook{
AccountID: accountID,
AppID: string(hookType),
HookType: hookType,
Status: model.HookStatusActive,
URL: url,
@@ -129,10 +117,10 @@ func (s *IntegrationHookHandlerSuite) TestListApps_Success() {
var body map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
s.Equal(true, body["success"])
data := body["data"].([]interface{})
data := body["payload"].([]interface{})
s.Len(data, 2)
app := data[0].(map[string]interface{})
s.Contains(app, "hooks")
}
func (s *IntegrationHookHandlerSuite) TestListApps_Empty() {
@@ -143,9 +131,7 @@ func (s *IntegrationHookHandlerSuite) TestListApps_Empty() {
var body map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
s.Equal(true, body["success"])
data := body["data"].([]interface{})
data := body["payload"].([]interface{})
s.Len(data, 0)
}
@@ -154,26 +140,24 @@ func (s *IntegrationHookHandlerSuite) TestListApps_Empty() {
// =====================
func (s *IntegrationHookHandlerSuite) TestGetApp_Success() {
app := s.createApp("Slack App", model.HookTypeSlack)
s.createApp("Slack App", model.HookTypeSlack)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/integrations/apps/"+s.uid(app.ID), nil)
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/integrations/apps/slack", nil)
s.router.ServeHTTP(w, req)
s.Equal(http.StatusOK, w.Code)
var body map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
s.Equal(true, body["success"])
data := body["data"].(map[string]interface{})
s.Equal("Slack App", data["name"])
s.Equal("slack", body["id"])
s.Equal("Slack App", body["name"])
}
func (s *IntegrationHookHandlerSuite) TestGetApp_InvalidID() {
func (s *IntegrationHookHandlerSuite) TestGetApp_UnknownID() {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/integrations/apps/abc", nil)
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/integrations/apps/unknown", nil)
s.router.ServeHTTP(w, req)
s.Equal(http.StatusBadRequest, w.Code)
s.Equal(http.StatusNotFound, w.Code)
}
func (s *IntegrationHookHandlerSuite) TestGetApp_NotFound() {
@@ -232,11 +216,9 @@ func (s *IntegrationHookHandlerSuite) TestGetHook_Success() {
var body map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &body))
s.Equal(true, body["success"])
data := body["data"].(map[string]interface{})
s.Equal("webhook", data["hook_type"])
s.Equal("https://example.com/hook", data["url"])
s.Equal("webhook", body["app_id"])
s.Equal("account", body["hook_type"])
s.Equal(true, body["status"])
}
func (s *IntegrationHookHandlerSuite) TestGetHook_NotFound() {
@@ -251,10 +233,12 @@ func (s *IntegrationHookHandlerSuite) TestGetHook_NotFound() {
// =====================
func (s *IntegrationHookHandlerSuite) TestCreateHook_Success() {
body, _ := json.Marshal(map[string]interface{}{"hook": map[string]string{
"hook_type": "webhook",
"url": "https://example.com/new_hook",
}})
body, _ := json.Marshal(map[string]interface{}{
"app_id": "webhook",
"settings": map[string]interface{}{
"project_id": "project-1",
},
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/integrations/hooks/", bytes.NewBuffer(body))
@@ -264,12 +248,12 @@ func (s *IntegrationHookHandlerSuite) TestCreateHook_Success() {
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.Equal(true, resp["success"])
data := resp["data"].(map[string]interface{})
s.Equal("webhook", data["hook_type"])
s.Equal("https://example.com/new_hook", data["url"])
s.NotNil(data["id"])
s.Equal("webhook", resp["app_id"])
s.Equal("account", resp["hook_type"])
s.Equal(true, resp["status"])
s.NotNil(resp["id"])
settings := resp["settings"].(map[string]interface{})
s.Equal("project-1", settings["project_id"])
}
func (s *IntegrationHookHandlerSuite) TestCreateHook_InvalidJSON() {
@@ -295,9 +279,12 @@ func (s *IntegrationHookHandlerSuite) TestCreateHook_InvalidAccountID() {
func (s *IntegrationHookHandlerSuite) TestUpdateHook_Success() {
hook := s.createHook(1, model.HookTypeWebhook, "https://example.com/hook")
body, _ := json.Marshal(map[string]interface{}{"hook": map[string]string{
"url": "https://example.com/updated_hook",
"status": "inactive",
body, _ := json.Marshal(map[string]interface{}{"hook": map[string]interface{}{
"status": "disabled",
"reference_id": "ref-123",
"settings": map[string]interface{}{
"channel": "support",
},
}})
w := httptest.NewRecorder()
@@ -308,11 +295,10 @@ func (s *IntegrationHookHandlerSuite) TestUpdateHook_Success() {
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.Equal(true, resp["success"])
data := resp["data"].(map[string]interface{})
s.Equal("https://example.com/updated_hook", data["url"])
s.Equal("inactive", data["status"])
s.Equal(false, resp["status"])
s.Equal("ref-123", resp["reference_id"])
settings := resp["settings"].(map[string]interface{})
s.Equal("support", settings["channel"])
}
func (s *IntegrationHookHandlerSuite) TestUpdateHook_NotFound() {
@@ -357,21 +343,14 @@ func (s *IntegrationHookHandlerSuite) TestDeleteHook_Success() {
req, _ := http.NewRequest("DELETE", "/api/v1/accounts/1/integrations/hooks/"+s.uid(hook.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))
s.Equal(true, resp["success"])
data := resp["data"].(map[string]interface{})
s.Equal(float64(hook.ID), data["id"])
s.Empty(w.Body.String())
}
func (s *IntegrationHookHandlerSuite) TestDeleteHook_NotFound() {
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/v1/accounts/1/integrations/hooks/9999", nil)
s.router.ServeHTTP(w, req)
// GORM soft-delete on non-existent ID returns nil error → handler returns 200 with {"id": 9999}
s.Equal(http.StatusOK, w.Code)
s.Equal(http.StatusNotFound, w.Code)
}
func (s *IntegrationHookHandlerSuite) TestDeleteHook_InvalidID() {
@@ -401,10 +380,7 @@ func (s *IntegrationHookHandlerSuite) TestProcessHookEvent_Success() {
var resp map[string]interface{}
s.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.Equal(true, resp["success"])
data := resp["data"].(map[string]interface{})
s.Equal("event processed", data["message"])
s.Equal("event processed", resp["message"])
}
func (s *IntegrationHookHandlerSuite) TestProcessHookEvent_NotFound() {
@@ -442,6 +418,7 @@ func (s *IntegrationHookHandlerSuite) TestProcessHookEvent_InactiveHook() {
hookTokenCounter++
hook := &model.IntegrationHook{
AccountID: 1,
AppID: "webhook",
HookType: model.HookTypeWebhook,
Status: model.HookStatusInactive,
URL: "https://example.com/hook",
@@ -462,4 +439,4 @@ func (s *IntegrationHookHandlerSuite) TestProcessHookEvent_InactiveHook() {
// TestIntegrationHookHandlerSuite runs the suite.
func TestIntegrationHookHandlerSuite(t *testing.T) {
suite.Run(t, new(IntegrationHookHandlerSuite))
}
}