feat: complete remaining Go backend modules
This commit is contained in:
1 parent
cea2751dc5
commit
aef5a97165
145 files changed
+18376
-199
No files matched your search
@@ -0,0 +1,177 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/billing"
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity"
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/jobs"
|
||||
)
|
||||
|
||||
func TestBillingMemberRoutesUseRefreshedSessionScope(t *testing.T) {
|
||||
service := &billingHTTPServiceStub{}
|
||||
h := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeUser, User: identity.User{ID: "db-user", ClientID: "platform", OrganizationID: "db-org", OrganizationName: "DB Org", Role: "user"}}, service, &billingAccountStoreStub{})
|
||||
|
||||
response := serveJSON(t, h, http.MethodGet, "/api/billing", nil)
|
||||
if response.Code != 200 || service.overviewOrganization != "db-org" || service.overviewAccount != "db-user" {
|
||||
t.Fatalf("status=%d scope=%s/%s body=%s", response.Code, service.overviewOrganization, service.overviewAccount, response.Body.String())
|
||||
}
|
||||
response = serveJSON(t, h, http.MethodPost, "/api/billing/quote", map[string]any{"provider": "bailian", "capability": "image.generate", "ownerId": "attacker", "organizationId": "other"})
|
||||
if response.Code != 200 || service.quote.AccountID != "db-user" || service.quote.OrganizationID != "db-org" || service.quote.Role != "user" {
|
||||
t.Fatalf("status=%d quote=%+v body=%s", response.Code, service.quote, response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBillingQuotePreparesProviderAndModelFromServerOwnedEngineTargets(t *testing.T) {
|
||||
service := &billingHTTPServiceStub{}
|
||||
h := billingTestHandler(t, identity.Session{User: identity.User{ID: "db-user", ClientID: "platform", OrganizationID: "db-org", Role: "user"}}, service, nil)
|
||||
h = NewBillingHandlerWithBuilder(h.(*billingHandler).authorizer, service, nil, jobs.ProviderJobBuilder{
|
||||
ImageEngine: "bailian", ImageProvider: "bailian", ImageModel: "wan-image",
|
||||
ImageEngines: map[string]jobs.ProviderTarget{"evolink": {Provider: "evolink", Model: "gpt-image-2"}},
|
||||
VideoEngine: "seedance", VideoProvider: "seedance", VideoModel: "seedance-2",
|
||||
NewID: func() string { return "quote-only" },
|
||||
})
|
||||
response := serveJSON(t, h, http.MethodPost, "/api/billing/quote", map[string]any{"engine": "evolink", "prompt": "hello", "quality": "high"})
|
||||
if response.Code != 200 || service.quote.Provider != "evolink" || service.quote.Capability != "image.generate" || service.quote.ReqKey != "gpt-image-2" || service.quote.Parameters["quality"] != "high" {
|
||||
t.Fatalf("status=%d quote=%+v body=%s", response.Code, service.quote, response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBillingRequiresOrganizationAndMapsDomainStatuses(t *testing.T) {
|
||||
service := &billingHTTPServiceStub{}
|
||||
h := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeUser, User: identity.User{ID: "user", ClientID: "platform", Role: "user"}}, service, nil)
|
||||
if got := serveJSON(t, h, http.MethodGet, "/api/billing", nil); got.Code != 422 {
|
||||
t.Fatalf("status=%d body=%s", got.Code, got.Body.String())
|
||||
}
|
||||
|
||||
service.err = &billing.StatusError{Status: 402, Err: billing.ErrInsufficientBalance}
|
||||
h = billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeUser, User: identity.User{ID: "user", ClientID: "platform", OrganizationID: "org", Role: "user"}}, service, nil)
|
||||
if got := serveJSON(t, h, http.MethodPost, "/api/billing/quote", map[string]any{}); got.Code != 402 {
|
||||
t.Fatalf("status=%d body=%s", got.Code, got.Body.String())
|
||||
}
|
||||
service.err = errors.New("database secret")
|
||||
got := serveJSON(t, h, http.MethodGet, "/api/billing", nil)
|
||||
if got.Code != 500 || bytes.Contains(got.Body.Bytes(), []byte("database secret")) {
|
||||
t.Fatalf("status=%d body=%s", got.Code, got.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBillingAdminRoutesRequireSuperAdminAndValidateWrites(t *testing.T) {
|
||||
service := &billingHTTPServiceStub{price: &billing.PriceRule{ID: "price-1", MarkupMultiplier: 1.2}}
|
||||
account := &billingAccountStoreStub{}
|
||||
orgAdmin := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeAdmin, User: identity.User{ID: "admin", ClientID: "platform", OrganizationID: "org", Role: "organization_admin"}}, service, account)
|
||||
if got := serveJSON(t, orgAdmin, http.MethodGet, "/api/admin/billing", nil); got.Code != 403 {
|
||||
t.Fatalf("organization admin status=%d", got.Code)
|
||||
}
|
||||
|
||||
super := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeAdmin, User: identity.User{ID: "root", ClientID: "platform", Role: "super_admin"}}, service, account)
|
||||
if got := serveJSON(t, super, http.MethodPatch, "/api/admin/billing/account", map[string]any{"accountName": " Acme ", "bankName": " Bank ", "accountNumber": " 123 ", "contact": " Ops "}); got.Code != 200 || account.saved.AccountName != "Acme" {
|
||||
t.Fatalf("account status=%d saved=%+v body=%s", got.Code, account.saved, got.Body.String())
|
||||
}
|
||||
if got := serveJSON(t, super, http.MethodPost, "/api/admin/billing/adjustments", map[string]any{"organizationId": "org", "amountYuan": 1.235, "direction": "debit", "note": " correction "}); got.Code != 200 || service.adjustment.AmountFen != 124 || service.adjustment.DeltaFen != -124 || service.adjustment.OperatorID != "root" {
|
||||
t.Fatalf("adjustment status=%d got=%+v body=%s", got.Code, service.adjustment, got.Body.String())
|
||||
}
|
||||
if got := serveJSON(t, super, http.MethodPatch, "/api/admin/billing/prices/price-1", map[string]any{"standardUnitPriceFen": 1, "markupMultiplier": 2}); got.Code != 400 {
|
||||
t.Fatalf("whitelist status=%d", got.Code)
|
||||
}
|
||||
if got := serveJSON(t, super, http.MethodPatch, "/api/admin/billing/prices/price-1", map[string]any{"markupMultiplier": .5}); got.Code != 400 {
|
||||
t.Fatalf("multiplier status=%d", got.Code)
|
||||
}
|
||||
service.price.Dimensions = []billing.ParameterDimension{{Key: "quality", Tiers: []billing.ParameterTier{{Value: "high", Enabled: true}}}}
|
||||
if got := serveJSON(t, super, http.MethodPatch, "/api/admin/billing/prices/price-1", map[string]any{"markupMultiplier": 2}); got.Code != 400 {
|
||||
t.Fatalf("tier-required status=%d", got.Code)
|
||||
}
|
||||
if got := serveJSON(t, super, http.MethodPatch, "/api/admin/billing/prices/price-1", map[string]any{"markupMultiplier": 2, "dimensionKey": "quality", "tierValue": "missing"}); got.Code != 400 {
|
||||
t.Fatalf("unknown tier status=%d", got.Code)
|
||||
}
|
||||
if got := serveJSON(t, super, http.MethodPatch, "/api/admin/billing/prices/price-1", map[string]any{"markupMultiplier": 2.123456, "dimensionKey": "quality", "tierValue": "high"}); got.Code != 200 || service.pricePatch.MarkupMultiplier != 2.1235 {
|
||||
t.Fatalf("patch status=%d patch=%+v body=%s", got.Code, service.pricePatch, got.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBillingHandlerRejectsWrongMethods(t *testing.T) {
|
||||
h := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeAdmin, User: identity.User{ID: "root", ClientID: "platform", Role: "super_admin"}}, &billingHTTPServiceStub{}, nil)
|
||||
got := serveJSON(t, h, http.MethodDelete, "/api/billing", nil)
|
||||
if got.Code != 405 || got.Header().Get("Allow") != http.MethodGet {
|
||||
t.Fatalf("status=%d allow=%q", got.Code, got.Header().Get("Allow"))
|
||||
}
|
||||
}
|
||||
|
||||
type billingHTTPServiceStub struct {
|
||||
overviewOrganization, overviewAccount string
|
||||
quote billing.QuoteCommand
|
||||
adjustment billing.AdjustmentCommand
|
||||
pricePatch billing.PricePatch
|
||||
price *billing.PriceRule
|
||||
err error
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
func (s *billingHTTPServiceStub) Quote(_ context.Context, command billing.QuoteCommand) (*billing.Quote, error) {
|
||||
s.quote = command
|
||||
return &billing.Quote{Currency: billing.CurrencyCNY}, s.err
|
||||
}
|
||||
func (s *billingHTTPServiceStub) AdminOverview(context.Context) (billing.AdminOverview, error) {
|
||||
return billing.AdminOverview{}, s.err
|
||||
}
|
||||
func (s *billingHTTPServiceStub) ListPrices(context.Context) ([]billing.PriceRule, error) {
|
||||
return nil, s.err
|
||||
}
|
||||
func (s *billingHTTPServiceStub) GetPrice(_ context.Context, _ string) (*billing.PriceRule, error) {
|
||||
return s.price, s.err
|
||||
}
|
||||
func (s *billingHTTPServiceStub) UpdatePrice(_ context.Context, _ string, patch billing.PricePatch) (*billing.PriceRule, error) {
|
||||
s.pricePatch = patch
|
||||
return s.price, s.err
|
||||
}
|
||||
func (s *billingHTTPServiceStub) Adjust(_ context.Context, command billing.AdjustmentCommand) (billing.AdjustmentResult, error) {
|
||||
s.adjustment = command
|
||||
return billing.AdjustmentResult{Wallet: billing.Wallet{OrganizationID: command.OrganizationID, Currency: billing.CurrencyCNY}}, s.err
|
||||
}
|
||||
|
||||
type billingAccountStoreStub struct{ saved billing.AccountConfig }
|
||||
|
||||
func (s *billingAccountStoreStub) Load(context.Context) (billing.AccountConfig, error) {
|
||||
return s.saved, nil
|
||||
}
|
||||
func (s *billingAccountStoreStub) Save(_ context.Context, value billing.AccountConfig) error {
|
||||
s.saved = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func billingTestHandler(t *testing.T, session identity.Session, service BillingHTTPService, accounts BillingAccountStore) http.Handler {
|
||||
t.Helper()
|
||||
authorizer, err := NewPlatformAuthorizer(AuthState{Required: true, Configured: true}, &fixedSessionResolver{session: session})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return NewBillingHandler(authorizer, service, accounts)
|
||||
}
|
||||
|
||||
type fixedSessionResolver struct{ session identity.Session }
|
||||
|
||||
func (r *fixedSessionResolver) Resolve(context.Context, string) (identity.Session, error) {
|
||||
return r.session, nil
|
||||
}
|
||||
|
||||
func serveJSON(t *testing.T, h http.Handler, method, path string, body any) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
var raw []byte
|
||||
if body != nil {
|
||||
raw, _ = json.Marshal(body)
|
||||
}
|
||||
req := httptest.NewRequest(method, path, bytes.NewReader(raw))
|
||||
req.AddCookie(&http.Cookie{Name: identity.SessionCookieName, Value: "signed"})
|
||||
response := httptest.NewRecorder()
|
||||
h.ServeHTTP(response, req)
|
||||
return response
|
||||
}
|
||||
Reference in new issue
Block a user