package httpapi import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/billing" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/localstore" ) func TestBillingBulkPriceUpdateAndLaterIndividualEdit(t *testing.T) { service := billing.NewService(localstore.New(), nil) h := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeAdmin, User: identity.User{ID: "root", ClientID: "platform", Role: "super_admin"}}, service, nil) before, err := service.ListPrices(context.Background()) if err != nil { t.Fatal(err) } ids := make([]string, len(before)) for i, rule := range before { ids[i] = rule.ID } response := serveJSON(t, h, http.MethodPatch, "/api/admin/billing/prices", map[string]any{"ruleIds": ids, "markupMultiplier": "1.35001"}) if response.Code != http.StatusOK { t.Fatalf("status=%d body=%s", response.Code, response.Body.String()) } var result struct { Rules []billing.PriceRule `json:"priceRules"` } if err := json.Unmarshal(response.Body.Bytes(), &result); err != nil { t.Fatal(err) } if len(result.Rules) != len(ids) { t.Fatalf("updated %d rules, want %d", len(result.Rules), len(ids)) } for _, rule := range result.Rules { if rule.MarkupMultiplier != 1.35 { t.Fatalf("%s markup=%v", rule.ID, rule.MarkupMultiplier) } for _, dimension := range rule.Dimensions { for _, tier := range dimension.Tiers { if tier.MarkupMultiplier != 1.35 { t.Fatalf("%s/%s/%v markup=%v", rule.ID, dimension.Key, tier.Value, tier.MarkupMultiplier) } } } } id := "base-evolink-gpt-image-2.5-flare" response = serveJSON(t, h, http.MethodPatch, "/api/admin/billing/prices/"+id, map[string]any{"markupMultiplier": 2, "dimensionKey": "quality", "tierValue": "high"}) if response.Code != http.StatusOK { t.Fatalf("single tier status=%d body=%s", response.Code, response.Body.String()) } // A new catalog read refreshes defaults and must preserve the single override. response = serveJSON(t, h, http.MethodGet, "/api/admin/billing/prices", nil) if response.Code != http.StatusOK { t.Fatalf("catalog reload status=%d body=%s", response.Code, response.Body.String()) } rule, err := service.GetPrice(context.Background(), id) if err != nil || rule == nil { t.Fatalf("rule=%v err=%v", rule, err) } for _, dimension := range rule.Dimensions { for _, tier := range dimension.Tiers { want := 1.35 if dimension.Key == "quality" && tier.Value == "high" { want = 2 } if tier.MarkupMultiplier != want { t.Fatalf("%s/%v markup=%v want=%v", dimension.Key, tier.Value, tier.MarkupMultiplier, want) } } } } func TestBillingBulkPriceUpdateRequiresSuperAdminAndValidBody(t *testing.T) { for _, role := range []string{"user", "organization_admin"} { t.Run(role, func(t *testing.T) { service := &billingHTTPServiceStub{} h := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeUser, User: identity.User{ID: "user", ClientID: "platform", OrganizationID: "org", Role: role}}, service, nil) response := serveJSON(t, h, http.MethodPatch, "/api/admin/billing/prices", map[string]any{"ruleIds": []string{"price"}, "markupMultiplier": 2}) if response.Code != http.StatusForbidden || len(service.bulkPricePatch.RuleIDs) != 0 { t.Fatalf("status=%d mutation=%+v", response.Code, service.bulkPricePatch) } }) } for _, body := range []string{ `{"ruleIds":["price"],"markupMultiplier":2,"standardUnitPriceFen":1}`, `{"ruleIds":[1],"markupMultiplier":2}`, `{"ruleIds":"price","markupMultiplier":2}`, `{"ruleIds":["price"],"markupMultiplier":2} {}`, `{"ruleIds":`, } { t.Run(body, func(t *testing.T) { service := &billingHTTPServiceStub{} h := billingTestHandler(t, identity.Session{AuthMode: identity.AuthModeAdmin, User: identity.User{ID: "root", ClientID: "platform", Role: "super_admin"}}, service, nil) request := httptest.NewRequest(http.MethodPatch, "/api/admin/billing/prices", strings.NewReader(body)) request.AddCookie(&http.Cookie{Name: identity.SessionCookieName, Value: "signed"}) response := httptest.NewRecorder() h.ServeHTTP(response, request) if response.Code != http.StatusBadRequest || len(service.bulkPricePatch.RuleIDs) != 0 { t.Fatalf("status=%d mutation=%+v body=%s", response.Code, service.bulkPricePatch, response.Body.String()) } }) } }