完成70%

This commit is contained in:
2026-07-24 07:56:22 +08:00
parent bb6431b319
commit c7f278fe79
92 changed files with 4741 additions and 14440 deletions
+33 -105
View File
@@ -1,123 +1,51 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$document = Get-Content -Raw -Encoding UTF8 -LiteralPath (Join-Path $root 'APP.openapi.json') | ConvertFrom-Json
$yaml = Get-Content -Raw -Encoding UTF8 -LiteralPath (Join-Path $root 'APP.openapi.yaml')
$snapshotPath = Join-Path $root 'APP.openapi.json'
$pagePath = Join-Path $root 'pages/profile/m06-help-center.vue'
$issues = New-Object System.Collections.Generic.List[string]
function Add-Issue {
param([string]$Message)
$script:issues.Add($Message)
}
function Get-Schema {
param([string]$Name)
$property = $document.components.schemas.PSObject.Properties[$Name]
if (-not $property) {
Add-Issue "JSON missing schema owner: $Name"
return $null
}
return $property.Value
}
function Assert-Required {
param([object]$Schema, [string]$SchemaName, [string[]]$Fields)
if (-not $Schema) { return }
foreach ($field in $Fields) {
if ($field -notin @($Schema.required)) {
Add-Issue "JSON $SchemaName.required missing: $field"
if (-not (Test-Path -LiteralPath $snapshotPath -PathType Leaf)) {
$issues.Add('missing protected OpenAPI snapshot')
} else {
$snapshot = Get-Content -Raw -Encoding UTF8 -LiteralPath $snapshotPath | ConvertFrom-Json
$path = '/genealogy/app/help-articles'
$pathProperty = $snapshot.paths.PSObject.Properties[$path]
$operation = if ($pathProperty) { $pathProperty.Value.PSObject.Properties['get'] } else { $null }
if (-not $operation) {
$issues.Add("protected snapshot does not declare GET $path")
} else {
$response = $operation.Value.responses.PSObject.Properties['200']
$responseValue = if ($response) { $response.Value } else { $null }
if ($responseValue -and $responseValue.'$ref') {
$responseName = ([string]$responseValue.'$ref').Split('/')[-1]
$responseValue = $snapshot.components.responses.PSObject.Properties[$responseName].Value
}
$media = if ($responseValue -and $responseValue.content) { $responseValue.content.PSObject.Properties['application/json'] } else { $null }
$responseRef = if ($media) { [string]$media.Value.schema.'$ref' } else { '' }
if ($responseRef -eq '#/components/schemas/RListHelpArticleVo') {
$issues.Add('protected snapshot asserts RListHelpArticleVo, while current Apifox only declares a generic ListResult item projection')
}
}
}
function Assert-NonEmptyString {
param([object]$Schema, [string]$SchemaName, [string]$Field)
if (-not $Schema) { return }
$property = $Schema.properties.PSObject.Properties[$Field]
if (-not $property) {
Add-Issue "JSON $SchemaName missing property: $Field"
return
}
if ($property.Value.type -ne 'string') {
Add-Issue "JSON $SchemaName.$Field must be string"
}
if ([int]$property.Value.minLength -lt 1) {
Add-Issue "JSON $SchemaName.$Field must declare minLength >= 1"
}
}
$path = '/genealogy/app/help-articles'
$pathProperty = $document.paths.PSObject.Properties[$path]
$operation = if ($pathProperty) { $pathProperty.Value.get } else { $null }
if (-not $operation) {
Add-Issue "JSON missing GET $path"
if (-not (Test-Path -LiteralPath $pagePath -PathType Leaf)) {
$issues.Add('missing M06 page')
} else {
$hasSaToken = $false
foreach ($securityRequirement in @($operation.security)) {
if ($securityRequirement.PSObject.Properties.Name -contains 'SaToken') { $hasSaToken = $true }
$page = Get-Content -Raw -Encoding UTF8 -LiteralPath $pagePath
foreach ($required in @('help-source-note', 'openPage("M07", {}, "M06")')) {
if (-not $page.Contains($required)) { $issues.Add("M06 page missing local/help fallback: $required") }
}
if (-not $hasSaToken) { Add-Issue "JSON GET $path must require SaToken" }
$response = $operation.responses.PSObject.Properties['200'].Value
if ($response.'$ref') {
$responseName = ([string]$response.'$ref').Split('/')[-1]
$response = $document.components.responses.PSObject.Properties[$responseName].Value
}
$media = @($response.content.PSObject.Properties)
$responseRef = if ($media.Count -gt 0) { [string]$media[0].Value.schema.'$ref' } else { '' }
if ($responseRef -ne '#/components/schemas/RListHelpArticleVo') {
Add-Issue "JSON GET $path must return RListHelpArticleVo; actual: $responseRef"
}
}
$envelope = Get-Schema 'RListHelpArticleVo'
$article = Get-Schema 'HelpArticleVo'
Assert-Required $envelope 'RListHelpArticleVo' @('code', 'data')
Assert-Required $article 'HelpArticleVo' @('helpCategory', 'helpTitle', 'helpContent')
if ($envelope) {
if ($envelope.properties.code.type -ne 'integer') {
Add-Issue 'JSON RListHelpArticleVo.code must be integer'
}
$data = $envelope.properties.data
if ($data.type -ne 'array' -or $data.items.'$ref' -ne '#/components/schemas/HelpArticleVo') {
Add-Issue 'JSON RListHelpArticleVo.data must be HelpArticleVo[]'
}
}
foreach ($field in @('helpCategory', 'helpTitle', 'helpContent')) {
Assert-NonEmptyString $article 'HelpArticleVo' $field
}
if ($article) {
$contentDescription = [string]$article.properties.helpContent.description
if ($contentDescription -notmatch '(?i)plain[ -]?text') {
Add-Issue 'JSON HelpArticleVo.helpContent must declare plain-text semantics'
}
}
foreach ($yamlFact in @(
' /genealogy/app/help-articles:',
'#/components/schemas/RListHelpArticleVo',
' RListHelpArticleVo:',
' HelpArticleVo:',
' - code',
' - data',
' - helpCategory',
' - helpTitle',
' - helpContent'
)) {
if (-not $yaml.Contains($yamlFact)) {
Add-Issue "YAML fact is missing: $yamlFact"
foreach ($forbidden in @('appApi.getHelp', '/help-articles/', 'helpId')) {
if ($page.Contains($forbidden)) { $issues.Add("M06 page must not invent an independent detail owner: $forbidden") }
}
}
if ($issues.Count -gt 0) {
$lines = New-Object System.Collections.Generic.List[string]
$lines.Add('HELP-CENTER-OPENAPI-CONTRACT BLOCKED')
foreach ($issue in $issues) { $lines.Add("- $issue") }
$lines.Add('- M06 will use the complete list response as its only remote owner; the detail endpoint and helpId are not consumed.')
$lines.Add('- Authenticated release tests must still cover published-only ordering, 401, malformed data, empty data, 5xx, timeout, and cancellation.')
$lines.Add('- Replace both protected exports from one backend version; do not hand-edit APP.openapi.json or APP.openapi.yaml.')
throw ($lines -join [Environment]::NewLine)
Write-Output 'HELP-CENTER-OPENAPI-CONTRACT BLOCKED'
foreach ($issue in $issues) { Write-Output "- $issue" }
Write-Output '- The list is the only declared remote candidate. Do not add a detail endpoint or consume the protected snapshot schema until Apifox and a real authenticated response agree.'
exit 1
}
Write-Output 'HELP-CENTER-OPENAPI-CONTRACT PASS'