Public/Tests/New-FunctionsFromSwagger.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 $PSScriptRoot -Parent) -Parent
    Import-Module -Name (Join-Path -Path $ModuleRoot -ChildPath 'tcs.utils.psd1') -Force
}

AfterAll {
    Remove-Module -Name tcs.utils -Force -ErrorAction SilentlyContinue
}

Describe 'New-FunctionsFromSwagger' {
    BeforeAll {
        function Get-ParseErrorCount {
            param([string]$Path)
            $tokens = $null
            $errors = $null
            $null = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$tokens, [ref]$errors)
            @($errors).Count
        }

        $SwaggerPath = Join-Path -Path $TestDrive -ChildPath 'swagger.json'
        @'
{
  "openapi": "3.0.1",
  "info": { "title": "Pet API", "version": "1.0" },
  "paths": {
    "/api/pets": {
      "get": { "operationId": "ListPets", "summary": "List pets",
        "parameters": [ { "name": "limit", "in": "query", "schema": { "type": "integer" } },
                        { "name": "kind", "in": "query", "schema": { "type": "string", "enum": ["cat", "dog"] } } ],
        "responses": { "200": { "description": "ok" } } },
      "post": { "operationId": "CreatePet", "summary": "Create a pet",
        "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } } } },
        "responses": { "200": { "description": "ok" } } }
    },
    "/api/pets/{petId}": {
      "get": { "operationId": "GetPet", "summary": "Get a pet",
        "parameters": [ { "name": "petId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } } ],
        "responses": { "200": { "description": "ok" } } },
      "delete": { "operationId": "DeletePet", "summary": "Delete a pet",
        "parameters": [ { "name": "petId", "in": "path", "required": true, "schema": { "type": "string" } } ],
        "responses": { "200": { "description": "ok" } } }
    }
  },
  "components": { "schemas": { "Pet": { "type": "object", "required": ["name"],
    "properties": { "name": { "type": "string", "description": "Pet name" }, "age": { "type": "integer" },
                    "kind": { "type": "string", "enum": ["cat", "dog"] } } } } }
}
'@
 | Set-Content -Path $SwaggerPath

        # Target module to generate into, and a snapshot of tcs.utils to prove nothing is written there
        $TargetModule = Join-Path -Path $TestDrive -ChildPath 'pet.api'
        $null = New-Item -Path (Join-Path -Path $TargetModule -ChildPath 'Public') -ItemType Directory -Force
        $null = New-Item -Path (Join-Path -Path $TargetModule -ChildPath 'Private') -ItemType Directory -Force
        Set-Content -Path (Join-Path -Path $TargetModule -ChildPath 'pet.api.psd1') -Value "@{`n RootModule = 'pet.api.psm1'`n ModuleVersion = '0.1.0'`n FunctionsToExport = @()`n}`n"
        $ModuleSnapshot = @(Get-ChildItem -Path $ModuleRoot -Recurse -Force | ForEach-Object FullName | Sort-Object)

        $GeneratorOutput = New-FunctionsFromSwagger -SwaggerPath $SwaggerPath -NounPrefix 'Pet' -StripPathPrefix '/api' -ModuleRoot $TargetModule -WarningAction SilentlyContinue
        $FunctionFolder = Join-Path -Path $TargetModule -ChildPath 'Public/Generated/Functions'
        $GeneratedFiles = @(Get-ChildItem -Path $TargetModule -Recurse -Filter '*.ps1')
    }

    It 'Generates wrapper and helper functions under the target module' {
        $names = @(Get-ChildItem -Path $FunctionFolder -Filter '*.ps1' | ForEach-Object BaseName)
        $names | Should -Contain 'Invoke-PetApi'
        $names | Should -Contain 'Get-PetPetsPetId'
        $names | Should -Contain 'Remove-PetPetsPetId'
        Test-Path -Path (Join-Path -Path $TargetModule -ChildPath 'Private/Generated/SwaggerParamValidation.ps1') | Should -BeTrue
        $GeneratorOutput | Should -Not -BeNullOrEmpty
    }

    It 'Generates files that parse without errors' {
        $GeneratedFiles.Count | Should -BeGreaterThan 0
        foreach ($file in $GeneratedFiles) {
            Get-ParseErrorCount -Path $file.FullName | Should -Be 0 -Because "$($file.Name) should parse"
        }
    }

    It 'Generates a case-insensitive ValidateSet for string enums' {
        Get-Content -Path (Join-Path -Path $FunctionFolder -ChildPath 'Find-PetPets.ps1') -Raw | Should -Match ([regex]::Escape("[ValidateSet('cat', 'dog', IgnoreCase=`$true)]"))
    }

    It 'Refreshes FunctionsToExport in the target module manifest' {
        $manifest = Import-PowerShellDataFile -Path (Join-Path -Path $TargetModule -ChildPath 'pet.api.psd1')
        $manifest.FunctionsToExport | Should -Contain 'Invoke-PetApi'
    }

    It 'Does not write into the tcs.utils module folder' {
        @(Get-ChildItem -Path $ModuleRoot -Recurse -Force | ForEach-Object FullName | Sort-Object) | Should -Be $ModuleSnapshot
    }

    It 'Refuses to overwrite existing generated files unless -UpdateExistingOnly or -PreviewOnly is used' {
        { New-FunctionsFromSwagger -SwaggerPath $SwaggerPath -NounPrefix 'Pet' -StripPathPrefix '/api' -ModuleRoot $TargetModule -WarningAction SilentlyContinue } | Should -Throw '*already contains*'
    }

    It 'Throws when the swagger file does not exist' {
        { New-FunctionsFromSwagger -SwaggerPath (Join-Path -Path $TestDrive -ChildPath 'missing.json') -NounPrefix 'Pet' -ModuleRoot $TargetModule } | Should -Throw '*not found*'
    }
}