mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
Multi-tenant commerce and billing: orders, subscriptions, invoices, plans, usage metering and a credit ledger. One Go binary. MIT OR Apache-2.0. Claude-Session: https://claude.ai/code/session_01SAtaDFm8VDiWrhYHNNtxMw
149 lines
5.9 KiB
Go
149 lines
5.9 KiB
Go
package billing
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/hanzoai/commerce/models/organization"
|
|
"github.com/hanzoai/commerce/util/test/ae"
|
|
)
|
|
|
|
// The co-resident host injects a period-spend reader (the finance ledger). With it
|
|
// set, the cap must enforce on the INJECTED spend even though ZERO commerce
|
|
// transactions exist — the exact unified-binary reality (usage is recorded on the
|
|
// finance path, commerce's transaction store is empty). Without the seam the cap
|
|
// summed 0 and never tripped in prod.
|
|
func TestSpendCap_InjectedReader_EnforcesOnFinanceSpend(t *testing.T) {
|
|
tc := ae.NewContext()
|
|
defer tc.Close()
|
|
|
|
org := &organization.Organization{}
|
|
org.Name = "inject-cap"
|
|
warmNamespace(org)
|
|
|
|
var gotOrg, gotProj, gotSvc string
|
|
SetPeriodSpendReader(func(_ context.Context, o string, _ bool, project, service string) (int64, error) {
|
|
gotOrg, gotProj, gotSvc = o, project, service
|
|
return 100, nil // $1.00 of finance-ledger spend, org-wide.
|
|
})
|
|
defer SetPeriodSpendReader(nil)
|
|
|
|
// A $1.00 hard cap. NO commerce usage is recorded — only the injected spend.
|
|
createCap(t, org, `{"title":"cap","threshold":100,"enforce":true}`)
|
|
|
|
v := authorize(t, org, "user=inject-cap&amount=1")
|
|
if v.Allow || v.Reason != "spend_cap" {
|
|
t.Fatalf("authorize = %+v, want deny spend_cap (injected finance spend must drive the cap)", v)
|
|
}
|
|
if v.SpentCents != 100 {
|
|
t.Fatalf("spentCents = %d, want 100 (from the injected reader, not commerce transactions)", v.SpentCents)
|
|
}
|
|
if gotOrg != "inject-cap" || gotProj != "" || gotSvc != "" {
|
|
t.Fatalf("reader got org=%q proj=%q svc=%q, want inject-cap / '' / '' (org-wide scope)", gotOrg, gotProj, gotSvc)
|
|
}
|
|
}
|
|
|
|
// An exhausted enforcing cap denies, and nothing in the environment can talk it out of
|
|
// that. The verdict is a function of the row and the spend — the two things an operator
|
|
// can see — so a cap reading "$100" cannot be quietly serving an unbounded one.
|
|
//
|
|
// The environment is set here to the spelling that used to disable enforcement, because
|
|
// the property worth pinning is that it now means nothing: a deployment that still
|
|
// carries the old variable, or a shell that exports it out of habit, does not reopen the
|
|
// ceiling.
|
|
func TestSpendCap_ExhaustedCapDeniesRegardlessOfEnvironment(t *testing.T) {
|
|
tc := ae.NewContext()
|
|
defer tc.Close()
|
|
|
|
org := &organization.Organization{}
|
|
org.Name = "enforce-flag"
|
|
warmNamespace(org)
|
|
|
|
// $1 hard cap already exhausted (via the injected reader).
|
|
SetPeriodSpendReader(func(_ context.Context, _ string, _ bool, _, _ string) (int64, error) {
|
|
return 100, nil
|
|
})
|
|
defer SetPeriodSpendReader(nil)
|
|
createCap(t, org, `{"title":"cap","threshold":100,"enforce":true}`)
|
|
|
|
if v := authorize(t, org, "user=enforce-flag&amount=1"); v.Allow || v.Reason != "spend_cap" {
|
|
t.Fatalf("authorize = %+v, want deny spend_cap", v)
|
|
}
|
|
|
|
t.Setenv("SPEND_CAP_ENFORCE", "false")
|
|
if v := authorize(t, org, "user=enforce-flag&amount=1"); v.Allow || v.Reason != "spend_cap" {
|
|
t.Fatalf("with SPEND_CAP_ENFORCE=false: authorize = %+v, want deny spend_cap — the cap is not switchable", v)
|
|
}
|
|
}
|
|
|
|
// TestSpendCap_ReaderError_FailsOpen pins F2-2: a finance-ledger read ERROR must NOT 402
|
|
// an under-cap customer. An enforce row whose spend can't be summed fails OPEN (allow) —
|
|
// a transient backend blip (e.g. a busy co-resident SQLite store under load) can't storm
|
|
// every capped org. A genuine over-cap, where spend IS known, still denies (tests above).
|
|
func TestSpendCap_ReaderError_FailsOpen(t *testing.T) {
|
|
tc := ae.NewContext()
|
|
defer tc.Close()
|
|
|
|
org := &organization.Organization{}
|
|
org.Name = "reader-error"
|
|
warmNamespace(org)
|
|
|
|
SetPeriodSpendReader(func(_ context.Context, _ string, _ bool, _, _ string) (int64, error) {
|
|
return 0, errors.New("finance store busy")
|
|
})
|
|
defer SetPeriodSpendReader(nil)
|
|
createCap(t, org, `{"title":"cap","threshold":100,"enforce":true}`)
|
|
|
|
// Flag ON (package default), hard enforce cap, spend UNKNOWN → FAIL OPEN, never 402.
|
|
if v := authorize(t, org, "user=reader-error&amount=1"); !v.Allow || v.Reason != "" {
|
|
t.Fatalf("reader error must FAIL OPEN: authorize = %+v, want ALLOW (not spend_cap)", v)
|
|
}
|
|
}
|
|
|
|
// The alert fires off the INJECTED spend too (the host calls FireSpendAlerts after
|
|
// fin.RecordUsage), so the "alert" half also works on the finance path with no
|
|
// commerce transactions.
|
|
func TestSpendCap_InjectedReader_AlertFiresOnFinanceSpend(t *testing.T) {
|
|
tc := ae.NewContext()
|
|
defer tc.Close()
|
|
|
|
org := &organization.Organization{}
|
|
org.Name = "inject-fire"
|
|
warmNamespace(org)
|
|
|
|
SetPeriodSpendReader(func(_ context.Context, _ string, _ bool, _, _ string) (int64, error) {
|
|
return 100, nil
|
|
})
|
|
defer SetPeriodSpendReader(nil)
|
|
|
|
createCap(t, org, `{"title":"cap","threshold":100,"enforce":true}`)
|
|
driveFire(t, org, "", "") // exercises checkAndFireSpendAlerts → scopeSpentCents → injected reader.
|
|
if ta := triggeredAt(t, org); ta != triggerStamp(currentPeriod(), levelOver) {
|
|
t.Fatalf("triggeredAt = %q, want over stamp (alert must fire off injected finance spend)", ta)
|
|
}
|
|
}
|
|
|
|
// Clearing the reader restores the standalone transaction-ledger path (no leakage
|
|
// across the unified-binary boundary).
|
|
func TestSpendCap_NilReader_UsesTransactionLedger(t *testing.T) {
|
|
tc := ae.NewContext()
|
|
defer tc.Close()
|
|
|
|
org := &organization.Organization{}
|
|
org.Name = "inject-nil"
|
|
warmNamespace(org)
|
|
SetPeriodSpendReader(nil) // explicit: standalone.
|
|
|
|
createCap(t, org, `{"title":"cap","threshold":100,"enforce":true}`)
|
|
// No injected reader and no commerce usage → spent 0 → ALLOW (transaction path).
|
|
if v := authorize(t, org, "user=inject-nil&amount=1"); !v.Allow {
|
|
t.Fatalf("nil reader + no usage must ALLOW (transaction ledger, spent 0): %+v", v)
|
|
}
|
|
// Record real commerce usage → the transaction path sums it → cap trips.
|
|
driveSeedUsage(t, org, 100, "", "")
|
|
if v := authorize(t, org, "user=inject-nil&amount=1"); v.Allow || v.Reason != "spend_cap" {
|
|
t.Fatalf("nil reader + $1 commerce usage must deny spend_cap: %+v", v)
|
|
}
|
|
}
|