diff --git a/tests/hurl/Makefile b/tests/hurl/Makefile index e16121d..dd40338 100644 --- a/tests/hurl/Makefile +++ b/tests/hurl/Makefile @@ -1,6 +1,6 @@ SHELL := /bin/bash ENV ?= dev -HURL_OPTS := --variables-file env/$(ENV).env --test +HURL_OPTS := --variables-file env/$(ENV).env --file-root . --test .PHONY: test test-flows test-modules test-negative report clean diff --git a/tests/hurl/flows/card-price-verification-flow.hurl b/tests/hurl/flows/card-price-verification-flow.hurl deleted file mode 100644 index 378f674..0000000 --- a/tests/hurl/flows/card-price-verification-flow.hurl +++ /dev/null @@ -1,381 +0,0 @@ -# ============================================================ -# 测试:卡导入 → 分配 → C 端价格验证 -# 生成时间:2026-03-30 -# 涉及模块:auth, iot_card(import), shop, package_series, package, -# shop_series_grant, shop_package_allocation, client_auth, client_asset -# ============================================================ -# 流程: -# 1. 平台管理员登录 -# 2. 查询运营商列表,获取 carrier_id -# 3. 获取对象存储上传 URL(purpose=iot_import) -# 4. 上传 Excel 文件(IoT 卡测试数据)到预签名 URL -# 5. 提交卡导入任务 -# 6. 轮询导入任务直到完成(status=3) -# 7. 获取一级代理店铺 ID(shop1_id) -# 8. 获取二级代理店铺 ID(shop2_id) -# 9. 获取套餐系列 ID(series_id)和套餐 ID(package_id) -# 10. 分配 ICCID_1 给一级代理(shop1) -# 11. 分配 ICCID_2 给二级代理(shop2) -# 12. 绑定两张卡到套餐系列 -# 13. C 端测试登录(ICCID_1) -# 14. 验证 ICCID_1 的可购套餐 → retail_price = 一级代理零售价(15000) -# 15. C 端测试登录(ICCID_2) -# 16. 验证 ICCID_2 的可购套餐 → retail_price = 二级代理零售价(18000) -# ============================================================ -# 前置条件: -# 1. 已运行 package-resource-full-flow.hurl(创建了 shop1/shop2/series/package) -# 2. 开启 logging.development=true(服务配置) -# 3. 已配置对象存储(S3/MinIO),步骤 3-4 依赖对象存储上传 -# 4. env 文件中已配置 agent1_username/agent2_username -# ============================================================ - - -# ══════════════════════════════════════════════════════════════ -# 第 1 步:平台管理员登录 -# ══════════════════════════════════════════════════════════════ - -POST {{base_url}}/api/auth/login -Content-Type: application/json -{ - "username": "{{admin_username}}", - "password": "{{admin_password}}", - "device": "web" -} -HTTP 200 -[Captures] -admin_token: jsonpath "$.data.access_token" -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.access_token" isString -jsonpath "$.data.user.user_type" == 1 - - -# ══════════════════════════════════════════════════════════════ -# 第 2 步:查询运营商列表,获取第一个可用 carrier_id -# ══════════════════════════════════════════════════════════════ - -GET {{base_url}}/api/admin/carriers?page=1&page_size=1&status=1 -Authorization: Bearer {{admin_token}} -HTTP 200 -[Captures] -carrier_id: jsonpath "$.data.items[0].id" -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.items" count >= 1 -jsonpath "$.data.items[0].id" isInteger - - -# ══════════════════════════════════════════════════════════════ -# 第 3 步:获取对象存储上传 URL(IoT 卡导入 Excel) -# ══════════════════════════════════════════════════════════════ - -POST {{base_url}}/api/admin/storage/upload-url -Authorization: Bearer {{admin_token}} -Content-Type: application/json -{ - "file_name": "hurl-test-cards.xlsx", - "content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "purpose": "iot_import" -} -HTTP 200 -[Captures] -upload_url: jsonpath "$.data.upload_url" -file_key: jsonpath "$.data.file_key" -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.upload_url" isString -jsonpath "$.data.file_key" isString -jsonpath "$.data.expires_in" isInteger - - -# ══════════════════════════════════════════════════════════════ -# 第 4 步:上传 Excel 文件到预签名 URL -# 文件路径相对于 Hurl 运行目录(tests/hurl/) -# ══════════════════════════════════════════════════════════════ - -PUT {{upload_url}} -Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet -file,testdata/hurl-test-cards.xlsx; -HTTP 200 - - -# ══════════════════════════════════════════════════════════════ -# 第 5 步:提交 IoT 卡导入任务 -# 导入 Excel 包含 ICCID_1(HURL-TEST-CARD-001)和 ICCID_2(HURL-TEST-CARD-002) -# ══════════════════════════════════════════════════════════════ - -POST {{base_url}}/api/admin/iot-cards/import -Authorization: Bearer {{admin_token}} -Content-Type: application/json -{ - "carrier_id": {{carrier_id}}, - "batch_no": "HURL-PRICE-TEST", - "file_key": "{{file_key}}" -} -HTTP 200 -[Captures] -import_task_id: jsonpath "$.data.task_id" -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.task_id" isInteger -jsonpath "$.data.task_no" isString - - -# ══════════════════════════════════════════════════════════════ -# 第 6 步:轮询导入任务,等待完成(status=3 表示已完成) -# 注:Hurl 不支持循环轮询,这里做 3 次间隔查询覆盖大多数场景 -# 如需更长等待,可手动重跑此步骤 -# ══════════════════════════════════════════════════════════════ - -# 第 1 次查询(立即) -GET {{base_url}}/api/admin/iot-cards/import-tasks/{{import_task_id}} -Authorization: Bearer {{admin_token}} -HTTP 200 -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.id" == {{import_task_id}} -jsonpath "$.data.status" isInteger - -# 等待 2 秒后第 2 次查询(Hurl 通过 delay 选项实现) -GET {{base_url}}/api/admin/iot-cards/import-tasks/{{import_task_id}} -Authorization: Bearer {{admin_token}} -[Options] -delay: 2000 -HTTP 200 -[Asserts] -jsonpath "$.code" == 0 - -# 等待 2 秒后第 3 次查询,断言任务成功完成 -GET {{base_url}}/api/admin/iot-cards/import-tasks/{{import_task_id}} -Authorization: Bearer {{admin_token}} -[Options] -delay: 2000 -HTTP 200 -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.status" == 3 -jsonpath "$.data.success_count" >= 1 - - -# ══════════════════════════════════════════════════════════════ -# 第 7 步:获取一级代理店铺 ID(shop1_id) -# 依赖 package-resource-full-flow.hurl 已创建 agent1_username 账号 -# ══════════════════════════════════════════════════════════════ - -GET {{base_url}}/api/admin/accounts?username={{agent1_username}}&page=1&page_size=1 -Authorization: Bearer {{admin_token}} -HTTP 200 -[Captures] -shop1_id: jsonpath "$.data.items[0].shop_id" -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.items" count >= 1 -jsonpath "$.data.items[0].username" == "{{agent1_username}}" -jsonpath "$.data.items[0].user_type" == 3 -jsonpath "$.data.items[0].shop_id" isInteger - - -# ══════════════════════════════════════════════════════════════ -# 第 8 步:获取二级代理店铺 ID(shop2_id) -# ══════════════════════════════════════════════════════════════ - -GET {{base_url}}/api/admin/accounts?username={{agent2_username}}&page=1&page_size=1 -Authorization: Bearer {{admin_token}} -HTTP 200 -[Captures] -shop2_id: jsonpath "$.data.items[0].shop_id" -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.items" count >= 1 -jsonpath "$.data.items[0].username" == "{{agent2_username}}" -jsonpath "$.data.items[0].user_type" == 3 -jsonpath "$.data.items[0].shop_id" isInteger - - -# ══════════════════════════════════════════════════════════════ -# 第 9 步:获取套餐系列 ID 和套餐 ID -# 依赖 package-resource-full-flow.hurl 创建的 "Hurl测试系列" -# ══════════════════════════════════════════════════════════════ - -# 获取套餐系列(按名称模糊匹配,取最新创建的) -GET {{base_url}}/api/admin/package-series?series_name=Hurl%E6%B5%8B%E8%AF%95%E7%B3%BB%E5%88%97&page=1&page_size=1 -Authorization: Bearer {{admin_token}} -HTTP 200 -[Captures] -series_id: jsonpath "$.data.items[0].id" -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.items" count >= 1 -jsonpath "$.data.items[0].series_name" == "Hurl测试系列" - -# 获取该系列下的套餐(取最新的) -GET {{base_url}}/api/admin/packages?series_id={{series_id}}&page=1&page_size=1 -Authorization: Bearer {{admin_token}} -HTTP 200 -[Captures] -package_id: jsonpath "$.data.items[0].id" -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.items" count >= 1 -jsonpath "$.data.items[0].series_id" == {{series_id}} - - -# ══════════════════════════════════════════════════════════════ -# 第 10 步:分配 ICCID_1(8986001234560001)给一级代理(shop1) -# 幂等处理:HTTP * 兼容已分配场景 -# ══════════════════════════════════════════════════════════════ - -POST {{base_url}}/api/admin/iot-cards/standalone/allocate -Authorization: Bearer {{admin_token}} -Content-Type: application/json -{ - "to_shop_id": {{shop1_id}}, - "selection_type": "list", - "iccids": ["{{test_iccid_1}}"], - "remark": "Hurl 价格验证测试 - 一级代理" -} -HTTP * -[Asserts] -jsonpath "$.code" isInteger - - -# ══════════════════════════════════════════════════════════════ -# 第 11 步:分配 ICCID_2(8986001234560002)给二级代理(shop2) -# ══════════════════════════════════════════════════════════════ - -POST {{base_url}}/api/admin/iot-cards/standalone/allocate -Authorization: Bearer {{admin_token}} -Content-Type: application/json -{ - "to_shop_id": {{shop2_id}}, - "selection_type": "list", - "iccids": ["{{test_iccid_2}}"], - "remark": "Hurl 价格验证测试 - 二级代理" -} -HTTP * -[Asserts] -jsonpath "$.code" isInteger - - -# ══════════════════════════════════════════════════════════════ -# 第 12 步:绑定两张卡到套餐系列(series_binding) -# 卡需要绑定套餐系列才能在 C 端看到可购套餐 -# ══════════════════════════════════════════════════════════════ - -PATCH {{base_url}}/api/admin/iot-cards/series-binding -Authorization: Bearer {{admin_token}} -Content-Type: application/json -{ - "iccids": ["{{test_iccid_1}}", "{{test_iccid_2}}"], - "series_id": {{series_id}} -} -HTTP 200 -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.success_count" >= 1 - - -# ══════════════════════════════════════════════════════════════ -# 第 13 步:C 端测试登录(ICCID_1,归属一级代理) -# 调用开发环境专用接口,传入 ICCID 自动绑定测试客户并签发 JWT -# ══════════════════════════════════════════════════════════════ - -POST {{base_url}}/api/c/v1/auth/dev-login -Content-Type: application/json -{ - "identifier": "{{test_iccid_1}}" -} -HTTP 200 -[Captures] -customer1_token: jsonpath "$.data.token" -customer1_id: jsonpath "$.data.customer_id" -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.token" isString -jsonpath "$.data.customer_id" isInteger - - -# ══════════════════════════════════════════════════════════════ -# 第 14 步:验证 ICCID_1 的可购套餐价格 = 一级代理零售价(15000 分) -# retail_price 来自一级代理在 shop_package_allocation 中设置的零售价 -# 前置条件:package-resource-full-flow.hurl 中一级代理已设置零售价 15000 -# ══════════════════════════════════════════════════════════════ - -GET {{base_url}}/api/c/v1/asset/packages?identifier={{test_iccid_1}} -Authorization: Bearer {{customer1_token}} -HTTP 200 -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.packages" isList -jsonpath "$.data.packages" count >= 1 -# 关键断言:一级代理归属的卡,C 端看到的是一级代理设置的零售价 15000 -jsonpath "$.data.packages[0].retail_price" == 15000 -jsonpath "$.data.packages[0].package_id" == {{package_id}} - - -# ══════════════════════════════════════════════════════════════ -# 第 15 步:C 端测试登录(ICCID_2,归属二级代理) -# ══════════════════════════════════════════════════════════════ - -POST {{base_url}}/api/c/v1/auth/dev-login -Content-Type: application/json -{ - "identifier": "{{test_iccid_2}}" -} -HTTP 200 -[Captures] -customer2_token: jsonpath "$.data.token" -customer2_id: jsonpath "$.data.customer_id" -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.token" isString -jsonpath "$.data.customer_id" isInteger - - -# ══════════════════════════════════════════════════════════════ -# 第 16 步:验证 ICCID_2 的可购套餐价格 = 二级代理零售价(18000 分) -# ══════════════════════════════════════════════════════════════ - -GET {{base_url}}/api/c/v1/asset/packages?identifier={{test_iccid_2}} -Authorization: Bearer {{customer2_token}} -HTTP 200 -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.packages" isList -jsonpath "$.data.packages" count >= 1 -# 关键断言:二级代理归属的卡,C 端看到的是二级代理设置的零售价 18000(与一级代理不同!) -jsonpath "$.data.packages[0].retail_price" == 18000 -jsonpath "$.data.packages[0].package_id" == {{package_id}} - - -# ══════════════════════════════════════════════════════════════ -# 第 17 步:用两个 token 访问对方的卡(越权验证) -# customer1 不应能查询 ICCID_2 的套餐(未绑定该资产) -# ══════════════════════════════════════════════════════════════ - -GET {{base_url}}/api/c/v1/asset/packages?identifier={{test_iccid_2}} -Authorization: Bearer {{customer1_token}} -HTTP 403 -[Asserts] -jsonpath "$.code" == 1005 - - -# ══════════════════════════════════════════════════════════════ -# 第 18 步:验证同一套餐两个代理视角价格确实不同(一致性检查) -# ══════════════════════════════════════════════════════════════ - -GET {{base_url}}/api/c/v1/asset/info?identifier={{test_iccid_1}} -Authorization: Bearer {{customer1_token}} -HTTP 200 -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.asset_type" == "card" -jsonpath "$.data.iccid" == "{{test_iccid_1}}" - -GET {{base_url}}/api/c/v1/asset/info?identifier={{test_iccid_2}} -Authorization: Bearer {{customer2_token}} -HTTP 200 -[Asserts] -jsonpath "$.code" == 0 -jsonpath "$.data.asset_type" == "card" -jsonpath "$.data.iccid" == "{{test_iccid_2}}" diff --git a/tests/hurl/testdata/hurl-test-cards.xlsx b/tests/hurl/testdata/hurl-test-cards.xlsx deleted file mode 100644 index d62e5e2..0000000 Binary files a/tests/hurl/testdata/hurl-test-cards.xlsx and /dev/null differ