diff --git a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 new file mode 100644 index 000000000..e025ba3b8 --- /dev/null +++ b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 @@ -0,0 +1,236 @@ +# Load the Helper Module +Import-Module -Name "$PSScriptRoot\..\Helper.psm1" -Verbose:$false + +# Localized messages +data LocalizedData +{ + # culture="en-US" + ConvertFrom-StringData -StringData @' + ErrorWebsiteNotRunning = The website '{0}' is not correctly running. + VerboseGettingWebsiteBindings = Getting bindings of the website '{0}'. + VerboseUrlReturnStatusCode = Url {0} returned status code {1}. + VerboseTestTargetFalseStatusCode = Status code of url {0} does not match the desired state. + VerboseTestTargetFalseExpectedContent = Content of url {0} does not match the desired state. +'@ +} + +<# + .SYNOPSYS + This will return a hashtable of results. Once the resource state will nerver be changed to 'absent', + this function or will return the resource in the present state or will throw an error. +#> +function Get-TargetResource +{ + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")] + [CmdletBinding()] + [OutputType([Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $WebSiteName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $RelativeUrl, + + [UInt16[]] + $ValidStatusCodes = [int][Net.HttpStatusCode]::OK, + + [String] + $ExpectedContent + ) + + if (Test-WebSiteRunning $WebSiteName $RelativeUrl $ValidStatusCodes $ExpectedContent) + { + return @{ + Ensure = 'Present' + WebSiteName = $WebSiteName + RelativeUrl = $RelativeUrl + ValidStatusCodes = $ValidStatusCodes + ExpectedContent = $ExpectedContent + } + } + else + { + $errorMessage = $LocalizedData.ErrorWebsiteNotRunning -f $WebSiteName + New-TerminatingError -ErrorId 'WebsiteNotRunning' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidResult' + } +} + +<# + .SYNOPSYS + Once this resource is only for check the state, this function will always throw an error. +#> +function Set-TargetResource +{ + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")] + [CmdletBinding()] + param + ( + [ValidateSet('Present', 'Absent')] + [String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $WebSiteName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $RelativeUrl, + + [UInt16[]] + $ValidStatusCodes = [int][Net.HttpStatusCode]::OK, + + [String] + $ExpectedContent + ) + + $errorMessage = $LocalizedData.ErrorWebsiteNotRunning -f $WebSiteName + New-TerminatingError -ErrorId 'WebsiteNotRunning' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidResult' +} + +<# + .SYNOPSYS + This tests the desired state. It will return $true if the website is alive or $false if it is not. +#> +function Test-TargetResource +{ + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")] + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [ValidateSet('Present', 'Absent')] + [String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $WebSiteName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $RelativeUrl, + + [UInt16[]] + $ValidStatusCodes = [int][Net.HttpStatusCode]::OK, + + [String] + $ExpectedContent + ) + + return Test-WebSiteRunning $WebSiteName $RelativeUrl $ValidStatusCodes $ExpectedContent +} + +#region Helper Functions + +function Test-WebSiteRunning +{ + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $WebSiteName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $RelativeUrl, + + [UInt16[]] + $ValidStatusCodes, + + [String] + $ExpectedContent + ) + + function Get-UrlStatus + { + [OutputType([Hashtable])] + param ([string] $Url) + + try + { + $webResponse = Invoke-WebRequest -Uri $Url -UseBasicParsing -DisableKeepAlive + + return @{ + StatusCode = $webResponse.StatusCode + Content = $webResponse.Content -replace "`r`n", "`n" + } + } + catch [Net.WebException] + { + return @{ + StatusCode = [int]$_.Exception.Response.StatusCode + Content = '' + } + } + } + + Write-Verbose -Message ($LocalizedData.VerboseGettingWebsiteBindings -f $WebSiteName) + + $bindings = Get-WebBinding -Name $WebSiteName + + foreach ($binding in $bindings) + { + if ($binding.Protocol -in @('http', 'https')) + { + # Extract IPv6 address + if ($binding.bindingInformation -match '^\[(.*?)\]\:(.*?)\:(.*?)$') + { + $ipAddress = $Matches[1] + $port = $Matches[2] + $hostName = $Matches[3] + } + else + { + $ipAddress, $port, $hostName = $binding.bindingInformation -split '\:' + } + + if (-not $hostName) + { + $hostName = 'localhost' + } + + $url = "$($binding.protocol)://$($hostName):$port$RelativeUrl" + + $urlStatus = Get-UrlStatus $url + + Write-Verbose -Message ($LocalizedData.VerboseUrlReturnStatusCode -f $url, $urlStatus.StatusCode) + + if ($ValidStatusCodes -notcontains $urlStatus.StatusCode) + { + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseStatusCode -f $url) + + return $false + } + + if ($ExpectedContent -and $urlStatus.Content -ne $ExpectedContent) + { + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseExpectedContent -f $url) + + return $false + } + } + } + + return $true +} + +#endregion + +Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.schema.mof b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.schema.mof new file mode 100644 index 000000000..9fcfeeecb --- /dev/null +++ b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.schema.mof @@ -0,0 +1,9 @@ +[ClassVersion("1.0.0"), FriendlyName("xWebSiteAlive")] +class MSFT_xWebSiteAlive : OMI_BaseResource +{ + [Write, ValueMap{"Present", "Absent"}, Values{"Present", "Absent"}] String Ensure; + [Key] String WebSiteName; + [Key] String RelativeUrl; + [Write] UInt16 ValidStatusCodes[]; + [Write] String ExpectedContent; +}; diff --git a/Examples/Sample_xWebSiteAlive_200ok.ps1 b/Examples/Sample_xWebSiteAlive_200ok.ps1 new file mode 100644 index 000000000..c035b88eb --- /dev/null +++ b/Examples/Sample_xWebSiteAlive_200ok.ps1 @@ -0,0 +1,17 @@ +Configuration Sample_xWebSiteAlive_200ok +{ + Import-DscResource -Module xWebAdministration + + xWebsite DefaultWebSite + { + Ensure = 'Present' + Name = 'Default Web Site' + State = 'Started' + } + + xWebSiteAlive WebSiteAlive + { + WebSiteName = 'Default Web Site' + RelativeUrl = '/' + } +} diff --git a/Examples/Sample_xWebSiteAlive_ExpectedContent.ps1 b/Examples/Sample_xWebSiteAlive_ExpectedContent.ps1 new file mode 100644 index 000000000..aa83ca0eb --- /dev/null +++ b/Examples/Sample_xWebSiteAlive_ExpectedContent.ps1 @@ -0,0 +1,52 @@ +Configuration Sample_xWebSiteAlive_ExpectedContent +{ + Import-DscResource -Module xWebAdministration + + xWebsite DefaultWebSite + { + Ensure = 'Present' + Name = 'Default Web Site' + State = 'Started' + } + + xWebSiteAlive WebSiteAlive + { + WebSiteName = 'Default Web Site' + RelativeUrl = '/iisstart.htm' + ValidStatusCodes = @(200) + ExpectedContent = @' + + + + +IIS Windows Server + + + +
+IIS +
+ + +'@ + } +} diff --git a/README.md b/README.md index 37a0ec3a7..e2c1e8f27 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build status](https://ci.appveyor.com/api/projects/status/gnsxkjxht31ctan1/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/xwebadministration/branch/master) -The **xWebAdministration** module contains the **xIISModule**, **xIISLogging**, **xWebAppPool**, **xWebsite**, **xWebApplication**, **xWebVirtualDirectory**, **xSSLSettings** and **xWebConfigKeyValue** DSC resources for creating and configuring various IIS artifacts. +The **xWebAdministration** module contains the **xIISModule**, **xIISLogging**, **xWebAppPool**, **xWebsite**, **xWebApplication**, **xWebVirtualDirectory**, **xSSLSettings**, **xWebConfigKeyValue** and **xWebSiteAlive** DSC resources for creating and configuring various IIS artifacts. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. @@ -235,10 +235,19 @@ Please check out common DSC Resources [contributing guidelines](https://github.c * **DefaultApplicationPool**: Name of the default application pool used by websites. * **AllowSubDirConfig**: Should IIS look for config files in subdirectories, either **true** or **false** +### xWebSiteAlive + +* **WebSiteName**: Name of the website that must be running, such as **Default Web Site**. +* **RelativeUrl**: A relative url to joint to each website binding. +* **ValidStatusCodes**: A list of HTTP status codes to be considered successful results. Defaults to **200 OK**. +* **ExpectedContent**: The content considered to be successful result. + ## Versions ### Unreleased +* Added **xWebSiteAlive**. + ### 1.17.0.0 * Added removal of self signed certificate to the integration tests of **xWebsite**, fixes #276. diff --git a/Tests/Integration/MSFT_xWebSiteAlive.Integration.Tests.ps1 b/Tests/Integration/MSFT_xWebSiteAlive.Integration.Tests.ps1 new file mode 100644 index 000000000..a77b77e80 --- /dev/null +++ b/Tests/Integration/MSFT_xWebSiteAlive.Integration.Tests.ps1 @@ -0,0 +1,93 @@ +$script:DSCModuleName = 'xWebAdministration' +$script:DSCResourceName = 'MSFT_xWebSiteAlive' + +#region HEADER +# Integration Test Template Version: 1.1.1 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Integration + +#endregion + +$tempIisConfigBackupName = "$($script:DSCResourceName)_" + (Get-Date).ToString('yyyyMMdd_HHmmss') +$tempRequestFilePath = $null + +# Using try/finally to always cleanup. +try +{ + $null = Backup-WebConfiguration -Name $tempIisConfigBackupName + + $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:DSCResourceName).config.ps1" + . $configFile + + $configData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + WebSiteName = 'WebsiteForxWebSiteAlive' + PhysicalPath = Join-Path $env:SystemDrive 'inetpub\wwwroot\' + HTTPPort = 80 + RequestFileName = 'xWebSiteAliveTest.html' + RequestFileContent = @' + + +IIS Windows Server + + + + +'@ + } + ) + } + + New-Website -Name $configData.AllNodes.WebSiteName ` + -PhysicalPath $configData.AllNodes.PhysicalPath ` + -Port $configData.AllNodes.HTTPPort ` + -Force ` + -ErrorAction Stop + + # Write without a BOM + $tempRequestFilePath = Join-Path $configData.AllNodes.PhysicalPath $configData.AllNodes.RequestFileName + [IO.File]::WriteAllText($tempRequestFilePath, $configData.AllNodes.RequestFileContent) + + #region Integration Tests + + Describe "$($script:DSCResourceName)_Integration" { + #region DEFAULT TESTS + It 'Should compile and apply the MOF without throwing' { + { + & "$($script:DSCResourceName)_Config" -OutputPath $TestDrive -ConfigurationData $configData + Start-DscConfiguration -Path $TestDrive ` + -ComputerName localhost -Wait -Verbose -Force + } | Should not throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not throw + } + #endregion + } + + #endregion +} +finally +{ + #region FOOTER + Restore-WebConfiguration -Name $tempIisConfigBackupName + Remove-WebConfigurationBackup -Name $tempIisConfigBackupName + + Remove-Item -Path $tempRequestFilePath -Force + + Restore-TestEnvironment -TestEnvironment $TestEnvironment + #endregion +} diff --git a/Tests/Integration/MSFT_xWebSiteAlive.config.ps1 b/Tests/Integration/MSFT_xWebSiteAlive.config.ps1 new file mode 100644 index 000000000..9e2d322f5 --- /dev/null +++ b/Tests/Integration/MSFT_xWebSiteAlive.config.ps1 @@ -0,0 +1,13 @@ +Configuration MSFT_xWebSiteAlive_Config { + Import-DscResource -ModuleName xWebAdministration + + Node $AllNodes.NodeName { + xWebSiteAlive WebSiteAlive + { + WebSiteName = $Node.WebSiteName + RelativeUrl = "/$($Node.RequestFileName)" + ValidStatusCodes = 200 + ExpectedContent = $Node.RequestFileContent + } + } +} diff --git a/Tests/Unit/MSFT_xWebSiteAlive.Tests.ps1 b/Tests/Unit/MSFT_xWebSiteAlive.Tests.ps1 new file mode 100644 index 000000000..f0f601f56 --- /dev/null +++ b/Tests/Unit/MSFT_xWebSiteAlive.Tests.ps1 @@ -0,0 +1,408 @@ +$script:DSCModuleName = 'xWebAdministration' +$script:DSCResourceName = 'MSFT_xWebSiteAlive' + +#region HEADER + +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +#endregion HEADER + +# Begin Testing +try +{ + InModuleScope -ModuleName $script:DSCResourceName -ScriptBlock { + + Describe "$script:DSCResourceName\Get-TargetResource" { + + $mockWebBindings = @( + @{ + Protocol = 'http' + bindingInformation = '*:80:' + } + ) + + $splat = @{ + WebSiteName = 'Default Web Site' + RelativeUrl = '/test' + ValidStatusCodes = 100, 200, 301 + ExpectedContent = @' + + +IIS Windows Server + + + + +'@ -replace "`r`n", "`n" # In the real execution DSC will send `n as line terminators + } + + Context 'The website is alive' { + $mockUrlResultOk = @{ + StatusCode = 200 + Content = @' + + +IIS Windows Server + + + + +'@ + } + + Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings } + + Mock -CommandName 'Invoke-WebRequest' -MockWith { $mockUrlResultOk } + + $result = Get-TargetResource @splat + + It 'Should return Ensure' { + $result.Ensure | Should Be 'Present' + } + + It 'Should return WebSiteName' { + $result.WebSiteName | Should Be $splat.WebSiteName + } + + It 'Should return RelativeUrl' { + $result.RelativeUrl | Should Be $splat.RelativeUrl + } + + It 'Should return ValidStatusCodes' { + $result.ValidStatusCodes | Should Be $splat.ValidStatusCodes + } + + It 'Should return ExpectedContent' { + $result.ExpectedContent | Should Be $splat.ExpectedContent + } + } + + Context 'The website is not alive' { + $mockUrlResultInternalServerError = @{ + StatusCode = 500 + Content = '' + } + + Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings } + + Mock -CommandName 'Invoke-WebRequest' -MockWith { $mockUrlResultInternalServerError } + + It 'Should throw an error' { + { Get-TargetResource @splat } | Should Throw + } + } + } + + Describe "$script:DSCResourceName\Set-TargetResource" { + It 'Should throw an error' { + $splat = @{ + WebSiteName = 'Default Web Site' + RelativeUrl = '/' + } + + { Set-TargetResource @splat } | Should Throw + } + } + + Describe "$script:DSCResourceName\Test-TargetResource" { + + $mockUrlResultOk = @{ + StatusCode = 200 + Content = '' + } + + Context 'There are multiple websites' { + $mockWebBindings01 = @( + @{ + Protocol = 'http' + bindingInformation = '*:2000:' + }, + @{ + Protocol = 'https' + bindingInformation = '*:3000:' + } + ) + + $mockWebBindings02 = @( + @{ + Protocol = 'http' + bindingInformation = '*:4000:' + }, + @{ + Protocol = 'https' + bindingInformation = '*:5000:' + } + ) + + $mockWebBindings03 = @( + @{ + Protocol = 'http' + bindingInformation = '*:6000:' + } + ) + + $splatWebsite02 = @{ + WebSiteName = 'WebSite02' + RelativeUrl = '/' + } + + Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq 'WebSite01' } -MockWith { return $mockWebBindings01 } + + Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splatWebsite02.WebSiteName } -MockWith { return $mockWebBindings02 } + + Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq 'WebSite03' } -MockWith { return $mockWebBindings03 } + + Mock -CommandName 'Invoke-WebRequest' -MockWith { $mockUrlResultOk } + + Test-TargetResource @splatWebsite02 + + It 'Should request the urls from the correct website' { + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://localhost:4000/' } + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'https://localhost:5000/' } + } + + It 'Should not request the urls from the undesired websites' { + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 0 -ParameterFilter { $Uri -eq 'http://localhost:2000/' } + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 0 -ParameterFilter { $Uri -eq 'https://localhost:3000/' } + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 0 -ParameterFilter { $Uri -eq 'http://localhost:6000/' } + } + } + + Context 'There are multiple website bindings' { + $mockWebBindings = @( + @{ + Protocol = 'http' + bindingInformation = '*:80:' + }, + @{ + Protocol = 'http' + bindingInformation = '*:11000:' + }, + @{ + Protocol = 'http' + bindingInformation = '[::1]:12000:' + }, + @{ + Protocol = 'https' + bindingInformation = '[0:0:0:0:0:ffff:d1ad:35a7]:13000:www.domaintest.com' + }, + @{ + Protocol = 'net.tcp' + bindingInformation = '808:*' + } + ) + + $splat = @{ + WebSiteName = 'Default Web Site' + RelativeUrl = '/' + } + + Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings } + + Mock -CommandName 'Invoke-WebRequest' -MockWith { $mockUrlResultOk } + + Test-TargetResource @splat + + It 'Should request the correct urls' { + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://localhost:80/' } + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://localhost:11000/' } + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://localhost:12000/' } + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'https://www.domaintest.com:13000/' } + } + + It 'Should request only the correct protocols' { + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 0 -ParameterFilter { $Uri.ToString().StartsWith('net.tcp') } + } + } + + Context 'There are multiple website bindings and a relative URL is passed' { + $mockWebBindings = @( + @{ + Protocol = 'http' + bindingInformation = '[::1]:11000:' + }, + @{ + Protocol = 'https' + bindingInformation = '[0:0:0:0:0:ffff:d1ad:35a7]:12000:www.domaintest01.com' + }, + @{ + Protocol = 'net.tcp' + bindingInformation = '808:*' + }, + @{ + Protocol = 'http' + bindingInformation = '*:80:www.domaintest02.com' + }, + @{ + Protocol = 'http' + bindingInformation = '*:13000:' + } + ) + + $splat = @{ + WebSiteName = 'Default Web Site' + RelativeUrl = '/relative/path/index.html' + } + + Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings } + + Mock -CommandName 'Invoke-WebRequest' -MockWith { $mockUrlResultOk } + + Test-TargetResource @splat + + It 'Should request the correct urls' { + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://localhost:11000/relative/path/index.html' } + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'https://www.domaintest01.com:12000/relative/path/index.html' } + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://www.domaintest02.com:80/relative/path/index.html' } + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://localhost:13000/relative/path/index.html' } + + } + + It 'Should request only the correct protocols' { + Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 0 -ParameterFilter { $Uri.ToString().StartsWith('net.tcp') } + } + } + + $mockWebBindings = @( + @{ + Protocol = 'http' + bindingInformation = '*:21000:' + } + ) + + $splat = @{ + WebSiteName = 'Default Web Site' + RelativeUrl = '/test' + ValidStatusCodes = 100, 200, 300 + } + + Context 'A list of valid status codes was passed and the website is alive' { + + Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings } + + Mock -CommandName 'Invoke-WebRequest' -ParameterFilter { $Uri -eq 'http://localhost:21000/test' } -MockWith { $mockUrlResultOk } + + It 'Should be true' { + Test-TargetResource @splat | Should be $true + } + } + + Context 'A list of valid status codes was passed and the website is not alive' { + $mockUrlResultInternalServerError = @{ + StatusCode = 500 + Content = '' + } + + Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings } + + Mock -CommandName 'Invoke-WebRequest' -ParameterFilter { $Uri -eq 'http://localhost:21000/test' } -MockWith { $mockUrlResultInternalServerError } + + It 'Should be false' { + Test-TargetResource @splat | Should be $false + } + } + + $splat = @{ + WebSiteName = 'Default Web Site' + RelativeUrl = '/test' + ValidStatusCodes = 200 + ExpectedContent = @' + + +IIS Windows Server + + + + +'@ -replace "`r`n", "`n" # In the real execution DSC will send `n as line terminators + } + + Context 'A expected content was passed and the result match' { + + $mockUrlResultContentMatch = @{ + StatusCode = 200 + Content = @' + + +IIS Windows Server + + + + +'@ + } + + Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings } + + Mock -CommandName 'Invoke-WebRequest' -ParameterFilter { $Uri -eq 'http://localhost:21000/test' } -MockWith { $mockUrlResultContentMatch } + + It 'Should be true' { + Test-TargetResource @splat | Should be $true + } + } + + Context 'A expected content was passed and the result does not match' { + + $mockUrlResultContentDontMatch = @{ + StatusCode = 200 + Content = @' + + +IIS Windows Server + + diff + + +'@ + } + + Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings } + + Mock -CommandName 'Invoke-WebRequest' -ParameterFilter { $Uri -eq 'http://localhost:21000/test' } -MockWith { $mockUrlResultContentDontMatch } + + It 'Should be false' { + Test-TargetResource @splat | Should be $false + } + } + + Context 'A expected content was passed and the website is not alive' { + $mockUrlResultInternalServerError = @{ + StatusCode = 500 + Content = @' + + +IIS Windows Server + + + + +'@ + } + + Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings } + + Mock -CommandName 'Invoke-WebRequest' -ParameterFilter { $Uri -eq 'http://localhost:21000/test' } -MockWith { $mockUrlResultInternalServerError } + + It 'Should be false' { + Test-TargetResource @splat | Should be $false + } + } + } + } +} +finally +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} diff --git a/appveyor.yml b/appveyor.yml index b1c94a908..acfa8b768 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,6 +2,10 @@ # environment configuration # #---------------------------------# version: 1.17.{build}.0 + +services: + - iis + install: - git clone https://github.com/PowerShell/DscResource.Tests - ps: |