3ab78dbf7a
- #32 License 激活迁移到 license_devices 表:Activate/Verify/Deactivate 全部改用 license_devices,新增 max_devices 校验和 GET /license/devices 端点; Activate 现在校验 shop_id 防跨租户激活 - #33 checkInventory 从 StockOutHandler 移到 StockService.CheckInventoryAvailability - #34 新增 util/response.go 统一错误响应工具(RespondError/RespondSuccess/RespondCreated) - #35 生产模式 CORS Origin='*' 启动时 Fatal - #36 生产模式 License 私钥未配置启动时 Fatal - #37 新增 util/page.go ValidatePageSize,应用到 partner/product/stock_in/stock_out handler Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
246 lines
7.2 KiB
Go
246 lines
7.2 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/wangjia/jiu/backend/internal/model"
|
|
"github.com/wangjia/jiu/backend/testutil"
|
|
)
|
|
|
|
func TestLicenseService_Activate_Success(t *testing.T) {
|
|
db := testutil.SetupTestDB()
|
|
shop := testutil.CreateTestShop(db, "LIC001")
|
|
|
|
expiry := time.Now().Add(30 * 24 * time.Hour)
|
|
lic := &model.License{
|
|
ShopID: shop.ID,
|
|
LicenseKey: "AAAAA-BBBBB-CCCCC-DDDDD",
|
|
IsActive: true,
|
|
ExpiresAt: &expiry,
|
|
MaxDevices: 3,
|
|
}
|
|
require.NoError(t, db.Create(lic).Error)
|
|
|
|
svc := NewLicenseService(db)
|
|
result, err := svc.Activate(shop.ID, "AAAAA-BBBBB-CCCCC-DDDDD", "device-001", "Test PC", "windows")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
|
|
// Verify device record was created
|
|
var dev model.LicenseDevice
|
|
require.NoError(t, db.Where("license_id = ? AND device_id = ?", lic.ID, "device-001").First(&dev).Error)
|
|
assert.Equal(t, "Test PC", dev.DeviceName)
|
|
assert.Equal(t, "windows", dev.Platform)
|
|
}
|
|
|
|
func TestLicenseService_Activate_SameDeviceIdempotent(t *testing.T) {
|
|
db := testutil.SetupTestDB()
|
|
shop := testutil.CreateTestShop(db, "LIC002")
|
|
|
|
lic := &model.License{
|
|
ShopID: shop.ID,
|
|
LicenseKey: "EEEEE-FFFFF-GGGGG-HHHHH",
|
|
IsActive: true,
|
|
MaxDevices: 3,
|
|
}
|
|
require.NoError(t, db.Create(lic).Error)
|
|
// Pre-bind the device
|
|
require.NoError(t, db.Create(&model.LicenseDevice{
|
|
LicenseID: lic.ID, ShopID: shop.ID, DeviceID: "same-device",
|
|
}).Error)
|
|
|
|
svc := NewLicenseService(db)
|
|
// Re-activating same device should succeed (idempotent)
|
|
result, err := svc.Activate(shop.ID, "EEEEE-FFFFF-GGGGG-HHHHH", "same-device", "Updated Name", "windows")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
|
|
// Still only one device record
|
|
var count int64
|
|
db.Model(&model.LicenseDevice{}).Where("license_id = ?", lic.ID).Count(&count)
|
|
assert.Equal(t, int64(1), count)
|
|
}
|
|
|
|
func TestLicenseService_Activate_DeviceLimitExceeded(t *testing.T) {
|
|
db := testutil.SetupTestDB()
|
|
shop := testutil.CreateTestShop(db, "LIC003")
|
|
|
|
lic := &model.License{
|
|
ShopID: shop.ID,
|
|
LicenseKey: "IIIII-JJJJJ-KKKKK-LLLLL",
|
|
IsActive: true,
|
|
MaxDevices: 2,
|
|
}
|
|
require.NoError(t, db.Create(lic).Error)
|
|
// Fill up the device limit
|
|
require.NoError(t, db.Create(&model.LicenseDevice{LicenseID: lic.ID, ShopID: shop.ID, DeviceID: "dev-1"}).Error)
|
|
require.NoError(t, db.Create(&model.LicenseDevice{LicenseID: lic.ID, ShopID: shop.ID, DeviceID: "dev-2"}).Error)
|
|
|
|
svc := NewLicenseService(db)
|
|
result, err := svc.Activate(shop.ID, "IIIII-JJJJJ-KKKKK-LLLLL", "dev-3", "", "")
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t, ErrDeviceLimitExceed, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestLicenseService_Activate_NotFound(t *testing.T) {
|
|
db := testutil.SetupTestDB()
|
|
shop := testutil.CreateTestShop(db, "LIC004")
|
|
|
|
svc := NewLicenseService(db)
|
|
result, err := svc.Activate(shop.ID, "NONEX-ISTEN-TTTTT-LICCC", "device-001", "", "")
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t, ErrLicenseNotFound, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestLicenseService_Activate_WrongShop(t *testing.T) {
|
|
db := testutil.SetupTestDB()
|
|
shop := testutil.CreateTestShop(db, "LIC004B")
|
|
otherShop := testutil.CreateTestShop(db, "LIC004C")
|
|
|
|
lic := &model.License{
|
|
ShopID: shop.ID, LicenseKey: "OTHSH-BBBBB-CCCCC-DDDDD", IsActive: true, MaxDevices: 3,
|
|
}
|
|
require.NoError(t, db.Create(lic).Error)
|
|
|
|
svc := NewLicenseService(db)
|
|
// otherShop cannot activate a license belonging to shop
|
|
result, err := svc.Activate(otherShop.ID, "OTHSH-BBBBB-CCCCC-DDDDD", "device-001", "", "")
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t, ErrLicenseNotFound, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestLicenseService_Activate_Inactive(t *testing.T) {
|
|
db := testutil.SetupTestDB()
|
|
shop := testutil.CreateTestShop(db, "LIC005")
|
|
|
|
lic := &model.License{
|
|
ShopID: shop.ID, LicenseKey: "MMMMM-NNNNN-OOOOO-PPPPP", IsActive: true, MaxDevices: 3,
|
|
}
|
|
require.NoError(t, db.Create(lic).Error)
|
|
require.NoError(t, db.Model(lic).Update("is_active", false).Error)
|
|
|
|
svc := NewLicenseService(db)
|
|
result, err := svc.Activate(shop.ID, "MMMMM-NNNNN-OOOOO-PPPPP", "device-001", "", "")
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t, ErrLicenseInactive, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestLicenseService_Activate_Expired(t *testing.T) {
|
|
db := testutil.SetupTestDB()
|
|
shop := testutil.CreateTestShop(db, "LIC006")
|
|
|
|
expiry := time.Now().Add(-24 * time.Hour)
|
|
lic := &model.License{
|
|
ShopID: shop.ID, LicenseKey: "QQQQQ-RRRRR-SSSSS-TTTTT", IsActive: true, ExpiresAt: &expiry, MaxDevices: 3,
|
|
}
|
|
require.NoError(t, db.Create(lic).Error)
|
|
|
|
svc := NewLicenseService(db)
|
|
result, err := svc.Activate(shop.ID, "QQQQQ-RRRRR-SSSSS-TTTTT", "device-001", "", "")
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t, ErrLicenseExpired, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestLicenseService_Verify_Success(t *testing.T) {
|
|
db := testutil.SetupTestDB()
|
|
shop := testutil.CreateTestShop(db, "LIC007")
|
|
|
|
expiry := time.Now().Add(30 * 24 * time.Hour)
|
|
lic := &model.License{
|
|
ShopID: shop.ID, LicenseKey: "UUUUU-VVVVV-WWWWW-XXXXX", IsActive: true, ExpiresAt: &expiry,
|
|
}
|
|
require.NoError(t, db.Create(lic).Error)
|
|
require.NoError(t, db.Create(&model.LicenseDevice{
|
|
LicenseID: lic.ID, ShopID: shop.ID, DeviceID: "my-device",
|
|
}).Error)
|
|
|
|
svc := NewLicenseService(db)
|
|
result, err := svc.Verify(shop.ID, "my-device")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
assert.Equal(t, lic.ID, result.ID)
|
|
}
|
|
|
|
func TestLicenseService_Verify_Expired(t *testing.T) {
|
|
db := testutil.SetupTestDB()
|
|
shop := testutil.CreateTestShop(db, "LIC008")
|
|
|
|
expiry := time.Now().Add(-1 * time.Hour)
|
|
lic := &model.License{
|
|
ShopID: shop.ID, LicenseKey: "YYYYY-ZZZZZ-AAAAA-BBBBB", IsActive: true, ExpiresAt: &expiry,
|
|
}
|
|
require.NoError(t, db.Create(lic).Error)
|
|
require.NoError(t, db.Create(&model.LicenseDevice{
|
|
LicenseID: lic.ID, ShopID: shop.ID, DeviceID: "expired-device",
|
|
}).Error)
|
|
|
|
svc := NewLicenseService(db)
|
|
result, err := svc.Verify(shop.ID, "expired-device")
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t, ErrLicenseExpired, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestLicenseService_Verify_NotFound(t *testing.T) {
|
|
db := testutil.SetupTestDB()
|
|
shop := testutil.CreateTestShop(db, "LIC009")
|
|
|
|
svc := NewLicenseService(db)
|
|
result, err := svc.Verify(shop.ID, "nonexistent-device")
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t, ErrLicenseNotFound, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestLicenseService_Verify_NoExpiry(t *testing.T) {
|
|
db := testutil.SetupTestDB()
|
|
shop := testutil.CreateTestShop(db, "LIC010")
|
|
|
|
lic := &model.License{
|
|
ShopID: shop.ID, LicenseKey: "CCCCC-DDDDD-EEEEE-FFFFF", IsActive: true, ExpiresAt: nil,
|
|
}
|
|
require.NoError(t, db.Create(lic).Error)
|
|
require.NoError(t, db.Create(&model.LicenseDevice{
|
|
LicenseID: lic.ID, ShopID: shop.ID, DeviceID: "lifetime-device",
|
|
}).Error)
|
|
|
|
svc := NewLicenseService(db)
|
|
result, err := svc.Verify(shop.ID, "lifetime-device")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
}
|
|
|
|
func TestGenerateKey(t *testing.T) {
|
|
testutil.InitConfig()
|
|
|
|
expiry := time.Now().Add(30 * 24 * time.Hour)
|
|
key := GenerateKey(1, "annual", &expiry)
|
|
|
|
assert.NotEmpty(t, key)
|
|
// 格式:XXXXX-XXXXX-XXXXX-XXXXX
|
|
assert.Equal(t, 23, len(key)) // 4*5 + 3 dashes = 23
|
|
assert.Equal(t, '-', rune(key[5]))
|
|
assert.Equal(t, '-', rune(key[11]))
|
|
assert.Equal(t, '-', rune(key[17]))
|
|
}
|