tests/Get-MsecAzureDevOpsSecureFile.Tests.ps1
|
#Requires -Module Pester # # Tests for Get-MsecAzureDevOpsSecureFile. # # The one thing that must never change: this command does not call the download endpoint. Secure # files hold signing certificates and private keys, and an inventory that fetches them to produce # itself is worse than no inventory. A test asserts no request ever goes near it. # # Kind is a guess from the file extension and is documented as one. The NAME is always returned so # the guess can be checked rather than trusted - a '.key' could be anything. BeforeAll { $modulePath = Join-Path $PSScriptRoot '..' 'msec.psm1' Import-Module $modulePath -Force -ErrorAction Stop } AfterAll { Remove-Module msec -Force -ErrorAction SilentlyContinue } Describe 'Get-MsecAzureDevOpsSecureFile' { BeforeEach { InModuleScope msec { $script:MsecSession = @{ TenantId = 't'; ClientId = 'c'; Tokens = @{} } function script:Set-FileMock { param($Files, $Open = @()) $script:MockFiles = $Files $script:MockOpen = @($Open) Mock Invoke-MsecAzureDevOpsRequest -MockWith { if ($Path -eq '_apis/projects') { return @([pscustomobject]@{ id = 'p1'; name = 'Viedoc4' }) } if ($Path -match '/securefiles$') { return $script:MockFiles } if ($Path -match '/pipelinePermissions/securefile/(\d+)$') { if ($script:MockOpen -contains [int] $Matches[1]) { return [pscustomobject]@{ allPipelines = [pscustomobject]@{ authorized = $true }; pipelines = @(1) } } return [pscustomobject]@{ pipelines = @(1) } } return @() } } function script:New-File { param([int] $Id, [string] $Name, [int] $AgeDays = 10) [pscustomobject]@{ id = $Id; name = $Name createdOn = [datetime]::UtcNow.AddDays(-$AgeDays) modifiedOn = [datetime]::UtcNow.AddDays(-$AgeDays) createdBy = [pscustomobject]@{ displayName = 'Ada' } } } } } It 'never requests the file contents' { InModuleScope msec { . Set-FileMock -Files @(New-File -Id 1 -Name 'signing.pfx') Get-MsecAzureDevOpsSecureFile -Organization 'contoso' | Out-Null # There IS a download endpoint. An inventory that fetches private keys to list them # would be worse than no inventory. Should -Invoke Invoke-MsecAzureDevOpsRequest -Times 0 -Exactly -ParameterFilter { $Path -match 'securefiles/.+' -and $Path -notmatch 'pipelinePermissions' } } } It 'guesses the kind from the extension and keeps the name' { $rows = InModuleScope msec { . Set-FileMock -Files @( (New-File -Id 1 -Name 'signing.pfx') (New-File -Id 2 -Name 'release.jks') (New-File -Id 3 -Name 'app.mobileprovision') (New-File -Id 4 -Name 'migrations.done.key') (New-File -Id 5 -Name 'notes.txt')) Get-MsecAzureDevOpsSecureFile -Organization 'contoso' } ($rows | Where-Object Name -eq 'signing.pfx').Kind | Should -Be 'Certificate' ($rows | Where-Object Name -eq 'release.jks').Kind | Should -Be 'Keystore' ($rows | Where-Object Name -eq 'app.mobileprovision').Kind | Should -Be 'ProvisioningProfile' # A .key could be anything - it is grouped, not identified, which is why the name stays. ($rows | Where-Object Name -eq 'migrations.done.key').Kind | Should -Be 'Key' ($rows | Where-Object Name -eq 'notes.txt').Kind | Should -Be 'Other' } It 'reports age, because signing material expires' { $rows = InModuleScope msec { . Set-FileMock -Files @(New-File -Id 1 -Name 'old.pfx' -AgeDays 1479) Get-MsecAzureDevOpsSecureFile -Organization 'contoso' } # Measured live: keys uploaded four years ago, still present. $rows.AgeDays | Should -Be 1479 $rows.AgeDays | Should -BeOfType [int] } It 'flags a file any pipeline may use' { $rows = InModuleScope msec { . Set-FileMock -Files @((New-File -Id 7 -Name 'open.pfx'), (New-File -Id 8 -Name 'closed.pfx')) -Open @(7) Get-MsecAzureDevOpsSecureFile -Organization 'contoso' } ($rows | Where-Object Name -eq 'open.pfx').OpenToAllPipelines | Should -BeTrue ($rows | Where-Object Name -eq 'closed.pfx').OpenToAllPipelines | Should -BeFalse } It 'throws a clear error when not connected' { InModuleScope msec { $script:MsecSession = $null { Get-MsecAzureDevOpsSecureFile -Organization 'contoso' } | Should -Throw '*Connect-Msec*' } } } |