From 13c4edeb37a514e4f94fbcc88781095f1768c326 Mon Sep 17 00:00:00 2001 From: Rosberg Linhares Lacerda Date: Sun, 28 May 2017 12:06:30 -0300 Subject: [PATCH 1/4] Initial version. --- .../MSFT_xWebSiteAlive.psm1 | 230 ++++++++++++++++++ .../MSFT_xWebSiteAlive.schema.mof | Bin 0 -> 672 bytes 2 files changed, 230 insertions(+) create mode 100644 DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 create mode 100644 DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.schema.mof diff --git a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 new file mode 100644 index 000000000..2ac89d6bc --- /dev/null +++ b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 @@ -0,0 +1,230 @@ +# 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}{1}' is not correctly running. + 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. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $WebSiteName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $RelativeUrl = '/', + + [int[]] + $ValidStatusCodes = [int][Net.HttpStatusCode]::OK, + + [String] + $ExpectedContent + ) + + if (Test-WebSiteRunning $WebSiteName $RelativeUrl $ValidStatusCodes $ExpectedContent) + { + return @{ + Ensure = 'Present' + WebSiteName = $WebSiteName + RelativeUrl = $RelativeUrl + ValidStatusCodes = [object[]]$ValidStatusCodes + ExpectedContent = $ExpectedContent + } + } + else + { + $errorMessage = $LocalizedData.ErrorWebsiteNotRunning -f $WebSiteName, $RelativeUrl + New-TerminatingError -ErrorId 'WebsiteNotRunning' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidResult' + } +} + +<# + .SYNOPSYS + This will set the desired state. +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [ValidateSet('Present', 'Absent')] + [String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $WebSiteName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $RelativeUrl = '/', + + [int[]] + $ValidStatusCodes = [int][Net.HttpStatusCode]::OK, + + [String] + $ExpectedContent + ) + + $errorMessage = $LocalizedData.ErrorWebsiteNotRunning -f $WebSiteName, $RelativeUrl + New-TerminatingError -ErrorId 'WebsiteNotRunning' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidResult' +} + +<# + .SYNOPSYS + This tests the desired state. If the state is not correct it will return $false. + If the state is correct it will return $true. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [ValidateSet('Present', 'Absent')] + [String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $WebSiteName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $RelativeUrl = '/', + + [int[]] + $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, + + [int[]] + $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 = '' + } + } + } + + $bindings = Get-WebBinding $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 \ No newline at end of file diff --git a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.schema.mof b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.schema.mof new file mode 100644 index 0000000000000000000000000000000000000000..50e6512c7ec74933cf56a768b46731538353a926 GIT binary patch literal 672 zcmb7>K}*9>5QOI}_#Z+}3PswB2R&)AAW{`;>md|rn}-BqT1+Y}(!Z{L`%4h`XRD}){tED3~{Tu604MpmJ$iNj!;VpFy_nF?%3q4Sc z)dNvcoq@>oN@eV!_O++UW?G~!$d2yyxPuw#gsC@RFTtIAr%rchmbjebWVR_jglag+a={hgJ~?~}D9($X Date: Mon, 5 Jun 2017 20:15:11 -0300 Subject: [PATCH 2/4] Added tests, samples and changed the read-me file. --- .../MSFT_xWebSiteAlive.psm1 | 16 +- Examples/Sample_xWebSiteAlive_200ok.ps1 | 17 + .../Sample_xWebSiteAlive_ExpectedContent.ps1 | 52 +++ README.md | 11 +- .../MSFT_xWebSiteAlive.Integration.Tests.ps1 | 94 ++++ .../Integration/MSFT_xWebSiteAlive.config.ps1 | 13 + Tests/Unit/MSFT_xWebSiteAlive.Tests.ps1 | 408 ++++++++++++++++++ 7 files changed, 602 insertions(+), 9 deletions(-) create mode 100644 Examples/Sample_xWebSiteAlive_200ok.ps1 create mode 100644 Examples/Sample_xWebSiteAlive_ExpectedContent.ps1 create mode 100644 Tests/Integration/MSFT_xWebSiteAlive.Integration.Tests.ps1 create mode 100644 Tests/Integration/MSFT_xWebSiteAlive.config.ps1 create mode 100644 Tests/Unit/MSFT_xWebSiteAlive.Tests.ps1 diff --git a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 index 2ac89d6bc..61f24d115 100644 --- a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 +++ b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 @@ -6,7 +6,7 @@ data LocalizedData { # culture="en-US" ConvertFrom-StringData -StringData @' - ErrorWebsiteNotRunning = The website '{0}{1}' is not correctly running. + ErrorWebsiteNotRunning = The website '{0}' is not correctly running. 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. @@ -31,7 +31,7 @@ function Get-TargetResource [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] - $RelativeUrl = '/', + $RelativeUrl, [int[]] $ValidStatusCodes = [int][Net.HttpStatusCode]::OK, @@ -52,7 +52,7 @@ function Get-TargetResource } else { - $errorMessage = $LocalizedData.ErrorWebsiteNotRunning -f $WebSiteName, $RelativeUrl + $errorMessage = $LocalizedData.ErrorWebsiteNotRunning -f $WebSiteName New-TerminatingError -ErrorId 'WebsiteNotRunning' ` -ErrorMessage $errorMessage ` -ErrorCategory 'InvalidResult' @@ -80,7 +80,7 @@ function Set-TargetResource [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] - $RelativeUrl = '/', + $RelativeUrl, [int[]] $ValidStatusCodes = [int][Net.HttpStatusCode]::OK, @@ -89,7 +89,7 @@ function Set-TargetResource $ExpectedContent ) - $errorMessage = $LocalizedData.ErrorWebsiteNotRunning -f $WebSiteName, $RelativeUrl + $errorMessage = $LocalizedData.ErrorWebsiteNotRunning -f $WebSiteName New-TerminatingError -ErrorId 'WebsiteNotRunning' ` -ErrorMessage $errorMessage ` -ErrorCategory 'InvalidResult' @@ -118,7 +118,7 @@ function Test-TargetResource [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] - $RelativeUrl = '/', + $RelativeUrl, [int[]] $ValidStatusCodes = [int][Net.HttpStatusCode]::OK, @@ -177,7 +177,7 @@ function Test-WebSiteRunning } } - $bindings = Get-WebBinding $WebSiteName + $bindings = Get-WebBinding -Name $WebSiteName foreach ($binding in $bindings) { @@ -227,4 +227,4 @@ function Test-WebSiteRunning #endregion -Export-ModuleMember -Function *-TargetResource \ No newline at end of file +Export-ModuleMember -Function *-TargetResource diff --git a/Examples/Sample_xWebSiteAlive_200ok.ps1 b/Examples/Sample_xWebSiteAlive_200ok.ps1 new file mode 100644 index 000000000..d1d39e054 --- /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..e7ee72883 --- /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..63ab31bcc --- /dev/null +++ b/Tests/Integration/MSFT_xWebSiteAlive.Integration.Tests.ps1 @@ -0,0 +1,94 @@ +$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 + +[string] $tempIisConfigBackupName = "$($script:DSCResourceName)_" + (Get-Date).ToString('yyyyMMdd_HHmmss') +[string] $tempWebSitePhysicalPath = Join-Path $env:SystemDrive 'inetpub\wwwroot\WebsiteForxWebSiteAlive' + +# 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 = $tempWebSitePhysicalPath + HTTPPort = 80 + RequestFileName = 'xWebSiteAliveTest.html' + RequestFileContent = @' + + +IIS Windows Server + + + + +'@ + } + ) + } + + New-Item -Path $configData.AllNodes.PhysicalPath -ItemType Directory | Out-Null + + New-Website -Name $configData.AllNodes.WebSiteName ` + -PhysicalPath $configData.AllNodes.PhysicalPath ` + -Port $configData.AllNodes.HTTPPort ` + -Force ` + -ErrorAction Stop + + # Write without a BOM + [IO.File]::WriteAllText((Join-Path $configData.AllNodes.PhysicalPath $configData.AllNodes.RequestFileName), $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 $tempWebSitePhysicalPath -Recurse -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 +} From 4711d256637f419dbe1d074d4d0d7e5245928b0b Mon Sep 17 00:00:00 2001 From: Rosberg Linhares Lacerda Date: Mon, 12 Jun 2017 14:52:03 -0300 Subject: [PATCH 3/4] Changing the type of the property ValidStatusCodes to UInt16 in the module. Avoiding files with unicode file encoding. Avoiding files with tab characters. --- .../MSFT_xWebSiteAlive.psm1 | 10 +++++----- .../MSFT_xWebSiteAlive.schema.mof | Bin 672 -> 335 bytes Examples/Sample_xWebSiteAlive_200ok.ps1 | 2 +- .../Sample_xWebSiteAlive_ExpectedContent.ps1 | 18 +++++++++--------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 index 61f24d115..7e9ac0105 100644 --- a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 +++ b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 @@ -1,4 +1,4 @@ -# Load the Helper Module +# Load the Helper Module Import-Module -Name "$PSScriptRoot\..\Helper.psm1" -Verbose:$false # Localized messages @@ -33,7 +33,7 @@ function Get-TargetResource [String] $RelativeUrl, - [int[]] + [UInt16[]] $ValidStatusCodes = [int][Net.HttpStatusCode]::OK, [String] @@ -82,7 +82,7 @@ function Set-TargetResource [String] $RelativeUrl, - [int[]] + [UInt16[]] $ValidStatusCodes = [int][Net.HttpStatusCode]::OK, [String] @@ -120,7 +120,7 @@ function Test-TargetResource [String] $RelativeUrl, - [int[]] + [UInt16[]] $ValidStatusCodes = [int][Net.HttpStatusCode]::OK, [String] @@ -147,7 +147,7 @@ function Test-WebSiteRunning [String] $RelativeUrl, - [int[]] + [UInt16[]] $ValidStatusCodes, [String] diff --git a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.schema.mof b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.schema.mof index 50e6512c7ec74933cf56a768b46731538353a926..9fcfeeecbbde0f5178adee2fd0c406e1eb23f3fd 100644 GIT binary patch literal 335 zcmZvX!D_-l5Qgu8yu+|3g&=zAp*`84ASJe#s)rC{O@7M4x{;kpQN(xeMq;7Gfq{W< zX88UhwVHVY&!%w$H5rXZV>L`@?hRaR&r5y4K%KVO=SDEK<{N5Q#!=OsQI^j?cYh!~ z(|fkqy=uk^+_b(z99`mwLKRydek8Qfwne6o7xm%6aH0}Y)BSC__Lv{Sl5+9JebLNu z>+#b6eZ%>e^roHnBfNsu5<;xKz2lozN$Z7^$x|n1>Rhz6oHjMMDF5x<4NgZ?f;w%S Kga~2j8u|x=vutPp literal 672 zcmb7>K}*9>5QOI}_#Z+}3PswB2R&)AAW{`;>md|rn}-BqT1+Y}(!Z{L`%4h`XRD}){tED3~{Tu604MpmJ$iNj!;VpFy_nF?%3q4Sc z)dNvcoq@>oN@eV!_O++UW?G~!$d2yyxPuw#gsC@RFTtIAr%rchmbjebWVR_jglag+a={hgJ~?~}D9($X From 5baf968c998f31a80c39448efcbd1cefcbb54112 Mon Sep 17 00:00:00 2001 From: Rosberg Linhares Lacerda Date: Wed, 14 Jun 2017 19:27:56 -0300 Subject: [PATCH 4/4] The IIS service is required for integration tests. It is not necessary to create a directory for the test website. A new binding on the same physical path is sufficient. Avoiding the 'There is no call to Write-Verbose' messages. --- .../MSFT_xWebSiteAlive.psm1 | 22 ++++++++++++------- .../MSFT_xWebSiteAlive.Integration.Tests.ps1 | 13 +++++------ appveyor.yml | 4 ++++ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 index 7e9ac0105..e025ba3b8 100644 --- a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 +++ b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 @@ -6,19 +6,22 @@ data LocalizedData { # culture="en-US" ConvertFrom-StringData -StringData @' - ErrorWebsiteNotRunning = The website '{0}' is not correctly running. - VerboseUrlReturnStatusCode = Url {0} returned status code {1}. - VerboseTestTargetFalseStatusCode = Status code of url {0} does not match the desired state. + 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. + 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 @@ -46,7 +49,7 @@ function Get-TargetResource Ensure = 'Present' WebSiteName = $WebSiteName RelativeUrl = $RelativeUrl - ValidStatusCodes = [object[]]$ValidStatusCodes + ValidStatusCodes = $ValidStatusCodes ExpectedContent = $ExpectedContent } } @@ -61,10 +64,11 @@ function Get-TargetResource <# .SYNOPSYS - This will set the desired state. + 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 ( @@ -97,11 +101,11 @@ function Set-TargetResource <# .SYNOPSYS - This tests the desired state. If the state is not correct it will return $false. - If the state is correct it will return $true. + 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 @@ -177,6 +181,8 @@ function Test-WebSiteRunning } } + Write-Verbose -Message ($LocalizedData.VerboseGettingWebsiteBindings -f $WebSiteName) + $bindings = Get-WebBinding -Name $WebSiteName foreach ($binding in $bindings) diff --git a/Tests/Integration/MSFT_xWebSiteAlive.Integration.Tests.ps1 b/Tests/Integration/MSFT_xWebSiteAlive.Integration.Tests.ps1 index 63ab31bcc..a77b77e80 100644 --- a/Tests/Integration/MSFT_xWebSiteAlive.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_xWebSiteAlive.Integration.Tests.ps1 @@ -18,8 +18,8 @@ $TestEnvironment = Initialize-TestEnvironment ` #endregion -[string] $tempIisConfigBackupName = "$($script:DSCResourceName)_" + (Get-Date).ToString('yyyyMMdd_HHmmss') -[string] $tempWebSitePhysicalPath = Join-Path $env:SystemDrive 'inetpub\wwwroot\WebsiteForxWebSiteAlive' +$tempIisConfigBackupName = "$($script:DSCResourceName)_" + (Get-Date).ToString('yyyyMMdd_HHmmss') +$tempRequestFilePath = $null # Using try/finally to always cleanup. try @@ -34,7 +34,7 @@ try @{ NodeName = 'localhost' WebSiteName = 'WebsiteForxWebSiteAlive' - PhysicalPath = $tempWebSitePhysicalPath + PhysicalPath = Join-Path $env:SystemDrive 'inetpub\wwwroot\' HTTPPort = 80 RequestFileName = 'xWebSiteAliveTest.html' RequestFileContent = @' @@ -50,8 +50,6 @@ try ) } - New-Item -Path $configData.AllNodes.PhysicalPath -ItemType Directory | Out-Null - New-Website -Name $configData.AllNodes.WebSiteName ` -PhysicalPath $configData.AllNodes.PhysicalPath ` -Port $configData.AllNodes.HTTPPort ` @@ -59,7 +57,8 @@ try -ErrorAction Stop # Write without a BOM - [IO.File]::WriteAllText((Join-Path $configData.AllNodes.PhysicalPath $configData.AllNodes.RequestFileName), $configData.AllNodes.RequestFileContent) + $tempRequestFilePath = Join-Path $configData.AllNodes.PhysicalPath $configData.AllNodes.RequestFileName + [IO.File]::WriteAllText($tempRequestFilePath, $configData.AllNodes.RequestFileContent) #region Integration Tests @@ -87,7 +86,7 @@ finally Restore-WebConfiguration -Name $tempIisConfigBackupName Remove-WebConfigurationBackup -Name $tempIisConfigBackupName - Remove-Item -Path $tempWebSitePhysicalPath -Recurse -Force + Remove-Item -Path $tempRequestFilePath -Force Restore-TestEnvironment -TestEnvironment $TestEnvironment #endregion 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: |