tests/QlikNPrinting-CLI.Tests.ps1
|
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0' } <# Pester tests for QlikNPrinting-CLI. No live NPrinting server is required: Invoke-RestMethod is mocked at the module boundary so request construction and response handling are verified in isolation. Run: Invoke-Pester -Path .\tests #> BeforeAll { $script:ModuleRoot = Split-Path -Parent $PSScriptRoot Import-Module (Join-Path $ModuleRoot 'QlikNPrinting-CLI.psd1') -Force function New-TestSession { # Seeds a fake $script:NPEnv so Invoke-NPRequest can run without connecting. InModuleScope QlikNPrinting-CLI { $script:NPEnv = @{ TrustAllCerts = $false Prefix = 'https' Computer = 'srv' Port = '4993' API = 'api' APIVersion = 'v1' URLServerBase = 'https://srv:4993' URLServerAPI = 'https://srv:4993/api/v1' URLServerNPE = 'https://srv:4993/npe' WebRequestSession = (New-Object Microsoft.PowerShell.Commands.WebRequestSession) } } } } Describe 'Module surface' { It 'has a valid manifest' { $m = Test-ModuleManifest (Join-Path $ModuleRoot 'QlikNPrinting-CLI.psd1') $m.Version | Should -Not -BeNullOrEmpty } It 'exports exactly the functions under src/Public' { $expected = Get-ChildItem (Join-Path $ModuleRoot 'src\Public') -Filter *.ps1 | Select-Object -ExpandProperty BaseName | Sort-Object (Get-Command -Module QlikNPrinting-CLI).Name | Sort-Object | Should -Be $expected } It 'exports the core functions' { $core = 'Connect-NPrinting', 'Invoke-NPRequest', 'Get-NPUsers', 'New-NPUser', 'Set-NPUser', 'Remove-NPUser', 'New-NPFilter', 'Get-NPConnections', 'Start-NPTask', 'New-NPOnDemandRequest' $names = (Get-Command -Module QlikNPrinting-CLI).Name foreach ($c in $core) { $names | Should -Contain $c } } It 'does not export private helpers' { foreach ($p in 'Add-NPProperty', 'Add-NPQueryParameter', 'Get-XSRFToken', 'Enable-NPTrustAllCerts') { Get-Command -Module QlikNPrinting-CLI -Name $p -ErrorAction SilentlyContinue | Should -BeNullOrEmpty } } } Describe 'Add-NPQueryParameter' { It 'starts a query string with ?' { InModuleScope QlikNPrinting-CLI { Add-NPQueryParameter -QueryString '' -Name 'limit' -Value '10' | Should -Be '?limit=10' } } It 'appends with & once a query already exists' { InModuleScope QlikNPrinting-CLI { Add-NPQueryParameter -QueryString '?limit=10' -Name 'name' -Value 'x' | Should -Be '?limit=10&name=x' } } It 'translates the * wildcard to %' { InModuleScope QlikNPrinting-CLI { Add-NPQueryParameter -QueryString '' -Name 'name' -Value 'Mar*' | Should -Be '?name=Mar%' } } } Describe 'Get-XSRFToken' { It 'throws when there is no active session' { InModuleScope QlikNPrinting-CLI { $script:NPEnv = $null { Get-XSRFToken } | Should -Throw '*Connect-NPrinting*' } } } Describe 'Invoke-NPRequest' { BeforeEach { New-TestSession } It 'builds an absolute URI from a relative API path' { Mock -ModuleName QlikNPrinting-CLI Invoke-RestMethod { [pscustomobject]@{ ok = $true } } Invoke-NPRequest -Path 'users' -Method Get | Out-Null Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-RestMethod -ParameterFilter { $Uri -eq 'https://srv:4993/api/v1/users' } } It 'passes absolute URLs through unchanged' { Mock -ModuleName QlikNPrinting-CLI Invoke-RestMethod { [pscustomobject]@{ ok = $true } } Invoke-NPRequest -Path 'https://other:1/api/v1/x' | Out-Null Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-RestMethod -ParameterFilter { $Uri -eq 'https://other:1/api/v1/x' } } It 'adds count, orderBy and page for -NPE requests' { Mock -ModuleName QlikNPrinting-CLI Invoke-RestMethod { [pscustomobject]@{ result = [pscustomobject]@{ data = [pscustomobject]@{ items = @('x') } } } } Invoke-NPRequest -Path 'objects' -NPE | Out-Null Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-RestMethod -ParameterFilter { $Uri -like '*/npe/objects?count=*' -and $Uri -like '*orderBy=Name*' -and $Uri -like '*page=1*' } } It 'unwraps the data.items envelope' { Mock -ModuleName QlikNPrinting-CLI Invoke-RestMethod { [pscustomobject]@{ data = [pscustomobject]@{ items = @(1, 2, 3) } } } Invoke-NPRequest -Path 'users' | Should -Be @(1, 2, 3) } It 'unwraps data when there are no items' { Mock -ModuleName QlikNPrinting-CLI Invoke-RestMethod { [pscustomobject]@{ data = 'payload' } } Invoke-NPRequest -Path 'users/1' | Should -Be 'payload' } It 'serialises hashtable bodies to JSON' { $captured = $null Mock -ModuleName QlikNPrinting-CLI Invoke-RestMethod { $script:captured = $Body; [pscustomobject]@{ ok = $true } } Invoke-NPRequest -Path 'users' -Method Post -Data @{ name = 'Marc' } | Out-Null Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-RestMethod -ParameterFilter { $Body -match '"name"\s*:\s*"Marc"' } } It 'passes an already-JSON string through unchanged' { Mock -ModuleName QlikNPrinting-CLI Invoke-RestMethod { [pscustomobject]@{ ok = $true } } Invoke-NPRequest -Path 'users' -Method Post -Data '{"name":"Marc"}' | Out-Null Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-RestMethod -ParameterFilter { $Body -eq '{"name":"Marc"}' } } It 'writes a non-terminating error and does not throw on request failure' { Mock -ModuleName QlikNPrinting-CLI Invoke-RestMethod { throw 'boom' } { Invoke-NPRequest -Path 'users' -ErrorAction SilentlyContinue } | Should -Not -Throw } It 'returns nothing (no error) when the response body is empty' { # Empty 2xx bodies are normal for PUT/DELETE (and POST before the Location # header is read). This must not raise the old "No results received" error. Mock -ModuleName QlikNPrinting-CLI Invoke-RestMethod { '' } $out = Invoke-NPRequest -Path 'filters/1' -Method Delete -ErrorAction Stop $out | Should -BeNullOrEmpty } # Note: surfacing the created id from the 201 Location header is verified by the # live integration round-trip (create -> get -> put -> delete). It cannot be # unit-tested here because Invoke-RestMethod's -ResponseHeadersVariable is set by # the real cmdlet and a mock cannot reliably populate the caller's out-variable. } Describe 'Connect-NPrinting' { BeforeEach { Mock -ModuleName QlikNPrinting-CLI Invoke-NPRequest { 'token' } } It 'builds the session URLs' { Connect-NPrinting -Computer srv -Port 4993 InModuleScope QlikNPrinting-CLI { $script:NPEnv.URLServerBase | Should -Be 'https://srv:4993' $script:NPEnv.URLServerAPI | Should -Be 'https://srv:4993/api/v1' $script:NPEnv.URLServerNPE | Should -Be 'https://srv:4993/npe' } } It 'parses a full URL supplied as -Computer' { Connect-NPrinting -Computer 'http://host:9999' InModuleScope QlikNPrinting-CLI { $script:NPEnv.Prefix | Should -Be 'http' $script:NPEnv.Computer | Should -Be 'host' $script:NPEnv.Port | Should -Be '9999' } } It 'requires credentials for NPrinting authentication' { { Connect-NPrinting -Computer srv -AuthScheme NPrinting } | Should -Throw '*requires -Credentials*' } It 'records the TrustAllCerts flag in the session' { Connect-NPrinting -Computer srv -TrustAllCerts InModuleScope QlikNPrinting-CLI { $script:NPEnv.TrustAllCerts | Should -BeTrue } } } Describe 'Write and action functions' { BeforeEach { Mock -ModuleName QlikNPrinting-CLI Invoke-NPRequest { } } It 'New-NPFilter POSTs to filters with the body' { New-NPFilter -InputObject @{ name = 'EU'; appId = 'a1' } Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -ParameterFilter { $Method -eq 'Post' -and $Path -eq 'filters' -and $Data.name -eq 'EU' } } It 'Set-NPFilter PUTs to filters/{id}' { Set-NPFilter -Id 'f1' -InputObject @{ enabled = $false } Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -ParameterFilter { $Method -eq 'Put' -and $Path -eq 'filters/f1' } } It 'Remove-NPFilter DELETEs filters/{id}' { Remove-NPFilter -Id 'f1' -Confirm:$false Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -ParameterFilter { $Method -eq 'Delete' -and $Path -eq 'filters/f1' } } It 'Remove-NPFilter -WhatIf does not call the API' { Remove-NPFilter -Id 'f1' -WhatIf Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -Times 0 } It 'Remove-NPUser accepts pipeline input by property name' { [pscustomobject]@{ id = 'u1' }, [pscustomobject]@{ id = 'u2' } | Remove-NPUser -Confirm:$false Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -Times 2 Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -ParameterFilter { $Path -eq 'users/u2' } } It 'New-NPUser builds the body and converts the SecureString password' { $pw = ConvertTo-SecureString 'S3cret!' -AsPlainText -Force New-NPUser -UserName jdoe -Email jdoe@x.com -Password $pw Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -ParameterFilter { $Method -eq 'Post' -and $Path -eq 'users' -and $Data.userName -eq 'jdoe' -and $Data.email -eq 'jdoe@x.com' -and $Data.password -eq 'S3cret!' } } It 'Set-NPUser -Property Roles targets the roles sub-resource' { Set-NPUser -Id 'u1' -Property Roles -InputObject @('r1', 'r2') Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -ParameterFilter { $Method -eq 'Put' -and $Path -eq 'users/u1/roles' } } It 'Get-NPConnections filters by appId' { Get-NPConnections -AppId 'a1' Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -ParameterFilter { $Method -eq 'Get' -and $Path -eq 'connections?appId=a1' } } It 'Invoke-NPConnectionReload POSTs to the reload action' { Invoke-NPConnectionReload -Id 'c1' -Confirm:$false Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -ParameterFilter { $Method -eq 'Post' -and $Path -eq 'connections/c1/reload' } } It 'Start-NPTask POSTs a new execution' { Start-NPTask -Id 't1' -Confirm:$false Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -ParameterFilter { $Method -eq 'Post' -and $Path -eq 'tasks/t1/executions' } } It 'Get-NPTaskExecutions targets a single execution when given -ExecutionId' { Get-NPTaskExecutions -TaskId 't1' -ExecutionId 'e1' Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -ParameterFilter { $Method -eq 'Get' -and $Path -eq 'tasks/t1/executions/e1' } } It 'New-NPOnDemandRequest builds a request body from -ReportId' { New-NPOnDemandRequest -ReportId 'r1' -OutputFormat 'pdf' Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -ParameterFilter { $Method -eq 'Post' -and $Path -eq 'ondemand/requests' -and $Data.reportId -eq 'r1' -and $Data.outputFormat -eq 'pdf' } } It 'Get-NPAuditLogs GETs audit/logs' { Get-NPAuditLogs Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-NPRequest -ParameterFilter { $Method -eq 'Get' -and $Path -eq 'audit/logs' } } } Describe 'Regression - GitHub issues' { Context 'Issue #7: Connect-NPrinting -TrustAllCerts on PowerShell 7' { BeforeEach { Mock -ModuleName QlikNPrinting-CLI Invoke-NPRequest { 'token' } } It 'does not raise the null-valued-method error when connecting with credentials' { $cred = [pscredential]::new('tech', (ConvertTo-SecureString 'p' -AsPlainText -Force)) { Connect-NPrinting -Computer np.example.com -TrustAllCerts -Credentials $cred } | Should -Not -Throw InModuleScope QlikNPrinting-CLI { $script:NPEnv.TrustAllCerts | Should -BeTrue } } It 'adds -SkipCertificateCheck per request on PowerShell 7' { if ($PSVersionTable.PSVersion.Major -le 5) { Set-ItResult -Skipped -Because 'per-request cert skip is a PowerShell 7 behavior' return } New-TestSession InModuleScope QlikNPrinting-CLI { $script:NPEnv.TrustAllCerts = $true } Mock -ModuleName QlikNPrinting-CLI Invoke-RestMethod { [pscustomobject]@{ ok = $true } } Invoke-NPRequest -Path 'users' | Out-Null Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-RestMethod -ParameterFilter { $SkipCertificateCheck -eq $true } } } Context 'Issue #5: ConvertTo-Json depth' { BeforeEach { New-TestSession } It 'serialises nested bodies beyond the default depth (does not truncate)' { # a->b->c->d is 4 levels deep; the ConvertTo-Json default of 2 would # stringify the inner levels as type names instead of JSON. $deep = @{ a = @{ b = @{ c = @{ d = 'deepvalue' } } } } Mock -ModuleName QlikNPrinting-CLI Invoke-RestMethod { [pscustomobject]@{ ok = $true } } Invoke-NPRequest -Path 'filters/1' -Method Put -Data $deep | Out-Null Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-RestMethod -ParameterFilter { $Body -match 'deepvalue' -and $Body -notmatch 'System\.Collections' } } It 'honours an explicit -Depth for deeper structures' { $deeper = @{ l1 = @{ l2 = @{ l3 = @{ l4 = @{ l5 = @{ l6 = 'bottom' } } } } } } Mock -ModuleName QlikNPrinting-CLI Invoke-RestMethod { [pscustomobject]@{ ok = $true } } Invoke-NPRequest -Path 'filters/1' -Method Put -Data $deeper -Depth 8 | Out-Null Should -Invoke -ModuleName QlikNPrinting-CLI Invoke-RestMethod -ParameterFilter { $Body -match 'bottom' } } } } # SIG # Begin signature block # MIIfdQYJKoZIhvcNAQcCoIIfZjCCH2ICAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBuvhchKhFLqtbK # UxFuh8SDBpXFp+OTKBRWY9G/pdGAkaCCGb0wggN5MIIC/qADAgECAhAcz51nzeIZ # /xLZmv82guWnMAoGCCqGSM49BAMDMHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVU # ZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u # MTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg # RUNDMB4XDTE5MDMwNzE5MzU0N1oXDTM0MDMwMzE5MzU0N1oweDELMAkGA1UEBhMC # VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhT # U0wgQ29ycDE0MDIGA1UEAwwrU1NMLmNvbSBDb2RlIFNpZ25pbmcgSW50ZXJtZWRp # YXRlIENBIEVDQyBSMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABOpt7gyJbfdl1TyX # rJy6JZGueJwq39d2z/FOJTbnNRuYrlS823MWKvLp+ziKPRCumlXWYiCS5X0xZxWv # 2FIxsD9Tf7tCm8JcqSsa6W8uRyjXT+yEBglVRcOJGZiIjeFxJKOCAUcwggFDMBIG # A1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh # 6M0weAYIKwYBBQUHAQEEbDBqMEYGCCsGAQUFBzAChjpodHRwOi8vd3d3LnNzbC5j # b20vcmVwb3NpdG9yeS9TU0xjb20tUm9vdENBLUVDQy0zODQtUjEuY3J0MCAGCCsG # AQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTARBgNVHSAECjAIMAYGBFUdIAAw # EwYDVR0lBAwwCgYIKwYBBQUHAwMwOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2Ny # bHMuc3NsLmNvbS9zc2wuY29tLWVjYy1Sb290Q0EuY3JsMB0GA1UdDgQWBBQyeLEO # kNtGzxrPtmMRbf4w52dUMDAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAw # ZgIxAIZwNaUUH2Oi1OfK9PES0J4Ay3EIm1mAOjpxEHItL3pSmV+5tJ/iQQqK2Dwg # evkxFQIxAIHLuf6CWo8Wvxn2XZR/+3do0Q/XjqQSbfhJlqwRUVPlxUz5aK1vpJwv # LRHaPzhzXTCCA5owggMgoAMCAQICEGVL9aWgte+AgghPboEa/wIwCgYIKoZIzj0E # AwMweDELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3Vz # dG9uMREwDwYDVQQKDAhTU0wgQ29ycDE0MDIGA1UEAwwrU1NMLmNvbSBDb2RlIFNp # Z25pbmcgSW50ZXJtZWRpYXRlIENBIEVDQyBSMjAeFw0yNTA2MDMwNzIxMTFaFw0y # ODA2MDIwNzIxMTFaMFIxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTES # MBAGA1UEBwwJQnJ1bnN3aWNrMQ0wCwYDVQQKDAROTmV0MQ0wCwYDVQQDDAROTmV0 # MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEngGfAKTCECZA4Zhxz9OCOhWt6MXoHPXU # shp0UnRnKAtpP3sLqxdectVGokuO/dG0WDX3G8STwQ0HOlOt/I142BmQQzV3Fg2N # DxEgAiJafiHnEHCaRRycBfcu7SUAMUoXo4IBkzCCAY8wDAYDVR0TAQH/BAIwADAf # BgNVHSMEGDAWgBQyeLEOkNtGzxrPtmMRbf4w52dUMDB5BggrBgEFBQcBAQRtMGsw # RwYIKwYBBQUHMAKGO2h0dHA6Ly9jZXJ0LnNzbC5jb20vU1NMY29tLVN1YkNBLWNv # ZGVTaWduaW5nLUVDQy0zODQtUjIuY2VyMCAGCCsGAQUFBzABhhRodHRwOi8vb2Nz # cHMuc3NsLmNvbTBRBgNVHSAESjBIMAgGBmeBDAEEATA8BgwrBgEEAYKpMAEDAwEw # LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cuc3NsLmNvbS9yZXBvc2l0b3J5MBMG # A1UdJQQMMAoGCCsGAQUFBwMDMEwGA1UdHwRFMEMwQaA/oD2GO2h0dHA6Ly9jcmxz # LnNzbC5jb20vU1NMY29tLVN1YkNBLWNvZGVTaWduaW5nLUVDQy0zODQtUjIuY3Js # MB0GA1UdDgQWBBTx/f9S3OD23I4gbVreRmoGuRyeKjAOBgNVHQ8BAf8EBAMCB4Aw # CgYIKoZIzj0EAwMDaAAwZQIxAICwlRFbAJ/sRgO3UFmz6ltoHAv8Q9KeczD/2g9t # Wb1rfVMbZLfOhUPNfFwrzjAlVgIwNWVDg2xdBz0Zxm50jDWLL3MVJRMXdmBLqDh6 # Tl1ozTY5O/jrXryFfY/4A8ChX61dMIIFcjCCA1qgAwIBAgIQdlP+uT3Z5+kmMqzW # Cr6sODANBgkqhkiG9w0BAQwFADBTMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xv # YmFsU2lnbiBudi1zYTEpMCcGA1UEAxMgR2xvYmFsU2lnbiBUaW1lc3RhbXBpbmcg # Um9vdCBSNDUwHhcNMjAwMzE4MDAwMDAwWhcNNDUwMzE4MDAwMDAwWjBTMQswCQYD # VQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEpMCcGA1UEAxMgR2xv # YmFsU2lnbiBUaW1lc3RhbXBpbmcgUm9vdCBSNDUwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQC6dDPsJ9wSOCEbxdNhKNZavE/fi8yRhEMkV7xkIbw7HB89 # T4ytB7fzxdcC6REUgpqqtJRyO3ENGu9oa4V5jq9m6liYDbrBfHnS/82zbzFF0AV0 # BAByaid+uDc/Oojtl4P1qzVND59ZO/Uv31nFfKUydmCWyO3u+AR+GVFyqL9EQXq8 # ex47AJu8uuCWv5D+jZvDcosAEvggOmA498HMhYr7h3kuoSsg5sughZEjtsQoB1Qo # 3uwQMU+K8s0UHx7dVRzqKDFM+SFqqM3zlmf6AUGbzQ8LaH+73vFD6hflsNxwIrNp # Nll0a8bliSp85QuBXas/j7jRdnLzfKKp4pdBv8yMRf5hyfZsBwsABOgVI0+CKi32 # 78P6ETZIodH9ejk6NF2jLA6bd1AgNEDdsQMxrV/pYodzlgNh95Sw2VxsT+cUxeHx # ew0jnM1wjB1q3kotiyq720IUBQeq+xTcMdP2H2zLvmhmRHBNbRf5cesFc46RknXr # aFwe9kRhGCli3RdmiOwouklv2z53/rkxH3UcGKKmR73Y7kiFO/2z4g8/KpjGmvqC # b7GlpYYdWjr6pGx0D3dSYWp/hyneOZuL7rNFYDAklxUSKoUwkyaslqYt6HBtC6ky # rSybKAp2QvJVYVGYlN7t9sUXbzwVELAOrbDexRb0ZdHML1pWCM+ZxPBVkcIseQID # AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E # FgQURrIcd+F7FfClOaFw3tHELuptst4wDQYJKoZIhvcNAQEMBQADggIBABZ8CmdK # AzyTKj4cT0ZVsJqeiOHrU/1MVXmE+Wy2n6kKtomrVAcUFkpJWvS4LoYUxH4ZifmH # iLzsstKVjOAM+fKUZqaYVxuh39FxfYy1+HEC3RO2vvqwMcMsZ+saGA4aTdwszzFc # YSipneNqLK5QSw460Gn7ijRE335LjhqQCdox2sovpff0Nyw1DRpizTx7PFZ3ZZVc # lHNwn2EvaWQjHUx5B8IXfDrtqm1xAxRiRcy3PlTYUXFC6juSQqUvVIGjsAxWWFa7 # 5JjuZscR+ahFF+JlKore4qjOxS329c6t8OMKCd1Te2ypbIZ+od42NQAPX4D9Rbtx # ZkPURCzQuwFOmZ4+TeFeVh8FeoIdssstpTO5OeXEt9pC4b3QlEKA+hiUO5NDqMiU # Om1+nfxPoMLT5aWqECZvBiJb4AHiSr8Z5USesK2rGdLN60fEYoHs8MJ6jUz9wiW3 # vCxwjqqtUvQUPKp4HQTTydUlgqday4x8H1cCO4cbyNf5VBodyhpLJ7HiSu/nmkAU # T6U8n9WjvpQ1nMLXPyjupBcrQ71kp9ev6VPnp3cexRIbMeJLxn+eHO6jOpRQXaZQ # BlJeRQMrtADgwe3YDcGuu0kJgYJaQkOvmWO4FNE8i93V8FTtcmfC9so+NYSHgA1S # lVBB1rINGUAvthNN97Fg1HbFVzluWqJeCnncMIIGhDCCBGygAwIBAgIRAIRyP7gw # DfuodbM7V8wmN4IwDQYJKoZIhvcNAQEMBQAwXjELMAkGA1UEBhMCQkUxGTAXBgNV # BAoTEEdsb2JhbFNpZ24gbnYtc2ExNDAyBgNVBAMTK0dsb2JhbFNpZ24gT2ZmbGlu # ZSBSNDUgVGltZXN0YW1waW5nIENBIDIwMjUwHhcNMjUxMDE1MDcyNDU2WhcNMzcw # MTEwMDAwMDAwWjBdMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBu # di1zYTEzMDEGA1UEAxMqR2xvYmFsc2lnbiBSNDUgVFNBIGZvciBBdXRoZW50aWNv # ZGUgMjAyNTEwMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAo5qib0Lz # OmONbaHJo3ptJ5114tIqUelWQN9js0nuN9WjqaPiWBNL5AOxM/dr0tmxVohS4Chu # z4W2U6J3IWR+eKF19ZaRti+3qylKyGrYFaThdEnRlJ+EB1in0Irq6BSGGL7kAGOv # iVGvH4hlnvFL4N5XhswQ3l1Ha3sXdUOCQ5AiXhZPccq60xCJjEEGLdABWJIRKU0m # k3ngJGj7X8ryWqCxTUrA//hy2iCY5OscrNA4y+k6xrvFR3pglhgdNAATbIDnwByr # xLp7VyaYprrUpY0wxuxc1DjRNhrKV+Xf2DK77Huj88GMem2i4WCmSrpU/G2qbl5W # bzNN/5w9iig1KY1wg3LrHzs3c6RdJh/iHLR9Sjkc7qJLidH3QQzT8TKmZX4weJLb # 5l17/Z53TFzQ/WO969H3MSCjRIsPJC5umBp70EK7CE3GEfFWtgvYig0hVmak3m81 # VBKI1zG5LcZxbYt3cQmJhZZaCOvcTjOaIHbYwEzQ8rOEXCHAe8rvW1GJAgMBAAGj # ggG8MIIBuDAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgw # DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUpFtFrgk/9CUBPthTUQJp85AI2BYwHwYD # VR0jBBgwFoAUdwI7ATEPHnR3w0jIwwdjVYilO6IwgaUGCCsGAQUFBwEBBIGYMIGV # MEIGCCsGAQUFBzABhjZodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9nc29mZmxp # bmVyNDV0aW1lc3RhbXBjYTIwMjUwTwYIKwYBBQUHMAKGQ2h0dHA6Ly9zZWN1cmUu # Z2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzb2ZmbGluZXI0NXRpbWVzdGFtcGNhMjAy # NS5jcnQwSgYDVR0fBEMwQTA/oD2gO4Y5aHR0cDovL2NybC5nbG9iYWxzaWduLmNv # bS9nc29mZmxpbmVyNDV0aW1lc3RhbXBjYTIwMjUuY3JsMEwGA1UdIARFMEMwQQYJ # KwYBBAGgMgEeMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24u # Y29tL3JlcG9zaXRvcnkvMA0GCSqGSIb3DQEBDAUAA4ICAQBwzMXQamU+SQhZibXB # p31NqVIF4I/z0kZD0ozcFy205DhbUAu8YViH+c82ypDkH3wk2LLrVB/T21VBcfjq # DS9p2ormMMBCSJrOB2WBno9HckZzSUgpdPCkH9s7ynoX4RZS/UBL9M2DJVc+hpKb # zCNmrQWhkoSTHbTk8/JFhmhiX5HPp+hiB8Pc3cX1zHLTN+uNDBoxyzJ6tkmevgal # UWKNxCHpICYj9lrJ+bVvBDkxBgr9+bfFuamAUwK2SHW6dEtzHp55mfeTAJNIORc/ # 95V+h+QM8wFgwtpV1EVB1ze4po3pxcWjSfAZo4qEvyknEQqqf8yDmNsBmgH7qg/a # 4NEbgnS+4QuEqLL+5hZvd+AyJh/nyNw6Rp8lXObSNvahrS23Qm6+Fy90gx2wALq8 # 6i70p+QYMcADZcO6NQABlqbGh92BzaLYXo7RlC6vMikqGjLgJe4KC5dfiA5E/uEI # seLYcD2e4RbL/jHs4TGU91Osx3O+S+2dEl65H2HZADgVLJETeXZDvUe4WBOiN6Zw # mVf9mIBjvHwt4f6GHWcWg5ZYakjdoW+eHKet5xRzkUAJP/wCRCcX9QiyB9WCOLwR # WmTycPaLhLO6gt1JgaT8xsX97eVfaJXs6mkMSzUkz+t++6KWyKOctzAVozo11N9r # l4BtPJXVW5VyQTV+bJ059TGLoTCCBqAwggSIoAMCAQICEQCD2oY3t58MhAyUe4QK # UngfMA0GCSqGSIb3DQEBDAUAMFMxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i # YWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIFRpbWVzdGFtcGluZyBS # b290IFI0NTAeFw0yNTA3MTYwMzA1MDRaFw00MTA3MTYwMDAwMDBaMF4xCzAJBgNV # BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTQwMgYDVQQDEytHbG9i # YWxTaWduIE9mZmxpbmUgUjQ1IFRpbWVzdGFtcGluZyBDQSAyMDI1MIICIjANBgkq # hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApHcW+O19i+LdAoZFYzS+5X+WYvnWoFqX # Afir1hynhUTdH4RW1Db+yOmrQ275jlsQ6bzoZ3nN0CMncZX4E0Qhpp6Qvx27+flp # fzeMQacD7VciWUiF3TLiu7wT2bBCSENUn3hfGMG4PJvYFvO5o4DA1iNvHhG4oSzc # todoJfb4c8EjVahCw/NLizB3ra+NWe2gZBSaZKraMxFt676yqx7RcQnjbF4R0OLG # ovsZt23vU69A5BdoPxdA9zu9rM+qTBsPDVUJexYwEVU0GY7BJ5mUWWniyAPHW0Wv # 4Azk5t7I0XUIjA3+2OGkr0dVBXVBDyEeGBVrYXEdhfVLwuh6HBGJFdIrEY5KoGlp # oT+4BBQe4XCH5sv15Uo+M72VKWjPA5Ex3nfFJC4P5FW1SR6olCSaIrtnZzc+zgmp # SyiD+GcE2udQRQHbDi74enXgazk0+ktpHZ1Z8oTvSaSIREovXSLbH3KC8uFIkXuc # l7XPH7ZGIrmF9eF4zuoo5FIUnsvV60kLqFDzPk+UbLmgZDUCPlFFBBehaaNvixEy # mx9ON2KXev+MfK6OZChqGbrOC2wvvAFHyKlTZbVHdqNiu0u5a2T1C9dSTRny1/hx # LwcxL9BWPzQLwhsiyXqUzM7uD0lD9+PYMaxUYgoVSxqb4xvPCiVqLNabI+WtjEzY # fQ0P+6tBTFsCAwEAAaOCAWIwggFeMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAK # BggrBgEFBQcDCDASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBR3AjsBMQ8e # dHfDSMjDB2NViKU7ojAfBgNVHSMEGDAWgBRGshx34XsV8KU5oXDe0cQu6m2y3jCB # jgYIKwYBBQUHAQEEgYEwfzA3BggrBgEFBQcwAYYraHR0cDovL29jc3AuZ2xvYmFs # c2lnbi5jb20vdGltZXN0YW1wcm9vdHI0NTBEBggrBgEFBQcwAoY4aHR0cDovL3Nl # Y3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvdGltZXN0YW1wcm9vdHI0NS5jcnQw # PwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NybC5nbG9iYWxzaWduLmNvbS90aW1l # c3RhbXByb290cjQ1LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEM # BQADggIBADKj7n7RbuRmMZZYXqlMPRJoR6X1n//quXGLVfOpFoR9Ya05L94w0ywB # jelyGGf+nAB+CZFQ7gUOd2a2bpfpW8Xw5ArM+YjPEf8AtC4E6Yr105U1YNjlTSER # oWJKc1hkSN5m4dpsYteFykzFQVwX50hYKH3yZ6Vcu6Ha0EA5ofzLpi2jK2jbRDCX # bFNLi5mO1xKRdB2AzAF0f5C00b4H3d5sCOB8njTvAwaTMGEMeTkLWM4Z9Y+3UOtO # po1QuxXbDpXVkLXraG25iL1VtvjxEAy4534nUINB9whORicJJSTLba6fOK2f/1QG # WEdewWLHAzE+N5oH0QoNRALpJ5JjIfeInvO+sQdBidnPuLKJ95HTj7XyMvJhFZjt # bHJGlEWx4UgKcuNKLDLXWALfwQDN2Dey3kTfd4yw4nQdk1PctLLK3F4L2nnLv94B # MkpY+Rfl53oOEN4yTvtwCYP+VDuZrktc7NacoTVxZnKGkv8a1akckdOwQZC+i8Ay # 1VyzMAX/Tb4+r3c65B7cpAtq3OoUijXUJgvZxci6TX78smL2TYy2tWn+8G4krnXv # y2ELR2XYnKEOS4MVmrSCsjM5nxSrghE10VDXQbEfa93lhikfFoIuINKzWDLqvu8Z # ucmxEufxpHjNnnRVXX/Zv5KQq8pu/MQoOz6DC74n5+O5bSwvT5sgMYIFDjCCBQoC # AQEwgYwweDELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdI # b3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDE0MDIGA1UEAwwrU1NMLmNvbSBDb2Rl # IFNpZ25pbmcgSW50ZXJtZWRpYXRlIENBIEVDQyBSMgIQZUv1paC174CCCE9ugRr/ # AjANBglghkgBZQMEAgEFAKBqMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwG # CisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCCg6yD9 # uATT86AX3fsmh5T+vK14PnLe3likTFiZjGP0LzAKBggqhkjOPQQDAgRnMGUCMQDj # 4ftIp4drM4yiJ+Iw7F2f1Jo722A9Li/1jHaHFVRfOMSyt/si+ZbbpQiEXF1rrz0C # MCN9493ObPCYJCL2dhmNS+B5gz1cZaa8XSnUV/WIIvKHQOV4tIZu0QtDJu9BNx4q # x6GCA4QwggOABgkqhkiG9w0BCQYxggNxMIIDbQIBATBzMF4xCzAJBgNVBAYTAkJF # MRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTQwMgYDVQQDEytHbG9iYWxTaWdu # IE9mZmxpbmUgUjQ1IFRpbWVzdGFtcGluZyBDQSAyMDI1AhEAhHI/uDAN+6h1sztX # zCY3gjALBglghkgBZQMEAgKgggFRMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEw # HAYJKoZIhvcNAQkFMQ8XDTI2MDcwMTE0NDYyOVowKwYJKoZIhvcNAQk0MR4wHDAL # BglghkgBZQMEAgKhDQYJKoZIhvcNAQEMBQAwPwYJKoZIhvcNAQkEMTIEMG5EEsAv # hWnvoEMSPJPBJ0tjXoTkaHsyNwtPIU1bgIFmRYSpEtt2Z+1SDUXmGhDFojCBqAYL # KoZIhvcNAQkQAgwxgZgwgZUwgZIwgY8EFB0kvxmra4s/HJGmWMXTVGSBI50uMHcw # YqRgMF4xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTQw # MgYDVQQDEytHbG9iYWxTaWduIE9mZmxpbmUgUjQ1IFRpbWVzdGFtcGluZyBDQSAy # MDI1AhEAhHI/uDAN+6h1sztXzCY3gjANBgkqhkiG9w0BAQwFAASCAYA+qclei8jg # SScAWwgb5myTH0nzNMBBxQar56u33DgtYXS30wQnU1gcpeLPT7ZaNbnAgEgqFBB+ # b7dS3RZj39b5y2exRmf2a6psG/zsepkMcr/rc1xDPZOshLdjW7uL3sW/D+1Jjg6D # PJHveP57Lp1lCDTwQedPgbjMR1gOhMOc196Rm7+ZcV8lfWAsnSMm96psjn/zkX+U # mXbmnfzabVRq6G1fpwcLAY4Gic5GD2dwl/QMt34HK/er8bA2sxT5AaNeLbJhSjkE # DoDRbklqX1nONLlV4eegnSx7WG+mLzbY7YcytJefp+sSd/cnZAuNoUxpLzem7vXW # oqOeqVESL2eGI1gWWHvZtwmyXNCJtzL0crmuZPJTTROC8rEhrb5UPGsn6FFuGhea # bil+8e9Yfnpcowjrj2gW7yDpd/Zheg/XloQHzmOEs2CSs4cBxOwgGX9ihODpW/k9 # vCTOsXmIo4SmpsVHbrMl9SFoIl3tqkIpilBdMaXumbkP6Q0SElDJcE8= # SIG # End signature block |