163 lines
8.6 KiB
Go
163 lines
8.6 KiB
Go
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
|
||
}
|