1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
package billing
import (
"context"
"fmt"
"os"
"testing"
"time"
"aigw/internal/controlplane"
"github.com/stripe/stripe-go/v86"
)
func TestOperationalStatusReadiness(t *testing.T) {
now := time.Now().UTC()
recent := now.Add(-time.Minute)
clean := OperationalStatus{
StripeEnabled: true, ReconciliationStatus: "clean", ReconciliationCompletedAt: &recent,
}
if !clean.Ready(now) {
t.Fatal("clean operational state should be ready")
}
oldWebhook := now.Add(-6 * time.Minute)
stuckWebhook := clean
stuckWebhook.UnprocessedWebhooks = 1
stuckWebhook.OldestUnprocessedWebhook = &oldWebhook
if stuckWebhook.Ready(now) {
t.Fatal("stuck webhook should fail readiness")
}
freshRefund := now.Add(-time.Minute)
processingRefund := clean
processingRefund.RefundBacklog = 1
processingRefund.OldestRefund = &freshRefund
if !processingRefund.Ready(now) {
t.Fatal("fresh refund operation should remain ready during its processing window")
}
unmetered := clean
unmetered.UnmeteredSuccesses = 1
if unmetered.Ready(now) {
t.Fatal("unmetered success must fail readiness")
}
}
func TestCustomerPortalSessionContractPostgres(t *testing.T) {
databaseURL := os.Getenv("AIGW_TEST_DATABASE_URL")
if databaseURL == "" {
t.Skip("AIGW_TEST_DATABASE_URL is not set")
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := controlplane.MigrateDatabase(ctx, databaseURL); err != nil {
t.Fatal(err)
}
service, err := New(ctx, Options{
DatabaseURL: databaseURL, Currency: "usd", StripeEnabled: true, StripeAPIKey: "rk_test_placeholder",
StripePortalReturnURL: "https://console.example.test/billing",
})
if err != nil {
t.Fatal(err)
}
t.Cleanup(service.Close)
suffix := time.Now().UnixNano()
var tenantID string
if err := service.db.QueryRow(ctx, `INSERT INTO tenants (slug,name) VALUES ($1,'Portal contract') RETURNING id::text`, fmt.Sprintf("portal-%d", suffix)).Scan(&tenantID); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if _, cleanupErr := service.db.Exec(context.Background(), `DELETE FROM tenants WHERE id=$1`, tenantID); cleanupErr != nil {
t.Errorf("cleanup portal contract tenant: %v", cleanupErr)
}
})
customerID := fmt.Sprintf("cus_portal_%d", suffix)
if _, err := service.db.Exec(ctx, `INSERT INTO stripe_customers (tenant_id,stripe_customer_id,email) VALUES ($1,$2,'billing@example.test')`, tenantID, customerID); err != nil {
t.Fatal(err)
}
service.createStripePortalSession = func(_ context.Context, params *stripe.BillingPortalSessionCreateParams) (*stripe.BillingPortalSession, error) {
if params.Customer == nil || *params.Customer != customerID || params.ReturnURL == nil || *params.ReturnURL != service.stripePortalReturnURL {
t.Fatalf("unexpected Portal params %+v", params)
}
return &stripe.BillingPortalSession{URL: "https://billing.stripe.test/session"}, nil
}
result, err := service.CreatePortalSession(ctx, tenantID)
if err != nil || result.URL != "https://billing.stripe.test/session" {
t.Fatalf("CreatePortalSession result=%+v err=%v", result, err)
}
service.createStripePortalSession = func(context.Context, *stripe.BillingPortalSessionCreateParams) (*stripe.BillingPortalSession, error) {
return nil, nil
}
if _, err := service.CreatePortalSession(ctx, tenantID); err == nil {
t.Fatal("incomplete Stripe Portal response was accepted")
}
}
|