Private/Generator/Tests/New-OpenApiGenPlan.Tests.ps1
|
BeforeAll { $env:TCS_CONFIG_ROOT = Join-Path -Path $TestDrive -ChildPath 'config' $env:TCS_SKIP_UPDATE_CHECK = '1' $env:TCS_TELEMETRY_OPTOUT = '1' $ModuleRoot = Split-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -Parent Import-Module -Name (Join-Path -Path $ModuleRoot -ChildPath 'tcs.openapi.psd1') -Force $RepoRoot = Split-Path -Path (Split-Path -Path $ModuleRoot -Parent) -Parent . (Join-Path -Path $RepoRoot -ChildPath 'tests/Helpers/New-TestOpenApiModel.ps1') } Describe 'New-OpenApiGenPlan' { BeforeAll { function New-TestPlan { param($Document, [string]$ModuleName = 'PetStore', [string]$NounPrefix = 'PetStore') InModuleScope tcs.openapi -Parameters @{ Document = $Document; ModuleName = $ModuleName; NounPrefix = $NounPrefix } { param($Document, $ModuleName, $NounPrefix) $option = Resolve-OpenApiGenOption -ModuleName $ModuleName -OutputPath '/out' -NounPrefix $NounPrefix -GeneratorVersion '0.1.0' $templates = Get-OpenApiGenTemplate -Path (Join-Path -Path $script:TcsOpenApiModuleRoot -ChildPath 'Templates') New-OpenApiGenPlan -Document $Document -Option $option -Template $templates } } $petstore = New-TestOpenApiModel -Name petstore $plan = New-TestPlan -Document $petstore } It 'plans the module layout' { $paths = @($plan.Files.RelativePath) foreach ($expected in @('PetStore.psd1', 'PetStore.psm1', 'README.md', 'Overrides.ps1', 'OpenApi/operations.json', 'OpenApi/source.json', 'Public/Pets/Get-PetStorePet.ps1', 'Public/Pets/New-PetStorePet.ps1', 'Public/Store/Get-PetStoreInventory.ps1', 'Public/Default/Get-PetStoreHealth.ps1', 'Public/_Connection/Set-PetStoreContext.ps1', 'Public/_Connection/Get-PetStoreContext.ps1', 'Public/_Connection/Remove-PetStoreContext.ps1')) { $paths | Should -Contain $expected } $plan.ModulePath | Should -Be (Join-Path -Path '/out' -ChildPath 'PetStore') $plan.ManifestPath | Should -Be (Join-Path -Path (Join-Path -Path '/out' -ChildPath 'PetStore') -ChildPath 'PetStore.psd1') } It 'marks Overrides.ps1 as the file that is never overwritten' { ($plan.Files | Where-Object -FilterScript { $_.RelativePath -eq 'Overrides.ps1' }).Kind | Should -Be 'Overrides' } It 'sorts files and functions ordinally' { $paths = @($plan.Files.RelativePath) $sorted = [string[]]$paths.Clone() [System.Array]::Sort($sorted, [System.StringComparer]::Ordinal) $paths | Should -Be $sorted $names = @($plan.Functions.Name) $sortedNames = [string[]]$names.Clone() [System.Array]::Sort($sortedNames, [System.StringComparer]::Ordinal) $names | Should -Be $sortedNames } It 'lists every function with its operation and file' { $pet = $plan.Functions | Where-Object -FilterScript { $_.OperationId -eq 'showPetById' } $pet.Name | Should -Be 'Get-PetStorePetById' $pet.File | Should -Be 'Public/Pets/Get-PetStorePetById.ps1' ($plan.Functions | Where-Object -FilterScript { $_.OperationId -eq 'petStats' }).Name | Should -Be 'Measure-PetStatistic' ($plan.Functions | Where-Object -FilterScript { $_.OperationId -eq 'getHealth' }).Name | Should -Be 'Get-PetStoreHealth' } It 'exports every function in the manifest' { $manifest = ($plan.Files | Where-Object -FilterScript { $_.Kind -eq 'Manifest' }).Content foreach ($name in $plan.Functions.Name) { $manifest | Should -Match ([regex]::Escape("'$name'")) } } It 'writes operation metadata sorted by operationId' { $json = ($plan.Files | Where-Object -FilterScript { $_.RelativePath -eq 'OpenApi/operations.json' }).Content $operations = $json | ConvertFrom-Json @($operations.OperationId) | Should -Be @('createPets', 'deletePet', 'getHealth', 'getInventory', 'getPetPhoto', 'listPets', 'petStats', 'showPetById', 'updatePet') } It 'writes the normalised document to source.json' { $json = ($plan.Files | Where-Object -FilterScript { $_.RelativePath -eq 'OpenApi/source.json' }).Content ($json | ConvertFrom-Json).Title | Should -Be 'Swagger Petstore' } It 'is deterministic' { $again = New-TestPlan -Document (New-TestOpenApiModel -Name petstore) for ($i = 0; $i -lt $plan.Files.Count; $i++) { $again.Files[$i].RelativePath | Should -BeExactly $plan.Files[$i].RelativePath $again.Files[$i].Content | Should -BeExactly $plan.Files[$i].Content } } It 'uses LF line endings only' { foreach ($file in $plan.Files) { $file.Content | Should -Not -Match "`r" } } It 'skips unsupported operations with OA070' { $reservedPlan = New-TestPlan -Document (New-TestOpenApiModel -Name reserved) -ModuleName 'Reserved' -NounPrefix '' $reservedPlan.Skipped.OperationId | Should -Be @('getRemote') ($reservedPlan.Findings | Where-Object -FilterScript { $_.Code -eq 'OA070' }).Operation | Should -Be 'getRemote' $reservedPlan.Functions.OperationId | Should -Not -Contain 'getRemote' } It 'collects OA040 and OA041 findings' { $reservedPlan = New-TestPlan -Document (New-TestOpenApiModel -Name reserved) -ModuleName 'Reserved' -NounPrefix '' @($reservedPlan.Findings | Where-Object -FilterScript { $_.Code -eq 'OA040' }).Count | Should -Be 3 @($reservedPlan.Findings | Where-Object -FilterScript { $_.Code -eq 'OA041' }).Count | Should -BeGreaterThan 3 } It 'skips an operation whose function does not bind, with an OA070 error' { InModuleScope tcs.openapi { Mock Test-OpenApiGenFunction { [pscustomobject]@{ IsValid = $false; Errors = @('Bind check failed: broken'); ParameterNames = @(); ParameterSets = @(); Syntax = $null } } -ParameterFilter { $FunctionName -eq 'Get-PetStorePet' } } $broken = New-TestPlan -Document $petstore $broken.Skipped.OperationId | Should -Be @('listPets') $finding = $broken.Findings | Where-Object -FilterScript { $_.Code -eq 'OA070' } $finding.Severity | Should -Be 'Error' $finding.Message | Should -Match 'broken' $broken.Functions.Name | Should -Not -Contain 'Get-PetStorePet' ($broken.Files | Where-Object -FilterScript { $_.RelativePath -eq 'OpenApi/operations.json' }).Content | Should -Not -Match 'listPets' } It 'never gives a command the name of an engine command or a connection command' { $document = New-TestDocument -Operations @( (New-TestOperation -OperationId 'invokeOpenApiRequest' -Method POST -Path '/x'), (New-TestOperation -OperationId 'getShopContext' -Method GET -Path '/context') ) $names = (New-TestPlan -Document $document -ModuleName 'Shop' -NounPrefix '').Functions.Name $names | Should -Contain 'Invoke-OpenApiRequestX' $names | Should -Contain 'Get-ShopContextContext' } It 'skips an operation without an operationId or with a duplicate one' { $document = New-TestDocument -Operations @( (New-TestOperation -OperationId 'getA' -Method GET -Path '/a'), (New-TestOperation -OperationId 'getA' -Method GET -Path '/b'), (New-TestOperation -OperationId '' -Method GET -Path '/c') ) $result = New-TestPlan -Document $document -ModuleName 'Dup' -NounPrefix '' $result.Functions.OperationId | Should -Contain 'getA' @($result.Skipped.Path) | Should -Be @('/b', '/c') @($result.Findings | Where-Object -FilterScript { $_.Code -eq 'OA070' -and $_.Severity -eq 'Error' }).Count | Should -Be 2 } It 'plans a module for a document without operations' { $empty = New-TestPlan -Document (New-TestDocument) -ModuleName 'Empty' -NounPrefix '' ($empty.Files | Where-Object -FilterScript { $_.RelativePath -eq 'OpenApi/operations.json' }).Content | Should -Be "[]`n" $empty.Functions.Count | Should -Be 3 } It 'never shadows a core PowerShell command when there is no noun prefix (OA042)' { $document = New-TestDocument -Operations @( (New-TestOperation -OperationId 'listItems' -Method GET -Path '/items'), (New-TestOperation -OperationId 'createItem' -Method POST -Path '/items'), (New-TestOperation -OperationId 'getWidget' -Method GET -Path '/widgets') ) $noPrefix = New-TestPlan -Document $document -ModuleName 'Modern' -NounPrefix '' @($noPrefix.Functions.Name) | Should -Contain 'Get-ModernItem' @($noPrefix.Functions.Name) | Should -Contain 'New-ModernItem' @($noPrefix.Functions.Name) | Should -Contain 'Get-Widget' @($noPrefix.Functions.Name) | Should -Not -Contain 'Get-Item' @($noPrefix.Findings | Where-Object -FilterScript { $_.Code -eq 'OA042' }).Count | Should -Be 2 $prefixed = New-TestPlan -Document $document -ModuleName 'Modern' -NounPrefix 'Md' @($prefixed.Functions.Name) | Should -Contain 'Get-MdItem' @($prefixed.Findings | Where-Object -FilterScript { $_.Code -eq 'OA042' }).Count | Should -Be 0 } } |