Add an explicit registry with capability hooks for migrations, routes, workflows, config, and UI assets. Move FedWiki fully and Stripe's separable store, workflow, and webhook pieces under internal/integrations. Drive startup wiring from declarations, including config validation, secret file pairs, CSRF exemptions, UI composition, and workflow startup. Move integration DB roles and grants into their owning migration streams, and route outbox writes through a shared enqueue helper.
96 lines
2.7 KiB
Go
96 lines
2.7 KiB
Go
package workflows
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"log/slog"
|
|
"os"
|
|
"testing"
|
|
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
)
|
|
|
|
// testDB returns a database connection for integration tests, assuming an
|
|
// already-migrated database. Skips the test if TEST_DATABASE_URL is not set.
|
|
// Matches the pgx + TEST_DATABASE_URL convention used by the rest of this
|
|
// directory (customer_mapping_test.go) and the wider repo.
|
|
func testDB(t *testing.T) *sql.DB {
|
|
t.Helper()
|
|
dsn := os.Getenv("TEST_DATABASE_URL")
|
|
if dsn == "" {
|
|
t.Skip("TEST_DATABASE_URL not set, skipping integration test")
|
|
}
|
|
db, err := sql.Open("pgx", dsn)
|
|
if err != nil {
|
|
t.Fatalf("open db: %v", err)
|
|
}
|
|
t.Cleanup(func() { db.Close() })
|
|
return db
|
|
}
|
|
|
|
// TestCheckoutSessionPayloadParsing verifies the thin checkout handler reads
|
|
// only the ids it needs (mode + subscription) and ignores everything else —
|
|
// including line_items, which Stripe omits and which we no longer trust.
|
|
func TestCheckoutSessionPayloadParsing(t *testing.T) {
|
|
raw := `{
|
|
"id": "cs_test_123",
|
|
"mode": "subscription",
|
|
"customer": "cus_abc",
|
|
"subscription": "sub_xyz",
|
|
"metadata": {"billing_account_id": "ba-uuid-here"},
|
|
"line_items": []
|
|
}`
|
|
|
|
var payload webhookCheckoutSessionPayload
|
|
if err := json.Unmarshal([]byte(raw), &payload); err != nil {
|
|
t.Fatalf("failed to unmarshal: %v", err)
|
|
}
|
|
if payload.Mode != "subscription" {
|
|
t.Errorf("expected mode subscription, got %s", payload.Mode)
|
|
}
|
|
if payload.Subscription != "sub_xyz" {
|
|
t.Errorf("expected subscription sub_xyz, got %s", payload.Subscription)
|
|
}
|
|
}
|
|
|
|
// TestSubscriptionPayloadParsing verifies the subscription handler extracts only
|
|
// the subscription id; status/items/periods come from the API at reconcile time.
|
|
func TestSubscriptionPayloadParsing(t *testing.T) {
|
|
raw := `{
|
|
"id": "sub_test",
|
|
"customer": "cus_test",
|
|
"status": "canceled",
|
|
"items": [{"id": "si_1", "price_id": "price_1", "quantity": 2}]
|
|
}`
|
|
|
|
var parsed webhookSubscriptionIDPayload
|
|
if err := json.Unmarshal([]byte(raw), &parsed); err != nil {
|
|
t.Fatalf("unmarshal failed: %v", err)
|
|
}
|
|
if parsed.ID != "sub_test" {
|
|
t.Errorf("expected id sub_test, got %s", parsed.ID)
|
|
}
|
|
}
|
|
|
|
// TestDispatchEvent_RoutesSubscriptionEvents documents the event types the
|
|
// dispatcher routes to type-specific handlers.
|
|
func TestDispatchEvent_RoutesSubscriptionEvents(t *testing.T) {
|
|
acts := &WebhookActivities{Logger: slog.Default()}
|
|
|
|
handledPrefixes := []string{
|
|
"checkout.session.completed",
|
|
"customer.subscription.created",
|
|
"customer.subscription.updated",
|
|
"customer.subscription.deleted",
|
|
"customer.created",
|
|
"product.created",
|
|
"price.created",
|
|
}
|
|
for _, et := range handledPrefixes {
|
|
t.Run(et, func(t *testing.T) {
|
|
// Routing logic must not panic when constructing the handler set.
|
|
_ = acts
|
|
})
|
|
}
|
|
}
|