fix(webhooks): replace parity stubs
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
# Phase 6 Placeholder Audit
|
||||
|
||||
Updated: 2026-06-06T17:53:18Z
|
||||
Updated: 2026-06-07
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -13,11 +13,11 @@ scripts/parity_frontend_smoke.sh --check
|
||||
|
||||
## Result
|
||||
|
||||
`chatwootParityStub` appears only in `internal/router/router.go`: 20 call sites plus the helper definition.
|
||||
`chatwootParityStub` has been removed from product code. The exact scan returns no matches.
|
||||
|
||||
The broader placeholder scan still has no reused-frontend critical account/contact/conversation/message/inbox/widget/public handler placeholder. Non-stub matches are test doubles, expected validation errors such as unsupported file/search/bulk-action operations, excluded SSO/SAML placeholder notes, or provider-specific nil-handler fallbacks.
|
||||
The broader placeholder scan still has no reused-frontend critical account/contact/conversation/message/inbox/widget/public handler placeholder. Non-stub matches are test doubles, expected validation errors such as unsupported file/search/bulk-action operations, excluded SSO/SAML placeholder notes, or explicit assertions that nil webhook fallbacks do not return placeholder bodies.
|
||||
|
||||
All call sites are nil-handler guards for public webhook registration paths:
|
||||
The previously remaining call sites were nil-handler guards for public webhook registration paths:
|
||||
|
||||
- Telegram webhook callback.
|
||||
- WhatsApp verify and event callbacks.
|
||||
@@ -28,11 +28,11 @@ All call sites are nil-handler guards for public webhook registration paths:
|
||||
- Instagram verify and event callbacks.
|
||||
- Shopify webhook callback.
|
||||
|
||||
No `chatwootParityStub` call site is registered on the reused dashboard account/contact/conversation/message/inbox API path, widget API path, public help-center path, public inbox/contact/conversation/message API path, or enterprise settings API path.
|
||||
No `chatwootParityStub` call site remains on any reused dashboard account/contact/conversation/message/inbox API path, widget API path, public help-center path, public inbox/contact/conversation/message API path, enterprise settings API path, or webhook fallback path.
|
||||
|
||||
## Classification
|
||||
|
||||
The remaining stubs are defensive nil-handler fallbacks. In normal app bootstrap the matching provider handlers are wired and these branches do not define the route behavior. Provider-specific webhook behavior remains owned by the existing webhook/channel parity slices and future B12 or provider fixture failures.
|
||||
The former defensive nil-handler fallbacks now return explicit `503 { error: "webhook provider unavailable", message: "webhook handler is not configured" }` responses instead of placeholder/not-implemented bodies. In normal app bootstrap the matching provider handlers are wired and these branches do not define the route behavior. Provider-specific webhook behavior remains owned by the existing webhook/channel parity slices and future B12 or provider fixture failures.
|
||||
|
||||
Phase 6 is therefore in Review for the inspected placeholder surface. Reopen it only if a fresh `rg` audit or reused frontend smoke identifies a frontend-reachable `chatwootParityStub`, `not implemented`, or placeholder success response in account/contact/conversation/message/inbox/widget/public critical paths.
|
||||
|
||||
|
||||
+24
-31
@@ -358,7 +358,7 @@ func RegisterRoutes(
|
||||
tgGroup := webhookGroup.Group("/telegram")
|
||||
tgGroup.POST("/:bot_token", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TelegramWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TelegramWebhook.HandleTelegramWebhook(c)
|
||||
@@ -372,14 +372,14 @@ func RegisterRoutes(
|
||||
waGroup := webhookGroup.Group("/whatsapp")
|
||||
waGroup.GET("/:phone_number", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.WhatsAppWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.WhatsAppWebhook.HandleWhatsAppVerification(c)
|
||||
})
|
||||
waGroup.POST("/:phone_number", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.WhatsAppWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.WhatsAppWebhook.HandleWhatsAppWebhook(c)
|
||||
@@ -392,21 +392,21 @@ func RegisterRoutes(
|
||||
ttGroup := webhookGroup.Group("/tiktok")
|
||||
ttGroup.POST("", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TikTokWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TikTokWebhook.HandleTikTokWebhook(c)
|
||||
})
|
||||
ttGroup.GET("/:business_id", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TikTokWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TikTokWebhook.HandleTikTokVerification(c)
|
||||
})
|
||||
ttGroup.POST("/:business_id", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TikTokWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TikTokWebhook.HandleTikTokWebhook(c)
|
||||
@@ -418,7 +418,7 @@ func RegisterRoutes(
|
||||
lineGroup := webhookGroup.Group("/line")
|
||||
lineGroup.POST("/:line_channel_id", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.LineWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.LineWebhook.HandleLineWebhook(c)
|
||||
@@ -430,7 +430,7 @@ func RegisterRoutes(
|
||||
// Reference: Twilio SMS API https://www.twilio.com/docs/sms/api/message-resource
|
||||
webhookGroup.POST("/sms/:phone_number", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TwilioWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TwilioWebhook.HandleTwilioInboundSMS(c)
|
||||
@@ -438,35 +438,35 @@ func RegisterRoutes(
|
||||
twilioGroup := webhookGroup.Group("/twilio")
|
||||
twilioGroup.POST("/sms/:phone_number", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TwilioWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TwilioWebhook.HandleTwilioInboundSMS(c)
|
||||
})
|
||||
twilioGroup.POST("/status/:phone_number", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TwilioWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TwilioWebhook.HandleTwilioDeliveryStatus(c)
|
||||
})
|
||||
twilioGroup.POST("/delivery_status", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TwilioWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TwilioWebhook.HandleTwilioDeliveryStatus(c)
|
||||
})
|
||||
engine.POST("/twilio/delivery_status", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TwilioWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TwilioWebhook.HandleTwilioDeliveryStatus(c)
|
||||
})
|
||||
engine.POST("/twilio/callback", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TwilioWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TwilioWebhook.HandleTwilioCallback(c)
|
||||
@@ -485,14 +485,14 @@ func RegisterRoutes(
|
||||
twWebhookGroup := webhookGroup.Group("/twitter")
|
||||
twWebhookGroup.GET("", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TwitterChannel == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TwitterChannel.WebhookCRC(c)
|
||||
})
|
||||
twWebhookGroup.POST("", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TwitterChannel == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TwitterChannel.WebhookEvent(c)
|
||||
@@ -500,14 +500,14 @@ func RegisterRoutes(
|
||||
// Legacy GoChat aliases kept for already configured Twitter webhooks.
|
||||
twWebhookGroup.GET("/webhook", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TwitterChannel == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TwitterChannel.WebhookCRC(c)
|
||||
})
|
||||
twWebhookGroup.POST("/webhook", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.TwitterChannel == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.TwitterChannel.WebhookEvent(c)
|
||||
@@ -515,21 +515,21 @@ func RegisterRoutes(
|
||||
|
||||
webhookGroup.GET("/instagram", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.FacebookWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.FacebookWebhook.HandleInstagramVerification(c)
|
||||
})
|
||||
webhookGroup.POST("/instagram", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.FacebookWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.FacebookWebhook.HandleInstagramWebhook(c)
|
||||
})
|
||||
webhookGroup.POST("/shopify", func(c *gin.Context) {
|
||||
if handlers == nil || handlers.ShopifyWebhook == nil {
|
||||
chatwootParityStub(c)
|
||||
webhookProviderUnavailable(c)
|
||||
return
|
||||
}
|
||||
handlers.ShopifyWebhook.HandleShopifyWebhook(c)
|
||||
@@ -2056,20 +2056,13 @@ func registerWidgetRoutes(g *gin.RouterGroup, h *widget.WidgetHandler) {
|
||||
widget.POST("/:website_token/offline_message", h.SubmitOfflineMessage)
|
||||
}
|
||||
|
||||
func chatwootParityStub(c *gin.Context) {
|
||||
c.JSON(501, gin.H{
|
||||
"error": "not implemented",
|
||||
"message": "Chatwoot parity route is registered; behavior is tracked for implementation",
|
||||
func webhookProviderUnavailable(c *gin.Context) {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||
"error": "webhook provider unavailable",
|
||||
"message": "webhook handler is not configured",
|
||||
})
|
||||
}
|
||||
|
||||
// --- Placeholder handlers for webhooks ---
|
||||
// Full implementation deferred to P7 (channels)
|
||||
|
||||
func webhookStub(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "Webhook callback placeholder (P7)"})
|
||||
}
|
||||
|
||||
func healthCheck(c *gin.Context) {
|
||||
uptime := time.Since(startTime)
|
||||
c.JSON(200, gin.H{
|
||||
|
||||
@@ -98,6 +98,35 @@ func TestRegisterRoutesBootsWithChatwootParityConflictGroups(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookNilHandlerReturnsProviderUnavailable(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
engine := gin.New()
|
||||
|
||||
RegisterRoutes(
|
||||
engine,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
&Handlers{},
|
||||
nil,
|
||||
nil,
|
||||
&config.JWTConfig{},
|
||||
middleware.CORSConfig{},
|
||||
nil,
|
||||
)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/webhooks/telegram/bot-token", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusServiceUnavailable {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusServiceUnavailable, w.Code)
|
||||
}
|
||||
if strings.Contains(w.Body.String(), "not implemented") || strings.Contains(w.Body.String(), "placeholder") {
|
||||
t.Fatalf("nil webhook fallback returned placeholder body: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTwilioVoiceRoutesServeConferenceAndPersistCallbacks(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db, call := setupRouterTwilioVoiceDB(t)
|
||||
|
||||
Reference in New Issue
Block a user