You've already forked agentic-coding-workflow
feat(app): wire jobs.Module + WebhookNotifier + integration test for webhook closed loop
This commit is contained in:
@@ -27,11 +27,14 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/audit"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/infra/clock"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/infra/crypto"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/infra/db"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/infra/git"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/infra/logger"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/infra/notify"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/jobs"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/jobs/handlers"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/project"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/transport/http/middleware"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/user"
|
||||
@@ -499,3 +502,92 @@ func TestPlan3_WorkspaceClosedLoop(t *testing.T) {
|
||||
require.Equal(t, http.StatusNoContent, deleteJSON("/api/v1/worktrees/"+wtResp.ID.String()).StatusCode)
|
||||
require.Equal(t, http.StatusNoContent, deleteJSON("/api/v1/workspaces/"+wsResp.ID.String()).StatusCode)
|
||||
}
|
||||
|
||||
func TestEndToEnd_WebhookClosedLoop(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
container, err := tcpg.Run(ctx, "postgres:16-alpine",
|
||||
tcpg.WithDatabase("acw"), tcpg.WithUsername("acw"), tcpg.WithPassword("acw"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = container.Terminate(ctx) })
|
||||
|
||||
dsn, err := container.ConnectionString(ctx, "sslmode=disable")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.Migrate(ctx, dsn, "file://../../migrations"))
|
||||
|
||||
pool, err := db.NewPool(ctx, dsn)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(pool.Close)
|
||||
|
||||
log := logger.New(logger.Options{Format: logger.FormatJSON, Level: logger.LevelInfo})
|
||||
|
||||
// Webhook receiver
|
||||
type recv struct {
|
||||
body map[string]any
|
||||
topic string
|
||||
eventID string
|
||||
sig string
|
||||
}
|
||||
received := make(chan recv, 4)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var body map[string]any
|
||||
rawBody, _ := io.ReadAll(r.Body)
|
||||
_ = json.Unmarshal(rawBody, &body)
|
||||
received <- recv{
|
||||
body: body,
|
||||
topic: r.Header.Get("X-ACW-Topic"),
|
||||
eventID: r.Header.Get("X-ACW-Event-Id"),
|
||||
sig: r.Header.Get("X-ACW-Signature"),
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
t.Cleanup(ts.Close)
|
||||
|
||||
auditRec := audit.NewPostgresRecorder(pool)
|
||||
notifyRepo := notify.NewPostgresRepository(pool)
|
||||
disp := notify.NewDispatcher(notifyRepo, log)
|
||||
|
||||
jobsRepo := jobs.NewPostgresRepository(pool)
|
||||
h := handlers.NewWebhookHandler(handlers.WebhookConfig{
|
||||
URL: ts.URL, Secret: "test-secret", Timeout: 2 * time.Second,
|
||||
}, clock.Real())
|
||||
n := jobs.NewWebhookNotifier(jobsRepo, jobs.WebhookNotifierConfig{
|
||||
Enabled: true,
|
||||
Topics: []string{"workspace.sync_failed"},
|
||||
Backoff: []time.Duration{50 * time.Millisecond, 200 * time.Millisecond},
|
||||
}, clock.Real())
|
||||
disp.Register(n)
|
||||
|
||||
// Run a single Worker (no full Module needed for this test).
|
||||
w := jobs.NewWorker(jobsRepo, map[jobs.JobType]jobs.Handler{jobs.TypeWebhookDeliver: h},
|
||||
jobs.WorkerOptions{
|
||||
ID: "itest", PollInterval: 20 * time.Millisecond,
|
||||
Backoff: []time.Duration{50 * time.Millisecond}, Clock: clock.Real(), Log: log,
|
||||
})
|
||||
wctx, wcancel := context.WithCancel(ctx)
|
||||
t.Cleanup(func() { wcancel(); w.Wait() })
|
||||
go w.Run(wctx)
|
||||
|
||||
// Suppress unused-variable vet warning.
|
||||
_ = auditRec
|
||||
|
||||
eventTime := time.Now()
|
||||
require.NoError(t, disp.Dispatch(ctx, notify.Message{
|
||||
ID: uuid.New(), UserID: uuid.New(),
|
||||
Topic: "workspace.sync_failed", Severity: notify.SeverityError,
|
||||
Title: "fetch failed", Body: "remote unreachable",
|
||||
CreatedAt: eventTime,
|
||||
}))
|
||||
|
||||
select {
|
||||
case got := <-received:
|
||||
require.Equal(t, "workspace.sync_failed", got.topic)
|
||||
require.NotEmpty(t, got.eventID)
|
||||
require.Contains(t, got.sig, "sha256=")
|
||||
require.Equal(t, "workspace.sync_failed", got.body["topic"])
|
||||
require.Equal(t, "fetch failed", got.body["title"])
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("webhook never fired")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user