feat(iot-card-import): 为导入任务接口添加平台用户权限控制
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m10s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m10s
- 在 Import/List/GetByID 接口添加用户类型校验 - 仅超级管理员和平台用户可访问 - 同步更新 OpenAPI 路由描述 - 补充集成测试覆盖权限拒绝场景
This commit is contained in:
@@ -7,7 +7,9 @@ import (
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
iotCardImportService "github.com/break/junhong_cmp_fiber/internal/service/iot_card_import"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
)
|
||||
|
||||
@@ -22,6 +24,11 @@ func NewIotCardImportHandler(service *iotCardImportService.Service) *IotCardImpo
|
||||
}
|
||||
|
||||
func (h *IotCardImportHandler) Import(c *fiber.Ctx) error {
|
||||
userType := middleware.GetUserTypeFromContext(c.UserContext())
|
||||
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
|
||||
return errors.New(errors.CodeForbidden, "仅平台用户可导入IoT卡")
|
||||
}
|
||||
|
||||
var req dto.ImportIotCardRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
@@ -40,6 +47,11 @@ func (h *IotCardImportHandler) Import(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
func (h *IotCardImportHandler) List(c *fiber.Ctx) error {
|
||||
userType := middleware.GetUserTypeFromContext(c.UserContext())
|
||||
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
|
||||
return errors.New(errors.CodeForbidden, "仅平台用户可查看导入任务")
|
||||
}
|
||||
|
||||
var req dto.ListImportTaskRequest
|
||||
if err := c.QueryParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
@@ -54,6 +66,11 @@ func (h *IotCardImportHandler) List(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
func (h *IotCardImportHandler) GetByID(c *fiber.Ctx) error {
|
||||
userType := middleware.GetUserTypeFromContext(c.UserContext())
|
||||
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
|
||||
return errors.New(errors.CodeForbidden, "仅平台用户可查看导入任务详情")
|
||||
}
|
||||
|
||||
idStr := c.Params("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
|
||||
@@ -30,7 +30,9 @@ func registerIotCardRoutes(router fiber.Router, handler *admin.IotCardHandler, i
|
||||
|
||||
Register(iotCards, doc, groupPath, "POST", "/import", importHandler.Import, RouteSpec{
|
||||
Summary: "批量导入IoT卡(ICCID+MSISDN)",
|
||||
Description: `## ⚠️ 接口变更说明(BREAKING CHANGE)
|
||||
Description: `仅平台用户可操作。
|
||||
|
||||
## ⚠️ 接口变更说明(BREAKING CHANGE)
|
||||
|
||||
本接口已从 ` + "`multipart/form-data`" + ` 改为 ` + "`application/json`" + `。
|
||||
文件格式从 CSV 升级为 Excel (.xlsx),解决长数字被转为科学记数法的问题。
|
||||
@@ -64,19 +66,21 @@ func registerIotCardRoutes(router fiber.Router, handler *admin.IotCardHandler, i
|
||||
})
|
||||
|
||||
Register(iotCards, doc, groupPath, "GET", "/import-tasks", importHandler.List, RouteSpec{
|
||||
Summary: "导入任务列表",
|
||||
Tags: []string{"IoT卡管理"},
|
||||
Input: new(dto.ListImportTaskRequest),
|
||||
Output: new(dto.ListImportTaskResponse),
|
||||
Auth: true,
|
||||
Summary: "导入任务列表",
|
||||
Description: "仅平台用户可操作。",
|
||||
Tags: []string{"IoT卡管理"},
|
||||
Input: new(dto.ListImportTaskRequest),
|
||||
Output: new(dto.ListImportTaskResponse),
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(iotCards, doc, groupPath, "GET", "/import-tasks/:id", importHandler.GetByID, RouteSpec{
|
||||
Summary: "导入任务详情",
|
||||
Tags: []string{"IoT卡管理"},
|
||||
Input: new(dto.GetImportTaskRequest),
|
||||
Output: new(dto.ImportTaskDetailResponse),
|
||||
Auth: true,
|
||||
Summary: "导入任务详情",
|
||||
Description: "仅平台用户可操作。",
|
||||
Tags: []string{"IoT卡管理"},
|
||||
Input: new(dto.GetImportTaskRequest),
|
||||
Output: new(dto.ImportTaskDetailResponse),
|
||||
Auth: true,
|
||||
})
|
||||
|
||||
Register(iotCards, doc, groupPath, "POST", "/standalone/allocate", handler.AllocateCards, RouteSpec{
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-02
|
||||
@@ -0,0 +1,54 @@
|
||||
## Context
|
||||
|
||||
当前后台接口中,设备导入任务相关接口在 Handler 层做了“仅平台用户/超级管理员可访问”的用户类型校验(基于 `middleware.GetUserTypeFromContext` + `constants.UserTypeSuperAdmin/UserTypePlatform`)。
|
||||
|
||||
IoT 卡导入任务相关接口(提交导入、任务列表、任务详情)未做同等校验,导致权限边界与设备导入不一致。
|
||||
|
||||
本变更目标是将 IoT 卡导入任务相关接口的访问权限收敛为与设备导入一致:仅超级管理员与平台用户可访问。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 对以下 3 个接口增加用户类型校验:仅允许超级管理员与平台用户访问。
|
||||
- 返回行为与现有模式一致:非允许用户类型直接返回 403(`errors.CodeForbidden`),错误消息使用中文。
|
||||
- OpenAPI 路由描述与实际权限一致。
|
||||
- 补齐/新增集成测试,覆盖非平台用户访问应被拒绝。
|
||||
|
||||
**Non-Goals:**
|
||||
- 不调整导入任务的数据模型与存储逻辑。
|
||||
- 不引入新的 RBAC 权限点或权限表配置(保持“用户类型硬校验”的现有风格)。
|
||||
- 不改动其他 IoT 卡管理接口的权限策略。
|
||||
|
||||
## Decisions
|
||||
|
||||
1) **在 Handler 层做用户类型校验(与设备导入对齐)**
|
||||
- 方案:在 `internal/handler/admin/iot_card_import.go` 的 `Import` / `List` / `GetByID` 入口处增加与 `internal/handler/admin/device_import.go` 同风格的校验。
|
||||
- 理由:
|
||||
- 与现有“设备导入”实现一致,减少认知负担。
|
||||
- 校验发生在参数解析与业务调用前,能最早拒绝无权限请求。
|
||||
- 备选方案:
|
||||
- 路由层中间件:更集中,但需要在路由注册处引入新中间件组合,且当前设备导入并未采用该方式。
|
||||
- Service 层校验:更“业务化”,但会改变当前导入模块的职责边界(设备导入限制目前在 Handler 层)。
|
||||
|
||||
2) **错误码与错误消息遵循现有约定**
|
||||
- 方案:使用 `errors.New(errors.CodeForbidden, "仅平台用户可...")` 风格,保持与设备导入一致。
|
||||
- 理由:该模块已有同类错误消息,避免引入新的错误码或文案风格。
|
||||
|
||||
3) **同步更新路由描述(OpenAPI)**
|
||||
- 方案:在 `internal/routes/iot_card.go` 对相关接口补充 `Description: "仅平台用户可操作。"`(与设备导入相同语义)。
|
||||
- 理由:避免文档与实际行为不一致,减少前后端联调成本。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[风险] 规格与实现不一致** → **缓解**:在本变更的 specs(delta spec)中明确将权限收敛为平台用户/超管,并在实现阶段对齐。
|
||||
- **[风险] 测试缺口导致回归** → **缓解**:新增集成测试覆盖非平台用户访问 403,并尽量复用现有测试基建(集成测试 env)。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
- 该变更为权限收敛,无数据迁移。
|
||||
- 发布后影响:非平台用户/超管将无法调用 IoT 卡导入与导入任务查询接口。
|
||||
- 回滚策略:如业务需要恢复原可见性,可回滚本变更提交(或通过后续变更重新放开)。
|
||||
|
||||
## Open Questions
|
||||
|
||||
- 是否需要将错误消息文案统一为同一句(如“仅平台用户可操作”),还是分别保留更具体的文案(导入/列表/详情)?(实现阶段可按现有 device_import 文案对齐)
|
||||
@@ -0,0 +1,37 @@
|
||||
# 限制 IoT 卡导入任务接口仅平台用户可访问
|
||||
|
||||
Feature ID: feature-iot-card-import-task-platform-only
|
||||
|
||||
## Why
|
||||
|
||||
目前设备导入任务列表/详情已限制为仅平台用户/超级管理员可访问,但 IoT 卡导入任务列表/详情/提交导入未做同等限制,存在权限边界不一致与潜在越权风险。
|
||||
|
||||
需要将 IoT 卡导入任务相关接口的访问权限收敛为与设备导入一致,避免非平台账号通过后台接口获取或操作导入任务。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **权限收敛**:IoT 卡导入相关接口仅允许超级管理员与平台用户访问;其他用户类型访问返回 403。
|
||||
- **接口范围**:
|
||||
- `POST /api/admin/iot-cards/import`
|
||||
- `GET /api/admin/iot-cards/import-tasks`
|
||||
- `GET /api/admin/iot-cards/import-tasks/:id`
|
||||
- **文档一致性**:补充路由描述,确保 OpenAPI 文档与实际权限一致。
|
||||
- **测试补齐**:新增/补充集成测试覆盖非平台用户访问上述接口应被拒绝。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
<!-- 无新增能力,本次为权限规则收敛 -->
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `iot-card-import-task`: 将“导入任务列表/详情/提交导入”的访问权限从“按 shop_id 数据权限过滤”调整为“仅平台用户/超级管理员可访问”。
|
||||
|
||||
## Impact
|
||||
|
||||
- 影响 API:`/api/admin/iot-cards/import`、`/api/admin/iot-cards/import-tasks`、`/api/admin/iot-cards/import-tasks/:id`
|
||||
- 预期涉及代码:
|
||||
- Handler:`internal/handler/admin/iot_card_import.go`
|
||||
- Routes/OpenAPI:`internal/routes/iot_card.go`
|
||||
- 集成测试:`tests/integration/iot_card_test.go`(或新增对应测试文件)
|
||||
@@ -0,0 +1,76 @@
|
||||
# iot-card-import-task Specification (Delta)
|
||||
|
||||
本变更用于收敛 IoT 卡导入任务相关接口的访问权限:仅超级管理员/平台用户可访问。
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 导入任务创建权限控制
|
||||
|
||||
系统 SHALL 仅允许超级管理员与平台用户创建 IoT 卡导入任务。
|
||||
|
||||
#### Scenario: 平台用户创建导入任务
|
||||
- **WHEN** 平台用户请求创建导入任务
|
||||
- **THEN** 系统创建导入任务并返回任务信息
|
||||
|
||||
#### Scenario: 非平台用户创建导入任务被拒绝
|
||||
- **WHEN** 非平台用户(代理账号/企业账号等)请求创建导入任务
|
||||
- **THEN** 系统返回 403(Forbidden),并返回统一错误码 `CodeForbidden`
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 导入任务列表查询
|
||||
|
||||
系统 SHALL 支持查询导入任务列表,用于管理和监控导入任务。
|
||||
|
||||
**查询条件**:
|
||||
- 任务状态(status): 可选,1-待处理 2-处理中 3-已完成 4-失败
|
||||
- 运营商 ID(carrier_id): 可选
|
||||
- 批次号(batch_no): 可选,模糊匹配
|
||||
- 创建时间范围: 可选
|
||||
|
||||
**分页**:
|
||||
- 默认每页 20 条,最大每页 100 条
|
||||
- 默认按创建时间倒序排列
|
||||
|
||||
**权限**:
|
||||
- 仅超级管理员/平台用户可查询导入任务列表
|
||||
|
||||
#### Scenario: 查询所有导入任务
|
||||
|
||||
- **WHEN** 平台管理员查询导入任务列表
|
||||
- **THEN** 系统返回导入任务列表,包含任务编号、状态、运营商、总数、成功数、跳过数、失败数、创建时间
|
||||
|
||||
#### Scenario: 按状态筛选导入任务
|
||||
|
||||
- **WHEN** 平台管理员查询状态为 2(处理中) 的导入任务
|
||||
- **THEN** 系统返回所有正在处理的导入任务列表
|
||||
|
||||
#### Scenario: 非平台用户查询导入任务列表被拒绝
|
||||
|
||||
- **WHEN** 非平台用户(代理账号/企业账号等)查询导入任务列表
|
||||
- **THEN** 系统返回 403(Forbidden),并返回统一错误码 `CodeForbidden`
|
||||
|
||||
### Requirement: 导入任务详情查询
|
||||
|
||||
系统 SHALL 支持查询单个导入任务的详细信息,包括跳过/失败记录详情。
|
||||
|
||||
**详情信息**:
|
||||
- 任务基本信息: 任务编号、状态、运营商、批次号、文件名
|
||||
- 进度统计: 总数、成功数、跳过数、失败数
|
||||
- 时间信息: 创建时间、开始时间、完成时间
|
||||
- 跳过记录详情: 行号、ICCID、原因
|
||||
- 失败记录详情: 行号、ICCID、原因
|
||||
- 错误信息: 任务级错误(如有)
|
||||
|
||||
**权限**:
|
||||
- 仅超级管理员/平台用户可查询导入任务详情
|
||||
|
||||
#### Scenario: 查询导入任务详情
|
||||
|
||||
- **WHEN** 平台管理员查询导入任务(ID 为 1)的详情
|
||||
- **THEN** 系统返回任务完整信息,包括跳过和失败记录的详细列表
|
||||
|
||||
#### Scenario: 非平台用户查询导入任务详情被拒绝
|
||||
|
||||
- **WHEN** 非平台用户(代理账号/企业账号等)查询导入任务详情
|
||||
- **THEN** 系统返回 403(Forbidden),并返回统一错误码 `CodeForbidden`
|
||||
@@ -0,0 +1,18 @@
|
||||
## 1. 权限校验收敛(IoT 卡导入任务)
|
||||
|
||||
- [x] 1.1 在 `internal/handler/admin/iot_card_import.go` 的 `Import`/`List`/`GetByID` 增加用户类型校验:仅 `UserTypeSuperAdmin`/`UserTypePlatform` 允许访问,其余返回 `CodeForbidden`
|
||||
- [x] 1.2 校验错误消息文案与设备导入保持同风格(中文、明确动作),并确认不会泄露底层错误细节
|
||||
|
||||
## 2. 路由描述与文档一致性
|
||||
|
||||
- [x] 2.1 在 `internal/routes/iot_card.go` 为 `POST /iot-cards/import`、`GET /iot-cards/import-tasks`、`GET /iot-cards/import-tasks/:id` 补充 `Description: "仅平台用户可操作。"`
|
||||
|
||||
## 3. 测试补齐
|
||||
|
||||
- [x] 3.1 在集成测试中新增用例:非平台用户(至少覆盖代理账号)访问上述 3 个接口应返回 403(Forbidden)
|
||||
- [x] 3.2 运行并通过相关测试:`go test -v ./tests/integration/...`(如存在既有失败,需明确区分是否由本变更引入)
|
||||
|
||||
## 4. 本地验证
|
||||
|
||||
- [x] 4.1 对修改过的 Go 文件执行 `lsp_diagnostics` 确保无新增错误/告警
|
||||
- [x] 4.2 运行 `go test ./...` 做一次全量回归验证(如耗时可接受)
|
||||
@@ -81,6 +81,20 @@ TBD - created by archiving change iot-card-standalone-management. Update Purpose
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入任务创建权限控制
|
||||
|
||||
系统 SHALL 仅允许超级管理员与平台用户创建 IoT 卡导入任务。
|
||||
|
||||
#### Scenario: 平台用户创建导入任务
|
||||
- **WHEN** 平台用户请求创建导入任务
|
||||
- **THEN** 系统创建导入任务并返回任务信息
|
||||
|
||||
#### Scenario: 非平台用户创建导入任务被拒绝
|
||||
- **WHEN** 非平台用户(代理账号/企业账号等)请求创建导入任务
|
||||
- **THEN** 系统返回 403(Forbidden),并返回统一错误码 `CodeForbidden`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入任务列表查询
|
||||
|
||||
系统 SHALL 支持查询导入任务列表,用于管理和监控导入任务。
|
||||
@@ -95,20 +109,24 @@ TBD - created by archiving change iot-card-standalone-management. Update Purpose
|
||||
- 默认每页 20 条,最大每页 100 条
|
||||
- 默认按创建时间倒序排列
|
||||
|
||||
**数据权限**:
|
||||
- 基于 shop_id 自动应用数据权限过滤
|
||||
- 代理只能看到自己店铺及下级店铺发起的导入任务
|
||||
**权限**:
|
||||
- 仅超级管理员/平台用户可查询导入任务列表
|
||||
|
||||
#### Scenario: 查询所有导入任务
|
||||
|
||||
- **WHEN** 管理员查询导入任务列表
|
||||
- **WHEN** 平台管理员查询导入任务列表
|
||||
- **THEN** 系统返回导入任务列表,包含任务编号、状态、运营商、总数、成功数、跳过数、失败数、创建时间
|
||||
|
||||
#### Scenario: 按状态筛选导入任务
|
||||
|
||||
- **WHEN** 管理员查询状态为 2(处理中) 的导入任务
|
||||
- **WHEN** 平台管理员查询状态为 2(处理中) 的导入任务
|
||||
- **THEN** 系统返回所有正在处理的导入任务列表
|
||||
|
||||
#### Scenario: 非平台用户查询导入任务列表被拒绝
|
||||
|
||||
- **WHEN** 非平台用户(代理账号/企业账号等)查询导入任务列表
|
||||
- **THEN** 系统返回 403(Forbidden),并返回统一错误码 `CodeForbidden`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入任务详情查询
|
||||
@@ -123,11 +141,19 @@ TBD - created by archiving change iot-card-standalone-management. Update Purpose
|
||||
- 失败记录详情: 行号、ICCID、原因
|
||||
- 错误信息: 任务级错误(如有)
|
||||
|
||||
**权限**:
|
||||
- 仅超级管理员/平台用户可查询导入任务详情
|
||||
|
||||
#### Scenario: 查询导入任务详情
|
||||
|
||||
- **WHEN** 管理员查询导入任务(ID 为 1)的详情
|
||||
- **WHEN** 平台管理员查询导入任务(ID 为 1)的详情
|
||||
- **THEN** 系统返回任务完整信息,包括跳过和失败记录的详细列表
|
||||
|
||||
#### Scenario: 非平台用户查询导入任务详情被拒绝
|
||||
|
||||
- **WHEN** 非平台用户(代理账号/企业账号等)查询导入任务详情
|
||||
- **THEN** 系统返回 403(Forbidden),并返回统一错误码 `CodeForbidden`
|
||||
|
||||
#### Scenario: 查询导入任务的跳过记录
|
||||
|
||||
- **WHEN** 管理员查询导入任务(ID 为 1)的跳过记录
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
pkgerrors "github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
pkggorm "github.com/break/junhong_cmp_fiber/pkg/gorm"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils/integ"
|
||||
@@ -184,6 +185,72 @@ func TestIotCard_ImportTaskList(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCard_ImportTask_PlatformOnly(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
shop := env.CreateTestShop("权限测试店铺", 1, nil)
|
||||
agentAccount := env.CreateTestAccount(fmt.Sprintf("agent_perm_%d", time.Now().UnixNano()), "password123", constants.UserTypeAgent, &shop.ID, nil)
|
||||
|
||||
task := &model.IotCardImportTask{
|
||||
TaskNo: fmt.Sprintf("TEST_PERM_%d", time.Now().UnixNano()),
|
||||
Status: model.ImportTaskStatusCompleted,
|
||||
CarrierID: 1,
|
||||
CarrierType: "CMCC",
|
||||
CarrierName: "中国移动",
|
||||
TotalCount: 1,
|
||||
}
|
||||
require.NoError(t, env.TX.Create(task).Error)
|
||||
|
||||
t.Run("代理账号提交导入任务应返回403", func(t *testing.T) {
|
||||
body, _ := json.Marshal(map[string]interface{}{
|
||||
"carrier_id": 1,
|
||||
"batch_no": "TEST_BATCH_PERM",
|
||||
"file_key": "imports/test.xlsx",
|
||||
})
|
||||
|
||||
resp, err := env.AsUser(agentAccount).Request("POST", "/api/admin/iot-cards/import", body)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 403, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pkgerrors.CodeForbidden, result.Code)
|
||||
assert.Contains(t, result.Message, "仅平台用户")
|
||||
})
|
||||
|
||||
t.Run("代理账号访问导入任务列表应返回403", func(t *testing.T) {
|
||||
resp, err := env.AsUser(agentAccount).Request("GET", "/api/admin/iot-cards/import-tasks?page=1&page_size=20", nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 403, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pkgerrors.CodeForbidden, result.Code)
|
||||
assert.Contains(t, result.Message, "仅平台用户")
|
||||
})
|
||||
|
||||
t.Run("代理账号访问导入任务详情应返回403", func(t *testing.T) {
|
||||
url := fmt.Sprintf("/api/admin/iot-cards/import-tasks/%d", task.ID)
|
||||
resp, err := env.AsUser(agentAccount).Request("GET", url, nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 403, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pkgerrors.CodeForbidden, result.Code)
|
||||
assert.Contains(t, result.Message, "仅平台用户")
|
||||
})
|
||||
}
|
||||
|
||||
func TestIotCard_ImportE2E(t *testing.T) {
|
||||
t.Skip("E2E测试:需要 Worker 服务运行处理异步导入任务")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user