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