完成40%

This commit is contained in:
rain
2026-07-23 17:21:27 +08:00
parent f1edc6b533
commit bb6431b319
114 changed files with 10931 additions and 877 deletions
+51 -24
View File
@@ -7,7 +7,6 @@ $issues = New-Object System.Collections.Generic.List[string]
$createPath = '/genealogy/app/genealogies'
$statusPath = '/genealogy/app/genealogy-bootstrap-operations/{operationKey}'
$settingsPath = '/genealogy/app/genealogies/{genealogyId}'
$regionSearchPath = '/genealogy/app/region/search'
$personPath = '/genealogy/app/genealogies/{genealogyId}/lineage/persons'
$personDetailPath = '/genealogy/app/genealogies/{genealogyId}/lineage/persons/{personId}'
@@ -30,6 +29,35 @@ function Test-IsJsonBoolean {
return $Value -is [System.Boolean] -and $Value -eq $Expected
}
function Test-IsNonNullable {
param([object]$Schema)
if (-not $Schema) { return $false }
$nullable = $Schema.PSObject.Properties['nullable']
return -not $nullable -or (Test-IsJsonBoolean $nullable.Value $false)
}
function Assert-AllowedSchemaKeywords {
param([object]$Schema, [string]$Label, [string[]]$Allowed)
if (-not $Schema) { return }
$annotations = @('title', 'description', 'example', 'examples', 'deprecated')
foreach ($property in @($Schema.PSObject.Properties)) {
if ($property.Name -like 'x-*' -or $property.Name -in $annotations -or $property.Name -in $Allowed) { continue }
Add-Issue "JSON $Label contains an unowned schema keyword: $($property.Name)"
}
}
function Test-IsPureSchemaRef {
param([object]$Schema, [string]$ExpectedRef, [string]$Label)
if (-not $Schema) { return $false }
$properties = @($Schema.PSObject.Properties.Name)
$actualRef = [string]$Schema.'$ref'
if ($properties.Count -ne 1 -or $properties[0] -cne '$ref' -or $actualRef -cne $ExpectedRef) {
Add-Issue "JSON $Label must be the sole exact local schema ref $ExpectedRef; actual: $actualRef"
return $false
}
return $true
}
$parityScript = Join-Path $PSScriptRoot 'openapi-yaml-json-parity-runtime-smoke.js'
$parityOutput = @(& node $parityScript 2>&1)
if ($LASTEXITCODE -ne 0 -or 'OPENAPI-YAML-JSON-PARITY PASS' -notin $parityOutput) {
@@ -180,8 +208,8 @@ function Assert-RequestBodyRef {
$media = $Operation.requestBody.content.PSObject.Properties['application/json']
if (-not (Test-IsJsonBoolean $Operation.requestBody.required $true) -or -not $media) {
Add-Issue "JSON $Label must require an application/json body"
} elseif ([string]$media.Value.schema.'$ref' -ne $ExpectedRef) {
Add-Issue "JSON $Label request body must use $ExpectedRef"
} else {
[void](Test-IsPureSchemaRef $media.Value.schema $ExpectedRef "$Label request body")
}
}
@@ -205,7 +233,8 @@ function Assert-ExactObject {
} elseif ($requiredProperty) {
Test-IsJsonArray $Schema.required
} else { $true }
if ($Schema.type -ne 'object' -or -not (Test-IsJsonBoolean $Schema.additionalProperties $false) -or
if ($Schema.type -ne 'object' -or -not (Test-IsNonNullable $Schema) -or
-not (Test-IsJsonBoolean $Schema.additionalProperties $false) -or
-not $requiredShapeValid -or
($actualFields -join ',') -ne ($expectedFields -join ',') -or
($actualRequired -join ',') -ne ($expectedRequired -join ',')) {
@@ -217,16 +246,18 @@ function Assert-ExactObject {
if ($MaxProperties -ge 0 -and [int]$Schema.maxProperties -ne $MaxProperties) {
Add-Issue "JSON $Label maxProperties must be $MaxProperties"
}
$allowedKeywords = @('type', 'properties', 'required', 'additionalProperties', 'nullable')
if ($MinProperties -ge 0) { $allowedKeywords += 'minProperties' }
if ($MaxProperties -ge 0) { $allowedKeywords += 'maxProperties' }
Assert-AllowedSchemaKeywords $Schema $Label $allowedKeywords
}
function Assert-PropertyRef {
param([object]$Schema, [string]$SchemaName, [string]$Field, [string]$ExpectedRef)
if (-not $Schema) { return }
$property = $Schema.properties.PSObject.Properties[$Field]
$actual = if ($property) { [string]$property.Value.'$ref' } else { '' }
if ($actual -ne $ExpectedRef) {
Add-Issue "JSON $SchemaName.$Field must use $ExpectedRef; actual: $actual"
}
if (-not $property) { Add-Issue "JSON $SchemaName.$Field must use $ExpectedRef"; return }
[void](Test-IsPureSchemaRef $property.Value $ExpectedRef "$SchemaName.$Field")
}
function Assert-ErrorSchema {
@@ -364,7 +395,6 @@ function Assert-AllDeclaredResponsesPrivateJson {
$createOperation = Get-Operation $createPath 'post'
$statusOperation = Get-Operation $statusPath 'get'
$settingsOperation = Get-Operation $settingsPath 'put'
$regionOperation = Get-Operation $regionSearchPath 'get'
$personOperation = Get-Operation $personPath 'post'
$personUpdateOperation = Get-Operation $personDetailPath 'put'
@@ -379,7 +409,6 @@ if ($document.paths.PSObject.Properties['/genealogy/region/search']) {
foreach ($entry in @(
[pscustomobject]@{ Operation = $createOperation; Label = "POST $createPath"; Path = $createPath },
[pscustomobject]@{ Operation = $statusOperation; Label = "GET $statusPath"; Path = $statusPath },
[pscustomobject]@{ Operation = $settingsOperation; Label = "PUT $settingsPath"; Path = $settingsPath },
[pscustomobject]@{ Operation = $regionOperation; Label = "GET $regionSearchPath"; Path = $regionSearchPath },
[pscustomobject]@{ Operation = $personOperation; Label = "POST $personPath"; Path = $personPath },
[pscustomobject]@{ Operation = $personUpdateOperation; Label = "PUT $personDetailPath"; Path = $personDetailPath },
@@ -391,7 +420,6 @@ foreach ($entry in @(
}
Assert-RequestBodyRef $createOperation "POST $createPath" '#/components/schemas/AppGenealogyBootstrapBody'
Assert-RequestBodyRef $settingsOperation "PUT $settingsPath" '#/components/schemas/AppGenealogySettingsUpdateBody'
$idempotencyHeader = Get-Parameter $createOperation 'Idempotency-Key' 'header' $createPath
if ($idempotencyHeader) {
@@ -556,7 +584,6 @@ $rootPersonBody = Get-Schema 'AppGenealogyRootPersonBody'
$operationKey = Get-Schema 'GenealogyBootstrapOperationKey'
$regionCode = Get-Schema 'GenealogyRegionCode'
$accessPreset = Get-Schema 'GenealogyAccessPreset'
$settingsBody = Get-Schema 'AppGenealogySettingsUpdateBody'
$bootstrapResult = Get-Schema 'GenealogyBootstrapResult'
$operationStatus = Get-Schema 'GenealogyBootstrapOperationStatus'
$pendingStatus = Get-Schema 'GenealogyBootstrapPendingStatus'
@@ -573,7 +600,7 @@ Assert-ExactObject $bootstrapBody 'AppGenealogyBootstrapBody' @(
Assert-PropertyRef $bootstrapBody 'AppGenealogyBootstrapBody' 'accessPreset' '#/components/schemas/GenealogyAccessPreset'
Assert-PropertyRef $bootstrapBody 'AppGenealogyBootstrapBody' 'rootPerson' '#/components/schemas/AppGenealogyRootPersonBody'
Assert-PropertyRef $bootstrapBody 'AppGenealogyBootstrapBody' 'regionCode' '#/components/schemas/GenealogyRegionCode'
Assert-StringField $bootstrapBody 'AppGenealogyBootstrapBody' 'genealogyName' 1 24
Assert-PropertyRef $bootstrapBody 'AppGenealogyBootstrapBody' 'genealogyName' '#/components/schemas/GenealogyName'
Assert-StringField $bootstrapBody 'AppGenealogyBootstrapBody' 'surname' 1 4
Assert-StringField $bootstrapBody 'AppGenealogyBootstrapBody' 'ancestralHall' 1 12
@@ -631,17 +658,20 @@ if ($regionCode) {
if ($accessPreset) {
$values = @($accessPreset.enum | Sort-Object)
$expected = @('MEMBER_ONLY', 'PUBLIC_APPLY') | Sort-Object
$nullable = $accessPreset.PSObject.Properties['nullable']
if ($accessPreset.type -ne 'string' -or -not (Test-IsJsonArray $accessPreset.enum) -or
($values -join ',') -ne ($expected -join ',')) {
Add-Issue 'JSON GenealogyAccessPreset must contain only MEMBER_ONLY/PUBLIC_APPLY'
($values -join ',') -ne ($expected -join ',') -or
($nullable -and -not (Test-IsJsonBoolean $nullable.Value $false))) {
Add-Issue 'JSON GenealogyAccessPreset must be non-null and contain only MEMBER_ONLY/PUBLIC_APPLY'
}
foreach ($keyword in @('not', 'allOf', 'anyOf', 'oneOf', 'const')) {
if ($accessPreset.PSObject.Properties[$keyword]) {
Add-Issue "JSON GenealogyAccessPreset must not define conflicting schema keyword: $keyword"
}
}
Assert-AllowedSchemaKeywords $accessPreset 'GenealogyAccessPreset' @('type', 'enum', 'nullable')
}
Assert-ExactObject $settingsBody 'AppGenealogySettingsUpdateBody' @(
'genealogyName', 'intro', 'accessPreset'
) @() 1 3
Assert-PropertyRef $settingsBody 'AppGenealogySettingsUpdateBody' 'accessPreset' '#/components/schemas/GenealogyAccessPreset'
foreach ($legacySchema in @('GenealogyCreateBody', 'AppGenealogyCreateBody', 'GenealogyUpdateBody', 'AppGenealogyUpdateBody')) {
if ($document.components.schemas.PSObject.Properties[$legacySchema]) {
Add-Issue "JSON legacy schema must be removed in the same migration: $legacySchema"
@@ -673,9 +703,7 @@ foreach ($schemaProperty in $document.components.schemas.PSObject.Properties) {
}
}
foreach ($presetDefinition in @(Get-ComposedPropertyDefinitions $schemaProperty.Value 'accessPreset' @{})) {
if ([string]$presetDefinition.'$ref' -ne '#/components/schemas/GenealogyAccessPreset') {
Add-Issue "JSON $($schemaProperty.Name).accessPreset must reference GenealogyAccessPreset"
}
[void](Test-IsPureSchemaRef $presetDefinition '#/components/schemas/GenealogyAccessPreset' "$($schemaProperty.Name).accessPreset")
}
}
@@ -967,7 +995,6 @@ foreach ($status in $statusErrorRefs.Keys) {
Assert-RetryAfter $statusResponses['404'] "GET $statusPath" '404'
Assert-RetryAfter $statusResponses['429'] "GET $statusPath" '429'
Assert-AllDeclaredResponsesPrivateJson $settingsOperation "PUT $settingsPath"
Assert-AllDeclaredResponsesPrivateJson $personOperation "POST $personPath"
Assert-AllDeclaredResponsesPrivateJson $personUpdateOperation "PUT $personDetailPath"
Assert-AllDeclaredResponsesPrivateJson $personDeleteOperation "DELETE $personDetailPath"