F5-LTM.Tests.ps1
# PSScriptAnalyzer - ignore creation of a SecureString using plain text for the contents of this script file [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] param() $scriptroot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) Import-Module (Join-Path $scriptroot 'F5-LTM\F5-LTM.psm1') -Force Describe 'Get-BigIPPartition' -Tags 'Unit' { InModuleScope F5-LTM { Context "Strict mode PS$($PSVersionTable.PSVersion.Major)" { Set-StrictMode -Version latest #region Arrange: Initialize Mocks # Mocking Invoke-RestMethodOverride for unit testing Module without F5 device connectivity Mock Invoke-RestMethodOverride { # Behavior (not state) verification is applied to this mock. # Therefore, the output need only meet the bare minimum requirements to maximize code coverage of the Subject Under Test. if ($URI -match 'JSON') { # This case included to support maximum code coverage [pscustomobject]@{ items=$null name='name' subPath='subPath' selfLink="https://localhost/mgmt/tm/sys/folder/~name?ver=12.1.2" } } else { [pscustomobject]@{ items=@(@{name='bogus item for testing';subPath='subPath'}) name='name' selfLink="https://localhost/mgmt/tm/sys/folder/~name?ver=12.1.2" } } } # Mock session with fictional IP,credentials, and version $mocksession = [pscustomobject]@{ Name = '192.168.1.1' BaseURL = 'https://192.168.1.1/mgmt/tm/ltm/' Credential = New-Object System.Management.Automation.PSCredential ('georgejetson', (ConvertTo-SecureString 'judyr0ck$!' -AsPlainText -Force)) LTMVersion = [Version]'11.5.1' WebSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession } | Add-Member -Name GetLink -MemberType ScriptMethod { param($Link) $Link -replace 'localhost', $this.Name } -PassThru # Add the mock session to the Script scope $Script:F5Session = $mocksession #endregion Arrange: Initialize Mocks It "Requests BigIP partitions *" { Get-BigIPPartition -F5Session $mocksession | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'string' } Assert-MockCalled Invoke-RestMethodOverride -Times 1 -Exactly -Scope It -ParameterFilter { $Uri.AbsoluteUri -eq ($mocksession.BaseURL -replace 'ltm/','sys/folder/?$select=name,subPath') } } # JSON test also forces a codecoverage scenario for a single item without an items property returned from the F5 It "Requests BigIP partitions by Name '<name>'" -TestCases @(@{name='Common'},@{name='Development'},@{name='Production'},@{name='JSON'}) { param($name) Get-BigIPPartition -F5Session $mocksession -Name $name | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'string' } Assert-MockCalled Invoke-RestMethodOverride -Times 1 -Exactly -Scope It -ParameterFilter { $Uri.AbsoluteUri -eq (($mocksession.BaseURL -replace 'ltm/','sys/folder') + ('/~{0}?$select=name,subPath' -f $name)) } } It "Requests BigIP partitions with Name [-Folder alias] '<name>'" -TestCases @(@{name='Common'}) { param($name) Get-BigIPPartition -F5Session $mocksession -Folder $name | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'string' } Assert-MockCalled Invoke-RestMethodOverride -Times 1 -Exactly -Scope It -ParameterFilter { $Uri.AbsoluteUri -eq (($mocksession.BaseURL -replace 'ltm/','sys/folder') + ('/~{0}?$select=name,subPath' -f $name)) } } It "Requests BigIP partitions with Name [-Partition alias] '<name>'" -TestCases @(@{name='Common'}) { param($name) Get-BigIPPartition -F5Session $mocksession -Partition $name | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'string' } Assert-MockCalled Invoke-RestMethodOverride -Times 1 -Exactly -Scope It -ParameterFilter { $Uri.AbsoluteUri -eq (($mocksession.BaseURL -replace 'ltm/','sys/folder') + ('/~{0}?$select=name,subPath' -f $name)) } } It "Requests BigIP partitions by Name[]" -TestCases @(@{name=@('Common','Development','Production')}) { param($name) Get-BigIPPartition -F5Session $mocksession -Name $name | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'string' } Assert-MockCalled Invoke-RestMethodOverride -Times 3 -Exactly -Scope It } It "Requests BigIP partitions by Name From Pipeline" -TestCases @(@{ object = ([pscustomobject]@{name = 'Common'}),([pscustomobject]@{name = 'Development'}) }) { param($object) $object | Get-BigIPPartition -F5Session $mocksession | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'string' } Assert-MockCalled Invoke-RestMethodOverride -Times ($object.Count) -Exactly -Scope It } } } } Describe 'Get-HealthMonitor' -Tags 'Unit' { InModuleScope F5-LTM { Context "Strict mode PS$($PSVersionTable.PSVersion.Major)" { Set-StrictMode -Version latest #region Arrange: Initialize Mocks # We aren't testing Get-HealthMonitorType here, just Get-HealthMonitor # Using a mock to return a static set of types for use by subsequent requests $healthmonitortypes = @('http','https','icmp','smtp','tcp') Mock Get-HealthMonitorType { $healthmonitortypes } # Mocking Invoke-RestMethodOverride for unit testing Module without F5 device connectivity Mock Invoke-RestMethodOverride { # Behavior (not state) verification is applied to this mock. # Therefore, the output need only meet the bare minimum requirements to maximize code coverage of the Subject Under Test. [pscustomobject]@{ kind="tm:ltm:monitor:http:httpstate" items=@('bogus item for testing') name='name' partition='partition' fullPath='/partition/name' selfLink="https://localhost/mgmt/tm/ltm/monitor/type/~partition~name?ver=12.1.2" } } # Mock session with fictional IP,credentials, and version $mocksession = [pscustomobject]@{ Name = '192.168.1.1' BaseURL = 'https://192.168.1.1/mgmt/tm/ltm/' Credential = New-Object System.Management.Automation.PSCredential ('georgejetson', (ConvertTo-SecureString 'judyr0ck$!' -AsPlainText -Force)) LTMVersion = [Version]'11.5.1' WebSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession } | Add-Member -Name GetLink -MemberType ScriptMethod { param($Link) $Link -replace 'localhost', $this.Name } -PassThru #endregion Arrange: Initialize Mocks It "Requests health monitors *" { Get-HealthMonitor -F5Session $mocksession | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'PoshLTM.HealthMonitor' } Assert-MockCalled Get-HealthMonitorType -Times 1 -Exactly -Scope It Assert-MockCalled Invoke-RestMethodOverride -Times $healthmonitortypes.Count -Exactly -Scope It } It "Requests health monitors of type '<type>'" -TestCases @(@{type='http'},@{type='https'}) { param($type) Get-HealthMonitor -F5Session $mocksession -Type $type | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'PoshLTM.HealthMonitor' } Assert-MockCalled Get-HealthMonitorType -Times 0 -Scope It Assert-MockCalled Invoke-RestMethodOverride -Times 1 -Exactly -Scope It -ParameterFilter { $Uri.AbsoluteUri -eq ('{0}monitor/{1}/' -f $mocksession.BaseURL,$type) } } It "Requests health monitors in partition '<partition>'" -TestCases @(@{partition='Development'},@{partition='Common'}) { param($partition) Get-HealthMonitor -F5Session $mocksession -Partition $partition | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'PoshLTM.HealthMonitor' } Assert-MockCalled Get-HealthMonitorType -Times 1 -Exactly -Scope It Assert-MockCalled Invoke-RestMethodOverride -Times $healthmonitortypes.Count -Exactly -Scope It } It "Requests health monitors in partition '<partition>' by Name '<name>'" -TestCases @(@{partition='Common';name='http'},@{partition='Development';name='Test'}) { param($partition, $name) Get-HealthMonitor -F5Session $mocksession -Partition $partition -Name $name | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'PoshLTM.HealthMonitor' } Assert-MockCalled Get-HealthMonitorType -Times 1 -Exactly -Scope It Assert-MockCalled Invoke-RestMethodOverride -Times $healthmonitortypes.Count -Exactly -Scope It } It "Requests health monitors by fullPath '<fullPath>'" -TestCases @(@{fullpath='/Common/https'}) { param($fullpath) Get-HealthMonitor -F5Session $mocksession -Name $fullPath | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'PoshLTM.HealthMonitor' } Assert-MockCalled Get-HealthMonitorType -Times 1 -Exactly -Scope It Assert-MockCalled Invoke-RestMethodOverride -Times $healthmonitortypes.Count -Exactly -Scope It } It "Requests health monitors by Name[]" -TestCases @(@{name=@('http','https','tcp')}) { param($name) Get-HealthMonitor -F5Session $mocksession -Name $name | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'PoshLTM.HealthMonitor' } Assert-MockCalled Get-HealthMonitorType -Times 1 -Exactly -Scope It Assert-MockCalled Invoke-RestMethodOverride -Times ($name.Count * $healthmonitortypes.Count) -Exactly -Scope It } It "Requests health monitors by Name From Pipeline" -TestCases @(@{name=@('http','https')}) { param($name) $name | Get-HealthMonitor -F5Session $mocksession | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'PoshLTM.HealthMonitor' } Assert-MockCalled Get-HealthMonitorType -Times 1 -Exactly -Scope It Assert-MockCalled Invoke-RestMethodOverride -Times ($name.Count * $healthmonitortypes.Count) -Exactly -Scope It } It "Requests health monitors by Name and Partition From Pipeline" -TestCases @(@{ object = ([pscustomobject]@{name = 'http'; partition = 'Common'}),([pscustomobject]@{name = 'host_ashx'; partition = 'Common'}) }) { param($object) $object | Get-HealthMonitor -F5Session $mocksession | ForEach-Object { $_.PSObject.TypeNames[0] | Should Be 'PoshLTM.HealthMonitor' } Assert-MockCalled Get-HealthMonitorType -Times 1 -Exactly -Scope It Assert-MockCalled Invoke-RestMethodOverride -Times ($object.Count * $healthmonitortypes.Count) -Exactly -Scope It } } } } Describe 'New-F5Session' -Tags 'Unit' { InModuleScope F5-LTM { Context "Strict mode PS$($PSVersionTable.PSVersion.Major)" { Set-StrictMode -Off #region Arrange: Initialize Mocks # Mocking Invoke-RestMethodOverride for unit testing Module without F5 device connectivity Mock Invoke-RestMethodOverride { switch ($LTMName) { 'version11' { if ($URI -match 'sys/version/') { '{"version":"11.5.1"}' } else { throw '404 Not found' } } Default { if ($URI -match 'mgmt/shared/authn/login') { [pscustomobject]@{token=@{token='dummytoken';starttime=[DateTime]::Now;uuid='9912a8f9-6fa9-474d-b00d-3f16226352b7'}} #} elseif ($URI -match 'mgmt/shared/authz/tokens') { # token extension request currently doesn't have to return anything, just not fail } elseif ($URI -match 'sys/version/') { '{"version":"12.1.0"}' } } } } Mock Invoke-WebRequest { $true } $credentials = New-Object System.Management.Automation.PSCredential ('georgejetson', (ConvertTo-SecureString 'judyr0ck$!' -AsPlainText -Force)) #endregion Arrange: Initialize Mocks It "`$Script:F5Session initialized on 1st call" { $testsession = New-F5Session -LTMName 'any' -LTMCredentials $credentials -PassThru Assert-MockCalled Invoke-WebRequest -Times 0 -Exactly -Scope It # Only v11 calls Invoke-WebRequest Assert-MockCalled Invoke-RestMethodOverride -Times 2 -Exactly -Scope It $Script:F5Session.BaseURL -eq $testsession.BaseURL | Should Be $true $Script:F5Session.LTMVersion -eq $testsession.LTMVersion | Should Be $true } It "`$Script:F5Session overridden with -Default switch" { $testsession = New-F5Session -LTMName 'newdefault' -LTMCredentials $credentials -Default -PassThru Assert-MockCalled Invoke-WebRequest -Times 0 -Exactly -Scope It # Only v11 calls Invoke-WebRequest Assert-MockCalled Invoke-RestMethodOverride -Times 2 -Exactly -Scope It $Script:F5Session.BaseURL -eq $testsession.BaseURL | Should Be $true $Script:F5Session.LTMVersion -eq $testsession.LTMVersion | Should Be $true } It "v11: Authentication with Credentials" { $testsession = New-F5Session -LTMName 'version11' -LTMCredentials $credentials -PassThru Assert-MockCalled Invoke-WebRequest -Times 1 -Exactly -Scope It # Only v11 calls Invoke-WebRequest Assert-MockCalled Invoke-RestMethodOverride -Times 2 -Exactly -Scope It $testsession.LTMVersion -eq [Version]'11.5.1' | Should Be $true } It "v12+: Authentication with X-F5-Auth-Token header" { $testsession = New-F5Session -LTMName 'version12' -LTMCredentials $credentials -PassThru Assert-MockCalled Invoke-WebRequest -Times 0 -Scope It # Only v11 calls Invoke-WebRequest Assert-MockCalled Invoke-RestMethodOverride -Times 2 -Exactly -Scope It $testsession.WebSession.Headers.Keys.Contains('X-F5-Auth-Token') | Should Be $true $testsession.WebSession.Headers.Keys.Contains('Token-Expiration') | Should Be $true } It "v12+: Authentication with X-F5-Auth-Token header + custom TokenLifespan" { $testsession = New-F5Session -LTMName 'version12' -LTMCredentials $credentials -TokenLifespan 36000 -PassThru Assert-MockCalled Invoke-WebRequest -Times 0 -Scope It # Only v11 calls Invoke-WebRequest Assert-MockCalled Invoke-RestMethodOverride -Times 3 -Exactly -Scope It #3rd call for TokenLifespan change $testsession.WebSession.Headers.Keys.Contains('X-F5-Auth-Token') | Should Be $true $testsession.WebSession.Headers.Keys.Contains('Token-Expiration') | Should Be $true } It "Throws an error if TokenLifespan is out of range" { { New-F5Session -LTMName 'version12' -LTMCredentials $credentials -TokenLifespan 60000 } | Should Throw } } } } |