thread-master/apps/backend/internal/module/usage/usecase/service_test.go

175 lines
7.3 KiB
Go

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)
}