Files
NianAIGC/backend/internal/usage/report_test.go

163 lines
8.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package usage
import (
"context"
"errors"
"fmt"
"reflect"
"testing"
"time"
)
func TestPersonalReportFiltersDuplicateAPIAndMockEvents(t *testing.T) {
repo := &reportRepository{events: []Event{{ID: "1", JobID: "job", OwnerID: "user", Source: "platform", Capability: "image.generate", Provider: "bailian", CreatedAt: "2026-08-13T01:00:00Z"}, {ID: "duplicate", JobID: "job", OwnerID: "user", Source: "platform", Capability: "image.generate", Provider: "bailian", CreatedAt: "2026-08-13T00:00:00Z"}, {ID: "api", JobID: "api", OwnerID: "user", Source: "api", Capability: "image.generate", Provider: "bailian", CreatedAt: "2026-08-13T00:00:00Z"}, {ID: "mock", JobID: "mock", OwnerID: "user", Source: "platform", Capability: "image.generate", Provider: "mock", CreatedAt: "2026-08-13T00:00:00Z"}}}
report, err := (Service{Repository: repo}).Personal(context.Background(), PersonalRequest{AccountID: "user", Preset: PresetToday, Now: time.Date(2026, 8, 13, 1, 0, 0, 0, time.UTC)})
if err != nil {
t.Fatal(err)
}
if report.Total != 1 || len(report.Recent) != 1 || report.ByCapability[0].Count != 1 {
t.Fatalf("report=%+v", report)
}
}
func TestAdminReportEmitsEmptyOptionArraysInsteadOfNull(t *testing.T) {
report, err := (Service{Repository: &reportRepository{}}).Admin(context.Background(), AdminRequest{Now: time.Date(2026, 8, 13, 1, 0, 0, 0, time.UTC)})
if err != nil {
t.Fatal(err)
}
if report.Options.Organizations == nil || report.Options.Accounts == nil || report.Options.Providers == nil || report.Trend == nil {
t.Fatalf("report contains nil collection: %#v", report)
}
}
func TestAdminReportBuildsAccountOptionsFromBaseRangeBeforeReportFilters(t *testing.T) {
repo := &reportRepository{events: []Event{
{ID: "selected", JobID: "job-selected", OwnerID: "account-a", Source: "platform", Capability: "image.generate", Provider: "bailian", OrganizationID: "org-a", AccountDisplayName: "甲账号", AccountUsername: "13800000001", CreatedAt: "2026-08-12T01:00:00Z"},
{ID: "same-org", JobID: "job-same-org", OwnerID: "account-b", Source: "platform", Capability: "video.generate", Provider: "seedance", OrganizationID: "org-a", AccountDisplayName: "乙账号", CreatedAt: "2026-08-12T00:00:00Z"},
{ID: "other-org", JobID: "job-other-org", OwnerID: "account-c", Source: "platform", Capability: "image.generate", Provider: "evolink", OrganizationID: "org-b", AccountDisplayName: "丙账号", CreatedAt: "2026-08-11T00:00:00Z"},
}}
report, err := (Service{Repository: repo}).Admin(context.Background(), AdminRequest{
Requester: Requester{OrganizationID: "org-a"},
OwnerID: "account-a",
Capability: "image.generate",
Provider: "bailian",
Now: time.Date(2026, 8, 13, 1, 0, 0, 0, time.UTC),
})
if err != nil {
t.Fatal(err)
}
if report.Summary.Total != 1 {
t.Fatalf("filtered report total = %d, want 1", report.Summary.Total)
}
want := []Option{{Value: "account-b", Label: "乙账号"}, {Value: "account-a", Label: "甲账号(13800000001)"}}
if !reflect.DeepEqual(report.Options.Accounts, want) {
t.Fatalf("account options = %#v, want %#v", report.Options.Accounts, want)
}
}
func TestAdminReportIncludesInjectedOrganizationsWithoutUsage(t *testing.T) {
source := organizationOptionSourceStub{options: []Option{
{Value: "org-no-usage", Label: "零用量组织"},
{Value: "org-used", Label: "组织目录名称"},
}}
repo := &reportRepository{events: []Event{{ID: "used", JobID: "job-used", OwnerID: "account", Source: "platform", Capability: "image.generate", Provider: "bailian", OrganizationID: "org-used", CreatedAt: "2026-08-12T01:00:00Z"}}}
report, err := (Service{Repository: repo, OrganizationOptions: source}).Admin(context.Background(), AdminRequest{Now: time.Date(2026, 8, 13, 1, 0, 0, 0, time.UTC)})
if err != nil {
t.Fatal(err)
}
want := []Option{{Value: "org-used", Label: "组织目录名称"}, {Value: "org-no-usage", Label: "零用量组织"}}
if !reflect.DeepEqual(report.Options.Organizations, want) {
t.Fatalf("organization options = %#v, want %#v", report.Options.Organizations, want)
}
recent := report.Recent.([]Record)
if len(recent) != 1 || recent[0].OrganizationName != "组织目录名称" {
t.Fatalf("recent records = %#v", recent)
}
}
func TestAdminReportAggregatesLongRangesByMonth(t *testing.T) {
repo := &reportRepository{events: []Event{
{ID: "jan", JobID: "job-jan", OwnerID: "account", Source: "platform", Capability: "image.generate", Provider: "bailian", CreatedAt: "2026-01-31T16:30:00Z"},
{ID: "apr", JobID: "job-apr", OwnerID: "account", Source: "platform", Capability: "image.generate", Provider: "bailian", CreatedAt: "2026-04-01T00:00:00Z"},
}}
report, err := (Service{Repository: repo}).Admin(context.Background(), AdminRequest{StartDate: "2026-01-01", EndDate: "2026-04-30"})
if err != nil {
t.Fatal(err)
}
want := []TrendPoint{{Date: "2026-02", Label: "2026-02", Count: 1}, {Date: "2026-04", Label: "2026-04", Count: 1}}
if !reflect.DeepEqual(report.Trend, want) {
t.Fatalf("trend = %#v, want %#v", report.Trend, want)
}
}
func TestAdminReportRejectsRangesLongerThan3660Days(t *testing.T) {
_, err := (Service{Repository: &reportRepository{}}).Admin(context.Background(), AdminRequest{StartDate: "2016-01-01", EndDate: "2026-08-13"})
if !errors.Is(err, ErrDateRangeTooLong) {
t.Fatalf("error = %v, want ErrDateRangeTooLong", err)
}
}
func TestAdminReportLimitsRecentAndUsesCanonicalLabels(t *testing.T) {
events := make([]Event, 101)
for i := range events {
provider := "evolink"
if i == 0 {
provider = "volcengine-visual"
}
events[i] = Event{ID: fmt.Sprintf("event-%03d", i), JobID: fmt.Sprintf("job-%03d", i), OwnerID: "account", Source: "platform", Capability: "image.generate", Provider: provider, CreatedAt: fmt.Sprintf("2026-08-12T%02d:%02d:00Z", i/60, i%60)}
}
report, err := (Service{Repository: &reportRepository{events: events}}).Admin(context.Background(), AdminRequest{Now: time.Date(2026, 8, 13, 1, 0, 0, 0, time.UTC)})
if err != nil {
t.Fatal(err)
}
recent := report.Recent.([]Record)
if len(recent) != 100 {
t.Fatalf("recent length = %d, want 100", len(recent))
}
if recent[99].ID != "event-001" {
t.Fatalf("last recent id = %q, want event-001", recent[99].ID)
}
if recent[0].OrganizationID != UnassignedOrganizationID {
t.Fatalf("organization id = %q, want %q", recent[0].OrganizationID, UnassignedOrganizationID)
}
if recent[0].ProviderLabel != "EvoLink" || report.Options.Providers[1] != (Option{Value: "volcengine-visual", Label: "即梦图片"}) {
t.Fatalf("provider labels: record=%q options=%#v", recent[0].ProviderLabel, report.Options.Providers)
}
}
func TestAdminReportIncludesRangeLabelAndDeterministicBreakdowns(t *testing.T) {
repo := &reportRepository{events: []Event{
{ID: "unassigned", JobID: "job-unassigned", OwnerID: "account-z", AccountDisplayName: "乙账号", Source: "platform", Capability: "image.generate", Provider: "bailian", CreatedAt: "2026-08-12T03:00:00Z"},
{ID: "org-b", JobID: "job-b", OwnerID: "account-b", AccountDisplayName: "甲账号", OrganizationID: "org-b", OrganizationName: "乙组织", Source: "platform", Capability: "image.generate", Provider: "bailian", CreatedAt: "2026-08-12T02:00:00Z"},
{ID: "org-a", JobID: "job-a", OwnerID: "account-a", AccountDisplayName: "丙账号", OrganizationID: "org-a", OrganizationName: "甲组织", Source: "platform", Capability: "image.generate", Provider: "bailian", CreatedAt: "2026-08-12T01:00:00Z"},
}}
report, err := (Service{Repository: repo}).Admin(context.Background(), AdminRequest{StartDate: "2026-08-11", EndDate: "2026-08-12"})
if err != nil {
t.Fatal(err)
}
if report.Range.Label != "2026-08-11 至 2026-08-12" {
t.Fatalf("range label = %q", report.Range.Label)
}
organizations := report.Organizations.([]organizationRow)
if got := []string{organizations[0].OrganizationID, organizations[1].OrganizationID, organizations[2].OrganizationID}; !reflect.DeepEqual(got, []string{"org-a", "org-b", UnassignedOrganizationID}) {
t.Fatalf("organization order = %#v", got)
}
accounts := report.Accounts.([]accountRow)
if got := []string{accounts[0].OwnerID, accounts[1].OwnerID, accounts[2].OwnerID}; !reflect.DeepEqual(got, []string{"account-z", "account-b", "account-a"}) {
t.Fatalf("account order = %#v", got)
}
}
type reportRepository struct{ events []Event }
func (r *reportRepository) Insert(e Event) (Event, bool, error) { return e, true, nil }
func (r *reportRepository) List(Filters) ([]Event, error) { return r.events, nil }
type organizationOptionSourceStub struct{ options []Option }
func (s organizationOptionSourceStub) ListOrganizationOptions(context.Context, Requester) ([]Option, error) {
return s.options, nil
}