Files
lijiaoqiao/supply-api/internal/domain/package_test.go

568 lines
17 KiB
Go
Raw Normal View History

package domain
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"lijiaoqiao/supply-api/internal/audit"
)
// mockPackageStoreForPackageTest Mock套餐存储
type mockPackageStoreForPackageTest struct {
packages map[int64]*Package
nextID int64
}
func newMockPackageStoreForPackageTest() *mockPackageStoreForPackageTest {
return &mockPackageStoreForPackageTest{
packages: make(map[int64]*Package),
nextID: 1,
}
}
func (m *mockPackageStoreForPackageTest) Create(ctx context.Context, pkg *Package) error {
pkg.ID = m.nextID
m.nextID++
m.packages[pkg.ID] = pkg
return nil
}
func (m *mockPackageStoreForPackageTest) GetByID(ctx context.Context, supplierID, id int64) (*Package, error) {
if pkg, ok := m.packages[id]; ok && pkg.SupplierID == supplierID {
return pkg, nil
}
return nil, errors.New("package not found")
}
func (m *mockPackageStoreForPackageTest) Update(ctx context.Context, pkg *Package) error {
m.packages[pkg.ID] = pkg
return nil
}
func (m *mockPackageStoreForPackageTest) List(ctx context.Context, supplierID int64) ([]*Package, error) {
var result []*Package
for _, pkg := range m.packages {
if pkg.SupplierID == supplierID {
result = append(result, pkg)
}
}
return result, nil
}
// mockAccountStoreForPackageTest Mock账号存储
type mockAccountStoreForPackageTest struct {
accounts map[int64]*Account
}
func newMockAccountStoreForPackageTest() *mockAccountStoreForPackageTest {
return &mockAccountStoreForPackageTest{
accounts: make(map[int64]*Account),
}
}
func (m *mockAccountStoreForPackageTest) Create(ctx context.Context, account *Account) error {
m.accounts[account.ID] = account
return nil
}
func (m *mockAccountStoreForPackageTest) GetByID(ctx context.Context, supplierID, id int64) (*Account, error) {
if account, ok := m.accounts[id]; ok && account.SupplierID == supplierID {
return account, nil
}
return nil, errors.New("account not found")
}
func (m *mockAccountStoreForPackageTest) Update(ctx context.Context, account *Account) error {
m.accounts[account.ID] = account
return nil
}
func (m *mockAccountStoreForPackageTest) List(ctx context.Context, supplierID int64) ([]*Account, error) {
var result []*Account
for _, account := range m.accounts {
if account.SupplierID == supplierID {
result = append(result, account)
}
}
return result, nil
}
// mockAuditStoreForPackageTest Mock审计存储
type mockAuditStoreForPackageTest struct{}
func (m *mockAuditStoreForPackageTest) Emit(ctx context.Context, event audit.Event) error {
return nil
}
func (m *mockAuditStoreForPackageTest) Query(ctx context.Context, filter audit.EventFilter) ([]audit.Event, error) {
return nil, nil
}
func (m *mockAuditStoreForPackageTest) QueryWithTotal(ctx context.Context, filter audit.EventFilter) ([]audit.Event, int64, error) {
return nil, 0, nil
}
func (m *mockAuditStoreForPackageTest) GetByID(ctx context.Context, eventID string) (audit.Event, error) {
return audit.Event{}, errors.New("not found")
}
// TestPackageStatusConstants 测试套餐状态常量
func TestPackageStatusConstants(t *testing.T) {
assert.Equal(t, PackageStatus("draft"), PackageStatusDraft)
assert.Equal(t, PackageStatus("active"), PackageStatusActive)
assert.Equal(t, PackageStatus("paused"), PackageStatusPaused)
assert.Equal(t, PackageStatus("sold_out"), PackageStatusSoldOut)
assert.Equal(t, PackageStatus("expired"), PackageStatusExpired)
}
// TestPackageStruct 测试套餐结构体
func TestPackageStruct(t *testing.T) {
now := time.Now()
pkg := &Package{
ID: 1,
SupplierID: 1001,
AccountID: 2001,
Platform: "openai",
Model: "gpt-4",
TotalQuota: 10000.0,
AvailableQuota: 8000.0,
SoldQuota: 2000.0,
ReservedQuota: 500.0,
PricePer1MInput: 0.5,
PricePer1MOutput: 1.5,
MinPurchase: 100.0,
StartAt: now,
EndAt: now.Add(30 * 24 * time.Hour),
ValidDays: 30,
MaxConcurrent: 10,
RateLimitRPM: 100,
Status: PackageStatusActive,
TotalOrders: 100,
TotalRevenue: 5000.0,
Rating: 4.5,
RatingCount: 50,
QuotaUnit: "tokens",
PriceUnit: "yuan",
CurrencyCode: "CNY",
Version: 1,
CreatedAt: now,
UpdatedAt: now,
}
assert.Equal(t, int64(1), pkg.ID)
assert.Equal(t, int64(1001), pkg.SupplierID)
assert.Equal(t, int64(2001), pkg.AccountID)
assert.Equal(t, "openai", pkg.Platform)
assert.Equal(t, "gpt-4", pkg.Model)
assert.Equal(t, 10000.0, pkg.TotalQuota)
assert.Equal(t, 8000.0, pkg.AvailableQuota)
assert.Equal(t, 2000.0, pkg.SoldQuota)
assert.Equal(t, 500.0, pkg.ReservedQuota)
assert.Equal(t, 0.5, pkg.PricePer1MInput)
assert.Equal(t, 1.5, pkg.PricePer1MOutput)
assert.Equal(t, PackageStatusActive, pkg.Status)
assert.Equal(t, 100, pkg.TotalOrders)
assert.Equal(t, 5000.0, pkg.TotalRevenue)
assert.Equal(t, 4.5, pkg.Rating)
assert.Equal(t, 50, pkg.RatingCount)
assert.Equal(t, "CNY", pkg.CurrencyCode)
assert.Equal(t, 1, pkg.Version)
}
// TestCreatePackageDraftRequest 测试创建套餐草稿请求
func TestCreatePackageDraftRequest(t *testing.T) {
req := &CreatePackageDraftRequest{
SupplierID: 1001,
AccountID: 2001,
Model: "gpt-4",
TotalQuota: 10000.0,
PricePer1MInput: 0.5,
PricePer1MOutput: 1.5,
ValidDays: 30,
MaxConcurrent: 10,
RateLimitRPM: 100,
}
assert.Equal(t, int64(1001), req.SupplierID)
assert.Equal(t, int64(2001), req.AccountID)
assert.Equal(t, "gpt-4", req.Model)
assert.Equal(t, 10000.0, req.TotalQuota)
assert.Equal(t, 0.5, req.PricePer1MInput)
assert.Equal(t, 1.5, req.PricePer1MOutput)
assert.Equal(t, 30, req.ValidDays)
assert.Equal(t, 10, req.MaxConcurrent)
assert.Equal(t, 100, req.RateLimitRPM)
}
// TestBatchUpdatePriceRequest 测试批量更新价格请求
func TestBatchUpdatePriceRequest(t *testing.T) {
req := &BatchUpdatePriceRequest{
Items: []BatchPriceItem{
{PackageID: 1, PricePer1MInput: 0.6},
{PackageID: 2, PricePer1MOutput: 1.6},
},
}
assert.Len(t, req.Items, 2)
assert.Equal(t, int64(1), req.Items[0].PackageID)
assert.Equal(t, 0.6, req.Items[0].PricePer1MInput)
}
// TestBatchUpdatePriceResponse 测试批量更新价格响应
func TestBatchUpdatePriceResponse(t *testing.T) {
resp := &BatchUpdatePriceResponse{
Total: 10,
SuccessCount: 8,
FailedCount: 2,
Failures: []BatchPriceFailure{
{PackageID: 1, ErrorCode: "ERR_001", Message: "invalid price"},
},
}
assert.Equal(t, 10, resp.Total)
assert.Equal(t, 8, resp.SuccessCount)
assert.Equal(t, 2, resp.FailedCount)
assert.Len(t, resp.Failures, 1)
assert.Equal(t, int64(1), resp.Failures[0].PackageID)
}
// TestInvariantPackageErrors 测试套餐相关不变量错误
func TestInvariantPackageErrors(t *testing.T) {
assert.Contains(t, ErrPackageSoldOutSystemOnly.Error(), "sold_out")
assert.Contains(t, ErrPackageExpiredCannotRestore.Error(), "expired package")
assert.Contains(t, ErrPriceBelowProtection.Error(), "price cannot be below")
}
// TestNewPackageService 测试创建套餐服务
func TestNewPackageService(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
assert.NotNil(t, svc)
}
// TestPackageService_CreateDraft 测试创建套餐草稿
func TestPackageService_CreateDraft(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
req := &CreatePackageDraftRequest{
SupplierID: 1001,
AccountID: 2001,
Model: "gpt-4",
TotalQuota: 10000.0,
PricePer1MInput: 0.5,
PricePer1MOutput: 1.5,
ValidDays: 30,
MaxConcurrent: 10,
RateLimitRPM: 100,
}
pkg, err := svc.CreateDraft(context.Background(), 1001, req)
assert.NoError(t, err)
assert.NotNil(t, pkg)
assert.Equal(t, int64(1001), pkg.SupplierID)
assert.Equal(t, "gpt-4", pkg.Model)
assert.Equal(t, PackageStatusDraft, pkg.Status)
assert.Equal(t, 10000.0, pkg.AvailableQuota)
assert.Equal(t, 1, pkg.Version)
}
// TestPackageService_Publish 测试发布套餐
func TestPackageService_Publish(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
// 先创建草稿
req := &CreatePackageDraftRequest{
SupplierID: 1001,
AccountID: 2001,
Model: "gpt-4",
TotalQuota: 10000.0,
PricePer1MInput: 0.5,
PricePer1MOutput: 1.5,
ValidDays: 30,
}
pkg, _ := svc.CreateDraft(context.Background(), 1001, req)
// 发布
published, err := svc.Publish(context.Background(), 1001, pkg.ID)
assert.NoError(t, err)
assert.NotNil(t, published)
assert.Equal(t, PackageStatusActive, published.Status)
}
// TestPackageService_Publish_ExpiredPackage 测试发布过期套餐
func TestPackageService_Publish_ExpiredPackage(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
// 创建并直接标记为 expired通过手动设置 store
req := &CreatePackageDraftRequest{
SupplierID: 1001,
AccountID: 2001,
Model: "gpt-4",
TotalQuota: 10000.0,
PricePer1MInput: 0.5,
PricePer1MOutput: 1.5,
ValidDays: 30,
}
pkg, _ := svc.CreateDraft(context.Background(), 1001, req)
pkgStore.packages[pkg.ID].Status = PackageStatusExpired
// 尝试发布过期套餐应该失败
_, err := svc.Publish(context.Background(), 1001, pkg.ID)
assert.Error(t, err)
}
// TestPackageService_Pause 测试暂停套餐
func TestPackageService_Pause(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
// 创建并发布
req := &CreatePackageDraftRequest{
SupplierID: 1001,
AccountID: 2001,
Model: "gpt-4",
TotalQuota: 10000.0,
PricePer1MInput: 0.5,
PricePer1MOutput: 1.5,
ValidDays: 30,
}
pkg, _ := svc.CreateDraft(context.Background(), 1001, req)
svc.Publish(context.Background(), 1001, pkg.ID)
// 暂停
paused, err := svc.Pause(context.Background(), 1001, pkg.ID)
assert.NoError(t, err)
assert.Equal(t, PackageStatusPaused, paused.Status)
}
// TestPackageService_Unlist 测试下架套餐
func TestPackageService_Unlist(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
// 创建并发布
req := &CreatePackageDraftRequest{
SupplierID: 1001,
AccountID: 2001,
Model: "gpt-4",
TotalQuota: 10000.0,
PricePer1MInput: 0.5,
PricePer1MOutput: 1.5,
ValidDays: 30,
}
pkg, _ := svc.CreateDraft(context.Background(), 1001, req)
svc.Publish(context.Background(), 1001, pkg.ID)
// 下架
unlisted, err := svc.Unlist(context.Background(), 1001, pkg.ID)
assert.NoError(t, err)
assert.Equal(t, PackageStatusExpired, unlisted.Status)
}
// TestPackageService_GetByID 测试获取套餐
func TestPackageService_GetByID(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
// 创建套餐
req := &CreatePackageDraftRequest{
SupplierID: 1001,
AccountID: 2001,
Model: "gpt-4",
TotalQuota: 10000.0,
PricePer1MInput: 0.5,
PricePer1MOutput: 1.5,
ValidDays: 30,
}
pkg, _ := svc.CreateDraft(context.Background(), 1001, req)
// 获取
found, err := svc.GetByID(context.Background(), 1001, pkg.ID)
assert.NoError(t, err)
assert.NotNil(t, found)
assert.Equal(t, pkg.ID, found.ID)
}
// TestPackageService_GetByID_NotFound 测试获取不存在的套餐
func TestPackageService_GetByID_NotFound(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
_, err := svc.GetByID(context.Background(), 1001, 9999)
assert.Error(t, err)
}
// TestPackageService_Clone 测试克隆套餐
func TestPackageService_Clone(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
// 创建并发布原套餐
req := &CreatePackageDraftRequest{
SupplierID: 1001,
AccountID: 2001,
Model: "gpt-4",
TotalQuota: 10000.0,
PricePer1MInput: 0.5,
PricePer1MOutput: 1.5,
ValidDays: 30,
MaxConcurrent: 10,
RateLimitRPM: 100,
}
original, _ := svc.CreateDraft(context.Background(), 1001, req)
svc.Publish(context.Background(), 1001, original.ID)
// 克隆
clone, err := svc.Clone(context.Background(), 1001, original.ID)
assert.NoError(t, err)
assert.NotNil(t, clone)
assert.NotEqual(t, original.ID, clone.ID)
assert.Equal(t, original.SupplierID, clone.SupplierID)
assert.Equal(t, original.AccountID, clone.AccountID)
assert.Equal(t, original.Model, clone.Model)
assert.Equal(t, original.TotalQuota, clone.TotalQuota)
assert.Equal(t, original.TotalQuota, clone.AvailableQuota) // 可用配额重置为总量
assert.Equal(t, 0.0, clone.SoldQuota) // 售出配额重置为0
assert.Equal(t, PackageStatusDraft, clone.Status) // 克隆后为草稿状态
}
// TestPackageService_Clone_NotFound 测试克隆不存在的套餐
func TestPackageService_Clone_NotFound(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
_, err := svc.Clone(context.Background(), 1001, 9999)
assert.Error(t, err)
}
// TestPackageService_BatchUpdatePrice 测试批量更新价格
func TestPackageService_BatchUpdatePrice(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
// 创建套餐
req := &CreatePackageDraftRequest{
SupplierID: 1001,
AccountID: 2001,
Model: "gpt-4",
TotalQuota: 10000.0,
PricePer1MInput: 0.5,
PricePer1MOutput: 1.5,
ValidDays: 30,
}
pkg, _ := svc.CreateDraft(context.Background(), 1001, req)
svc.Publish(context.Background(), 1001, pkg.ID)
// 批量更新价格
batchReq := &BatchUpdatePriceRequest{
Items: []BatchPriceItem{
{PackageID: pkg.ID, PricePer1MInput: 0.6, PricePer1MOutput: 1.6},
},
}
resp, err := svc.BatchUpdatePrice(context.Background(), 1001, batchReq)
assert.NoError(t, err)
assert.NotNil(t, resp)
assert.Equal(t, 1, resp.Total)
assert.Equal(t, 1, resp.SuccessCount)
assert.Equal(t, 0, resp.FailedCount)
}
// TestPackageService_BatchUpdatePrice_NegativePrice 测试批量更新价格-负数价格
func TestPackageService_BatchUpdatePrice_NegativePrice(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
// 创建套餐
req := &CreatePackageDraftRequest{
SupplierID: 1001,
AccountID: 2001,
Model: "gpt-4",
TotalQuota: 10000.0,
PricePer1MInput: 0.5,
PricePer1MOutput: 1.5,
ValidDays: 30,
}
pkg, _ := svc.CreateDraft(context.Background(), 1001, req)
svc.Publish(context.Background(), 1001, pkg.ID)
// 批量更新价格为负数
batchReq := &BatchUpdatePriceRequest{
Items: []BatchPriceItem{
{PackageID: pkg.ID, PricePer1MInput: -0.1, PricePer1MOutput: 1.6},
},
}
resp, err := svc.BatchUpdatePrice(context.Background(), 1001, batchReq)
assert.NoError(t, err)
assert.Equal(t, 1, resp.Total)
assert.Equal(t, 0, resp.SuccessCount)
assert.Equal(t, 1, resp.FailedCount)
assert.Contains(t, resp.Failures[0].Message, "price cannot be negative")
}
// TestPackageService_BatchUpdatePrice_NotFound 测试批量更新价格-套餐不存在
func TestPackageService_BatchUpdatePrice_NotFound(t *testing.T) {
pkgStore := newMockPackageStoreForPackageTest()
acctStore := newMockAccountStoreForPackageTest()
auditStore := &mockAuditStoreForPackageTest{}
svc := NewPackageService(pkgStore, acctStore, auditStore)
batchReq := &BatchUpdatePriceRequest{
Items: []BatchPriceItem{
{PackageID: 9999, PricePer1MInput: 0.6, PricePer1MOutput: 1.6},
},
}
resp, err := svc.BatchUpdatePrice(context.Background(), 1001, batchReq)
assert.NoError(t, err)
assert.Equal(t, 1, resp.Total)
assert.Equal(t, 0, resp.SuccessCount)
assert.Equal(t, 1, resp.FailedCount)
assert.Equal(t, "NOT_FOUND", resp.Failures[0].ErrorCode)
}