From 00cae60fe1483d35c307c96ecd1735c87ad0fb6d Mon Sep 17 00:00:00 2001 From: huang Date: Tue, 31 Mar 2026 15:26:09 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E5=8D=A1=E4=BB=B7?= =?UTF-8?q?=E6=A0=BC=E9=AA=8C=E8=AF=81=20hurl=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/hurl/Makefile | 2 +- .../flows/card-price-verification-flow.hurl | 381 ------------------ tests/hurl/testdata/hurl-test-cards.xlsx | Bin 5426 -> 0 bytes 3 files changed, 1 insertion(+), 382 deletions(-) delete mode 100644 tests/hurl/flows/card-price-verification-flow.hurl delete mode 100644 tests/hurl/testdata/hurl-test-cards.xlsx 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 d62e5e22c82915a6da2066511bf127d28cabf832..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5426 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