feat: 实现企业设备授权功能并归档 OpenSpec 变更
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m39s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m39s
- 新增企业设备授权模块(Model、DTO、Service、Handler、Store) - 实现设备授权的创建、查询、更新、删除等完整业务逻辑 - 添加企业卡授权与设备授权的关联关系 - 新增 2 个数据库迁移脚本 - 同步 OpenSpec delta specs 到 main specs - 归档 add-enterprise-device-authorization 变更 - 更新 API 文档和路由配置 - 新增完整的集成测试和单元测试覆盖
This commit is contained in:
@@ -394,3 +394,13 @@ func (s *EnterpriseCardAuthorizationStore) GetByID(ctx context.Context, id uint)
|
||||
}
|
||||
return &auth, nil
|
||||
}
|
||||
|
||||
func (s *EnterpriseCardAuthorizationStore) RevokeByDeviceAuthID(ctx context.Context, deviceAuthID uint, revokedBy uint) error {
|
||||
now := time.Now()
|
||||
return s.db.WithContext(ctx).Model(&model.EnterpriseCardAuthorization{}).
|
||||
Where("device_auth_id = ? AND revoked_at IS NULL", deviceAuthID).
|
||||
Updates(map[string]interface{}{
|
||||
"revoked_by": revokedBy,
|
||||
"revoked_at": now,
|
||||
}).Error
|
||||
}
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func uniqueCardAuthTestPrefix() string {
|
||||
return fmt.Sprintf("ECA%d", time.Now().UnixNano()%1000000000)
|
||||
}
|
||||
|
||||
func TestEnterpriseCardAuthorizationStore_RevokeByDeviceAuthID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseCardAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueCardAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierName: "测试运营商",
|
||||
CarrierType: "CMCC",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(carrier).Error)
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: prefix + "0001", CardType: "normal", CarrierID: carrier.ID, Status: 2},
|
||||
{ICCID: prefix + "0002", CardType: "normal", CarrierID: carrier.ID, Status: 2},
|
||||
{ICCID: prefix + "0003", CardType: "normal", CarrierID: carrier.ID, Status: 2},
|
||||
}
|
||||
for _, c := range cards {
|
||||
require.NoError(t, tx.Create(c).Error)
|
||||
}
|
||||
|
||||
deviceAuthID := uint(12345)
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseCardAuthorization{
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, DeviceAuthID: &deviceAuthID},
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, DeviceAuthID: &deviceAuthID},
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[2].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, DeviceAuthID: nil},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("成功撤销指定设备授权ID关联的卡授权", func(t *testing.T) {
|
||||
revokerID := uint(2)
|
||||
err := store.RevokeByDeviceAuthID(ctx, deviceAuthID, revokerID)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := store.ListByEnterprise(ctx, enterprise.ID, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, cards[2].ID, result[0].CardID)
|
||||
|
||||
revokedResult, err := store.ListByEnterprise(ctx, enterprise.ID, true)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, revokedResult, 3)
|
||||
|
||||
for _, auth := range revokedResult {
|
||||
if auth.DeviceAuthID != nil && *auth.DeviceAuthID == deviceAuthID {
|
||||
assert.NotNil(t, auth.RevokedAt)
|
||||
assert.NotNil(t, auth.RevokedBy)
|
||||
assert.Equal(t, revokerID, *auth.RevokedBy)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("设备授权ID不存在时不报错", func(t *testing.T) {
|
||||
err := store.RevokeByDeviceAuthID(ctx, 99999, uint(1))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseCardAuthorizationStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseCardAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueCardAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierName: "测试运营商",
|
||||
CarrierType: "CMCC",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(carrier).Error)
|
||||
|
||||
card := &model.IotCard{
|
||||
ICCID: prefix + "0001",
|
||||
CardType: "normal",
|
||||
CarrierID: carrier.ID,
|
||||
Status: 2,
|
||||
}
|
||||
require.NoError(t, tx.Create(card).Error)
|
||||
|
||||
t.Run("成功创建卡授权记录", func(t *testing.T) {
|
||||
auth := &model.EnterpriseCardAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
CardID: card.ID,
|
||||
AuthorizedBy: 1,
|
||||
AuthorizedAt: time.Now(),
|
||||
AuthorizerType: 2,
|
||||
Remark: "测试授权",
|
||||
}
|
||||
|
||||
err := store.Create(ctx, auth)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, auth.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseCardAuthorizationStore_BatchCreate(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseCardAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueCardAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierName: "测试运营商",
|
||||
CarrierType: "CMCC",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(carrier).Error)
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: prefix + "0001", CardType: "normal", CarrierID: carrier.ID, Status: 2},
|
||||
{ICCID: prefix + "0002", CardType: "normal", CarrierID: carrier.ID, Status: 2},
|
||||
}
|
||||
for _, c := range cards {
|
||||
require.NoError(t, tx.Create(c).Error)
|
||||
}
|
||||
|
||||
t.Run("成功批量创建卡授权记录", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseCardAuthorization{
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
}
|
||||
|
||||
err := store.BatchCreate(ctx, auths)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, auth := range auths {
|
||||
assert.NotZero(t, auth.ID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("空列表不报错", func(t *testing.T) {
|
||||
err := store.BatchCreate(ctx, []*model.EnterpriseCardAuthorization{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseCardAuthorizationStore_ListByEnterprise(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseCardAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueCardAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierName: "测试运营商",
|
||||
CarrierType: "CMCC",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(carrier).Error)
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: prefix + "0001", CardType: "normal", CarrierID: carrier.ID, Status: 2},
|
||||
{ICCID: prefix + "0002", CardType: "normal", CarrierID: carrier.ID, Status: 2},
|
||||
}
|
||||
for _, c := range cards {
|
||||
require.NoError(t, tx.Create(c).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseCardAuthorization{
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, RevokedBy: ptrUintCA(1), RevokedAt: &now},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("获取未撤销的授权记录", func(t *testing.T) {
|
||||
result, err := store.ListByEnterprise(ctx, enterprise.ID, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, cards[0].ID, result[0].CardID)
|
||||
})
|
||||
|
||||
t.Run("获取所有授权记录包括已撤销", func(t *testing.T) {
|
||||
result, err := store.ListByEnterprise(ctx, enterprise.ID, true)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func ptrUintCA(v uint) *uint {
|
||||
return &v
|
||||
}
|
||||
|
||||
func TestEnterpriseCardAuthorizationStore_GetActiveAuthsByCardIDs(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseCardAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueCardAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
carrier := &model.Carrier{
|
||||
CarrierName: "测试运营商",
|
||||
CarrierType: "CMCC",
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(carrier).Error)
|
||||
|
||||
cards := []*model.IotCard{
|
||||
{ICCID: prefix + "0001", CardType: "normal", CarrierID: carrier.ID, Status: 2},
|
||||
{ICCID: prefix + "0002", CardType: "normal", CarrierID: carrier.ID, Status: 2},
|
||||
{ICCID: prefix + "0003", CardType: "normal", CarrierID: carrier.ID, Status: 2},
|
||||
}
|
||||
for _, c := range cards {
|
||||
require.NoError(t, tx.Create(c).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseCardAuthorization{
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, CardID: cards[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, RevokedBy: ptrUintCA(1), RevokedAt: &now},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("获取有效授权的卡ID映射", func(t *testing.T) {
|
||||
cardIDs := []uint{cards[0].ID, cards[1].ID, cards[2].ID}
|
||||
result, err := store.GetActiveAuthsByCardIDs(ctx, enterprise.ID, cardIDs)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, result[cards[0].ID])
|
||||
assert.False(t, result[cards[1].ID])
|
||||
assert.False(t, result[cards[2].ID])
|
||||
})
|
||||
|
||||
t.Run("空卡ID列表返回空映射", func(t *testing.T) {
|
||||
result, err := store.GetActiveAuthsByCardIDs(ctx, enterprise.ID, []uint{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
160
internal/store/postgres/enterprise_device_authorization_store.go
Normal file
160
internal/store/postgres/enterprise_device_authorization_store.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type EnterpriseDeviceAuthorizationStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
func NewEnterpriseDeviceAuthorizationStore(db *gorm.DB, redis *redis.Client) *EnterpriseDeviceAuthorizationStore {
|
||||
return &EnterpriseDeviceAuthorizationStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EnterpriseDeviceAuthorizationStore) Create(ctx context.Context, auth *model.EnterpriseDeviceAuthorization) error {
|
||||
return s.db.WithContext(ctx).Create(auth).Error
|
||||
}
|
||||
|
||||
func (s *EnterpriseDeviceAuthorizationStore) BatchCreate(ctx context.Context, auths []*model.EnterpriseDeviceAuthorization) error {
|
||||
if len(auths) == 0 {
|
||||
return nil
|
||||
}
|
||||
batchSize := 100
|
||||
for i := 0; i < len(auths); i += batchSize {
|
||||
end := i + batchSize
|
||||
if end > len(auths) {
|
||||
end = len(auths)
|
||||
}
|
||||
batch := auths[i:end]
|
||||
if err := s.db.WithContext(ctx).Create(batch).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EnterpriseDeviceAuthorizationStore) GetByID(ctx context.Context, id uint) (*model.EnterpriseDeviceAuthorization, error) {
|
||||
var auth model.EnterpriseDeviceAuthorization
|
||||
err := s.db.WithContext(ctx).Where("id = ?", id).First(&auth).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth, nil
|
||||
}
|
||||
|
||||
func (s *EnterpriseDeviceAuthorizationStore) GetByDeviceID(ctx context.Context, deviceID uint) (*model.EnterpriseDeviceAuthorization, error) {
|
||||
var auth model.EnterpriseDeviceAuthorization
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("device_id = ? AND revoked_at IS NULL", deviceID).
|
||||
First(&auth).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth, nil
|
||||
}
|
||||
|
||||
func (s *EnterpriseDeviceAuthorizationStore) GetByEnterpriseID(ctx context.Context, enterpriseID uint, includeRevoked bool) ([]*model.EnterpriseDeviceAuthorization, error) {
|
||||
var auths []*model.EnterpriseDeviceAuthorization
|
||||
query := s.db.WithContext(ctx).Where("enterprise_id = ?", enterpriseID)
|
||||
if !includeRevoked {
|
||||
query = query.Where("revoked_at IS NULL")
|
||||
}
|
||||
err := query.Find(&auths).Error
|
||||
return auths, err
|
||||
}
|
||||
|
||||
type DeviceAuthListOptions struct {
|
||||
EnterpriseID *uint
|
||||
DeviceIDs []uint
|
||||
AuthorizerID *uint
|
||||
IncludeRevoked bool
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
func (s *EnterpriseDeviceAuthorizationStore) ListByEnterprise(ctx context.Context, opts DeviceAuthListOptions) ([]*model.EnterpriseDeviceAuthorization, int64, error) {
|
||||
var auths []*model.EnterpriseDeviceAuthorization
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.EnterpriseDeviceAuthorization{})
|
||||
|
||||
if opts.EnterpriseID != nil {
|
||||
query = query.Where("enterprise_id = ?", *opts.EnterpriseID)
|
||||
}
|
||||
|
||||
if len(opts.DeviceIDs) > 0 {
|
||||
query = query.Where("device_id IN ?", opts.DeviceIDs)
|
||||
}
|
||||
|
||||
if opts.AuthorizerID != nil {
|
||||
query = query.Where("authorized_by = ?", *opts.AuthorizerID)
|
||||
}
|
||||
|
||||
if !opts.IncludeRevoked {
|
||||
query = query.Where("revoked_at IS NULL")
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if opts.Page > 0 && opts.PageSize > 0 {
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
}
|
||||
|
||||
err := query.Order("authorized_at DESC").Find(&auths).Error
|
||||
return auths, total, err
|
||||
}
|
||||
|
||||
func (s *EnterpriseDeviceAuthorizationStore) RevokeByIDs(ctx context.Context, ids []uint, revokedBy uint) error {
|
||||
now := time.Now()
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.EnterpriseDeviceAuthorization{}).
|
||||
Where("id IN ? AND revoked_at IS NULL", ids).
|
||||
Updates(map[string]interface{}{
|
||||
"revoked_by": revokedBy,
|
||||
"revoked_at": now,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (s *EnterpriseDeviceAuthorizationStore) GetActiveAuthsByDeviceIDs(ctx context.Context, enterpriseID uint, deviceIDs []uint) (map[uint]bool, error) {
|
||||
if len(deviceIDs) == 0 {
|
||||
return make(map[uint]bool), nil
|
||||
}
|
||||
|
||||
var auths []model.EnterpriseDeviceAuthorization
|
||||
err := s.db.WithContext(ctx).
|
||||
Select("device_id").
|
||||
Where("enterprise_id = ? AND device_id IN ? AND revoked_at IS NULL", enterpriseID, deviceIDs).
|
||||
Find(&auths).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[uint]bool, len(auths))
|
||||
for _, auth := range auths {
|
||||
result[auth.DeviceID] = true
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *EnterpriseDeviceAuthorizationStore) ListDeviceIDsByEnterprise(ctx context.Context, enterpriseID uint) ([]uint, error) {
|
||||
var deviceIDs []uint
|
||||
err := s.db.WithContext(ctx).
|
||||
Model(&model.EnterpriseDeviceAuthorization{}).
|
||||
Where("enterprise_id = ? AND revoked_at IS NULL", enterpriseID).
|
||||
Pluck("device_id", &deviceIDs).Error
|
||||
return deviceIDs, err
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func uniqueDeviceAuthTestPrefix() string {
|
||||
return fmt.Sprintf("EDA%d", time.Now().UnixNano()%1000000000)
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_Create(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
device := &model.Device{
|
||||
DeviceNo: prefix + "_001",
|
||||
DeviceName: "测试设备1",
|
||||
Status: 2,
|
||||
}
|
||||
require.NoError(t, tx.Create(device).Error)
|
||||
|
||||
t.Run("成功创建授权记录", func(t *testing.T) {
|
||||
auth := &model.EnterpriseDeviceAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
DeviceID: device.ID,
|
||||
AuthorizedBy: 1,
|
||||
AuthorizedAt: time.Now(),
|
||||
AuthorizerType: 2,
|
||||
Remark: "测试授权",
|
||||
}
|
||||
|
||||
err := store.Create(ctx, auth)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, auth.ID)
|
||||
assert.Equal(t, enterprise.ID, auth.EnterpriseID)
|
||||
assert.Equal(t, device.ID, auth.DeviceID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_BatchCreate(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "_001", DeviceName: "测试设备1", Status: 2},
|
||||
{DeviceNo: prefix + "_002", DeviceName: "测试设备2", Status: 2},
|
||||
{DeviceNo: prefix + "_003", DeviceName: "测试设备3", Status: 2},
|
||||
}
|
||||
for _, d := range devices {
|
||||
require.NoError(t, tx.Create(d).Error)
|
||||
}
|
||||
|
||||
t.Run("成功批量创建授权记录", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseDeviceAuthorization{
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[2].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
}
|
||||
|
||||
err := store.BatchCreate(ctx, auths)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, auth := range auths {
|
||||
assert.NotZero(t, auth.ID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("空列表不报错", func(t *testing.T) {
|
||||
err := store.BatchCreate(ctx, []*model.EnterpriseDeviceAuthorization{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_GetByID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
device := &model.Device{
|
||||
DeviceNo: prefix + "_001",
|
||||
DeviceName: "测试设备1",
|
||||
Status: 2,
|
||||
}
|
||||
require.NoError(t, tx.Create(device).Error)
|
||||
|
||||
auth := &model.EnterpriseDeviceAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
DeviceID: device.ID,
|
||||
AuthorizedBy: 1,
|
||||
AuthorizedAt: time.Now(),
|
||||
AuthorizerType: 2,
|
||||
Remark: "测试备注",
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
|
||||
t.Run("成功获取授权记录", func(t *testing.T) {
|
||||
result, err := store.GetByID(ctx, auth.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, auth.ID, result.ID)
|
||||
assert.Equal(t, enterprise.ID, result.EnterpriseID)
|
||||
assert.Equal(t, device.ID, result.DeviceID)
|
||||
assert.Equal(t, "测试备注", result.Remark)
|
||||
})
|
||||
|
||||
t.Run("记录不存在返回错误", func(t *testing.T) {
|
||||
_, err := store.GetByID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_GetByDeviceID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
device := &model.Device{
|
||||
DeviceNo: prefix + "_001",
|
||||
DeviceName: "测试设备1",
|
||||
Status: 2,
|
||||
}
|
||||
require.NoError(t, tx.Create(device).Error)
|
||||
|
||||
auth := &model.EnterpriseDeviceAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
DeviceID: device.ID,
|
||||
AuthorizedBy: 1,
|
||||
AuthorizedAt: time.Now(),
|
||||
AuthorizerType: 2,
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
|
||||
t.Run("成功通过设备ID获取授权记录", func(t *testing.T) {
|
||||
result, err := store.GetByDeviceID(ctx, device.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, auth.ID, result.ID)
|
||||
assert.Equal(t, enterprise.ID, result.EnterpriseID)
|
||||
})
|
||||
|
||||
t.Run("设备未授权返回错误", func(t *testing.T) {
|
||||
_, err := store.GetByDeviceID(ctx, 99999)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("已撤销的授权不返回", func(t *testing.T) {
|
||||
device2 := &model.Device{
|
||||
DeviceNo: prefix + "_002",
|
||||
DeviceName: "测试设备2",
|
||||
Status: 2,
|
||||
}
|
||||
require.NoError(t, tx.Create(device2).Error)
|
||||
|
||||
now := time.Now()
|
||||
revokedAuth := &model.EnterpriseDeviceAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
DeviceID: device2.ID,
|
||||
AuthorizedBy: 1,
|
||||
AuthorizedAt: now,
|
||||
AuthorizerType: 2,
|
||||
RevokedBy: ptrUint(1),
|
||||
RevokedAt: &now,
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, revokedAuth))
|
||||
|
||||
_, err := store.GetByDeviceID(ctx, device2.ID)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_GetByEnterpriseID(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "_001", DeviceName: "测试设备1", Status: 2},
|
||||
{DeviceNo: prefix + "_002", DeviceName: "测试设备2", Status: 2},
|
||||
}
|
||||
for _, d := range devices {
|
||||
require.NoError(t, tx.Create(d).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseDeviceAuthorization{
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, RevokedBy: ptrUint(1), RevokedAt: &now},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("获取未撤销的授权记录", func(t *testing.T) {
|
||||
result, err := store.GetByEnterpriseID(ctx, enterprise.ID, false)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, devices[0].ID, result[0].DeviceID)
|
||||
})
|
||||
|
||||
t.Run("获取所有授权记录包括已撤销", func(t *testing.T) {
|
||||
result, err := store.GetByEnterpriseID(ctx, enterprise.ID, true)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_ListByEnterprise(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
devices := make([]*model.Device, 5)
|
||||
for i := 0; i < 5; i++ {
|
||||
devices[i] = &model.Device{
|
||||
DeviceNo: fmt.Sprintf("%s_%03d", prefix, i+1),
|
||||
DeviceName: fmt.Sprintf("测试设备%d", i+1),
|
||||
Status: 2,
|
||||
}
|
||||
require.NoError(t, tx.Create(devices[i]).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
for i, d := range devices {
|
||||
auth := &model.EnterpriseDeviceAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
DeviceID: d.ID,
|
||||
AuthorizedBy: uint(i + 1),
|
||||
AuthorizedAt: now.Add(time.Duration(i) * time.Minute),
|
||||
AuthorizerType: 2,
|
||||
}
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("分页查询", func(t *testing.T) {
|
||||
opts := DeviceAuthListOptions{
|
||||
EnterpriseID: &enterprise.ID,
|
||||
Page: 1,
|
||||
PageSize: 2,
|
||||
}
|
||||
|
||||
result, total, err := store.ListByEnterprise(ctx, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(5), total)
|
||||
assert.Len(t, result, 2)
|
||||
})
|
||||
|
||||
t.Run("按授权人过滤", func(t *testing.T) {
|
||||
authorizerID := uint(1)
|
||||
opts := DeviceAuthListOptions{
|
||||
EnterpriseID: &enterprise.ID,
|
||||
AuthorizerID: &authorizerID,
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
}
|
||||
|
||||
result, total, err := store.ListByEnterprise(ctx, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Len(t, result, 1)
|
||||
})
|
||||
|
||||
t.Run("按设备ID过滤", func(t *testing.T) {
|
||||
opts := DeviceAuthListOptions{
|
||||
EnterpriseID: &enterprise.ID,
|
||||
DeviceIDs: []uint{devices[0].ID, devices[1].ID},
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
}
|
||||
|
||||
result, total, err := store.ListByEnterprise(ctx, opts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
assert.Len(t, result, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_RevokeByIDs(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "_001", DeviceName: "测试设备1", Status: 2},
|
||||
{DeviceNo: prefix + "_002", DeviceName: "测试设备2", Status: 2},
|
||||
}
|
||||
for _, d := range devices {
|
||||
require.NoError(t, tx.Create(d).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseDeviceAuthorization{
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("成功撤销授权", func(t *testing.T) {
|
||||
revokerID := uint(2)
|
||||
err := store.RevokeByIDs(ctx, []uint{auths[0].ID}, revokerID)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := store.GetByID(ctx, auths[0].ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result.RevokedAt)
|
||||
assert.NotNil(t, result.RevokedBy)
|
||||
assert.Equal(t, revokerID, *result.RevokedBy)
|
||||
})
|
||||
|
||||
t.Run("已撤销的记录不再被重复撤销", func(t *testing.T) {
|
||||
err := store.RevokeByIDs(ctx, []uint{auths[0].ID}, uint(3))
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := store.GetByID(ctx, auths[0].ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint(2), *result.RevokedBy)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_GetActiveAuthsByDeviceIDs(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "_001", DeviceName: "测试设备1", Status: 2},
|
||||
{DeviceNo: prefix + "_002", DeviceName: "测试设备2", Status: 2},
|
||||
{DeviceNo: prefix + "_003", DeviceName: "测试设备3", Status: 2},
|
||||
}
|
||||
for _, d := range devices {
|
||||
require.NoError(t, tx.Create(d).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseDeviceAuthorization{
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2, RevokedBy: ptrUint(1), RevokedAt: &now},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("获取有效授权的设备ID映射", func(t *testing.T) {
|
||||
deviceIDs := []uint{devices[0].ID, devices[1].ID, devices[2].ID}
|
||||
result, err := store.GetActiveAuthsByDeviceIDs(ctx, enterprise.ID, deviceIDs)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, result[devices[0].ID])
|
||||
assert.False(t, result[devices[1].ID])
|
||||
assert.False(t, result[devices[2].ID])
|
||||
})
|
||||
|
||||
t.Run("空设备ID列表返回空映射", func(t *testing.T) {
|
||||
result, err := store.GetActiveAuthsByDeviceIDs(ctx, enterprise.ID, []uint{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterpriseDeviceAuthorizationStore_ListDeviceIDsByEnterprise(t *testing.T) {
|
||||
tx := testutils.NewTestTransaction(t)
|
||||
rdb := testutils.GetTestRedis(t)
|
||||
testutils.CleanTestRedisKeys(t, rdb)
|
||||
|
||||
store := NewEnterpriseDeviceAuthorizationStore(tx, rdb)
|
||||
ctx := context.Background()
|
||||
|
||||
prefix := uniqueDeviceAuthTestPrefix()
|
||||
|
||||
enterprise := &model.Enterprise{
|
||||
EnterpriseName: prefix + "_测试企业",
|
||||
EnterpriseCode: prefix,
|
||||
Status: 1,
|
||||
}
|
||||
require.NoError(t, tx.Create(enterprise).Error)
|
||||
|
||||
devices := []*model.Device{
|
||||
{DeviceNo: prefix + "_001", DeviceName: "测试设备1", Status: 2},
|
||||
{DeviceNo: prefix + "_002", DeviceName: "测试设备2", Status: 2},
|
||||
}
|
||||
for _, d := range devices {
|
||||
require.NoError(t, tx.Create(d).Error)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
auths := []*model.EnterpriseDeviceAuthorization{
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[0].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
{EnterpriseID: enterprise.ID, DeviceID: devices[1].ID, AuthorizedBy: 1, AuthorizedAt: now, AuthorizerType: 2},
|
||||
}
|
||||
for _, auth := range auths {
|
||||
require.NoError(t, store.Create(ctx, auth))
|
||||
}
|
||||
|
||||
t.Run("获取企业授权设备ID列表", func(t *testing.T) {
|
||||
result, err := store.ListDeviceIDsByEnterprise(ctx, enterprise.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
assert.Contains(t, result, devices[0].ID)
|
||||
assert.Contains(t, result, devices[1].ID)
|
||||
})
|
||||
|
||||
t.Run("无授权记录返回空列表", func(t *testing.T) {
|
||||
result, err := store.ListDeviceIDsByEnterprise(ctx, 99999)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func ptrUint(v uint) *uint {
|
||||
return &v
|
||||
}
|
||||
Reference in New Issue
Block a user