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>
251 lines
8.5 KiB
Go
251 lines
8.5 KiB
Go
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)
|
|
|
|
expiry := time.Now().Add(30 * 24 * time.Hour)
|
|
lic := &model.License{
|
|
ShopID: shop.ID,
|
|
LicenseKey: "LHACT-BBBBB-CCCCC-DDDDD",
|
|
IsActive: true,
|
|
ExpiresAt: &expiry,
|
|
MaxDevices: 3,
|
|
}
|
|
require.NoError(t, db.Create(lic).Error)
|
|
|
|
w := makeRequest(r, "POST", "/api/v1/license/activate", token, map[string]interface{}{
|
|
"license_key": "LHACT-BBBBB-CCCCC-DDDDD",
|
|
"device_id": "device-123",
|
|
"device_name": "Test Machine",
|
|
"platform": "windows",
|
|
})
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
|
|
// Verify device was recorded in license_devices
|
|
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)
|
|
}
|
|
|
|
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)
|
|
|
|
lic := &model.License{
|
|
ShopID: shop.ID, LicenseKey: "LHBND-BBBBB-CCCCC-DDDDD", IsActive: true, MaxDevices: 1,
|
|
}
|
|
require.NoError(t, db.Create(lic).Error)
|
|
// Fill the single allowed slot
|
|
require.NoError(t, db.Create(&model.LicenseDevice{
|
|
LicenseID: lic.ID, ShopID: shop.ID, DeviceID: "existing-device",
|
|
}).Error)
|
|
|
|
w := makeRequest(r, "POST", "/api/v1/license/activate", token, map[string]interface{}{
|
|
"license_key": "LHBND-BBBBB-CCCCC-DDDDD",
|
|
"device_id": "different-device",
|
|
})
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestLicenseHandler_Activate_Expired(t *testing.T) {
|
|
db := testutil.SetupTestDB()
|
|
shop := testutil.CreateTestShop(db, "LH005")
|
|
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
|
|
token := getAuthToken(user.ID, shop.ID, "admin")
|
|
r := setupProtectedRouter(db)
|
|
|
|
expiry := time.Now().Add(-24 * time.Hour)
|
|
lic := &model.License{
|
|
ShopID: shop.ID, LicenseKey: "LHEXP-BBBBB-CCCCC-DDDDD", IsActive: true, ExpiresAt: &expiry, MaxDevices: 3,
|
|
}
|
|
require.NoError(t, db.Create(lic).Error)
|
|
|
|
w := makeRequest(r, "POST", "/api/v1/license/activate", token, map[string]interface{}{
|
|
"license_key": "LHEXP-BBBBB-CCCCC-DDDDD",
|
|
"device_id": "device-123",
|
|
})
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
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)
|
|
}
|