Tests/ntp/Get-NTPPeer.Tests.ps1
|
#Requires -Version 5.1 #Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } <# .SYNOPSIS Pester v5 tests for the Get-NTPPeer function .DESCRIPTION Comprehensive test coverage for Get-NTPPeer including happy path with English and French locale output, zero peers, pipeline input, per-machine failure isolation, parameter validation, and property type assertions. All external calls (Invoke-Command) are mocked. No real network or w32tm access is required. .EXAMPLE Invoke-Pester -Path .\Get-NTPPeer.Tests.ps1 -Output Detailed Runs all tests with detailed output. .EXAMPLE Invoke-Pester -Path .\Get-NTPPeer.Tests.ps1 -Output Detailed -Tag 'HappyPath' Runs only happy-path tests. .NOTES Author: Franck SALLET (k9fr4n) Version: 1.0.0 Last Modified: 2026-03-12 Requires: Pester 5.x, PowerShell 5.1+ Permissions: None required (all external commands are mocked) #> BeforeAll { # Dot-source the function under test $script:functionPath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Public\ntp\Get-NTPPeer.ps1' . $script:functionPath #region Mock data -- 2 peers (English) $script:mockTwoPeersEN = @( '#Peers: 2' '' 'Peer: ntp1.example.com,0x8' 'State: Active' 'Time Remaining: 512.34s' 'Last Successful Sync Time: 3/12/2026 8:00:00 PM' 'Poll Interval: 10 (1024s)' '' 'Peer: ntp2.example.com,0x8' 'State: Pending' 'Time Remaining: 1024.00s' 'Last Successful Sync Time: 3/12/2026 7:30:00 PM' 'Poll Interval: 10 (1024s)' ) #endregion #region Mock data -- 0 peers $script:mockZeroPeers = @( '#Peers: 0' ) #endregion #region Mock data -- 1 peer (French) $script:mockOnePeerFR = @( '#Homologues : 1' '' 'Homologue : ntp-fr.pool.ntp.org,0x8' 'Etat : Actif' 'Temps restant : 256.12s' 'Heure de la derniere synchronisation reussie : 12/03/2026 20:00:00' 'Intervalle d''interrogation : 10 (1024s)' ) #endregion } Describe -Name 'Get-NTPPeer' -Fixture { # ------------------------------------------------------------------- # Context 1: Happy path -- local machine, 2 English peers # ------------------------------------------------------------------- Context -Name 'Happy path - local machine with 2 English peers' -Tag 'HappyPath' -Fixture { BeforeAll { Mock -CommandName 'Invoke-Command' -MockWith { return $script:mockTwoPeersEN } } It -Name 'Should return exactly 2 peer objects' -Test { $result = @(Get-NTPPeer) $result.Count | Should -Be 2 } It -Name 'Should set ComputerName to the local machine name' -Test { $result = @(Get-NTPPeer) $result[0].ComputerName | Should -Be $env:COMPUTERNAME $result[1].ComputerName | Should -Be $env:COMPUTERNAME } It -Name 'Should return objects with all expected properties' -Test { $result = @(Get-NTPPeer) $expectedProps = @( 'ComputerName', 'PeerName', 'PeerFlags', 'State', 'TimeRemaining', 'LastSyncTime', 'PollInterval', 'Timestamp' ) foreach ($prop in $expectedProps) { $result[0].PSObject.Properties.Name | Should -Contain $prop } } It -Name 'Should have PSTypeName PSWinOps.NTPPeer' -Test { $result = @(Get-NTPPeer) $result[0].PSObject.TypeNames | Should -Contain 'PSWinOps.NTPPeer' } It -Name 'Should invoke Invoke-Command exactly once' -Test { Get-NTPPeer | Out-Null Should -Invoke -CommandName 'Invoke-Command' -Times 1 -Exactly } It -Name 'First peer should be ntp1.example.com with Active state' -Test { $result = @(Get-NTPPeer) $result[0].PeerName | Should -Be 'ntp1.example.com' $result[0].State | Should -Be 'Active' } It -Name 'Second peer should be ntp2.example.com with Pending state' -Test { $result = @(Get-NTPPeer) $result[1].PeerName | Should -Be 'ntp2.example.com' $result[1].State | Should -Be 'Pending' } } # ------------------------------------------------------------------- # Context 2: Happy path -- remote machine # ------------------------------------------------------------------- Context -Name 'Happy path - remote machine' -Tag 'HappyPath' -Fixture { BeforeAll { Mock -CommandName 'Invoke-Command' -MockWith { return $script:mockTwoPeersEN } -ParameterFilter { $ComputerName -eq 'REMOTE01' } } It -Name 'Should set ComputerName to the remote machine name' -Test { $result = @(Get-NTPPeer -ComputerName 'REMOTE01') $result[0].ComputerName | Should -Be 'REMOTE01' $result[1].ComputerName | Should -Be 'REMOTE01' } It -Name 'Should invoke Invoke-Command with -ComputerName parameter' -Test { Get-NTPPeer -ComputerName 'REMOTE01' | Out-Null Should -Invoke -CommandName 'Invoke-Command' -Times 1 -Exactly -ParameterFilter { $ComputerName -eq 'REMOTE01' } } } # ------------------------------------------------------------------- # Context 3: Pipeline input -- multiple machines # ------------------------------------------------------------------- Context -Name 'Pipeline input - multiple machines' -Fixture { BeforeAll { Mock -CommandName 'Invoke-Command' -MockWith { return $script:mockTwoPeersEN } } It -Name 'Should return peers for each machine in the pipeline' -Test { $result = @('ALPHA', 'BRAVO' | Get-NTPPeer) $result.Count | Should -Be 4 } It -Name 'Should contain correct ComputerName values from pipeline' -Test { $result = @('ALPHA', 'BRAVO' | Get-NTPPeer) ($result | Where-Object { $_.ComputerName -eq 'ALPHA' }).Count | Should -Be 2 ($result | Where-Object { $_.ComputerName -eq 'BRAVO' }).Count | Should -Be 2 } It -Name 'Should invoke Invoke-Command once per machine' -Test { 'ALPHA', 'BRAVO' | Get-NTPPeer | Out-Null Should -Invoke -CommandName 'Invoke-Command' -Times 2 -Exactly } } # ------------------------------------------------------------------- # Context 4: Zero peers configured # ------------------------------------------------------------------- Context -Name 'Zero peers configured' -Fixture { BeforeAll { Mock -CommandName 'Invoke-Command' -MockWith { return $script:mockZeroPeers } } It -Name 'Should not emit any objects' -Test { $result = @(Get-NTPPeer -ComputerName $env:COMPUTERNAME) $result.Count | Should -Be 0 } It -Name 'Should emit a warning about no peers' -Test { Get-NTPPeer -ComputerName $env:COMPUTERNAME -WarningVariable 'warnMsg' -WarningAction SilentlyContinue | Out-Null $warnMsg | Should -Not -BeNullOrEmpty $warnMsg[0] | Should -Match 'No NTP peers configured' } } # ------------------------------------------------------------------- # Context 5: French locale output # ------------------------------------------------------------------- Context -Name 'French locale output - 1 peer' -Tag 'Locale' -Fixture { BeforeAll { Mock -CommandName 'Invoke-Command' -MockWith { return $script:mockOnePeerFR } } It -Name 'Should return exactly 1 peer object' -Test { $result = @(Get-NTPPeer) $result.Count | Should -Be 1 } It -Name 'Should parse PeerName correctly from French output' -Test { $result = @(Get-NTPPeer) $result[0].PeerName | Should -Be 'ntp-fr.pool.ntp.org' } It -Name 'Should parse PeerFlags correctly from French output' -Test { $result = @(Get-NTPPeer) $result[0].PeerFlags | Should -Be '0x8' } It -Name 'Should parse State from French output' -Test { $result = @(Get-NTPPeer) $result[0].State | Should -Be 'Actif' } It -Name 'Should parse TimeRemaining as double from French output' -Test { $result = @(Get-NTPPeer) $result[0].TimeRemaining | Should -Be 256.12 } It -Name 'Should parse PollInterval as int from French output' -Test { $result = @(Get-NTPPeer) $result[0].PollInterval | Should -Be 10 } } # ------------------------------------------------------------------- # Context 6: Per-machine failure isolation # ------------------------------------------------------------------- Context -Name 'Per-machine failure isolation' -Fixture { BeforeAll { # Default mock returns data for any call (covers local path) Mock -CommandName 'Invoke-Command' -MockWith { return $script:mockTwoPeersEN } # Specific mock for BADSERVER throws Mock -CommandName 'Invoke-Command' -MockWith { throw 'Connection refused' } -ParameterFilter { $ComputerName -eq 'BADSERVER' } } It -Name 'Should return peers for the good machine despite bad machine error' -Test { $result = @(Get-NTPPeer -ComputerName 'BADSERVER', $env:COMPUTERNAME -ErrorAction SilentlyContinue) $result.Count | Should -Be 2 } It -Name 'Should write a non-terminating error for the bad machine' -Test { $result = @(Get-NTPPeer -ComputerName 'BADSERVER', $env:COMPUTERNAME -ErrorVariable 'errVar' -ErrorAction SilentlyContinue) $errVar | Should -Not -BeNullOrEmpty "$errVar" | Should -Match 'BADSERVER' $result.Count | Should -Be 2 } It -Name 'Should continue processing after the failed machine' -Test { Get-NTPPeer -ComputerName 'BADSERVER', $env:COMPUTERNAME -ErrorAction SilentlyContinue | Out-Null Should -Invoke -CommandName 'Invoke-Command' -Times 2 -Exactly } } # ------------------------------------------------------------------- # Context 7: Parameter validation # ------------------------------------------------------------------- Context -Name 'Parameter validation' -Fixture { It -Name 'Should throw on empty string ComputerName' -Test { { Get-NTPPeer -ComputerName '' } | Should -Throw } It -Name 'Should throw on null ComputerName' -Test { { Get-NTPPeer -ComputerName $null } | Should -Throw } It -Name 'Should throw on empty array element' -Test { { Get-NTPPeer -ComputerName @('') } | Should -Throw } } # ------------------------------------------------------------------- # Context 8: Peer properties validation # ------------------------------------------------------------------- Context -Name 'Peer properties type and value validation' -Fixture { BeforeAll { Mock -CommandName 'Invoke-Command' -MockWith { return $script:mockTwoPeersEN } } It -Name 'PeerName should have flags stripped (no comma or 0x)' -Test { $result = @(Get-NTPPeer) $result[0].PeerName | Should -Not -Match ',0x' $result[0].PeerName | Should -Be 'ntp1.example.com' } It -Name 'PeerFlags should contain the raw hex flag' -Test { $result = @(Get-NTPPeer) $result[0].PeerFlags | Should -Be '0x8' } It -Name 'TimeRemaining should be a double' -Test { $result = @(Get-NTPPeer) $result[0].TimeRemaining | Should -BeOfType ([double]) $result[0].TimeRemaining | Should -Be 512.34 } It -Name 'PollInterval should be an int' -Test { $result = @(Get-NTPPeer) $result[0].PollInterval | Should -BeOfType ([int]) $result[0].PollInterval | Should -Be 10 } It -Name 'LastSyncTime should not be null for valid data' -Test { $result = @(Get-NTPPeer) $result[0].LastSyncTime | Should -Not -BeNullOrEmpty } It -Name 'Timestamp should be a valid ISO 8601 string' -Test { $result = @(Get-NTPPeer) { [datetime]::Parse($result[0].Timestamp) } | Should -Not -Throw } It -Name 'State should be a non-empty string' -Test { $result = @(Get-NTPPeer) $result[0].State | Should -Not -BeNullOrEmpty $result[0].State | Should -BeOfType ([string]) } } } |