411 lines
24 KiB
PowerShell
411 lines
24 KiB
PowerShell
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 }
|
||
}
|
||
|
||
|
||
|