完成50%
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
$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')
|
||||
$issues = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
function Add-Issue {
|
||||
param([string]$Message)
|
||||
$script:issues.Add($Message)
|
||||
}
|
||||
|
||||
function Get-Post {
|
||||
param([string]$Path)
|
||||
$pathProperty = $document.paths.PSObject.Properties[$Path]
|
||||
if (-not $pathProperty -or -not $pathProperty.Value.post) {
|
||||
Add-Issue "JSON missing POST $Path"
|
||||
return $null
|
||||
}
|
||||
return $pathProperty.Value.post
|
||||
}
|
||||
|
||||
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-SecurityAndClient {
|
||||
param([object]$Operation, [string]$Label)
|
||||
if (-not $Operation) { return }
|
||||
$hasSaToken = $false
|
||||
foreach ($requirement in @($Operation.security)) {
|
||||
if ($requirement.PSObject.Properties.Name -contains 'SaToken') { $hasSaToken = $true }
|
||||
}
|
||||
if (-not $hasSaToken) { Add-Issue "JSON $Label must require SaToken" }
|
||||
$client = @($Operation.parameters | Where-Object { $_.name -eq 'clientid' -and $_.in -eq 'header' })
|
||||
if ($client.Count -ne 1 -or $client[0].required -ne $true -or $client[0].schema.type -ne 'string') {
|
||||
Add-Issue "JSON $Label must require one string clientid header"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ResponseRef {
|
||||
param([object]$Operation, [string]$Label)
|
||||
if (-not $Operation) { return '' }
|
||||
$response = $Operation.responses.PSObject.Properties['200'].Value
|
||||
if (-not $response) {
|
||||
Add-Issue "JSON $Label missing 200 response"
|
||||
return ''
|
||||
}
|
||||
if ($response.'$ref') {
|
||||
$responseName = ([string]$response.'$ref').Split('/')[-1]
|
||||
$response = $document.components.responses.PSObject.Properties[$responseName].Value
|
||||
}
|
||||
$media = @($response.content.PSObject.Properties)
|
||||
if ($media.Count -eq 0) {
|
||||
Add-Issue "JSON $Label missing response content"
|
||||
return ''
|
||||
}
|
||||
return [string]$media[0].Value.schema.'$ref'
|
||||
}
|
||||
|
||||
$singlePath = '/genealogy/app/notifications/{notificationId}/read'
|
||||
$allPath = '/genealogy/app/notifications/read-all'
|
||||
$notificationIdPattern = '^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$'
|
||||
$single = Get-Post $singlePath
|
||||
$all = Get-Post $allPath
|
||||
Assert-SecurityAndClient $single "POST $singlePath"
|
||||
Assert-SecurityAndClient $all "POST $allPath"
|
||||
|
||||
if ((Get-ResponseRef $single "POST $singlePath") -ne '#/components/schemas/RVoid') {
|
||||
Add-Issue "JSON POST $singlePath must return RVoid"
|
||||
}
|
||||
if ((Get-ResponseRef $all "POST $allPath") -ne '#/components/schemas/RVoid') {
|
||||
Add-Issue "JSON POST $allPath must return RVoid"
|
||||
}
|
||||
|
||||
if ($single) {
|
||||
$ids = @($single.parameters | Where-Object { $_.name -eq 'notificationId' -and $_.in -eq 'path' })
|
||||
if ($ids.Count -ne 1 -or $ids[0].required -ne $true -or $ids[0].schema.type -ne 'string' -or
|
||||
$ids[0].schema.minLength -ne 1 -or $ids[0].schema.maxLength -ne 128 -or
|
||||
$ids[0].schema.pattern -ne $notificationIdPattern) {
|
||||
Add-Issue 'JSON notificationId path must be a required 1..128 URL-safe opaque string'
|
||||
}
|
||||
if ($single.requestBody) { Add-Issue 'JSON mark-read operation must not accept a request body' }
|
||||
}
|
||||
if ($all -and $all.requestBody) { Add-Issue 'JSON read-all operation must not accept a request body' }
|
||||
|
||||
$void = Get-Schema 'RVoid'
|
||||
if ($void) {
|
||||
if ('code' -notin @($void.required) -or $void.properties.code.type -ne 'integer') {
|
||||
Add-Issue 'JSON RVoid must require integer code'
|
||||
}
|
||||
}
|
||||
|
||||
$notice = Get-Schema 'NotificationVo'
|
||||
if ($notice) {
|
||||
if ('notificationId' -notin @($notice.required)) {
|
||||
Add-Issue 'JSON NotificationVo.required missing: notificationId'
|
||||
}
|
||||
$id = $notice.properties.notificationId
|
||||
if ($id.type -ne 'string' -or [int]$id.minLength -ne 1 -or [int]$id.maxLength -ne 128 -or
|
||||
$id.pattern -ne $notificationIdPattern) {
|
||||
Add-Issue 'JSON NotificationVo.notificationId must be the same 1..128 URL-safe opaque string'
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($yamlFact in @(
|
||||
' /genealogy/app/notifications/{notificationId}/read:',
|
||||
' /genealogy/app/notifications/read-all:',
|
||||
'#/components/schemas/RVoid',
|
||||
' NotificationVo:',
|
||||
' RVoid:',
|
||||
' - notificationId',
|
||||
' - code'
|
||||
)) {
|
||||
if (-not $yaml.Contains($yamlFact)) { Add-Issue "YAML fact is missing: $yamlFact" }
|
||||
}
|
||||
|
||||
if ($issues.Count -gt 0) {
|
||||
$lines = New-Object System.Collections.Generic.List[string]
|
||||
$lines.Add('NOTIFICATION-READ-STATE-OPENAPI-CONTRACT BLOCKED')
|
||||
foreach ($issue in $issues) { $lines.Add("- $issue") }
|
||||
$lines.Add('- Both operations must be idempotent for the current account; repeated calls return success without duplicate side effects.')
|
||||
$lines.Add('- The backend must define cross-account 403/404 behavior, read-all cutoff semantics, concurrent new-message behavior, and count/list refresh rules.')
|
||||
$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 'NOTIFICATION-READ-STATE-OPENAPI-CONTRACT PASS'
|
||||
Reference in New Issue
Block a user