Files
baiyue-firstleg/scripts/phase1-business-maturity-regression.ps1
T
2026-07-30 18:01:36 +08:00

411 lines
24 KiB
PowerShell
Raw 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]$RepositoryRoot = (Split-Path -Parent $PSScriptRoot),
[string]$GatewayBaseUrl = "http://127.0.0.1:9000",
[string]$MerchantAdminUsername = "merchant_admin",
[Parameter(Mandatory = $true)][string]$MerchantAdminPassword,
[string]$PlatformAdminUsername = "platform_admin",
[string]$PlatformAdminPassword = "",
[string]$TemporaryAdminPassword = "Phase1Temp456!",
[string]$MemberInitialPassword = "Phase1Member123!",
[string]$MemberResetPassword = "Phase1Member456!",
[string]$OutputPath = ""
)
$ErrorActionPreference = "Stop"
$startedAt = Get-Date
$stamp = Get-Date -Format "yyyyMMddHHmmssfff"
$prefix = "p1m_$stamp"
$results = [System.Collections.Generic.List[object]]::new()
$testData = [ordered]@{ Prefix = $prefix }
$adminPasswordChanged = $false
$memberId = $null
$memberUsername = "${prefix}_member"
$tenantUsername = "${prefix}_tenant"
$roleId = $null
$latestAdminAccess = $null
$latestAdminRefresh = $null
$cleanupResidual = [ordered]@{
retainedTestMemberIds = [System.Collections.Generic.List[long]]::new()
undeletedRoleIds = [System.Collections.Generic.List[long]]::new()
crossTenantIds = [System.Collections.Generic.List[long]]::new()
}
if ([string]::IsNullOrWhiteSpace($PlatformAdminPassword)) {
$PlatformAdminPassword = $MerchantAdminPassword
}
if ([string]::IsNullOrWhiteSpace($OutputPath)) {
$OutputPath = Join-Path $RepositoryRoot "reports\phase1-business-maturity-regression-$stamp.json"
}
if (-not (Test-Path -LiteralPath $RepositoryRoot -PathType Container)) {
throw "仓库根目录不存在:$RepositoryRoot"
}
function Add-Check([string]$Name, [bool]$Passed, [string]$Evidence, [string]$Category = "业务") {
$script:results.Add([pscustomobject]@{
name = $Name
category = $Category
passed = $Passed
evidence = $Evidence
checkedAt = (Get-Date).ToString("o")
})
Write-Host "[$(if ($Passed) { '通过' } else { '失败' })] $Name - $Evidence"
}
function Require-Check([string]$Name, [bool]$Passed, [string]$Evidence, [string]$Category = "业务") {
Add-Check $Name $Passed $Evidence $Category
if (-not $Passed) { throw "验收失败:$Name;$Evidence" }
}
function Invoke-Api([string]$Method, [string]$Path, [object]$Body = $null, [string]$AccessToken = $null) {
$headers = @{}
if (-not [string]::IsNullOrWhiteSpace($AccessToken)) {
$headers.Authorization = "Bearer $AccessToken"
}
$arguments = @{
Method = $Method
Uri = "$($GatewayBaseUrl.TrimEnd('/'))$Path"
Headers = $headers
TimeoutSec = 20
UseBasicParsing = $true
}
if ($null -ne $Body) {
$arguments.ContentType = "application/json; charset=utf-8"
$jsonText = $Body | ConvertTo-Json -Depth 20 -Compress
$arguments.Body = [Text.Encoding]::UTF8.GetBytes($jsonText)
}
try {
$response = Invoke-WebRequest @arguments
$contentText = $response.Content
if ($null -ne $response.RawContentStream) {
try { $contentText = [Text.Encoding]::UTF8.GetString($response.RawContentStream.ToArray()) } catch { }
}
$json = if ([string]::IsNullOrWhiteSpace($contentText)) { $null } else { $contentText | ConvertFrom-Json }
return [pscustomobject]@{ HttpStatus = [int]$response.StatusCode; Body = $json }
} catch {
$status = 0
if ($null -ne $_.Exception.Response) {
try { $status = [int]$_.Exception.Response.StatusCode } catch { $status = 0 }
}
$json = $null
if (-not [string]::IsNullOrWhiteSpace($_.ErrorDetails.Message)) {
try { $json = $_.ErrorDetails.Message | ConvertFrom-Json } catch { $json = $null }
}
if ($null -eq $json -and $null -ne $_.Exception.Response) {
try {
$stream = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($stream, [Text.Encoding]::UTF8)
$content = $reader.ReadToEnd()
$reader.Dispose()
if (-not [string]::IsNullOrWhiteSpace($content)) {
$json = $content | ConvertFrom-Json
}
} catch {
$json = $null
}
}
return [pscustomobject]@{ HttpStatus = $status; Body = $json }
}
}
function Require-Success([string]$Name, [object]$Response, [string]$Category = "业务") {
$ok = $Response.HttpStatus -eq 200 -and $null -ne $Response.Body -and $Response.Body.code -eq "0"
$code = if ($null -eq $Response.Body) { "无响应码" } else { $Response.Body.code }
Require-Check $Name $ok "HTTP=$($Response.HttpStatus), code=$code" $Category
return $Response.Body.data
}
function Require-Failure([string]$Name, [object]$Response, [int]$HttpStatus, [string[]]$Codes, [string]$Category = "异常") {
$code = if ($null -eq $Response.Body) { $null } else { [string]$Response.Body.code }
$ok = $Response.HttpStatus -eq $HttpStatus -and ($Codes.Count -eq 0 -or $code -in $Codes)
Require-Check $Name $ok "HTTP=$($Response.HttpStatus), code=$(if ($null -eq $code) { '无' } else { $code })" $Category
}
function Login([string]$Username, [string]$Password, [string]$Name) {
$response = Invoke-Api "POST" "/api/auth/login" @{ username = $Username; password = $Password }
return Require-Success $Name $response "认证"
}
function Restore-AdminPassword {
if (-not $script:adminPasswordChanged) { return }
try {
$login = Login $MerchantAdminUsername $TemporaryAdminPassword "恢复前重新登录管理员"
$restore = Invoke-Api "PUT" "/api/auth/password" @{
oldPassword = $TemporaryAdminPassword
newPassword = $MerchantAdminPassword
} $login.accessToken
if ($restore.HttpStatus -eq 200 -and $restore.Body.code -eq "0") {
$script:adminPasswordChanged = $false
Add-Check "恢复管理员原密码" $true "恢复成功" "清理"
} else {
Add-Check "恢复管理员原密码" $false "HTTP=$($restore.HttpStatus)" "清理"
}
} catch {
Add-Check "恢复管理员原密码" $false "恢复过程异常,需人工检查测试账号" "清理"
}
}
function Invoke-BestEffortCleanup {
# 清理范围必须同时满足一期脚本的成员编号和显示名特征,禁止按单一模糊条件处理正常成员。
$cleanupLogin = $null
foreach ($candidatePassword in @($MerchantAdminPassword, $TemporaryAdminPassword)) {
if ($null -ne $cleanupLogin) { break }
$attempt = Invoke-Api "POST" "/api/auth/login" @{
username = $MerchantAdminUsername
password = $candidatePassword
}
if ($attempt.HttpStatus -eq 200 -and $null -ne $attempt.Body -and $attempt.Body.code -eq "0") {
$cleanupLogin = $attempt.Body.data
}
}
if ($null -eq $cleanupLogin) {
Add-Check "清理阶段登录商户管理员" $false "原密码和临时密码均无法登录" "清理"
return
}
Add-Check "清理阶段登录商户管理员" $true "登录成功" "清理"
$memberResponse = Invoke-Api "GET" "/api/merchant/v1/iam/members" $null $cleanupLogin.accessToken
if ($memberResponse.HttpStatus -ne 200 -or $null -eq $memberResponse.Body -or $memberResponse.Body.code -ne "0") {
Add-Check "扫描历史一期测试成员" $false "HTTP=$($memberResponse.HttpStatus)" "清理"
return
}
$testMembers = @($memberResponse.Body.data | Where-Object {
$_.memberNo -like "P1-*" -and $_.displayName -like "一期验收成员*"
})
foreach ($testMember in $testMembers) {
$id = [long]$testMember.membershipId
if ($testMember.status -ne "DISABLED") {
$disable = Invoke-Api "PUT" "/api/merchant/v1/iam/members/$id/status" @{ status = "DISABLED" } $cleanupLogin.accessToken
if ($disable.HttpStatus -ne 200 -or $disable.Body.code -ne "0") {
Add-Check "停用历史测试成员 $id" $false "HTTP=$($disable.HttpStatus)" "清理"
if ($id -notin $cleanupResidual.retainedTestMemberIds) { $cleanupResidual.retainedTestMemberIds.Add($id) }
continue
}
}
$clearRoles = Invoke-Api "PUT" "/api/merchant/v1/iam/members/roles" @{
membershipId = $id
roleIds = @()
} $cleanupLogin.accessToken
$cleared = $clearRoles.HttpStatus -eq 200 -and $null -ne $clearRoles.Body -and $clearRoles.Body.code -eq "0"
Add-Check "清理历史测试成员角色 $id" $cleared "HTTP=$($clearRoles.HttpStatus)" "清理"
if ($id -notin $cleanupResidual.retainedTestMemberIds) { $cleanupResidual.retainedTestMemberIds.Add($id) }
}
$roleResponse = Invoke-Api "GET" "/api/merchant/v1/iam/roles" $null $cleanupLogin.accessToken
if ($roleResponse.HttpStatus -ne 200 -or $null -eq $roleResponse.Body -or $roleResponse.Body.code -ne "0") {
Add-Check "扫描历史一期测试角色" $false "HTTP=$($roleResponse.HttpStatus)" "清理"
} else {
$testRoles = @($roleResponse.Body.data | Where-Object {
$_.roleCode -like "P1_SELF_*" -and $_.roleType -eq "CUSTOM"
})
foreach ($testRole in $testRoles) {
$id = [long]$testRole.id
$delete = Invoke-Api "DELETE" "/api/merchant/v1/iam/roles/$id" $null $cleanupLogin.accessToken
$deleted = $delete.HttpStatus -eq 200 -and $null -ne $delete.Body -and $delete.Body.code -eq "0"
Add-Check "删除历史测试角色 $id" $deleted "HTTP=$($delete.HttpStatus)" "清理"
if (-not $deleted) { $cleanupResidual.undeletedRoleIds.Add($id) }
}
}
$logout = Invoke-Api "POST" "/api/auth/logout" @{ refreshToken = $cleanupLogin.refreshToken } $cleanupLogin.accessToken
Add-Check "退出清理会话" ($logout.HttpStatus -eq 200 -and $logout.Body.code -eq "0") "HTTP=$($logout.HttpStatus)" "清理"
}
$fatalMessage = $null
try {
# 登录、刷新及刷新令牌轮换。
$initialLogin = Login $MerchantAdminUsername $MerchantAdminPassword "商户管理员登录"
$initialAccess = $initialLogin.accessToken
$initialRefresh = $initialLogin.refreshToken
$refreshResponse = Invoke-Api "POST" "/api/auth/refresh" @{ refreshToken = $initialRefresh }
$rotated = Require-Success "刷新令牌并轮换会话" $refreshResponse "认证"
$latestAdminAccess = $rotated.accessToken
$latestAdminRefresh = $rotated.refreshToken
Require-Failure "旧刷新令牌不可重复使用" (Invoke-Api "POST" "/api/auth/refresh" @{ refreshToken = $initialRefresh }) 401 @("A0401") "认证"
Require-Failure "刷新后旧访问令牌失效" (Invoke-Api "GET" "/api/merchant/v1/iam/profile" $null $initialAccess) 401 @("A0401") "认证"
# 个人资料查询、修改、回显和恢复。
$profile = Require-Success "查询本人个人资料" (Invoke-Api "GET" "/api/merchant/v1/iam/profile" $null $latestAdminAccess) "个人资料"
$updatedName = "一期验收-$stamp"
$updated = Require-Success "编辑本人个人资料" (Invoke-Api "PUT" "/api/merchant/v1/iam/profile" @{
displayName = $updatedName
phone = $profile.phone
email = $profile.email
} $latestAdminAccess) "个人资料"
Require-Check "个人资料修改后正确回显" ($updated.displayName -eq $updatedName) "displayName 已更新" "个人资料"
$null = Require-Success "恢复本人个人资料" (Invoke-Api "PUT" "/api/merchant/v1/iam/profile" @{
displayName = $profile.displayName
phone = $profile.phone
email = $profile.email
} $latestAdminAccess) "清理"
# 修改密码失败和成功场景,随后恢复测试管理员密码。
Require-Failure "错误旧密码禁止修改密码" (Invoke-Api "PUT" "/api/auth/password" @{
oldPassword = "WrongOldPassword999!"
newPassword = $TemporaryAdminPassword
} $latestAdminAccess) 400 @("A0400") "密码"
$null = Require-Success "正确旧密码修改成功" (Invoke-Api "PUT" "/api/auth/password" @{
oldPassword = $MerchantAdminPassword
newPassword = $TemporaryAdminPassword
} $latestAdminAccess) "密码"
$adminPasswordChanged = $true
Require-Failure "修改密码后旧访问令牌失效" (Invoke-Api "GET" "/api/merchant/v1/iam/profile" $null $latestAdminAccess) 401 @("A0401") "密码"
Require-Failure "修改密码后旧刷新令牌失效" (Invoke-Api "POST" "/api/auth/refresh" @{ refreshToken = $latestAdminRefresh }) 401 @("A0401") "密码"
Restore-AdminPassword
$adminLogin = Login $MerchantAdminUsername $MerchantAdminPassword "恢复密码后重新登录管理员"
$latestAdminAccess = $adminLogin.accessToken
$latestAdminRefresh = $adminLogin.refreshToken
# 平台身份不能访问商户端接口。
$platformLogin = Login $PlatformAdminUsername $PlatformAdminPassword "平台管理员登录"
Require-Failure "平台令牌访问商户接口返回403" (Invoke-Api "GET" "/api/merchant/v1/iam/members" $null $platformLogin.accessToken) 403 @("A0403") "权限"
# 创建仅用于本次验收的自助保护角色,并授予最小成员权限。
$role = Require-Success "创建验收角色" (Invoke-Api "POST" "/api/merchant/v1/iam/roles" @{
roleCode = "P1_SELF_$stamp"
roleName = "一期自助保护验收角色"
description = "一期自动化验收数据,禁止用于生产授权"
} $latestAdminAccess) "成员"
$roleId = [long]$role.id
$testData.RoleId = $roleId
$editedRole = Require-Success "编辑验收角色" (Invoke-Api "PUT" "/api/merchant/v1/iam/roles/$roleId" @{
roleName = "一期自助保护验收角色-已编辑"
description = "一期自动化验收编辑回显"
} $latestAdminAccess) "角色"
Require-Check "角色编辑结果正确回显" ($editedRole.roleName -eq "一期自助保护验收角色-已编辑") "roleName 已更新" "角色"
$disabledRole = Require-Success "停用自定义角色" (Invoke-Api "PUT" "/api/merchant/v1/iam/roles/$roleId/status" @{ status = "DISABLED" } $latestAdminAccess) "角色"
Require-Check "角色停用状态正确回显" ($disabledRole.status -eq "DISABLED") "status=DISABLED" "角色"
$enabledRole = Require-Success "重新启用自定义角色" (Invoke-Api "PUT" "/api/merchant/v1/iam/roles/$roleId/status" @{ status = "ACTIVE" } $latestAdminAccess) "角色"
Require-Check "角色启用状态正确回显" ($enabledRole.status -eq "ACTIVE") "status=ACTIVE" "角色"
$permissions = Require-Success "查询成员按钮权限" (Invoke-Api "GET" "/api/merchant/v1/iam/permissions" $null $latestAdminAccess) "权限"
$neededCodes = @("merchant:member:list", "merchant:member:status")
$permissionIds = @($permissions | Where-Object { $_.permissionCode -in $neededCodes } | ForEach-Object { [long]$_.id })
Require-Check "自助保护所需权限已初始化" ($permissionIds.Count -eq $neededCodes.Count) "权限数量=$($permissionIds.Count)" "权限"
$null = Require-Success "授予验收角色最小权限" (Invoke-Api "PUT" "/api/merchant/v1/iam/roles/permissions" @{
roleId = $roleId
permissionIds = $permissionIds
} $latestAdminAccess) "权限"
$roleDetail = Require-Success "查询角色当前权限回显" (Invoke-Api "GET" "/api/merchant/v1/iam/roles/$roleId" $null $latestAdminAccess) "权限"
Require-Check "角色 permissionIds 完整回显" (@($roleDetail.permissionIds).Count -eq $permissionIds.Count -and @($permissionIds | Where-Object { $_ -notin @($roleDetail.permissionIds) }).Count -eq 0) "permissionIds 数量=$(@($roleDetail.permissionIds).Count)" "权限"
# 新增成员、列表/详情角色回显及编辑。
$member = Require-Success "新增企业成员及登录账号" (Invoke-Api "POST" "/api/merchant/v1/iam/members" @{
username = $memberUsername
password = $MemberInitialPassword
displayName = "一期验收成员"
phone = "13800000001"
email = "$memberUsername@example.com"
memberType = "EMPLOYEE"
memberNo = "P1-$stamp"
roleIds = @($roleId)
} $latestAdminAccess) "成员"
$memberId = [long]$member.membershipId
$testData.MemberUsername = $memberUsername
$testData.MembershipId = $memberId
Require-Check "新增响应包含当前角色" ($roleId -in @($member.roleIds)) "roleIds 已回显" "成员"
Require-Failure "已分配成员的角色禁止删除" (Invoke-Api "DELETE" "/api/merchant/v1/iam/roles/$roleId" $null $latestAdminAccess) 409 @("A0409") "角色"
$members = Require-Success "查询企业成员列表" (Invoke-Api "GET" "/api/merchant/v1/iam/members" $null $latestAdminAccess) "成员"
$listed = @($members | Where-Object { [long]$_.membershipId -eq $memberId } | Select-Object -First 1)
Require-Check "列表返回姓名联系方式及角色" ($listed.Count -eq 1 -and $listed[0].displayName -eq "一期验收成员" -and $listed[0].phone -eq "13800000001" -and $roleId -in @($listed[0].roleIds)) "成员列表字段完整" "成员"
$detail = Require-Success "查询成员详情" (Invoke-Api "GET" "/api/merchant/v1/iam/members/$memberId" $null $latestAdminAccess) "成员"
Require-Check "成员详情角色正确回显" ($roleId -in @($detail.roleIds)) "roleIds 已回显" "成员"
$edited = Require-Success "编辑企业成员" (Invoke-Api "PUT" "/api/merchant/v1/iam/members/$memberId" @{
displayName = "一期验收成员-已编辑"
phone = "13800000002"
email = "${memberUsername}_edited@example.com"
memberType = "EMPLOYEE"
memberNo = "P1-$stamp"
roleIds = @($roleId)
} $latestAdminAccess) "成员"
Require-Check "成员编辑结果正确" ($edited.displayName -eq "一期验收成员-已编辑" -and $edited.phone -eq "13800000002") "姓名和手机已更新" "成员"
# 重置密码、启停、本人保护和会话撤销。
$memberLogin = Login $memberUsername $MemberInitialPassword "新增成员可登录"
$null = Require-Success "管理员重置成员密码" (Invoke-Api "PUT" "/api/merchant/v1/iam/members/$memberId/password" @{
newPassword = $MemberResetPassword
} $latestAdminAccess) "成员"
Require-Failure "重置密码后成员旧访问令牌失效" (Invoke-Api "GET" "/api/merchant/v1/iam/profile" $null $memberLogin.accessToken) 401 @("A0401") "成员"
Require-Failure "成员原密码登录失败" (Invoke-Api "POST" "/api/auth/login" @{ username = $memberUsername; password = $MemberInitialPassword }) 401 @("A0401") "成员"
$memberLogin = Login $memberUsername $MemberResetPassword "成员使用重置密码登录"
Require-Failure "普通成员不能停用本人" (Invoke-Api "PUT" "/api/merchant/v1/iam/members/$memberId/status" @{ status = "DISABLED" } $memberLogin.accessToken) 400 @("A0400") "权限"
$ownerMembershipId = [long](Require-Success "重新读取管理员个人资料" (Invoke-Api "GET" "/api/merchant/v1/iam/profile" $null $latestAdminAccess) "成员").userId
# 个人资料返回的是 IAM 用户 ID,不是成员 ID;从成员列表按 authUserId 定位所有者成员。
$adminClaimsPayload = $latestAdminAccess.Split('.')[1].Replace('-', '+').Replace('_', '/')
switch ($adminClaimsPayload.Length % 4) { 2 { $adminClaimsPayload += '==' } 3 { $adminClaimsPayload += '=' } }
$adminClaims = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($adminClaimsPayload)) | ConvertFrom-Json
$ownerMembershipId = [long]$adminClaims.membership_id
Require-Failure "企业所有者不可被停用" (Invoke-Api "PUT" "/api/merchant/v1/iam/members/$ownerMembershipId/status" @{ status = "DISABLED" } $latestAdminAccess) 403 @("A0403") "权限"
$null = Require-Success "停用普通成员" (Invoke-Api "PUT" "/api/merchant/v1/iam/members/$memberId/status" @{ status = "DISABLED" } $latestAdminAccess) "成员"
Require-Failure "停用成员旧访问令牌失效" (Invoke-Api "GET" "/api/merchant/v1/iam/profile" $null $memberLogin.accessToken) 401 @("A0401") "成员"
Require-Failure "停用成员禁止登录" (Invoke-Api "POST" "/api/auth/login" @{ username = $memberUsername; password = $MemberResetPassword }) 403 @("A0403") "成员"
$null = Require-Success "重新启用普通成员" (Invoke-Api "PUT" "/api/merchant/v1/iam/members/$memberId/status" @{ status = "ACTIVE" } $latestAdminAccess) "成员"
$memberLogin = Login $memberUsername $MemberResetPassword "启用后成员恢复登录"
# 第二租户只能看到自己的数据,不能读取第一租户成员。
$tenantPassword = $MemberInitialPassword
$registration = Require-Success "注册跨租户验收企业" (Invoke-Api "POST" "/api/auth/register" @{
username = $tenantUsername
password = $tenantPassword
displayName = "跨租户验收所有者"
companyName = "跨租户验收企业-$stamp"
legalPerson = "验收人员"
legalPhone = "13800000003"
contactEmail = "$tenantUsername@example.com"
countryCode = "CN"
address = "一期自动化验收地址"
}) "租户隔离"
$testData.CrossTenantUsername = $tenantUsername
$testData.CrossTenantId = [long]$registration.tenantId
$crossTenantLogin = Login $tenantUsername $tenantPassword "跨租户账号登录"
Require-Failure "跨租户读取成员被拒绝" (Invoke-Api "GET" "/api/merchant/v1/iam/members/$memberId" $null $crossTenantLogin.accessToken) 404 @("A0404") "租户隔离"
# 退出必须同时使访问和刷新会话失效。
$null = Require-Success "成员退出登录" (Invoke-Api "POST" "/api/auth/logout" @{ refreshToken = $memberLogin.refreshToken } $memberLogin.accessToken) "认证"
Require-Failure "退出后访问令牌失效" (Invoke-Api "GET" "/api/merchant/v1/iam/profile" $null $memberLogin.accessToken) 401 @("A0401") "认证"
Require-Failure "退出后刷新令牌失效" (Invoke-Api "POST" "/api/auth/refresh" @{ refreshToken = $memberLogin.refreshToken }) 401 @("A0401") "认证"
# 无删除成员接口,最终停用并保留带前缀的可审计测试数据。
$null = Require-Success "清理时停用验收成员" (Invoke-Api "PUT" "/api/merchant/v1/iam/members/$memberId/status" @{ status = "DISABLED" } $latestAdminAccess) "清理"
$null = Require-Success "清理成员角色关系" (Invoke-Api "PUT" "/api/merchant/v1/iam/members/roles" @{
membershipId = $memberId
roleIds = @()
} $latestAdminAccess) "清理"
$null = Require-Success "删除临时验收角色" (Invoke-Api "DELETE" "/api/merchant/v1/iam/roles/$roleId" $null $latestAdminAccess) "清理"
$null = Require-Success "退出跨租户验收账号" (Invoke-Api "POST" "/api/auth/logout" @{ refreshToken = $crossTenantLogin.refreshToken } $crossTenantLogin.accessToken) "清理"
$null = Require-Success "退出商户管理员" (Invoke-Api "POST" "/api/auth/logout" @{ refreshToken = $latestAdminRefresh } $latestAdminAccess) "清理"
} catch {
$fatalMessage = $_.Exception.Message
Add-Check "脚本主流程" $false $fatalMessage "执行"
} finally {
Restore-AdminPassword
if ($testData.Contains("CrossTenantId")) { $cleanupResidual.crossTenantIds.Add([long]$testData.CrossTenantId) }
Invoke-BestEffortCleanup
$finishedAt = Get-Date
$passed = @($results | Where-Object { $_.passed }).Count
$failed = @($results | Where-Object { -not $_.passed }).Count
$report = [ordered]@{
reportName = "一期业务成熟度真实 API 回归报告"
startedAt = $startedAt.ToString("o")
finishedAt = $finishedAt.ToString("o")
gatewayBaseUrl = $GatewayBaseUrl
repositoryRoot = (Resolve-Path -LiteralPath $RepositoryRoot).Path
success = ($failed -eq 0 -and [string]::IsNullOrWhiteSpace($fatalMessage))
summary = [ordered]@{ total = $results.Count; passed = $passed; failed = $failed }
testData = $testData
residualData = $cleanupResidual
results = $results
fatalMessage = $fatalMessage
securityNote = "报告不包含密码、访问令牌或刷新令牌"
}
$directory = Split-Path -Parent $OutputPath
if (-not (Test-Path -LiteralPath $directory)) { New-Item -ItemType Directory -Path $directory -Force | Out-Null }
$report | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $OutputPath -Encoding UTF8
Write-Host "报告已生成:$OutputPath"
Write-Output ($report | ConvertTo-Json -Depth 12)
if (-not $report.success) { exit 1 }
}