package handler import ( "net/http" "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 TestLicenseHandler_Activate_Success(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "LH001") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) // 码池里放一张未用兑换码(30 天) require.NoError(t, db.Create(&model.LicenseCode{ Code: "JIUKUTEST0001", Type: "annual", Tier: "standard", DurationDays: 30, MaxDevices: 3, Status: "unused", }).Error) // 请求带连字符/会被归一化为 JIUKUTEST0001 w := makeRequest(r, "POST", "/api/v1/license/activate", token, map[string]interface{}{ "license_key": "JIUKU-TEST-0001", "device_id": "device-123", "device_name": "Test Machine", "platform": "windows", }) assert.Equal(t, http.StatusOK, w.Code) // 兑换后门店生成授权行 + 设备绑定 var lic model.License require.NoError(t, db.Where("shop_id = ?", shop.ID).First(&lic).Error) var dev model.LicenseDevice require.NoError(t, db.Where("license_id = ? AND device_id = ?", lic.ID, "device-123").First(&dev).Error) assert.Equal(t, "Test Machine", dev.DeviceName) // 码被标记已用 var lc model.LicenseCode require.NoError(t, db.Where("code = ?", "JIUKUTEST0001").First(&lc).Error) assert.Equal(t, "redeemed", lc.Status) } func TestLicenseHandler_Activate_MissingFields(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "LH002") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) // 缺少 device_id w := makeRequest(r, "POST", "/api/v1/license/activate", token, map[string]interface{}{ "license_key": "LHACT-BBBBB-CCCCC-DDDDD", }) assert.Equal(t, http.StatusBadRequest, w.Code) // 缺少 license_key w = makeRequest(r, "POST", "/api/v1/license/activate", token, map[string]interface{}{ "device_id": "device-123", }) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestLicenseHandler_Activate_NotFound(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "LH003") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) w := makeRequest(r, "POST", "/api/v1/license/activate", token, map[string]interface{}{ "license_key": "NONEX-ISTEN-TTTTT-LICCC", "device_id": "device-123", }) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestLicenseHandler_Activate_DeviceLimitExceeded(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "LH004") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) // 既有授权:1 个设备名额已占满 future := time.Now().Add(30 * 24 * time.Hour) lic := &model.License{ ShopID: shop.ID, LicenseKey: "TRIAL-LH004", IsActive: true, MaxDevices: 1, ExpiresAt: &future, } require.NoError(t, db.Create(lic).Error) require.NoError(t, db.Create(&model.LicenseDevice{ LicenseID: lic.ID, ShopID: shop.ID, DeviceID: "existing-device", }).Error) // 码不提升设备上限(max_devices=0) require.NoError(t, db.Create(&model.LicenseCode{ Code: "JIUKUDEVLIMIT1", Type: "annual", Tier: "standard", DurationDays: 365, MaxDevices: 0, Status: "unused", }).Error) w := makeRequest(r, "POST", "/api/v1/license/activate", token, map[string]interface{}{ "license_key": "JIUKU-DEVL-IMIT1", "device_id": "different-device", }) assert.Equal(t, http.StatusBadRequest, w.Code) // 设备超限 → 整笔回滚:码仍未使用 var lc model.LicenseCode require.NoError(t, db.Where("code = ?", "JIUKUDEVLIMIT1").First(&lc).Error) assert.Equal(t, "unused", lc.Status) } func TestLicenseHandler_Activate_NoAuth(t *testing.T) { db := testutil.SetupTestDB() r := setupProtectedRouter(db) w := makeRequest(r, "POST", "/api/v1/license/activate", "", map[string]interface{}{ "license_key": "XXXXX-XXXXX-XXXXX-XXXXX", "device_id": "device-123", }) assert.Equal(t, http.StatusUnauthorized, w.Code) } func TestLicenseHandler_Verify_Success(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "LH006") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) expiry := time.Now().Add(30 * 24 * time.Hour) lic := &model.License{ ShopID: shop.ID, LicenseKey: "LHVFY-BBBBB-CCCCC-DDDDD", 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) w := makeRequest(r, "GET", "/api/v1/license/verify?device_id=my-device", token, nil) assert.Equal(t, http.StatusOK, w.Code) } func TestLicenseHandler_Verify_MissingDeviceID(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "LH007") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) w := makeRequest(r, "GET", "/api/v1/license/verify", token, nil) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestLicenseHandler_Verify_NotFound(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "LH008") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) w := makeRequest(r, "GET", "/api/v1/license/verify?device_id=nonexistent-device", token, nil) assert.Equal(t, http.StatusForbidden, w.Code) } func TestLicenseHandler_Verify_Expired(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "LH009") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) expiry := time.Now().Add(-1 * time.Hour) lic := &model.License{ ShopID: shop.ID, LicenseKey: "LHVEX-BBBBB-CCCCC-DDDDD", 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) w := makeRequest(r, "GET", "/api/v1/license/verify?device_id=expired-device", token, nil) assert.Equal(t, http.StatusForbidden, w.Code) } func TestLicenseHandler_Verify_NoAuth(t *testing.T) { db := testutil.SetupTestDB() r := setupProtectedRouter(db) w := makeRequest(r, "GET", "/api/v1/license/verify?device_id=any", "", nil) assert.Equal(t, http.StatusUnauthorized, w.Code) } func TestLicenseHandler_Deactivate_Success(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "LH010") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) expiry := time.Now().Add(30 * 24 * time.Hour) lic := &model.License{ ShopID: shop.ID, LicenseKey: "LHDAC-BBBBB-CCCCC-DDDDD", 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: "deactivate-device", }).Error) w := makeRequest(r, "POST", "/api/v1/license/deactivate", token, map[string]interface{}{ "device_id": "deactivate-device", }) assert.Equal(t, http.StatusOK, w.Code) // Verify device was removed var count int64 db.Model(&model.LicenseDevice{}).Where("shop_id = ? AND device_id = ?", shop.ID, "deactivate-device").Count(&count) assert.Equal(t, int64(0), count) } func TestLicenseHandler_Deactivate_MissingDeviceID(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "LH011") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) w := makeRequest(r, "POST", "/api/v1/license/deactivate", token, map[string]interface{}{}) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestLicenseHandler_Deactivate_NoAuth(t *testing.T) { db := testutil.SetupTestDB() r := setupProtectedRouter(db) w := makeRequest(r, "POST", "/api/v1/license/deactivate", "", map[string]interface{}{ "device_id": "any-device", }) assert.Equal(t, http.StatusUnauthorized, w.Code) }