$ErrorActionPreference = "Stop" $base = "http://127.0.0.1:8755/api/v1" $origin = "http://127.0.0.1:9346" function Invoke-Json { param( [string]$Method, [string]$Path, [string]$Token, [object]$Body = $null, [hashtable]$ExtraHeaders = @{} ) $headers = @{} if ($Token) { $headers.Authorization = "Bearer $Token" } foreach ($key in $ExtraHeaders.Keys) { $headers[$key] = $ExtraHeaders[$key] } $parameters = @{ Method = $Method Uri = "$base$Path" Headers = $headers UseBasicParsing = $true } if ($null -ne $Body) { $parameters.ContentType = "application/json" $parameters.Body = ($Body | ConvertTo-Json -Depth 10 -Compress) } try { $response = Invoke-WebRequest @parameters return @{ Status = [int]$response.StatusCode Body = ($response.Content | ConvertFrom-Json) Headers = $response.Headers } } catch { $response = $_.Exception.Response $content = $_.ErrorDetails.Message if (-not $content) { $reader = New-Object System.IO.StreamReader($response.GetResponseStream()) $content = $reader.ReadToEnd() $reader.Dispose() } return @{ Status = [int]$response.StatusCode Body = ($content | ConvertFrom-Json) Headers = $response.Headers } } } function Assert-True { param([bool]$Condition, [string]$Message) if (-not $Condition) { throw $Message } } function Upload-Document { param( [string]$Token, [string]$File, [hashtable]$Metadata, [bool]$Attachment = $false ) $path = if ($Attachment) { "/attachments" } else { "/documents" } $metadataJson = $Metadata | ConvertTo-Json -Depth 10 -Compress $metadataFile = [System.IO.Path]::GetTempFileName() try { [System.IO.File]::WriteAllText( $metadataFile, $metadataJson, [System.Text.UTF8Encoding]::new($false) ) $output = & curl.exe -sS -X POST "$base$path" ` -H "Authorization: Bearer $Token" ` -F "file=@$File" ` -F "metadata=<$metadataFile" if ($LASTEXITCODE -ne 0) { throw "curl上传失败" } } finally { Remove-Item -LiteralPath $metadataFile -Force -ErrorAction SilentlyContinue } $body = $output | ConvertFrom-Json Assert-True ($body.code -eq "OK") "上传失败:$output" return $body.data } function Issue-Token { param([string]$Username) $response = Invoke-Json POST "/auth/login" "" @{ username = $Username password = $env:DMS_TEST_USER_PASSWORD keepSignedIn = $false } Assert-True ($response.Status -eq 200) "$Username 登录失败" return $response.Body.data.accessToken } $adminToken = Issue-Token "admin" $userToken = Issue-Token "user" $auditorToken = Issue-Token "auditor" $health = Invoke-Json GET "/health" "" $null @{Origin = $origin} Assert-True ($health.Status -eq 200) "健康检查失败" Assert-True ( $health.Headers["Access-Control-Expose-Headers"] -match "Content-Disposition" -and $health.Headers["Access-Control-Expose-Headers"] -match "X-Request-Id" ) "CORS暴露响应头缺失" $tree = Invoke-Json GET "/categories/tree" $adminToken Assert-True ($tree.Status -eq 200) "分类树查询失败" $categoryId = [string]$tree.Body.data[0].id $sampleFile = ( Get-ChildItem -LiteralPath ( Join-Path $PSScriptRoot "..\..\dms-test-storage\original" ) -Filter "*.docx" -File | Select-Object -First 1 ).FullName Assert-True ([bool]$sampleFile) "未找到联调上传样本文件" $main = Upload-Document $adminToken $sampleFile @{ documentName = "F8_RECYCLE_MAIN" documentType = "MAIN" summary = "B8 HTTP MAIN" categoryId = $categoryId securityLevel = "INTERNAL" visibilityType = "CUSTOM" status = "PUBLISHED" tags = @("B8", "F8") } $attachment = Upload-Document $adminToken $sampleFile @{ documentName = "F8_RECYCLE_ATTACHMENT" attachmentType = "OTHER" summary = "B8 HTTP ATTACHMENT" tags = @("B8", "F8") } $true $users = Invoke-Json GET "/users?keyword=user&page=1&pageSize=20" $adminToken $subjectUser = $users.Body.data.items | Where-Object {$_.username -eq "user"} | Select-Object -First 1 $permission = Invoke-Json PUT "/documents/$($main.id)/permissions" $adminToken @{ visibilityType = "CUSTOM" documentRowVersion = $main.rowVersion entries = @( @{ subjectType = "USER" subjectId = [string]$subjectUser.id canView = $true canDownload = $true canEdit = $false canManagePermission = $false canDelete = $false } ) } Assert-True ($permission.Status -eq 200) "权限配置失败" $binding = Invoke-Json POST "/main-plans/$($main.id)/attachments/bind" $adminToken @{ attachmentIds = @([string]$attachment.id) mainPlanRowVersion = $permission.Body.data.documentRowVersion } Assert-True ($binding.Status -eq 200) "附件挂载失败" $main.rowVersion = $binding.Body.data.mainPlanRowVersion $deleteMain = Invoke-Json DELETE "/documents/$($main.id)?rowVersion=$($main.rowVersion)" $adminToken Assert-True ($deleteMain.Status -eq 200) "主案删除失败" $bin = Invoke-Json GET "/recycle-bin/documents?keyword=F8_RECYCLE_MAIN" $adminToken $null @{Origin = $origin} Assert-True ($bin.Status -eq 200 -and $bin.Body.data.total -eq 1) "主案未进入回收站" $deletedMain = $bin.Body.data.items[0] Assert-True ($null -ne $deletedMain.deletedBy) "删除人快照缺失" $wrongVersion = Invoke-Json POST "/recycle-bin/documents/$($main.id)/restore" $adminToken @{ rowVersion = [Math]::Max(0, [int]$deletedMain.rowVersion - 1) } Assert-True ( $wrongVersion.Status -eq 409 -and $wrongVersion.Body.code -eq "DATA_VERSION_CONFLICT" ) "rowVersion冲突未按契约返回" $restoreMain = Invoke-Json POST "/recycle-bin/documents/$($main.id)/restore" $adminToken @{ rowVersion = [int]$deletedMain.rowVersion } Assert-True ($restoreMain.Status -eq 200) "主案恢复失败" Assert-True ( $restoreMain.Body.data.restoredPermissionCount -eq 1 -and $restoreMain.Body.data.restoredBindingCount -eq 1 ) "主案ACL或挂载未恢复" $restoredPermissions = Invoke-Json GET "/documents/$($main.id)/permissions" $adminToken $restoredAttachments = Invoke-Json GET "/main-plans/$($main.id)/attachments" $adminToken Assert-True ($restoredPermissions.Body.data.entries.Count -eq 1) "恢复后ACL不同步" Assert-True ($restoredAttachments.Body.data.total -eq 1) "恢复后挂载不同步" $sub = Upload-Document $adminToken $sampleFile @{ documentName = "F8_RECYCLE_SUB_PLAN" documentType = "SUB_PLAN" summary = "B8 HTTP SUB PLAN" categoryId = $categoryId securityLevel = "INTERNAL" visibilityType = "CUSTOM" status = "PUBLISHED" tags = @("B8", "F8") parentDocumentId = [string]$main.id } $deleteSub = Invoke-Json DELETE "/documents/$($sub.id)?rowVersion=$($sub.rowVersion)" $adminToken Assert-True ($deleteSub.Status -eq 200) "子方案删除失败" $subBin = Invoke-Json GET "/recycle-bin/documents?keyword=F8_RECYCLE_SUB_PLAN" $adminToken $deletedSub = $subBin.Body.data.items[0] $currentMainPermission = Invoke-Json GET "/documents/$($main.id)/permissions" $adminToken $deleteParent = Invoke-Json DELETE ( "/documents/$($main.id)?rowVersion=" + $currentMainPermission.Body.data.documentRowVersion ) $adminToken Assert-True ($deleteParent.Status -eq 200) "父主案二次删除失败" $invalidParent = Invoke-Json POST "/recycle-bin/documents/$($sub.id)/restore" $adminToken @{ rowVersion = [int]$deletedSub.rowVersion } Assert-True ( $invalidParent.Status -eq 409 -and $invalidParent.Body.code -eq "RESTORE_PARENT_INVALID" ) "无效父主案冲突未返回" $mainBinAgain = Invoke-Json GET "/recycle-bin/documents?keyword=F8_RECYCLE_MAIN" $adminToken $restoreMainAgain = Invoke-Json POST "/recycle-bin/documents/$($main.id)/restore" $adminToken @{ rowVersion = [int]$mainBinAgain.Body.data.items[0].rowVersion } Assert-True ($restoreMainAgain.Status -eq 200) "主案二次恢复失败" $restoreSub = Invoke-Json POST "/recycle-bin/documents/$($sub.id)/restore" $adminToken @{ rowVersion = [int]$deletedSub.rowVersion } Assert-True ($restoreSub.Status -eq 200) "子方案恢复失败" $mainPermissionAfterSub = Invoke-Json GET "/documents/$($main.id)/permissions" $adminToken $unbind = Invoke-Json DELETE ( "/main-plans/$($main.id)/attachments/$($attachment.id)" + "?mainPlanRowVersion=" + $mainPermissionAfterSub.Body.data.documentRowVersion ) $adminToken Assert-True ($unbind.Status -eq 200) "解除挂载失败" $deleteAttachment = Invoke-Json DELETE ( "/attachments/$($attachment.id)?rowVersion=$($attachment.rowVersion)" ) $adminToken Assert-True ($deleteAttachment.Status -eq 200) "附件删除失败" $attachmentBin = Invoke-Json GET "/recycle-bin/documents?keyword=F8_RECYCLE_ATTACHMENT" $adminToken $restoreAttachment = Invoke-Json POST ( "/recycle-bin/documents/$($attachment.id)/restore" ) $adminToken @{ rowVersion = [int]$attachmentBin.Body.data.items[0].rowVersion } Assert-True ($restoreAttachment.Status -eq 200) "附件恢复失败" Assert-True ( $restoreAttachment.Body.data.restoredBindingCount -eq 0 -and $restoreAttachment.Body.data.document.mountedPlanCount -eq 0 ) "附件错误恢复了历史挂载" foreach ($token in @($userToken, $auditorToken)) { $forbidden = Invoke-Json GET "/recycle-bin/documents" $token Assert-True ( $forbidden.Status -eq 403 -and $forbidden.Body.code -eq "FORBIDDEN" ) "非管理员未被拒绝" } $oldPath = Invoke-Json POST "/documents/$($main.id)/restore" $adminToken @{ rowVersion = 0 } Assert-True ($oldPath.Status -eq 404) "旧恢复路径未保持404" $restoreAudits = Invoke-Json GET ( "/audit/logs?actionType=RESTORE_DOCUMENT&page=1&pageSize=100" ) $auditorToken Assert-True ($restoreAudits.Body.data.total -ge 3) "恢复审计缺失" $viewAudits = Invoke-Json GET ( "/audit/logs?actionType=VIEW_DOCUMENT&targetId=$($main.id)&page=1&pageSize=100" ) $auditorToken Assert-True ($viewAudits.Body.data.total -eq 0) "恢复响应产生了VIEW_DOCUMENT" # 最终仅保留三条F8回收站样本,全部通过正式DELETE接口产生。 $deleteSubFinal = Invoke-Json DELETE ( "/documents/$($sub.id)?rowVersion=$($restoreSub.Body.data.document.rowVersion)" ) $adminToken Assert-True ($deleteSubFinal.Status -eq 200) "F8子方案最终删除失败" $mainPermissionFinal = Invoke-Json GET "/documents/$($main.id)/permissions" $adminToken $deleteMainFinal = Invoke-Json DELETE ( "/documents/$($main.id)?rowVersion=" + $mainPermissionFinal.Body.data.documentRowVersion ) $adminToken Assert-True ($deleteMainFinal.Status -eq 200) "F8主案最终删除失败" $deleteAttachmentFinal = Invoke-Json DELETE ( "/attachments/$($attachment.id)" + "?rowVersion=$($restoreAttachment.Body.data.document.rowVersion)" ) $adminToken Assert-True ($deleteAttachmentFinal.Status -eq 200) "F8附件最终删除失败" $finalBin = Invoke-Json GET "/recycle-bin/documents?page=1&pageSize=100" $adminToken $finalItems = @($finalBin.Body.data.items | Where-Object { $_.documentName -in @( "F8_RECYCLE_MAIN", "F8_RECYCLE_SUB_PLAN", "F8_RECYCLE_ATTACHMENT" ) }) Assert-True ($finalItems.Count -eq 3) "F8三条回收站样本不完整" [pscustomobject]@{ mainId = [string]$main.id subPlanId = [string]$sub.id attachmentId = [string]$attachment.id recycleBinTotal = $finalBin.Body.data.total restoreAuditTotal = $restoreAudits.Body.data.total corsExposeHeaders = $health.Headers["Access-Control-Expose-Headers"] } | ConvertTo-Json -Compress