package usecase_test import ( "context" "testing" "apps/backend/internal/module/usage/domain" "apps/backend/internal/module/usage/repository" "apps/backend/internal/module/usage/usecase" "github.com/stretchr/testify/require" ) func newUsage(uid int64, mode string) *usecase.Service { r := repository.NewMemory() res := &usecase.StaticResolver{Map: map[string]string{ "1000001:ai_copy": mode, "1000001:web_search": mode, }} // also allow mixed tests if mode == "mixed" { res.Map = map[string]string{ "1000001:ai_copy": domain.KeyModeByok, "1000001:web_search": domain.KeyModePlatform, } } return usecase.New(r, res) } func TestUSG_01_NewMemberSummarySplit(t *testing.T) { svc := newUsage(1_000_001, domain.KeyModePlatform) sum, err := svc.GetSummary(context.Background(), 1_000_001, "") require.NoError(t, err) require.Equal(t, domain.PlanFree, sum.PlanID) require.Equal(t, 0, sum.Platform.CreditsUsed) require.Equal(t, 0, sum.Byok.CallCount) require.Greater(t, sum.Platform.CreditsRemaining, 0) } func TestUSG_02_PlatformOnly(t *testing.T) { svc := newUsage(1_000_001, domain.KeyModePlatform) _, err := svc.RecordCall(context.Background(), 1_000_001, domain.MeterAICopy, domain.KeyModePlatform, "copy", "test") require.NoError(t, err) sum, err := svc.GetSummary(context.Background(), 1_000_001, domain.CurrentMonthKey()) require.NoError(t, err) require.Equal(t, 1, sum.Platform.CreditsUsed) require.Equal(t, 0, sum.Byok.CallCount) } func TestUSG_03_ByokOnly(t *testing.T) { svc := newUsage(1_000_001, domain.KeyModeByok) before, _ := svc.GetSummary(context.Background(), 1_000_001, "") _, err := svc.RecordCall(context.Background(), 1_000_001, domain.MeterAICopy, domain.KeyModeByok, "copy", "test") require.NoError(t, err) sum, err := svc.GetSummary(context.Background(), 1_000_001, domain.CurrentMonthKey()) require.NoError(t, err) require.Equal(t, before.Platform.CreditsRemaining, sum.Platform.CreditsRemaining) require.Equal(t, 1, sum.Byok.CallCount) require.Equal(t, 0, sum.Platform.CreditsUsed) } func TestUSG_04_MixedAIByokSearchPlatform(t *testing.T) { svc := newUsage(1_000_001, "mixed") _, _ = svc.RecordCall(context.Background(), 1_000_001, domain.MeterAICopy, domain.KeyModeByok, "ai", "t") _, _ = svc.RecordCall(context.Background(), 1_000_001, domain.MeterWebSearch, domain.KeyModePlatform, "search", "t") sum, err := svc.GetSummary(context.Background(), 1_000_001, domain.CurrentMonthKey()) require.NoError(t, err) require.Equal(t, 1, sum.Platform.CreditsUsed) // search only require.Equal(t, 1, sum.Byok.CallCount) // ai only } func TestUSG_05_ListEventsHasKeyMode(t *testing.T) { svc := newUsage(1_000_001, domain.KeyModePlatform) _, _ = svc.RecordCall(context.Background(), 1_000_001, domain.MeterAICopy, domain.KeyModePlatform, "x", "t") list, err := svc.ListEvents(context.Background(), 1_000_001, "", "all", 10) require.NoError(t, err) require.NotEmpty(t, list) require.Equal(t, domain.KeyModePlatform, list[0].KeyMode) require.NotEmpty(t, list[0].Meter) } func TestUSG_06_FilterByok(t *testing.T) { svc := newUsage(1_000_001, "mixed") _, _ = svc.RecordCall(context.Background(), 1_000_001, domain.MeterAICopy, domain.KeyModeByok, "a", "t") _, _ = svc.RecordCall(context.Background(), 1_000_001, domain.MeterWebSearch, domain.KeyModePlatform, "s", "t") list, err := svc.ListEvents(context.Background(), 1_000_001, "", domain.KeyModeByok, 50) require.NoError(t, err) require.Len(t, list, 1) require.Equal(t, domain.KeyModeByok, list[0].KeyMode) } func TestUSG_07_FilterPlatform(t *testing.T) { svc := newUsage(1_000_001, "mixed") _, _ = svc.RecordCall(context.Background(), 1_000_001, domain.MeterAICopy, domain.KeyModeByok, "a", "t") _, _ = svc.RecordCall(context.Background(), 1_000_001, domain.MeterWebSearch, domain.KeyModePlatform, "s", "t") list, err := svc.ListEvents(context.Background(), 1_000_001, "", domain.KeyModePlatform, 50) require.NoError(t, err) require.Len(t, list, 1) require.Equal(t, domain.KeyModePlatform, list[0].KeyMode) } func TestUSG_08_NoMergedTotalOnly(t *testing.T) { svc := newUsage(1_000_001, "mixed") _, _ = svc.RecordCall(context.Background(), 1_000_001, domain.MeterAICopy, domain.KeyModeByok, "a", "t") _, _ = svc.RecordCall(context.Background(), 1_000_001, domain.MeterWebSearch, domain.KeyModePlatform, "s", "t") sum, err := svc.GetSummary(context.Background(), 1_000_001, domain.CurrentMonthKey()) require.NoError(t, err) // Must have separate blocks; platform remaining is not reduced by byok require.NotNil(t, sum.Platform) require.NotNil(t, sum.Byok) require.Equal(t, 1, sum.Platform.CallCount) require.Equal(t, 1, sum.Byok.CallCount) // total_credits is plan allotment (platform), not call merge require.Equal(t, sum.Platform.CreditsTotal, sum.TotalCredits) } func TestST_07_ByokNoCreditCharge(t *testing.T) { svc := newUsage(1_000_001, domain.KeyModeByok) before, _ := svc.GetSummary(context.Background(), 1_000_001, "") mode, err := svc.PrepareCall(context.Background(), 1_000_001, domain.MeterAICopy) require.NoError(t, err) require.Equal(t, domain.KeyModeByok, mode) _, err = svc.RecordCall(context.Background(), 1_000_001, domain.MeterAICopy, mode, "byok", "ai") require.NoError(t, err) after, _ := svc.GetSummary(context.Background(), 1_000_001, domain.CurrentMonthKey()) require.Equal(t, before.Platform.CreditsRemaining, after.Platform.CreditsRemaining) require.Equal(t, 1, after.Byok.CallCount) } func TestST_09_NoKeyFails(t *testing.T) { r := repository.NewMemory() svc := usecase.New(r, &usecase.StaticResolver{Map: map[string]string{}}) _, err := svc.PrepareCall(context.Background(), 1_000_001, domain.MeterAICopy) require.ErrorIs(t, err, domain.ErrNoKey) } func TestST_13_PlatformQuota(t *testing.T) { svc := newUsage(1_000_001, domain.KeyModePlatform) // burn free plan for i := 0; i < 80; i++ { _, err := svc.RecordCall(context.Background(), 1_000_001, domain.MeterAICopy, domain.KeyModePlatform, "x", "t") require.NoError(t, err) } _, err := svc.PrepareCall(context.Background(), 1_000_001, domain.MeterAICopy) require.ErrorIs(t, err, domain.ErrQuotaExceeded) } func TestUSG_11_PurchasePlan(t *testing.T) { svc := newUsage(1_000_001, domain.KeyModePlatform) p, err := svc.PurchasePlan(context.Background(), 1_000_001, domain.PlanStarter, "mock-pay-1") require.NoError(t, err) require.Equal(t, domain.PlanStarter, p.PlanID) prefs, _ := svc.GetPrefs(context.Background(), 1_000_001) require.Equal(t, domain.PlanStarter, prefs.PlanID) list, err := svc.ListMyPurchases(context.Background(), 1_000_001, 10) require.NoError(t, err) require.NotEmpty(t, list) } func TestUSG_10_TenantAnalyticsSplit(t *testing.T) { svc := newUsage(1_000_001, "mixed") _, _ = svc.RecordCall(context.Background(), 1_000_001, domain.MeterAICopy, domain.KeyModeByok, "a", "t") _, _ = svc.RecordCall(context.Background(), 1_000_001, domain.MeterWebSearch, domain.KeyModePlatform, "s", "t") ten, err := svc.TenantSummary(context.Background(), domain.CurrentMonthKey()) require.NoError(t, err) require.Equal(t, 1, ten.PlatformCreditsUsed) require.Equal(t, 1, ten.ByokCallCount) } func TestUSG_14_MemberCannotSetOthersPrefs(t *testing.T) { svc := newUsage(1_000_001, domain.KeyModePlatform) _, err := svc.SetPrefs(context.Background(), 1_000_001, 1_000_002, false, domain.PlanPro, nil) require.ErrorIs(t, err, domain.ErrForbidden) }