Files
baiyue-firstleg/scripts/order-warehouse-real-api-regression.ps1
2026-07-30 18:01:36 +08:00

231 lines
18 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
param(
[string]$GatewayBaseUrl = 'http://127.0.0.1:19002',
[string]$OrderBaseUrl = 'http://127.0.0.1:19204',
[string]$WarehouseBaseUrl = 'http://127.0.0.1:19205',
[string]$MerchantUsername = 'merchant_admin',
[string]$PlatformUsername = 'platform_admin',
[string]$AdminPassword = 'ChangeMe123!',
[string]$MysqlPassword = '123456',
[string]$OutputPath = ''
)
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
if ([string]::IsNullOrWhiteSpace($OutputPath)) {
$OutputPath = Join-Path $root ("reports\订单仓库往来单位真实API回归-{0}.json" -f (Get-Date -Format 'yyyyMMdd-HHmmss'))
}
$mysql = 'D:\baiyue-runtime\mysql\server\bin\mysql.exe'
$results = [System.Collections.Generic.List[object]]::new()
$startedAt = Get-Date
$testData = [ordered]@{}
$previousMysqlPassword = $env:MYSQL_PWD
$env:MYSQL_PWD = $MysqlPassword
function Add-Result([string]$Name,[bool]$Passed,[string]$Evidence) {
$results.Add([pscustomobject]@{ name=$Name; passed=$Passed; evidence=$Evidence })
Write-Host "[$(if($Passed){'通过'}else{'失败'})] $Name - $Evidence"
if (-not $Passed) { throw "$Name:$Evidence" }
}
function Invoke-Api([string]$Method,[string]$Url,[object]$Body=$null,[string]$Token=$null) {
$headers=@{}; if($Token){$headers.Authorization="Bearer $Token"}
$args=@{Method=$Method;Uri=$Url;Headers=$headers;TimeoutSec=15;UseBasicParsing=$true}
if($null -ne $Body){
$args.ContentType='application/json; charset=utf-8'
$jsonText=$Body|ConvertTo-Json -Depth 15 -Compress
$args.Body=[Text.Encoding]::UTF8.GetBytes($jsonText)
}
try {
$response=Invoke-WebRequest @args
$content=$response.Content
if($null -ne $response.RawContentStream){try{$content=[Text.Encoding]::UTF8.GetString($response.RawContentStream.ToArray())}catch{}}
$json=$null; if($content){try{$json=$content|ConvertFrom-Json}catch{}}
return [pscustomobject]@{status=[int]$response.StatusCode;json=$json;body=$content}
} catch {
$status=0; if($_.Exception.Response){$status=[int]$_.Exception.Response.StatusCode}
$content=$_.ErrorDetails.Message; $json=$null; if($content){try{$json=$content|ConvertFrom-Json}catch{}}
if($null -eq $json -and $null -ne $_.Exception.Response){
try {
$reader=New-Object IO.StreamReader($_.Exception.Response.GetResponseStream(),[Text.Encoding]::UTF8)
$content=$reader.ReadToEnd();$reader.Dispose()
if($content){$json=$content|ConvertFrom-Json}
} catch {$json=$null}
}
return [pscustomobject]@{status=$status;json=$json;body=$content}
}
}
function Require-Success([string]$Name,$Response) {
$ok=$Response.status -eq 200 -and $Response.json.code -eq '0'
Add-Result $Name $ok "HTTP=$($Response.status),code=$($Response.json.code)"
return $Response.json.data
}
function Require-Http([string]$Name,$Response,[int]$Status,[string]$Code='') {
$ok=$Response.status -eq $Status -and ([string]::IsNullOrWhiteSpace($Code) -or $Response.json.code -eq $Code)
Add-Result $Name $ok "HTTP=$($Response.status),code=$($Response.json.code)"
}
function Require-Check([string]$Name,[bool]$Condition,[string]$Evidence) { Add-Result $Name $Condition $Evidence }
function Invoke-Sql([string]$Database,[string]$Sql) {
# MySQL 会在标准错误流输出命令行密码提示,但成功退出码仍为 0;避免将提示误判为 SQL 失败。
$previousErrorAction=$ErrorActionPreference
$ErrorActionPreference='Continue'
try {
$output=& $mysql '--default-character-set=utf8mb4' '--host=127.0.0.1' '--port=3306' '--user=root' "--database=$Database" "--execute=$Sql" 2>$null
$exitCode=$LASTEXITCODE
} finally {
$ErrorActionPreference=$previousErrorAction
}
if($exitCode -ne 0){throw "SQL执行失败:$output"}
}
function Invoke-SqlScalar([string]$Database,[string]$Sql) {
$previousErrorAction=$ErrorActionPreference
$ErrorActionPreference='Continue'
try {
$output=& $mysql '--batch' '--skip-column-names' '--default-character-set=utf8mb4' '--host=127.0.0.1' '--port=3306' '--user=root' "--database=$Database" "--execute=$Sql" 2>$null
$exitCode=$LASTEXITCODE
} finally {
$ErrorActionPreference=$previousErrorAction
}
if($exitCode -ne 0){throw 'SQL标量查询失败'}
return [string](@($output)[0])
}
function Login([string]$Username) {
return Require-Success "$Username 登录" (Invoke-Api POST "$GatewayBaseUrl/api/auth/login" @{username=$Username;password=$AdminPassword})
}
$activeWarehouse=190000000000000001L
$inactiveWarehouse=190000000000000002L
$crossWarehouse=190000000000000003L
$activePartner=191000000000000001L
$inactivePartner=191000000000000002L
$crossPartner=191000000000000003L
$orderId=$null
$fatal=$null
$permissionPreexisting=$false
try {
if(-not(Test-Path -LiteralPath $mysql)){throw "找不到 MySQL 客户端:$mysql"}
foreach($url in @("$GatewayBaseUrl/actuator/health","$OrderBaseUrl/actuator/health","$WarehouseBaseUrl/actuator/health")) {
$health=Invoke-Api GET $url
Require-Check "服务健康:$url" ($health.status -eq 200) "HTTP=$($health.status)"
}
# 回归数据只使用固定测试 ID,重复执行会恢复到启用/停用基线。
$permissionPreexisting=(Invoke-SqlScalar baiyue_iam "SELECT COUNT(*) FROM iam_permission WHERE id=5102018;") -eq '1'
Invoke-Sql baiyue_iam @"
INSERT INTO iam_permission(id,tenant_id,app_code,permission_type,parent_id,permission_code,permission_name,sort_no,status,deleted)
VALUES(5102018,0,'PLATFORM','BUTTON',5102010,'platform:order:fulfillment','登记履约快递',80,'ACTIVE',0)
ON DUPLICATE KEY UPDATE permission_name=VALUES(permission_name),status='ACTIVE',deleted=0;
INSERT INTO iam_role_permission(role_id,permission_id,tenant_id) VALUES(4000001,5102018,0)
ON DUPLICATE KEY UPDATE tenant_id=VALUES(tenant_id);
"@
Invoke-Sql baiyue_warehouse @"
INSERT INTO wh_warehouse(id,tenant_id,warehouse_code,warehouse_name,warehouse_type,country_code,timezone,status,remark)
VALUES
($inactiveWarehouse,10001,'REG-INACTIVE','回归停用仓','DOMESTIC','CN','Asia/Shanghai','INACTIVE','自动化回归'),
($crossWarehouse,10002,'REG-CROSS','其他企业仓库','DOMESTIC','CN','Asia/Shanghai','ACTIVE','自动化回归')
ON DUPLICATE KEY UPDATE status=VALUES(status),warehouse_name=VALUES(warehouse_name);
INSERT INTO wh_warehouse_address(id,tenant_id,warehouse_id,address_type,country_code,state_province,city,district,postal_code,address_line1)
VALUES
(190000000000000012,10001,$inactiveWarehouse,'OPERATING','CN','广东省','深圳市','龙岗区','518000','停用仓测试地址'),
(190000000000000013,10002,$crossWarehouse,'OPERATING','CN','广东省','广州市','白云区','510000','跨租户仓测试地址')
ON DUPLICATE KEY UPDATE address_line1=VALUES(address_line1);
INSERT INTO wh_service_capability(id,tenant_id,warehouse_id,capability_code,capability_name,enabled,cutoff_time,description)
VALUES
(190000000000000022,10001,$inactiveWarehouse,'RECEIVING','头程收货',1,'15:00:00','自动化回归'),
(190000000000000023,10002,$crossWarehouse,'RECEIVING','头程收货',1,'15:30:00','自动化回归')
ON DUPLICATE KEY UPDATE enabled=1,cutoff_time=VALUES(cutoff_time);
INSERT INTO wh_partner(id,tenant_id,partner_code,partner_name,partner_type,contact_name,contact_phone,contact_email,country_code,province,city,district,address_line,postal_code,status)
VALUES
($inactivePartner,10001,'REG-INACTIVE','回归停用往来单位','CUSTOMER','停用联系人','13800000011','inactive@example.com','CN','广东省','深圳市','龙岗区','停用往来单位地址','518000','INACTIVE'),
($crossPartner,10002,'REG-CROSS','其他企业往来单位','CUSTOMER','跨租户联系人','13800000012','cross@example.com','CN','广东省','广州市','白云区','跨租户往来单位地址','510000','ACTIVE')
ON DUPLICATE KEY UPDATE status=VALUES(status),partner_name=VALUES(partner_name);
UPDATE wh_warehouse SET status='ACTIVE' WHERE id=$activeWarehouse AND tenant_id=10001;
UPDATE wh_partner SET status='ACTIVE' WHERE id=$activePartner AND tenant_id=10001;
"@
$merchant=Login $MerchantUsername; $merchantToken=$merchant.accessToken
$platform=Login $PlatformUsername; $platformToken=$platform.accessToken
$warehouses=Require-Success '商户查询启用仓库' (Invoke-Api GET "$GatewayBaseUrl/api/merchant/v1/warehouses?page=1&size=50" $null $merchantToken)
$warehouseIds=@($warehouses.records|ForEach-Object{[long]$_.id})
Require-Check '仓库列表强制启用过滤' ($activeWarehouse -in $warehouseIds -and $inactiveWarehouse -notin $warehouseIds) "返回ID=$($warehouseIds -join ',')"
$warehouseDetail=Require-Success '仓库详情回显地址和收货能力' (Invoke-Api GET "$GatewayBaseUrl/api/merchant/v1/warehouses/$activeWarehouse" $null $merchantToken)
Require-Check '仓库地址完整' (@($warehouseDetail.addresses).Count -gt 0 -and $warehouseDetail.addresses[0].addressLine1) "地址=$($warehouseDetail.addresses[0].addressLine1)"
$receiving=@($warehouseDetail.capabilities|Where-Object{$_.capabilityCode -eq 'RECEIVING' -and $_.enabled})|Select-Object -First 1
Require-Check '收货截单时间回显' ($null -ne $receiving -and $receiving.cutoffTime) "截单时间=$($receiving.cutoffTime)"
Require-Http '停用仓库详情不可见' (Invoke-Api GET "$GatewayBaseUrl/api/merchant/v1/warehouses/$inactiveWarehouse" $null $merchantToken) 404 'A0404'
Require-Http '跨租户仓库详情不可见' (Invoke-Api GET "$GatewayBaseUrl/api/merchant/v1/warehouses/$crossWarehouse" $null $merchantToken) 404 'A0404'
$partners=Require-Success '商户查询启用往来单位' (Invoke-Api GET "$GatewayBaseUrl/api/merchant/v1/partners/candidates" $null $merchantToken)
$partnerIds=@($partners|ForEach-Object{[long]$_.id})
Require-Check '往来单位强制启用过滤' ($activePartner -in $partnerIds -and $inactivePartner -notin $partnerIds) "返回ID=$($partnerIds -join ',')"
Require-Http '停用往来单位不可见' (Invoke-Api GET "$GatewayBaseUrl/api/merchant/v1/partners/$inactivePartner" $null $merchantToken) 404 'A0404'
Require-Http '跨租户往来单位不可见' (Invoke-Api GET "$GatewayBaseUrl/api/merchant/v1/partners/$crossPartner" $null $merchantToken) 404 'A0404'
$stamp=Get-Date -Format 'MMddHHmmss'
$party=@{companyName='客户端可调整公司';contactName='联系人';phone='13800138000';email='order@example.com';countryCode='CN';province='广东省';city='深圳市';district='南山区';addressLine='订单地址快照1号';postalCode='518000'}
$cargo=@{cargoName='回归货物';packageType='CARTON';packageCount=1;grossWeightKg=10;volumeCbm=1;dangerousGoods=$false}
$payload=@{customerId=$activePartner;customerName='伪造客户名';transportMode='SEA';tradeTerm='FOB';destinationCountryCode='US';destinationWarehouseName='海外目的仓';expectedShipDate=(Get-Date).AddDays(10).ToString('yyyy-MM-dd');customerRemark='真实API回归';sender=$party;receiver=$party;cargoes=@($cargo);pickupMethod='PICKUP';receivingWarehouseId=$activeWarehouse;receivingWarehouseName='伪造收货仓名';channelCode='REG';channelName='回归渠道';taxMode='BUY_DECLARATION';containerMode='LCL';customsInspection=$false;destinationType='OVERSEAS'}
$created=Require-Success '创建订单草稿' (Invoke-Api POST "$GatewayBaseUrl/api/merchant/v1/orders/first-leg" $payload $merchantToken)
$orderId=[long]$created; $testData.orderId=$orderId
$detail=Require-Success '查询新建草稿' (Invoke-Api GET "$GatewayBaseUrl/api/merchant/v1/orders/first-leg/$orderId" $null $merchantToken)
Require-Check '订单使用服务端可信名称快照' ($detail.order.customerName -eq '深圳示例贸易有限公司' -and $detail.order.receivingWarehouseName -eq '深圳头程收货仓') "客户=$($detail.order.customerName),仓库=$($detail.order.receivingWarehouseName)"
Require-Check '订单保存仓库地址和收货时间快照' ($detail.order.receivingWarehouseAddress -match '示例物流园' -and $detail.order.receivingTime -match '16:00') "地址=$($detail.order.receivingWarehouseAddress),收货=$($detail.order.receivingTime)"
$payload.sender.addressLine='订单编辑后地址2号'; $payload.customerName='再次伪造名称'; $payload.receivingWarehouseName='再次伪造仓名'
$null=Require-Success '编辑并重新保存草稿' (Invoke-Api PUT "$GatewayBaseUrl/api/merchant/v1/orders/first-leg/$orderId" $payload $merchantToken)
$detail=Require-Success '读取编辑后的草稿' (Invoke-Api GET "$GatewayBaseUrl/api/merchant/v1/orders/first-leg/$orderId" $null $merchantToken)
$sender=@($detail.parties|Where-Object{$_.partyType -eq 'SENDER'})|Select-Object -First 1
Require-Check '订单参与方地址保存为历史快照' ($sender.addressLine -eq '订单编辑后地址2号') "地址=$($sender.addressLine)"
Require-Check '编辑仍不信任客户端主数据名称' ($detail.order.customerName -eq '深圳示例贸易有限公司' -and $detail.order.receivingWarehouseName -eq '深圳头程收货仓') '可信名称未被伪造值覆盖'
Invoke-Sql baiyue_warehouse "UPDATE wh_warehouse SET status='INACTIVE' WHERE id=$activeWarehouse AND tenant_id=10001;"
Require-Http '仓库停用后禁止保存草稿' (Invoke-Api PUT "$GatewayBaseUrl/api/merchant/v1/orders/first-leg/$orderId" $payload $merchantToken) 404 'A0404'
Invoke-Sql baiyue_warehouse "UPDATE wh_warehouse SET status='ACTIVE' WHERE id=$activeWarehouse AND tenant_id=10001;"
Invoke-Sql baiyue_warehouse "UPDATE wh_partner SET status='INACTIVE' WHERE id=$activePartner AND tenant_id=10001;"
Require-Http '往来单位停用后禁止保存草稿' (Invoke-Api PUT "$GatewayBaseUrl/api/merchant/v1/orders/first-leg/$orderId" $payload $merchantToken) 404 'A0404'
Invoke-Sql baiyue_warehouse "UPDATE wh_partner SET status='ACTIVE' WHERE id=$activePartner AND tenant_id=10001;"
Invoke-Sql baiyue_iam "UPDATE iam_tenant SET status='PENDING' WHERE id=10001;"
Require-Http '企业审核未通过禁止创建订单' (Invoke-Api POST "$GatewayBaseUrl/api/merchant/v1/orders/first-leg" $payload $merchantToken) 403 'A0403'
Invoke-Sql baiyue_iam "UPDATE iam_tenant SET status='ACTIVE' WHERE id=10001;"
$null=Require-Success '提交草稿' (Invoke-Api POST "$GatewayBaseUrl/api/merchant/v1/orders/first-leg/$orderId/submit" $null $merchantToken)
Require-Http '重复提交返回409' (Invoke-Api POST "$GatewayBaseUrl/api/merchant/v1/orders/first-leg/$orderId/submit" $null $merchantToken) 409 'A0409'
$null=Require-Success '平台履约写入快递信息' (Invoke-Api PUT "$GatewayBaseUrl/api/platform/v1/orders/first-leg/$orderId/express?tenantId=10001" @{expressCompany='顺丰速运';expressTrackingNo="SF$stamp"} $platformToken)
Require-Http '商户身份不能写平台快递接口' (Invoke-Api PUT "$GatewayBaseUrl/api/platform/v1/orders/first-leg/$orderId/express?tenantId=10001" @{expressCompany='顺丰速运';expressTrackingNo="SF$stamp-X"} $merchantToken) 403 'A0403'
$detail=Require-Success '商户订单详情只读展示快递信息' (Invoke-Api GET "$GatewayBaseUrl/api/merchant/v1/orders/first-leg/$orderId" $null $merchantToken)
Require-Check '快递公司和单号真实回显' ($detail.order.expressCompany -eq '顺丰速运' -and $detail.order.expressTrackingNo -eq "SF$stamp") "快递=$($detail.order.expressCompany),单号=$($detail.order.expressTrackingNo)"
foreach($item in @(
@{name='Order merchant api-docs';url="$OrderBaseUrl/v3/api-docs/merchant"},
@{name='Warehouse merchant api-docs';url="$WarehouseBaseUrl/v3/api-docs/merchant"}
)) {
$doc=Invoke-Api GET $item.url
Require-Check $item.name ($doc.status -eq 200 -and $doc.body -match '"openapi"') "HTTP=$($doc.status)"
}
$gatewayConfig=Get-Content -LiteralPath (Join-Path $root 'gateway\src\main\resources\application-cn.yml') -Raw -Encoding UTF8
Require-Check 'Gateway Order api-docs 路由源配置' ($gatewayConfig -match 'order-service/v3/api-docs') '已静态审查源配置'
Require-Check 'Gateway Warehouse api-docs 路由源配置' ($gatewayConfig -match 'warehouse-service/v3/api-docs') '已静态审查源配置'
} catch {
$fatal=$_.Exception.Message
$results.Add([pscustomobject]@{name='脚本主流程';passed=$false;evidence=$fatal})
} finally {
try { Invoke-Sql baiyue_iam "UPDATE iam_tenant SET status='ACTIVE' WHERE id=10001;" } catch {}
try {
if($orderId) {
Invoke-Sql baiyue_order "DELETE FROM ord_order_internal_note WHERE tenant_id=10001 AND order_id=$orderId; DELETE FROM ord_order_status_history WHERE tenant_id=10001 AND order_id=$orderId; DELETE FROM ord_order_cargo WHERE tenant_id=10001 AND order_id=$orderId; DELETE FROM ord_order_party WHERE tenant_id=10001 AND order_id=$orderId; DELETE FROM ord_outbox_event WHERE tenant_id=10001 AND aggregate_id=$orderId; DELETE FROM ord_order WHERE tenant_id=10001 AND id=$orderId;"
}
} catch {}
try { Invoke-Sql baiyue_warehouse "UPDATE wh_warehouse SET status='ACTIVE' WHERE id=$activeWarehouse AND tenant_id=10001; UPDATE wh_partner SET status='ACTIVE' WHERE id=$activePartner AND tenant_id=10001; DELETE FROM wh_service_capability WHERE id IN (190000000000000022,190000000000000023); DELETE FROM wh_warehouse_address WHERE id IN (190000000000000012,190000000000000013); DELETE FROM wh_partner WHERE id IN ($inactivePartner,$crossPartner); DELETE FROM wh_warehouse WHERE id IN ($inactiveWarehouse,$crossWarehouse);" } catch {}
try { if(-not $permissionPreexisting){Invoke-Sql baiyue_iam "DELETE FROM iam_role_permission WHERE permission_id=5102018; DELETE FROM iam_permission WHERE id=5102018;"} } catch {}
$passed=@($results|Where-Object{$_.passed}).Count; $failed=@($results|Where-Object{-not $_.passed}).Count
$report=[ordered]@{reportName='订单仓库往来单位真实API回归报告';startedAt=$startedAt.ToString('o');finishedAt=(Get-Date).ToString('o');stack=[ordered]@{gateway=$GatewayBaseUrl;order=$OrderBaseUrl;warehouse=$WarehouseBaseUrl};success=($failed -eq 0);summary=[ordered]@{total=$results.Count;passed=$passed;failed=$failed};testData=$testData;results=$results;fatalMessage=$fatal;securityNote='报告不保存密码或令牌'}
$dir=Split-Path -Parent $OutputPath; if(-not(Test-Path $dir)){New-Item -ItemType Directory -Path $dir -Force|Out-Null}
$report|ConvertTo-Json -Depth 12|Set-Content -LiteralPath $OutputPath -Encoding UTF8
if ($null -eq $previousMysqlPassword) { Remove-Item Env:MYSQL_PWD -ErrorAction SilentlyContinue } else { $env:MYSQL_PWD=$previousMysqlPassword }
Write-Host "报告已生成:$OutputPath"
if(-not $report.success){exit 1}
}