若干更新迭代
This commit is contained in:
1 parent
deb9cc5bcf
commit
4ac786aafb
64 files changed
+3251
-291
No files matched your search
@@ -28,6 +28,96 @@ func TestBillingMemberRoutesUseRefreshedSessionScope(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBillingBalanceUsesOnlyRefreshedSessionOrganization(t *testing.T) {
|
||||
service := &billingHTTPServiceStub{}
|
||||
h := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeUser, User: identity.User{ID: "member", ClientID: "platform", OrganizationID: "db-org", OrganizationName: "DB Org", Role: "user"}}, service, nil)
|
||||
response := serveJSON(t, h, http.MethodGet, "/api/billing/balance?organizationId=attacker&orgID=attacker", nil)
|
||||
if response.Code != 200 || response.Header().Get("Cache-Control") != "no-store" || service.walletOrganization != "db-org" {
|
||||
t.Fatalf("status=%d cache=%q scope=%q body=%s", response.Code, response.Header().Get("Cache-Control"), service.walletOrganization, response.Body.String())
|
||||
}
|
||||
var payload map[string]json.RawMessage
|
||||
if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(payload) != 2 || string(payload["organization"]) != `{"id":"db-org","name":"DB Org"}` {
|
||||
t.Fatalf("unexpected organization or extra fields: %s", response.Body.String())
|
||||
}
|
||||
var wallet billing.Wallet
|
||||
if err := json.Unmarshal(payload["wallet"], &wallet); err != nil || wallet.OrganizationID != "db-org" || wallet.BalanceFen != 0 {
|
||||
t.Fatalf("zero wallet=%+v err=%v body=%s", wallet, err, response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBillingBalanceRejectsUnboundAndUnauthenticatedSessions(t *testing.T) {
|
||||
service := &billingHTTPServiceStub{}
|
||||
h := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeUser, User: identity.User{ID: "member", ClientID: "platform", Role: "user"}}, service, nil)
|
||||
if got := serveJSON(t, h, http.MethodGet, "/api/billing/balance?organizationId=other", nil); got.Code != 422 || service.walletOrganization != "" {
|
||||
t.Fatalf("unbound status=%d scope=%q body=%s", got.Code, service.walletOrganization, got.Body.String())
|
||||
}
|
||||
for _, credential := range []struct{ name, header, value string }{
|
||||
{"missing", "", ""}, {"bearer", "Authorization", "Bearer other"}, {"api-key", "X-API-Key", "other"},
|
||||
} {
|
||||
t.Run(credential.name, func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodGet, "/api/billing/balance", nil)
|
||||
if credential.header != "" {
|
||||
request.Header.Set(credential.header, credential.value)
|
||||
}
|
||||
response := httptest.NewRecorder()
|
||||
h.ServeHTTP(response, request)
|
||||
if response.Code != 401 || service.walletOrganization != "" {
|
||||
t.Fatalf("status=%d scope=%q body=%s", response.Code, service.walletOrganization, response.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
authorizer, err := NewPlatformAuthorizer(AuthState{Required: true, Configured: true}, &platformSessionResolverStub{outcome: "unauthenticated"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
invalidSession := NewBillingHandler(authorizer, service, nil)
|
||||
if got := serveJSON(t, invalidSession, http.MethodGet, "/api/billing/balance", nil); got.Code != 401 || service.walletOrganization != "" {
|
||||
t.Fatalf("invalid session status=%d scope=%q body=%s", got.Code, service.walletOrganization, got.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBillingBalanceHidesStoreErrors(t *testing.T) {
|
||||
service := &billingHTTPServiceStub{err: errors.New("database secret")}
|
||||
h := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeUser, User: identity.User{ID: "member", ClientID: "platform", OrganizationID: "org", Role: "user"}}, service, nil)
|
||||
got := serveJSON(t, h, http.MethodGet, "/api/billing/balance", nil)
|
||||
if got.Code != 500 || bytes.Contains(got.Body.Bytes(), []byte("database secret")) || got.Header().Get("Cache-Control") != "no-store" {
|
||||
t.Fatalf("status=%d body=%s", got.Code, got.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBillingBalanceReadsOnlyWallet(t *testing.T) {
|
||||
store := &balanceOnlyStore{}
|
||||
h := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeUser, User: identity.User{ID: "member", ClientID: "platform", OrganizationID: "org", Role: "user"}}, billing.NewService(store, nil), nil)
|
||||
got := serveJSON(t, h, http.MethodGet, "/api/billing/balance", nil)
|
||||
if got.Code != 200 || store.walletCalls != 1 || store.organizationID != "org" {
|
||||
t.Fatalf("status=%d walletCalls=%d org=%q body=%s", got.Code, store.walletCalls, store.organizationID, got.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
type balanceOnlyStore struct {
|
||||
emptyBillingStore
|
||||
walletCalls int
|
||||
organizationID string
|
||||
}
|
||||
|
||||
func (s *balanceOnlyStore) BillingWallet(_ context.Context, organizationID string) (billing.Wallet, error) {
|
||||
s.walletCalls++
|
||||
s.organizationID = organizationID
|
||||
return billing.Wallet{OrganizationID: organizationID, BalanceFen: 0, Currency: billing.CurrencyCNY}, nil
|
||||
}
|
||||
func (*balanceOnlyStore) BillingLedger(context.Context, string, string, int) ([]billing.LedgerEntry, error) {
|
||||
panic("balance endpoint accessed ledger")
|
||||
}
|
||||
func (*balanceOnlyStore) ListBillingPriceRules(context.Context, bool) ([]billing.PriceRule, error) {
|
||||
panic("balance endpoint accessed catalog")
|
||||
}
|
||||
func (*balanceOnlyStore) BillingWallets(context.Context) ([]billing.Wallet, error) {
|
||||
panic("balance endpoint accessed all wallets")
|
||||
}
|
||||
|
||||
func TestBillingOverviewResponsesEncodeEmptyCollectionsAsArrays(t *testing.T) {
|
||||
service := billing.NewService(&emptyBillingStore{}, nil)
|
||||
member := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeUser, User: identity.User{ID: "user", ClientID: "platform", OrganizationID: "org", Role: "user"}}, service, nil)
|
||||
@@ -135,6 +225,7 @@ func TestBillingHandlerRejectsWrongMethods(t *testing.T) {
|
||||
|
||||
type billingHTTPServiceStub struct {
|
||||
overviewOrganization, overviewAccount string
|
||||
walletOrganization string
|
||||
quote billing.QuoteCommand
|
||||
adjustment billing.AdjustmentCommand
|
||||
pricePatch billing.PricePatch
|
||||
@@ -143,6 +234,11 @@ type billingHTTPServiceStub struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *billingHTTPServiceStub) Wallet(_ context.Context, organizationID string) (billing.Wallet, error) {
|
||||
s.walletOrganization = organizationID
|
||||
return billing.Wallet{OrganizationID: organizationID, Currency: billing.CurrencyCNY}, s.err
|
||||
}
|
||||
|
||||
func (s *billingHTTPServiceStub) Overview(_ context.Context, organizationID, accountID string) (billing.Overview, error) {
|
||||
s.overviewOrganization, s.overviewAccount = organizationID, accountID
|
||||
return billing.Overview{Wallet: billing.Wallet{OrganizationID: organizationID, Currency: billing.CurrencyCNY}}, s.err
|
||||
|
||||
Reference in new issue
Block a user