mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-24 03:04:16 +00:00
server.test_mode survived the build-tag refactor only to feed five behavioral branches: the registry's fall-back to the default hold when the user's hold is unreachable, backfill warning suppression for external holds, the appview listener close on shutdown, the hold's relay-crawl skip, and the hold's appview-issuer tolerance. Every one of them is a "this is a local development build" decision, which is what the tag already says, and local development has to build with the tag or nothing resolves. So they read atproto.TestModeBuild now, and the flag, SetTestMode, IsTestMode, the middleware option, the backfill constructor parameter, the never-read field on RemoteHoldAuthorizer, the example and template YAML lines, and the docker-compose env vars are gone. The registry keeps the fallback as a field seeded from the constant so the production-path tests can pin it off under the tag. The 24 SetTestMode calls in tests were dead already: stripping them and running the affected packages tagged changed nothing. Tests that resolve a loopback did:web used to t.Fatal naming the tag, which left a bare `go test ./...` permanently red in five packages. They now live under `//go:build testmode`: whole-file constraints where every test needs it, and sibling *_testmode_test.go files holding the moved tests plus their fixtures where a file mixed. The harness carries the constraint too, with its package doc in an untagged doc.go so the package still exists without it. An untagged run compiles those tests out and passes; make test keeps the tag and runs everything. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UwYzaG3Yy7uA8FbZ5qk3tQ
94 lines
3.4 KiB
Go
94 lines
3.4 KiB
Go
//go:build billing
|
|
|
|
package billing
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stripe/stripe-go/v84"
|
|
)
|
|
|
|
// Tier resolution used to key on the price ID alone, which quietly inverted
|
|
// what a price change is supposed to do. Stripe prices are immutable, so
|
|
// raising a price means creating a new one, and Stripe never moves existing
|
|
// subscribers off the old one. Matching on price therefore dropped exactly the
|
|
// subscribers a price change is meant to leave untouched — and dropped them
|
|
// into the silent branch, where the event is answered 200, recorded as
|
|
// processed, and can never be redelivered.
|
|
|
|
func productPrice(priceID, productID string) *stripe.Price {
|
|
p := &stripe.Price{ID: priceID}
|
|
if productID != "" {
|
|
p.Product = &stripe.Product{ID: productID}
|
|
}
|
|
return p
|
|
}
|
|
|
|
func tierTestConfig() *Config {
|
|
return &Config{Tiers: []BillingTierConfig{
|
|
{Name: "free"},
|
|
{
|
|
Name: "supporter",
|
|
StripeProduct: "prod_supporter",
|
|
StripePriceMonthly: "price_supporter_v2",
|
|
},
|
|
{
|
|
Name: "pro",
|
|
StripePriceMonthly: "price_pro_only",
|
|
},
|
|
}}
|
|
}
|
|
|
|
// TestResolveTier_GrandfatheredPriceResolvesByProduct is the whole point of
|
|
// keying on the product: a subscriber still billing on price_supporter_v1,
|
|
// which no longer appears anywhere in the config, keeps their tier.
|
|
func TestResolveTier_GrandfatheredPriceResolvesByProduct(t *testing.T) {
|
|
m := &Manager{cfg: tierTestConfig()}
|
|
|
|
name, rank := m.resolveTier(productPrice("price_supporter_v1_retired", "prod_supporter"))
|
|
if name != "supporter" || rank != 1 {
|
|
t.Errorf("resolveTier(retired price, live product) = (%q, %d), want (\"supporter\", 1) — "+
|
|
"a subscriber on the old price lost their tier", name, rank)
|
|
}
|
|
}
|
|
|
|
// TestResolveTier_FallsBackToPriceWhenNoProductConfigured: a config written
|
|
// before stripe_product existed must keep resolving exactly as it did.
|
|
func TestResolveTier_FallsBackToPriceWhenNoProductConfigured(t *testing.T) {
|
|
m := &Manager{cfg: tierTestConfig()}
|
|
|
|
name, rank := m.resolveTier(productPrice("price_pro_only", "prod_pro_unconfigured"))
|
|
if name != "pro" || rank != 2 {
|
|
t.Errorf("resolveTier = (%q, %d), want (\"pro\", 2) — the price fallback stopped working", name, rank)
|
|
}
|
|
}
|
|
|
|
// TestResolveTier_ProductWinsOverPrice: when both could match, the product
|
|
// decides. Otherwise a price ID left behind on the wrong tier could outvote the
|
|
// product and grant the wrong entitlement.
|
|
func TestResolveTier_ProductWinsOverPrice(t *testing.T) {
|
|
m := &Manager{cfg: &Config{Tiers: []BillingTierConfig{
|
|
{Name: "free"},
|
|
{Name: "supporter", StripeProduct: "prod_supporter"},
|
|
{Name: "pro", StripePriceMonthly: "price_shared"},
|
|
}}}
|
|
|
|
name, _ := m.resolveTier(productPrice("price_shared", "prod_supporter"))
|
|
if name != "supporter" {
|
|
t.Errorf("resolveTier = %q, want \"supporter\" — the price ID outvoted the product", name)
|
|
}
|
|
}
|
|
|
|
// TestResolveTier_UnknownEverythingIsUnresolved keeps the negative honest: a
|
|
// price on a product the config has never heard of must not resolve to a tier.
|
|
func TestResolveTier_UnknownEverythingIsUnresolved(t *testing.T) {
|
|
m := &Manager{cfg: tierTestConfig()}
|
|
|
|
if name, rank := m.resolveTier(productPrice("price_unknown", "prod_unknown")); name != "" || rank != -1 {
|
|
t.Errorf("resolveTier(unknown) = (%q, %d), want (\"\", -1)", name, rank)
|
|
}
|
|
if name, rank := m.resolveTier(nil); name != "" || rank != -1 {
|
|
t.Errorf("resolveTier(nil) = (%q, %d), want (\"\", -1)", name, rank)
|
|
}
|
|
}
|