Tests/PSAIPerplexityDecisions.Tests.ps1
|
# Import the local module and mock its HTTP boundary so every test stays offline. BeforeAll { # Resolve the manifest from this test file's repository location. $modulePath = Join-Path (Split-Path $PSScriptRoot -Parent) 'PSAIPerplexityDecisions.psd1' # Import the module under test into the Pester session. Import-Module $modulePath -Force # Provide a harmless placeholder key that must never be sent to a real service. $env:PERPLEXITY_API_KEY = 'test-key-never-used-for-network' # Define a reusable valid request fixture from the documented request schema. $testState = @{ item = 'Headphones'; review = 'The battery stopped charging.' } $testQuestions = @{ defect = @{ type = 'noul'; instructions = 'Does this report a defect?' } sentiment = @{ type = 'choice' instructions = 'What is the sentiment?' criteria = @{ positive = 'Mostly satisfied'; negative = 'Mostly dissatisfied' } } severity = @{ type = 'score' instructions = 'How severe is the problem?' criteria = @('Minor', 'Major') } } } # Verify the command sends the documented request and returns the parsed answer object. Describe 'Invoke-PerplexityDecision' { # Return a representative typed response without contacting the API. BeforeEach { Mock Invoke-RestMethod -ModuleName PSAIPerplexityDecisions { [pscustomobject] @{ answers = [pscustomobject] @{ defect = [pscustomobject] @{ noul = 0.9 } } } } } # Confirm endpoint, model, request data, bearer header, and timeout. It 'posts the documented payload and returns the API response' { $result = Invoke-PerplexityDecision -State $testState -Questions $testQuestions -TimeoutSec 45 $result.answers.defect.noul | Should -Be 0.9 Should -Invoke Invoke-RestMethod -ModuleName PSAIPerplexityDecisions -Times 1 -ParameterFilter { $Uri -eq 'https://api.perplexity.ai/v1/decisions' -and $Method -eq 'Post' -and $Headers.Authorization -eq 'Bearer test-key-never-used-for-network' -and $ContentType -eq 'application/json' -and $TimeoutSec -eq 45 -and (($Body | ConvertFrom-Json).model -eq 'pplx-decider-v1-27b') -and (($Body | ConvertFrom-Json).questions.defect.type -eq 'noul') } } # Verify that PowerShell question helpers are converted to the same named wire map. It 'converts helper question objects to the documented API question fields' { $questions = @( New-PerplexityYesNoQuestion -Name urgent -Question 'Does this need attention today?' New-PerplexityDecisionQuestion -Name route -Type Choice ` -Instructions 'Which team should handle this?' ` -Criteria @{ support = 'Technical issue'; sales = 'Pricing or renewal issue' } ) $null = Invoke-PerplexityDecision -State $testState -Question $questions Should -Invoke Invoke-RestMethod -ModuleName PSAIPerplexityDecisions -Times 1 -ParameterFilter { $payload = $Body | ConvertFrom-Json $payload.questions.urgent.type -eq 'noul' -and $payload.questions.urgent.instructions -eq 'Does this need attention today?' -and $null -eq $payload.questions.urgent.criteria -and $payload.questions.route.type -eq 'choice' -and $payload.questions.route.criteria.support -eq 'Technical issue' } } # Ensure the helper does not add Jev-only criteria to the Perplexity Noul contract. It 'rejects criteria on a Noul question' { { New-PerplexityDecisionQuestion -Name binary -Type Noul ` -Instructions 'Is the issue urgent?' ` -Criteria @{ true = 'Urgent'; false = 'Not urgent' } } | Should -Throw '*does not accept Criteria in the Perplexity contract*' } # Reject unsupported question types before the mocked HTTP command is reached. It 'rejects an unsupported question type without making an HTTP request' { $invalidQuestions = @{ invalid = @{ type = 'text'; instructions = 'Unsupported type.' } } { Invoke-PerplexityDecision -State $testState -Questions $invalidQuestions } | Should -Throw "*Question 'invalid' type must be noul, choice, or score.*" Should -Invoke Invoke-RestMethod -ModuleName PSAIPerplexityDecisions -Times 0 } # Return a synthetic HTTP error and make sure its status reaches the caller. It 'includes the HTTP status in a useful terminating error' { Mock Invoke-RestMethod -ModuleName PSAIPerplexityDecisions { $response = [System.Net.Http.HttpResponseMessage]::new([System.Net.HttpStatusCode]::BadRequest) $response.Content = [System.Net.Http.StringContent]::new('{"error":"invalid request"}') $exception = [System.Net.Http.HttpRequestException]::new('Bad request') $exception | Add-Member -MemberType NoteProperty -Name Response -Value $response -Force throw $exception } { Invoke-PerplexityDecision -State $testState -Questions $testQuestions } | Should -Throw '*HTTP 400*invalid request*' } # Ensure the missing-key path fails before any request is attempted. It 'requires the environment key before making an HTTP request' { $previousKey = $env:PERPLEXITY_API_KEY Remove-Item Env:PERPLEXITY_API_KEY -ErrorAction SilentlyContinue try { { Invoke-PerplexityDecision -State $testState -Questions $testQuestions } | Should -Throw '*PERPLEXITY_API_KEY is not set*' Should -Invoke Invoke-RestMethod -ModuleName PSAIPerplexityDecisions -Times 0 } finally { $env:PERPLEXITY_API_KEY = $previousKey } } } |