From 67aa7428f560db905e31d9d673ad115a944d16c7 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 25 Mar 2023 14:42:32 +0100 Subject: [PATCH] SqlRS: Add new integration test jobs (#1877) --- CHANGELOG.md | 17 +- appveyor.yml | 4 + azure-pipelines.yml | 89 +++- .../Public/Import-SqlDscPreferredModule.ps1 | 24 +- .../DSC_SqlRS.Integration.Tests.ps1 | 189 +------- tests/Integration/DSC_SqlRS.config.ps1 | 64 +-- .../DSC_SqlRS_Default.Integration.Tests.ps1 | 450 ++++++++++++++++++ .../Integration/DSC_SqlRS_Default.config.ps1 | 280 +++++++++++ tests/Integration/DSC_SqlSetup.config.ps1 | 2 + tests/TestHelpers/CommonTestHelper.psm1 | 77 +++ 10 files changed, 936 insertions(+), 260 deletions(-) create mode 100644 tests/Integration/DSC_SqlRS_Default.Integration.Tests.ps1 create mode 100644 tests/Integration/DSC_SqlRS_Default.config.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index efc8701ff..30dd8d1f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SqlServerDsc - New GitHub Actions workflow that run PSScriptAnalyzer for PRs so any issues are shown directly in the PR's changed files ([issue #1860](https://github.com/dsccommunity/SqlServerDsc/issues/1860)). + - Added a separate integration test jobs for SQL Server Reporting Services + to be able to test configuring SQL Server Reportings Services using + other values that the default values. ### Changed @@ -22,9 +25,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed the regular expression `features?` from the GitVersion configuration. Before, if a fix commit mentioned the word feature but means a SQL Server feature GitVersion would bump minor instead of patch number. + - When running in Azure Pipelines any existing SqlServer module is removed + before running integration tests, so the tests can update to latest version. - `Get-SqlDscAudit` - The parameter `Name` is no longer mandatory. When left out all the current audits are returned ([issue #1812](https://github.com/dsccommunity/SqlServerDsc/issues/1812)). +- `Import-SqlDscPreferredModule` + - Now correctly preserves paths that is set in the session for the environment + variable `$env:PSModulePath`. If the module _SqlServer_ or _SQLPS_ are not + found the command will populate the `$env:PSModulePath` with the + unique paths from all targets; session, user, and machine. This is done + so that any new path that was added to the machine or user target will + also be set in the session. + - Now imports the preferred module into the global scope so that MOF-based + resources (that is in another module scope) can use the imported module. ### Fixed @@ -41,8 +55,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The parameter `SqlSysAdminAccounts` is no longer mandatory to allow installation where the database engine is not installed. - `SqlRS` - - Test renamed to `When Reports virtual directory is different` so it is more correct and not a duplicate - Fixed issue of configuring reporting services ([issue #1868](https://github.com/dsccommunity/SqlServerDsc/issues/1868)). + - Test renamed to `When Reports virtual directory is different` so it + is more correct and not a duplicate. ## [16.1.0] - 2023-02-28 diff --git a/appveyor.yml b/appveyor.yml index 2720208d8..ffa55bc2a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -45,6 +45,10 @@ install: winrm quickconfig -quiet # cSpell: disable-line + Import-Module -Name ./tests/TestHelpers/CommonTestHelper.psm1 + Remove-PowerShellModuleFromCI -Name @('SqlServer', 'SQLPS') + Remove-Module -Name CommonTestHelper + # DEBUG: If running on own AppVeyor project, comment the line below that skips if it is not a pull request build_script: - pwsh: | diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a81aa747a..9d7353352 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,6 +19,7 @@ variables: sourceFolderName: source defaultBranch: main +# cSpell: ignore setvariable updatebuildnumber DSCSQLTEST hqrmtest quickconfig stages: - stage: Build jobs: @@ -171,11 +172,6 @@ stages: pool: vmImage: $(JOB_VMIMAGE) timeoutInMinutes: 0 - variables: - # This sets environment variable $env:CI. - CI: true - # This sets environment variable $env:SqlServerDscCI. - SqlServerDscCI: true steps: - task: DownloadPipelineArtifact@2 displayName: 'Download Build Artifact' @@ -190,6 +186,12 @@ stages: targetType: 'inline' script: 'winrm quickconfig -quiet' pwsh: false + - powershell: | + Import-Module -Name ./tests/TestHelpers/CommonTestHelper.psm1 + Remove-PowerShellModuleFromCI -Name @('SqlServer', 'SQLPS') + Remove-Module -Name CommonTestHelper + name: removeSqlServerModule + displayName: 'Remove SqlServer module' - powershell: | ./build.ps1 -Tasks test -CodeCoverageThreshold 0 -PesterTag $(TEST_CONFIGURATION) -PesterPath @( # Run the integration tests in a specific group order. @@ -210,7 +212,7 @@ stages: 'tests/Integration/DSC_SqlTraceFlag.Integration.Tests.ps1' # Group 3 'tests/Integration/DSC_SqlRole.Integration.Tests.ps1' - 'tests/Integration/DSC_SqlRS.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlRS_Default.Integration.Tests.ps1' 'tests/Integration/DSC_SqlDatabaseUser.Integration.Tests.ps1' 'tests/Integration/DSC_SqlReplication.Integration.Tests.ps1' 'tests/Integration/DSC_SqlAudit.Integration.Tests.ps1' @@ -237,6 +239,81 @@ stages: testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' testRunTitle: 'Integration ($(TEST_CONFIGURATION) / $(JOB_VMIMAGE))' + - job: Test_Integration_RS + displayName: 'Integration Reporting Services' + strategy: + matrix: + SQL2016_WIN2019: + JOB_VMIMAGE: 'windows-2019' + TEST_CONFIGURATION: 'Integration_SQL2016' + SQL2016_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_SQL2016' + SQL2017_WIN2019: + JOB_VMIMAGE: 'windows-2019' + TEST_CONFIGURATION: 'Integration_SQL2017' + SQL2017_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_SQL2017' + SQL2019_WIN2019: + JOB_VMIMAGE: 'windows-2019' + TEST_CONFIGURATION: 'Integration_SQL2019' + SQL2019_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_SQL2019' + SQL2022_WIN2019: + JOB_VMIMAGE: 'windows-2019' + TEST_CONFIGURATION: 'Integration_SQL2022' + SQL2022_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_SQL2022' + variables: + SKIP_DATABASE_ENGINE_DEFAULT_INSTANCE: true + SKIP_ANALYSIS_MULTI_INSTANCE: true + SKIP_ANALYSIS_TABULAR_INSTANCE: true + pool: + vmImage: $(JOB_VMIMAGE) + timeoutInMinutes: 0 + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: PowerShell@2 + name: configureWinRM + displayName: 'Configure WinRM' + inputs: + targetType: 'inline' + script: 'winrm quickconfig -quiet' + pwsh: false + - powershell: | + Import-Module -Name ./tests/TestHelpers/CommonTestHelper.psm1 + Remove-PowerShellModuleFromCI -Name @('SqlServer', 'SQLPS') + Remove-Module -Name CommonTestHelper + name: removeSqlServerModule + displayName: 'Remove SqlServer module' + - powershell: | + ./build.ps1 -Tasks test -CodeCoverageThreshold 0 -PesterTag $(TEST_CONFIGURATION) -PesterPath @( + # Run the integration tests in a specific group order. + # Group 1 + 'tests/Integration/DSC_SqlSetup.Integration.Tests.ps1' + # Group 2 + 'tests/Integration/DSC_SqlRSSetup.Integration.Tests.ps1' + # Group 3 + 'tests/Integration/DSC_SqlRS.Integration.Tests.ps1' + ) + name: test + displayName: 'Run Reporting Services Integration Test' + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Integration RS ($(TEST_CONFIGURATION) / $(JOB_VMIMAGE))' + - job: Code_Coverage displayName: 'Publish Code Coverage' dependsOn: Test_Unit diff --git a/source/Public/Import-SqlDscPreferredModule.ps1 b/source/Public/Import-SqlDscPreferredModule.ps1 index a9db52bfc..0395d2f8c 100644 --- a/source/Public/Import-SqlDscPreferredModule.ps1 +++ b/source/Public/Import-SqlDscPreferredModule.ps1 @@ -4,6 +4,7 @@ .DESCRIPTION Imports the module SqlServer (preferred) or SQLPS in a standardized way. + The module is always imported globally. .PARAMETER PreferredModule Specifies the name of the preferred module. Defaults to 'SqlServer'. @@ -100,7 +101,26 @@ function Import-SqlDscPreferredModule PowerShell session environment variable PSModulePath to make sure it contains all paths. #> - Set-PSModulePath -Path ([System.Environment]::GetEnvironmentVariable('PSModulePath', 'Machine')) + + <# + Get the environment variables from all targets session, user and machine. + Casts the value to System.String to convert $null values to empty string. + #> + $modulePathSession = [System.String] [System.Environment]::GetEnvironmentVariable('PSModulePath') + $modulePathUser = [System.String] [System.Environment]::GetEnvironmentVariable('PSModulePath', 'User') + $modulePathMachine = [System.String] [System.Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') + + $modulePath = $modulePathSession + ';' + $modulePathUser + ';' + $modulePathMachine + + $modulePathArray = $modulePath -split ';' | + Where-Object -FilterScript { + -not [System.String]::IsNullOrEmpty($_) + } | + Sort-Object -Unique + + $modulePath = $modulePathArray -join ';' + + Set-PSModulePath -Path $modulePath } # Get the newest SQLPS module if more than one exist. @@ -134,7 +154,7 @@ function Import-SqlDscPreferredModule SQLPS has unapproved verbs, disable checking to ignore Warnings. Suppressing verbose so all cmdlet is not listed. #> - $importedModule = Import-Module -Name $availableModuleName -DisableNameChecking -Verbose:$false -Force:$Force -PassThru -ErrorAction 'Stop' + $importedModule = Import-Module -Name $availableModuleName -DisableNameChecking -Verbose:$false -Force:$Force -Global -PassThru -ErrorAction 'Stop' <# SQLPS returns two entries, one with module type 'Script' and another with module type 'Manifest'. diff --git a/tests/Integration/DSC_SqlRS.Integration.Tests.ps1 b/tests/Integration/DSC_SqlRS.Integration.Tests.ps1 index fa6a1fa69..f375787d3 100644 --- a/tests/Integration/DSC_SqlRS.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlRS.Integration.Tests.ps1 @@ -155,6 +155,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', $resourceCurrentState.DatabaseInstanceName | Should -Be $ConfigurationData.AllNodes.DatabaseInstanceName $resourceCurrentState.IsInitialized | Should -Be $true $resourceCurrentState.UseSsl | Should -Be $false + $resourceCurrentState.ReportServerReservedUrl | Should -Contain 'http://+:80' } It 'Should return $true when Test-DscConfiguration is run' { @@ -225,194 +226,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } } - Context ('When using configuration <_>') -ForEach @( - "$($script:dscResourceName)_InstallReportingServices_ConfigureSsl_Config" - ) { - BeforeAll { - $configurationName = $_ - } - - AfterEach { - Wait-ForIdleLcm - } - - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } - - $resourceCurrentState.UseSsl | Should -Be $true - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - - <# - We expect this to throw any error. Usually 'Unable to connect to the remote server' but it - can also throw and 'The underlying connection was closed: An unexpected error occurred on a send'. - When we support SSL fully with this resource, this should not throw at all. So leaving this - as this without testing for the correct error message on purpose. - #> - It 'Should not be able to access the ReportServer site and throw an error message' { - if ($script:sqlVersion -in @('140', '150', '160')) - { - # SSRS 2017 and 2019 do not support multiple instances - $reportServerUri = 'http://{0}/ReportServer' -f $env:COMPUTERNAME - } - else - { - $reportServerUri = 'http://{0}/ReportServer_{1}' -f $env:COMPUTERNAME, $ConfigurationData.AllNodes.InstanceName - } - - { Invoke-WebRequest -Uri $reportServerUri -UseDefaultCredentials } | Should -Throw - } - } - - Context ('When using configuration <_>') -ForEach @( - "$($script:dscResourceName)_InstallReportingServices_RestoreToNoSsl_Config" - ) { - BeforeAll { - $configurationName = $_ - } - - AfterEach { - Wait-ForIdleLcm - } - - It 'Should compile and apply the MOF without throwing' { - { - $configurationParameters = @{ - OutputPath = $TestDrive - # The variable $ConfigurationData was dot-sourced above. - ConfigurationData = $ConfigurationData - } - - & $configurationName @configurationParameters - - $startDscConfigurationParameters = @{ - Path = $TestDrive - ComputerName = 'localhost' - Wait = $true - Verbose = $true - Force = $true - ErrorAction = 'Stop' - } - - Start-DscConfiguration @startDscConfigurationParameters - } | Should -Not -Throw - } - - It 'Should be able to call Get-DscConfiguration without throwing' { - { - $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop - } | Should -Not -Throw - } - - It 'Should have set the resource and all the parameters should match' { - $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { - $_.ConfigurationName -eq $configurationName ` - -and $_.ResourceId -eq $resourceId - } - - $resourceCurrentState.UseSsl | Should -Be $false - } - - It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be 'True' - } - - It 'Should be able to access the ReportServer site without any error' { - if ($script:sqlVersion -in @('140', '150', '160')) - { - # SSRS 2017 and 2019 do not support multiple instances - $reportServerUri = 'http://{0}/ReportServer' -f $env:COMPUTERNAME - } - else - { - $reportServerUri = 'http://{0}/ReportServer_{1}' -f $env:COMPUTERNAME, $ConfigurationData.AllNodes.InstanceName - } - - try - { - $webRequestReportServer = Invoke-WebRequest -Uri $reportServerUri -UseDefaultCredentials - # if the request finishes successfully this should return status code 200. - $webRequestStatusCode = $webRequestReportServer.StatusCode -as [int] - } - catch - { - <# - If the request generated an exception i.e. "HTTP Error 503. The service is unavailable." - we can pull the status code from the Exception.Response property. - #> - $webRequestResponse = $_.Exception.Response - $webRequestStatusCode = $webRequestResponse.StatusCode -as [int] - } - - $webRequestStatusCode | Should -BeExactly 200 - } - - It 'Should be able to access the Reports site without any error' { - if ($script:sqlVersion -in @('140', '150', '160')) - { - # SSRS 2017 and 2019 do not support multiple instances - $reportsUri = 'http://{0}/Reports' -f $env:COMPUTERNAME - } - else - { - $reportsUri = 'http://{0}/Reports_{1}' -f $env:COMPUTERNAME, $ConfigurationData.AllNodes.InstanceName - } - - try - { - $webRequestReportServer = Invoke-WebRequest -Uri $reportsUri -UseDefaultCredentials - # if the request finishes successfully this should return status code 200. - $webRequestStatusCode = $webRequestReportServer.StatusCode -as [int] - } - catch - { - <# - If the request generated an exception i.e. "HTTP Error 503. The service is unavailable." - we can pull the status code from the Exception.Response property. - #> - $webRequestResponse = $_.Exception.Response - $webRequestStatusCode = $webRequestResponse.StatusCode -as [int] - } - - $webRequestStatusCode | Should -BeExactly 200 - } - } - Context ('When using configuration <_>') -ForEach @( "$($script:dscResourceName)_StopReportingServicesInstance_Config" ) { diff --git a/tests/Integration/DSC_SqlRS.config.ps1 b/tests/Integration/DSC_SqlRS.config.ps1 index 02c09e83a..f7625eeff 100644 --- a/tests/Integration/DSC_SqlRS.config.ps1 +++ b/tests/Integration/DSC_SqlRS.config.ps1 @@ -178,69 +178,7 @@ Configuration DSC_SqlRS_InstallReportingServices_Config DatabaseInstanceName = $Node.DatabaseInstanceName Encrypt = 'Optional' - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.RunAs_UserName, (ConvertTo-SecureString -String $Node.RunAs_Password -AsPlainText -Force)) - } - } -} - -<# - .SYNOPSIS - Enables SSL on the Reporting Services. -#> -Configuration DSC_SqlRS_InstallReportingServices_ConfigureSsl_Config -{ - Import-DscResource -ModuleName 'SqlServerDsc' - - node $AllNodes.NodeName - { - SqlRS 'Integration_Test' - { - # Instance name for the Reporting Services. - InstanceName = $Node.InstanceName - UseSsl = $true - Encrypt = 'Optional' - - <# - Instance for Reporting Services databases. - Note: This instance is created in a prior integration test. - #> - DatabaseServerName = $Node.DatabaseServerName - DatabaseInstanceName = $Node.DatabaseInstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @( - $Node.RunAs_UserName, (ConvertTo-SecureString -String $Node.RunAs_Password -AsPlainText -Force)) - } - } -} - -<# - .SYNOPSIS - Disables SSL on the Reporting Services. -#> -Configuration DSC_SqlRS_InstallReportingServices_RestoreToNoSsl_Config -{ - Import-DscResource -ModuleName 'SqlServerDsc' - - node $AllNodes.NodeName - { - SqlRS 'Integration_Test' - { - # Instance name for the Reporting Services. - InstanceName = $Node.InstanceName - UseSsl = $false - Encrypt = 'Optional' - - <# - Instance for Reporting Services databases. - Note: This instance is created in a prior integration test. - #> - DatabaseServerName = $Node.DatabaseServerName - DatabaseInstanceName = $Node.DatabaseInstanceName + ReportServerReservedUrl = @('http://+:80') PsDscRunAsCredential = New-Object ` -TypeName System.Management.Automation.PSCredential ` diff --git a/tests/Integration/DSC_SqlRS_Default.Integration.Tests.ps1 b/tests/Integration/DSC_SqlRS_Default.Integration.Tests.ps1 new file mode 100644 index 000000000..f4c6de7cf --- /dev/null +++ b/tests/Integration/DSC_SqlRS_Default.Integration.Tests.ps1 @@ -0,0 +1,450 @@ +BeforeDiscovery { + try + { + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' + } + + <# + Need to define that variables here to be used in the Pester Discover to + build the ForEach-blocks. + #> + $script:dscResourceFriendlyName = 'SqlRS' + $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" +} + +BeforeAll { + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + + # Need to define the variables here which will be used in Pester Run. + $script:dscModuleName = 'SqlServerDsc' + $script:dscResourceFriendlyName = 'SqlRS' + $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" + + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Integration' + + <# + This is used in both the configuration file and in this script file + to run the correct tests depending of what version of SQL Server is + being tested in the current job. + #> + if (Test-ContinuousIntegrationTaskCategory -Category 'Integration_SQL2022') + { + $script:sqlVersion = '160' + } + elseif (Test-ContinuousIntegrationTaskCategory -Category 'Integration_SQL2019') + { + $script:sqlVersion = '150' + } + elseif (Test-ContinuousIntegrationTaskCategory -Category 'Integration_SQL2017') + { + $script:sqlVersion = '140' + } + else + { + $script:sqlVersion = '130' + } + + Write-Verbose -Message ('Running integration tests for SSRS version {0}' -f $script:sqlVersion) -Verbose + + $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName)_Default.config.ps1" + . $configFile +} + +AfterAll { + Restore-TestEnvironment -TestEnvironment $script:testEnvironment + + Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force +} + +Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', 'Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_CreateDependencies_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterEach { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_InstallReportingServices_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterEach { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseServerName | Should -Be $ConfigurationData.AllNodes.DatabaseServerName + $resourceCurrentState.DatabaseInstanceName | Should -Be $ConfigurationData.AllNodes.DatabaseInstanceName + $resourceCurrentState.IsInitialized | Should -Be $true + $resourceCurrentState.UseSsl | Should -Be $false + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + + It 'Should be able to access the ReportServer site without any error' { + # Wait for 1 minute for the ReportServer to be ready. + Start-Sleep -Seconds 30 + + if ($script:sqlVersion -in @('140', '150', '160')) + { + # SSRS 2017 and 2019 do not support multiple instances + $reportServerUri = 'http://{0}/ReportServer' -f $env:COMPUTERNAME + } + else + { + $reportServerUri = 'http://{0}/ReportServer_{1}' -f $env:COMPUTERNAME, $ConfigurationData.AllNodes.InstanceName + } + + try + { + $webRequestReportServer = Invoke-WebRequest -Uri $reportServerUri -UseDefaultCredentials + # if the request finishes successfully this should return status code 200. + $webRequestStatusCode = $webRequestReportServer.StatusCode -as [int] + } + catch + { + <# + If the request generated an exception i.e. "HTTP Error 503. The service is unavailable." + we can pull the status code from the Exception.Response property. + #> + $webRequestResponse = $_.Exception.Response + $webRequestStatusCode = $webRequestResponse.StatusCode -as [int] + } + + $webRequestStatusCode | Should -BeExactly 200 + } + + It 'Should be able to access the Reports site without any error' { + if ($script:sqlVersion -in @('140', '150', '160')) + { + # SSRS 2017 and 2019 do not support multiple instances + $reportsUri = 'http://{0}/Reports' -f $env:COMPUTERNAME + } + else + { + $reportsUri = 'http://{0}/Reports_{1}' -f $env:COMPUTERNAME, $ConfigurationData.AllNodes.InstanceName + } + + try + { + $webRequestReportServer = Invoke-WebRequest -Uri $reportsUri -UseDefaultCredentials + # if the request finishes successfully this should return status code 200. + $webRequestStatusCode = $webRequestReportServer.StatusCode -as [int] + } + catch + { + <# + If the request generated an exception i.e. "HTTP Error 503. The service is unavailable." + we can pull the status code from the Exception.Response property. + #> + $webRequestResponse = $_.Exception.Response + $webRequestStatusCode = $webRequestResponse.StatusCode -as [int] + } + + $webRequestStatusCode | Should -BeExactly 200 + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_InstallReportingServices_ConfigureSsl_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterEach { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.UseSsl | Should -Be $true + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + + <# + We expect this to throw any error. Usually 'Unable to connect to the remote server' but it + can also throw and 'The underlying connection was closed: An unexpected error occurred on a send'. + When we support SSL fully with this resource, this should not throw at all. So leaving this + as this without testing for the correct error message on purpose. + #> + It 'Should not be able to access the ReportServer site and throw an error message' { + if ($script:sqlVersion -in @('140', '150', '160')) + { + # SSRS 2017 and 2019 do not support multiple instances + $reportServerUri = 'http://{0}/ReportServer' -f $env:COMPUTERNAME + } + else + { + $reportServerUri = 'http://{0}/ReportServer_{1}' -f $env:COMPUTERNAME, $ConfigurationData.AllNodes.InstanceName + } + + { Invoke-WebRequest -Uri $reportServerUri -UseDefaultCredentials } | Should -Throw + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_InstallReportingServices_RestoreToNoSsl_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterEach { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.UseSsl | Should -Be $false + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + + It 'Should be able to access the ReportServer site without any error' { + if ($script:sqlVersion -in @('140', '150', '160')) + { + # SSRS 2017 and 2019 do not support multiple instances + $reportServerUri = 'http://{0}/ReportServer' -f $env:COMPUTERNAME + } + else + { + $reportServerUri = 'http://{0}/ReportServer_{1}' -f $env:COMPUTERNAME, $ConfigurationData.AllNodes.InstanceName + } + + try + { + $webRequestReportServer = Invoke-WebRequest -Uri $reportServerUri -UseDefaultCredentials + # if the request finishes successfully this should return status code 200. + $webRequestStatusCode = $webRequestReportServer.StatusCode -as [int] + } + catch + { + <# + If the request generated an exception i.e. "HTTP Error 503. The service is unavailable." + we can pull the status code from the Exception.Response property. + #> + $webRequestResponse = $_.Exception.Response + $webRequestStatusCode = $webRequestResponse.StatusCode -as [int] + } + + $webRequestStatusCode | Should -BeExactly 200 + } + + It 'Should be able to access the Reports site without any error' { + if ($script:sqlVersion -in @('140', '150', '160')) + { + # SSRS 2017 and 2019 do not support multiple instances + $reportsUri = 'http://{0}/Reports' -f $env:COMPUTERNAME + } + else + { + $reportsUri = 'http://{0}/Reports_{1}' -f $env:COMPUTERNAME, $ConfigurationData.AllNodes.InstanceName + } + + try + { + $webRequestReportServer = Invoke-WebRequest -Uri $reportsUri -UseDefaultCredentials + # if the request finishes successfully this should return status code 200. + $webRequestStatusCode = $webRequestReportServer.StatusCode -as [int] + } + catch + { + <# + If the request generated an exception i.e. "HTTP Error 503. The service is unavailable." + we can pull the status code from the Exception.Response property. + #> + $webRequestResponse = $_.Exception.Response + $webRequestStatusCode = $webRequestResponse.StatusCode -as [int] + } + + $webRequestStatusCode | Should -BeExactly 200 + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_StopReportingServicesInstance_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterEach { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + } +} diff --git a/tests/Integration/DSC_SqlRS_Default.config.ps1 b/tests/Integration/DSC_SqlRS_Default.config.ps1 new file mode 100644 index 000000000..02c09e83a --- /dev/null +++ b/tests/Integration/DSC_SqlRS_Default.config.ps1 @@ -0,0 +1,280 @@ +#region HEADER +# Integration Test Config Template Version: 1.2.0 +#endregion + +$configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') +if (Test-Path -Path $configFile) +{ + <# + Allows reading the configuration data from a JSON file, + for real testing scenarios outside of the CI. + #> + $ConfigurationData = Get-Content -Path $configFile | ConvertFrom-Json +} +else +{ + # Get a spare drive letter + $mockLastDrive = ((Get-Volume).DriveLetter | Sort-Object | Select-Object -Last 1) + $mockIsoMediaDriveLetter = [char](([int][char]$mockLastDrive) + 1) + + if ($script:sqlVersion -eq '160') + { + # SQL2022 + $instanceName = 'SSRS' + } + elseif ($script:sqlVersion -eq '150') + { + # SQL2019 + $instanceName = 'SSRS' + } + elseif ($script:sqlVersion -eq '140') + { + # SQL2017 + $instanceName = 'SSRS' + } + else + { + # SQL2016 + $instanceName = 'DSCRS2016' + } + + $ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + + RunAs_UserName = "$env:COMPUTERNAME\SqlInstall" + RunAs_Password = 'P@ssw0rd1' + Service_UserName = "$env:COMPUTERNAME\svc-Reporting" + Service_Password = 'yig-C^Equ3' + + InstanceName = $instanceName + Features = 'RS' + InstallSharedDir = 'C:\Program Files\Microsoft SQL Server' + InstallSharedWOWDir = 'C:\Program Files (x86)\Microsoft SQL Server' + UpdateEnabled = 'False' + SuppressReboot = $true # Make sure we don't reboot during testing. + ForceReboot = $false + + ImagePath = "$env:TEMP\SQL2016.iso" + DriveLetter = $mockIsoMediaDriveLetter + + DatabaseServerName = $env:COMPUTERNAME + DatabaseInstanceName = 'DSCSQLTEST' + + CertificateFile = $env:DscPublicCertificatePath + } + ) + } +} + +<# + .SYNOPSIS + Add dependencies for configuring Reporting Services. Mounts the ISO, + create the service account, make sure .NET Framework 4.5 is installed, + and installs the Reporting Services, +#> +Configuration DSC_SqlRS_CreateDependencies_Config +{ + Import-DscResource -ModuleName 'PSDscResources' -ModuleVersion '2.12.0.0' + Import-DscResource -ModuleName 'StorageDsc' -ModuleVersion '4.9.0.0' + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + User 'CreateReportingServicesServiceAccount' + { + Ensure = 'Present' + UserName = Split-Path -Path $Node.Service_UserName -Leaf + Password = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Service_UserName, (ConvertTo-SecureString -String $Node.Service_Password -AsPlainText -Force)) + } + + WindowsFeature 'NetFramework45' + { + Name = 'NET-Framework-45-Core' + Ensure = 'Present' + } + + if ($script:sqlVersion -eq '130') + { + MountImage 'MountIsoMedia' + { + ImagePath = $Node.ImagePath + DriveLetter = $Node.DriveLetter + Ensure = 'Present' + } + + WaitForVolume 'WaitForMountOfIsoMedia' + { + DriveLetter = $Node.DriveLetter + RetryIntervalSec = 5 + RetryCount = 10 + } + + SqlSetup 'InstallReportingServicesInstance' + { + InstanceName = $Node.InstanceName + Features = $Node.Features + SourcePath = "$($Node.DriveLetter):\" + BrowserSvcStartupType = 'Automatic' + InstallSharedDir = $Node.InstallSharedDir + InstallSharedWOWDir = $Node.InstallSharedWOWDir + UpdateEnabled = $Node.UpdateEnabled + SuppressReboot = $Node.SuppressReboot + ForceReboot = $Node.ForceReboot + RSSvcAccount = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Service_UserName, (ConvertTo-SecureString -String $Node.Service_Password -AsPlainText -Force)) + + DependsOn = @( + '[WaitForVolume]WaitForMountOfIsoMedia' + '[User]CreateReportingServicesServiceAccount' + '[WindowsFeature]NetFramework45' + ) + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @( + $Node.RunAs_UserName, (ConvertTo-SecureString -String $Node.RunAs_Password -AsPlainText -Force)) + } + } + <# + DSC_SqlRSSetup.Integration.Tests.ps1 will have installed SSRS 2017 or 2019. + We just need to start SSRS. + #> + elseif ($script:sqlVersion -in @('140', '150', '160')) + { + Service 'StartReportingServicesInstance' + { + Name = 'SQLServerReportingServices' + State = 'Running' + } + } + } +} + +<# + .SYNOPSIS + Configures the Reporting Services. +#> +Configuration DSC_SqlRS_InstallReportingServices_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlRS 'Integration_Test' + { + # Instance name for the Reporting Services. + InstanceName = $Node.InstanceName + + <# + Instance for Reporting Services databases. + Note: This instance is created in a prior integration test. + #> + DatabaseServerName = $Node.DatabaseServerName + DatabaseInstanceName = $Node.DatabaseInstanceName + Encrypt = 'Optional' + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @( + $Node.RunAs_UserName, (ConvertTo-SecureString -String $Node.RunAs_Password -AsPlainText -Force)) + } + } +} + +<# + .SYNOPSIS + Enables SSL on the Reporting Services. +#> +Configuration DSC_SqlRS_InstallReportingServices_ConfigureSsl_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlRS 'Integration_Test' + { + # Instance name for the Reporting Services. + InstanceName = $Node.InstanceName + UseSsl = $true + Encrypt = 'Optional' + + <# + Instance for Reporting Services databases. + Note: This instance is created in a prior integration test. + #> + DatabaseServerName = $Node.DatabaseServerName + DatabaseInstanceName = $Node.DatabaseInstanceName + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @( + $Node.RunAs_UserName, (ConvertTo-SecureString -String $Node.RunAs_Password -AsPlainText -Force)) + } + } +} + +<# + .SYNOPSIS + Disables SSL on the Reporting Services. +#> +Configuration DSC_SqlRS_InstallReportingServices_RestoreToNoSsl_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlRS 'Integration_Test' + { + # Instance name for the Reporting Services. + InstanceName = $Node.InstanceName + UseSsl = $false + Encrypt = 'Optional' + + <# + Instance for Reporting Services databases. + Note: This instance is created in a prior integration test. + #> + DatabaseServerName = $Node.DatabaseServerName + DatabaseInstanceName = $Node.DatabaseInstanceName + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @( + $Node.RunAs_UserName, (ConvertTo-SecureString -String $Node.RunAs_Password -AsPlainText -Force)) + } + } +} + +<# + .SYNOPSIS + Stops the Reporting Services instance to save resource on the build worker. +#> +Configuration DSC_SqlRS_StopReportingServicesInstance_Config +{ + Import-DscResource -ModuleName 'PSDscResources' -ModuleVersion '2.12.0.0' + + node $AllNodes.NodeName + { + if ($script:sqlVersion -eq '130') + { + Service ('StopReportingServicesInstance{0}' -f $Node.InstanceName) + { + Name = ('ReportServer${0}' -f $Node.InstanceName) + State = 'Stopped' + } + } + elseif ($script:sqlVersion -in @('140', '150', '160')) + { + Service 'StopReportingServicesInstance' + { + Name = 'SQLServerReportingServices' + State = 'Stopped' + } + } + } +} diff --git a/tests/Integration/DSC_SqlSetup.config.ps1 b/tests/Integration/DSC_SqlSetup.config.ps1 index 47482d3d2..0dccbeffc 100644 --- a/tests/Integration/DSC_SqlSetup.config.ps1 +++ b/tests/Integration/DSC_SqlSetup.config.ps1 @@ -462,6 +462,8 @@ Configuration DSC_SqlSetup_InstallSqlServerModule_Config $moduleVersion = '{0}-{1}' -f $moduleVersion, $sqlServerModule.PrivateData.PSData.Prerelease } } + + Write-Verbose -Message ('Found SqlServer module v{0}.' -f $moduleVersion) -Verbose } return @{ diff --git a/tests/TestHelpers/CommonTestHelper.psm1 b/tests/TestHelpers/CommonTestHelper.psm1 index e1aae11e7..2bc0125a1 100644 --- a/tests/TestHelpers/CommonTestHelper.psm1 +++ b/tests/TestHelpers/CommonTestHelper.psm1 @@ -438,3 +438,80 @@ function Test-SetupArgument $argumentValue | Should -Be $ExpectedArgument.$argumentKey -Because 'the argument should have been set to the correct value when calling setup.exe' } } + +<# + .SYNOPSIS + Remove installed module. + + .PARAMETER Name + Specifies an array of module names to remove. Defaults to 'SqlServer' and + 'SQLPS'. + + .NOTES + Removes any existing versions of the SqlServer and SQLPS module. + Importing module SqlServerDsc will import the module SqlServer or SQLPS. + If SqlServer is imported it will render it locked and it is not possible + to switch to another version. Also, regardless of SqlServer or SQLPS it + could load the wrong assembly versions which will break SqlServerDsc if, + for example, SqlServer is switch to another version. +#> +function Remove-PowerShellModuleFromCI +{ + param + ( + [Parameter()] + [System.String[]] + $Name = @('SqlServer', 'SQLPS') + ) + + Write-Information -MessageData 'Checking if any path in $env:PSModulePath contain a module that are not suppose to be present.' -InformationAction 'Continue' + + $sqlServerModule = Get-Module -Name $Name -ListAvailable + + if ($sqlServerModule) + { + $existingModulesString = $sqlServerModule | + Select-Object -Property @( + 'Name', + 'Version', + @{ + Name = 'Prerelease' + Expression = { $_.PrivateData.PSData.Prerelease } + }, + 'Path' + ) | + Out-String + + Write-Information -MessageData ('Existing modules: {0}' -f $existingModulesString) -InformationAction 'Continue' + + Write-Information -MessageData 'Removing the found modules.' -InformationAction 'Continue' + + # Remove versions, removes each file to detect if any file cannot be removed (e.g. locked assembly). + $sqlServerModule | + ForEach-Object -Process { + Write-Information -MessageData ('Removing module version: {0}' -f $_.ModuleBase) -InformationAction 'Continue' + + $_.ModuleBase | + Remove-Item -Recurse -Force -ErrorAction 'Stop' + } + + # Remove the module folder that is left by previous call. + $sqlServerModule | + ForEach-Object -Process { + $parentFolder = Split-Path -Path $_.ModuleBase + + # Only remove if the path exist and does not end with '/Modules'. + if ($parentFolder -notmatch 'modules$' -and (Test-Path -Path $parentFolder)) + { + Write-Information -MessageData ('Removing module folder: {0}' -f $parentFolder) -InformationAction 'Continue' + + $parentFolder | + Remove-Item -Recurse -Force -ErrorAction 'Stop' + } + } + } + else + { + Write-Information -MessageData 'No existing SqlServer or SQLPS modules, nothing to remove.' -InformationAction 'Continue' + } +}