From 8c7dc71a15ae647b528e5fdae24b4558485280d4 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 16 Feb 2019 14:36:55 +0100 Subject: [PATCH 1/2] Added DSC resource PSRepository --- CHANGELOG.md | 10 +- .../MSFT_PSRepository/MSFT_PSRepository.psm1 | 308 ++++++++ .../MSFT_PSRepository.schema.mfl | 18 + .../MSFT_PSRepository.schema.mof | 14 + .../en-US/MSFT_PSRepository.strings.psd1 | 22 + DSC/Examples/README.md | 1 + .../1-PSRepository_AddRepositoryConfig.ps1 | 78 +++ .../2-PSRepository_RemoveRepositoryConfig.ps1 | 74 ++ .../PowerShellGet.ResourceHelper.psm1 | 143 ++++ .../PowerShellGet.ResourceHelper.strings.psd1 | 9 +- .../MSFT_PSRepository.Integration.Tests.ps1 | 209 ++++++ .../Integration/MSFT_PSRepository.config.ps1 | 128 ++++ DSC/Tests/Unit/MSFT_PSRepository.Tests.ps1 | 452 ++++++++++++ .../PowerShellGet.ResourceHelper.Tests.ps1 | 655 +++++++++++++----- 14 files changed, 1930 insertions(+), 191 deletions(-) create mode 100644 DSC/DSCResources/MSFT_PSRepository/MSFT_PSRepository.psm1 create mode 100644 DSC/DSCResources/MSFT_PSRepository/MSFT_PSRepository.schema.mfl create mode 100644 DSC/DSCResources/MSFT_PSRepository/MSFT_PSRepository.schema.mof create mode 100644 DSC/DSCResources/MSFT_PSRepository/en-US/MSFT_PSRepository.strings.psd1 create mode 100644 DSC/Examples/Resources/PSRepository/1-PSRepository_AddRepositoryConfig.ps1 create mode 100644 DSC/Examples/Resources/PSRepository/2-PSRepository_RemoveRepositoryConfig.ps1 create mode 100644 DSC/Tests/Integration/MSFT_PSRepository.Integration.Tests.ps1 create mode 100644 DSC/Tests/Integration/MSFT_PSRepository.config.ps1 create mode 100644 DSC/Tests/Unit/MSFT_PSRepository.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index d658af92..38eb9d11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ - Made the list entries in the CHANGELOG.md to use dash `-` throughout to be consequent (before there was a mix of dashes and asterisk). - Update the AppVeyor CI test pipeline with a new job to run tests for - the DSC resources, primarily for the resource `PSModule`. + the DSC resources. The new job uses the test framework used for the DSC Resource Kit, the [DscResource.Tests](https://github.com/PowerShell/DscResource.Tests) repository. - Update .gitignore to ignore the [DscResource.Tests](https://github.com/PowerShell/DscResource.Tests) @@ -41,9 +41,9 @@ - Refactored the Get-TargetResource to return the correct hash table when the current state is absent. - Added new examples. -- Changed the AppVeyor CI build pipeline so it added the DSC resource - `PSModule` and dependent helper modules (the `Modules` folder) to the - AppVeyor artifact. +- Changed the AppVeyor CI build pipeline so it adds the DSC resources + and dependent helper modules (the `Modules` folder) to the AppVeyor + artifact. - Added the `.MetaTestOptIn.json` file to opt-in for a lot of common test in the DscResource.Tests test framework that tests the DSC resources. - The examples under the folder `DSC/Examples` will be [published to PowerShell Gallery](https://github.com/PowerShell/DscResource.Tests#publish-examples-to-powershell-gallery) @@ -57,7 +57,7 @@ - In the file `appveyor.yml` the PowerShell Gallery API key was added for the account 'dscresourcekit', which can only be decrypted using the PowerShell AppVeyor account. - +- Added DSC resource PSRepository. ## 2.0.4 diff --git a/DSC/DSCResources/MSFT_PSRepository/MSFT_PSRepository.psm1 b/DSC/DSCResources/MSFT_PSRepository/MSFT_PSRepository.psm1 new file mode 100644 index 00000000..4bccac95 --- /dev/null +++ b/DSC/DSCResources/MSFT_PSRepository/MSFT_PSRepository.psm1 @@ -0,0 +1,308 @@ +$resourceModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent + +# Import localization helper functions. +$helperName = 'PowerShellGet.LocalizationHelper' +$dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" +Import-Module -Name $dscResourcesFolderFilePath + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_PSRepository' -ScriptRoot $PSScriptRoot + +# Import resource helper functions. +$helperName = 'PowerShellGet.ResourceHelper' +$dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" +Import-Module -Name $dscResourcesFolderFilePath + +<# + .SYNOPSIS + Returns the current state of the repository. + + .PARAMETER Name + Specifies the name of the repository to manage. +#> +function Get-TargetResource { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $returnValue = @{ + Ensure = 'Absent' + Name = $Name + SourceLocation = $null + ScriptSourceLocation = $null + PublishLocation = $null + ScriptPublishLocation = $null + InstallationPolicy = $null + PackageManagementProvider = $null + Trusted = $false + Registered = $false + } + + Write-Verbose -Message ($localizedData.GetTargetResourceMessage -f $Name) + + $repository = Get-PSRepository -Name $Name -ErrorAction 'SilentlyContinue' + + if ($repository) { + $returnValue.Ensure = 'Present' + $returnValue.SourceLocation = $repository.SourceLocation + $returnValue.ScriptSourceLocation = $repository.ScriptSourceLocation + $returnValue.PublishLocation = $repository.PublishLocation + $returnValue.ScriptPublishLocation = $repository.ScriptPublishLocation + $returnValue.InstallationPolicy = $repository.InstallationPolicy + $returnValue.PackageManagementProvider = $repository.PackageManagementProvider + $returnValue.Trusted = $repository.Trusted + $returnValue.Registered = $repository.Registered + } + else { + Write-Verbose -Message ($localizedData.RepositoryNotFound -f $Name) + } + + return $returnValue +} + +<# + .SYNOPSIS + Determines if the repository is in the desired state. + + .PARAMETER Ensure + If the repository should be present or absent on the server + being configured. Default values is 'Present'. + + .PARAMETER Name + Specifies the name of the repository to manage. + + .PARAMETER SourceLocation + Specifies the URI for discovering and installing modules from + this repository. A URI can be a NuGet server feed, HTTP, HTTPS, + FTP or file location. + + .PARAMETER ScriptSourceLocation + Specifies the URI for the script source location. + + .PARAMETER PublishLocation + Specifies the URI of the publish location. For example, for + NuGet-based repositories, the publish location is similar + to http://someNuGetUrl.com/api/v2/Packages. + + .PARAMETER ScriptPublishLocation + Specifies the URI for the script publish location. + + .PARAMETER InstallationPolicy + Specifies the installation policy. Valid values are 'Trusted' + or 'Untrusted'. The default value is 'Untrusted'. + + .PARAMETER PackageManagementProvider + Specifies a OneGet package provider. Default value is 'NuGet'. +#> +function Test-TargetResource { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String] + $SourceLocation, + + [Parameter()] + [System.String] + $ScriptSourceLocation, + + [Parameter()] + [System.String] + $PublishLocation, + + [Parameter()] + [System.String] + $ScriptPublishLocation, + + [Parameter()] + [ValidateSet('Trusted', 'Untrusted')] + [System.String] + $InstallationPolicy = 'Untrusted', + + [Parameter()] + [System.String] + $PackageManagementProvider = 'NuGet' + ) + + Write-Verbose -Message ($localizedData.TestTargetResourceMessage -f $Name) + + $returnValue = $false + + $getTargetResourceResult = Get-TargetResource -Name $Name + + if ($Ensure -eq $getTargetResourceResult.Ensure) { + if ($getTargetResourceResult.Ensure -eq 'Present' ) { + $returnValue = Test-DscParameterState ` + -CurrentValues $getTargetResourceResult ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck @( + 'SourceLocation' + 'ScriptSourceLocation' + 'PublishLocation' + 'ScriptPublishLocation' + 'InstallationPolicy' + 'PackageManagementProvider' + ) + } + else { + $returnValue = $true + } + } + + if ($returnValue) { + Write-Verbose -Message ($localizedData.InDesiredState -f $Name) + } + else { + Write-Verbose -Message ($localizedData.NotInDesiredState -f $Name) + } + + return $returnValue +} + +<# + .SYNOPSIS + Creates, removes or updates the repository. + + .PARAMETER Ensure + If the repository should be present or absent on the server + being configured. Default values is 'Present'. + + .PARAMETER Name + Specifies the name of the repository to manage. + + .PARAMETER SourceLocation + Specifies the URI for discovering and installing modules from + this repository. A URI can be a NuGet server feed, HTTP, HTTPS, + FTP or file location. + + .PARAMETER ScriptSourceLocation + Specifies the URI for the script source location. + + .PARAMETER PublishLocation + Specifies the URI of the publish location. For example, for + NuGet-based repositories, the publish location is similar + to http://someNuGetUrl.com/api/v2/Packages. + + .PARAMETER ScriptPublishLocation + Specifies the URI for the script publish location. + + .PARAMETER InstallationPolicy + Specifies the installation policy. Valid values are 'Trusted' + or 'Untrusted'. The default value is 'Untrusted'. + + .PARAMETER PackageManagementProvider + Specifies a OneGet package provider. Default value is 'NuGet'. +#> +function Set-TargetResource { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] + [CmdletBinding()] + param + ( + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String] + $SourceLocation, + + [Parameter()] + [System.String] + $ScriptSourceLocation, + + [Parameter()] + [System.String] + $PublishLocation, + + [Parameter()] + [System.String] + $ScriptPublishLocation, + + [Parameter()] + [ValidateSet('Trusted', 'Untrusted')] + [System.String] + $InstallationPolicy = 'Untrusted', + + [Parameter()] + [System.String] + $PackageManagementProvider = 'NuGet' + ) + + $getTargetResourceResult = Get-TargetResource -Name $Name + + # Determine if the repository should be present or absent. + if ($Ensure -eq 'Present') { + $repositoryParameters = New-SplatParameterHashTable ` + -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames @( + 'Name' + 'SourceLocation' + 'ScriptSourceLocation' + 'PublishLocation' + 'ScriptPublishLocation' + 'InstallationPolicy' + 'PackageManagementProvider' + ) + + # Determine if the repository is already present. + if ($getTargetResourceResult.Ensure -eq 'Present') { + Write-Verbose -Message ($localizedData.RepositoryExist -f $Name) + + # Repository exist, update the properties. + Set-PSRepository @repositoryParameters -ErrorAction 'Stop' + } + else { + Write-Verbose -Message ($localizedData.RepositoryDoesNotExist -f $Name) + + # Repository did not exist, create the repository. + Register-PSRepository @repositoryParameters -ErrorAction 'Stop' + } + } + else { + if ($getTargetResourceResult.Ensure -eq 'Present') { + Write-Verbose -Message ($localizedData.RemoveExistingRepository -f $Name) + + # Repository did exist, remove the repository. + Unregister-PSRepository -Name $Name -ErrorAction 'Stop' + } + } +} diff --git a/DSC/DSCResources/MSFT_PSRepository/MSFT_PSRepository.schema.mfl b/DSC/DSCResources/MSFT_PSRepository/MSFT_PSRepository.schema.mfl new file mode 100644 index 00000000..4e3610b7 --- /dev/null +++ b/DSC/DSCResources/MSFT_PSRepository/MSFT_PSRepository.schema.mfl @@ -0,0 +1,18 @@ +#pragma namespace("\\\\.\\root\\default") +instance of __namespace{ name="MS_409";}; +#pragma namespace("\\\\.\\root\\default\\MS_409") + +[AMENDMENT, LOCALE("MS_409")] +class MSFT_PSModule : OMI_BaseResource +{ + [Key, Description("Specifies the name of the repository to manage.") : Amended] String Name; + [Description("If the repository should be present or absent on the server being configured. Default values is 'Present'.") : Amended] String Ensure; + [Description("Specifies the URI for discovering and installing modules from this repository. A URI can be a NuGet server feed, HTTP, HTTPS, FTP or file location.") : Amended] String SourceLocation; + [Description("Specifies the URI for the script source location.") : Amended] String ScriptSourceLocation; + [Description("Specifies the URI of the publish location. For example, for NuGet-based repositories, the publish location is similar to http://someNuGetUrl.com/api/v2/Packages.") : Amended] String PublishLocation; + [Description("Specifies the URI for the script publish location.") : Amended] String ScriptPublishLocation; + [Description("Specifies the installation policy. Valid values are 'Trusted' or 'Untrusted'. The default value is 'Untrusted'.") : Amended] String InstallationPolicy; + [Description("Specifies a OneGet package provider. Default value is 'NuGet'.") : Amended] String PackageManagementProvider; + [Description("Specifies if the repository is trusted.") : Amended] Boolean Trusted; + [Description("Specifies if the repository is registered.") : Amended] Boolean Registered; +}; diff --git a/DSC/DSCResources/MSFT_PSRepository/MSFT_PSRepository.schema.mof b/DSC/DSCResources/MSFT_PSRepository/MSFT_PSRepository.schema.mof new file mode 100644 index 00000000..e94b7d0d --- /dev/null +++ b/DSC/DSCResources/MSFT_PSRepository/MSFT_PSRepository.schema.mof @@ -0,0 +1,14 @@ +[ClassVersion("1.0.0.0"),FriendlyName("PSRepository")] +class MSFT_PSRepository : OMI_BaseResource +{ + [Key] String Name; + [Write, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Write] String SourceLocation; + [Write] String ScriptSourceLocation; + [Write] String PublishLocation; + [Write] String ScriptPublishLocation; + [Write, ValueMap{"Trusted","Untrusted"}, Values{"Trusted","Untrusted"}] String InstallationPolicy; + [Write] String PackageManagementProvider; + [Read] Boolean Trusted; + [Read] Boolean Registered; +}; diff --git a/DSC/DSCResources/MSFT_PSRepository/en-US/MSFT_PSRepository.strings.psd1 b/DSC/DSCResources/MSFT_PSRepository/en-US/MSFT_PSRepository.strings.psd1 new file mode 100644 index 00000000..81c7c755 --- /dev/null +++ b/DSC/DSCResources/MSFT_PSRepository/en-US/MSFT_PSRepository.strings.psd1 @@ -0,0 +1,22 @@ +# +# Copyright (c) Microsoft Corporation. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# culture = "en-US" +ConvertFrom-StringData -StringData @' + GetTargetResourceMessage = Return the current state of the repository '{0}'. + RepositoryNotFound = The repository '{0}' was not found. + TestTargetResourceMessage = Determining if the repository '{0}' is in the desired state. + InDesiredState = Repository '{0}' is in the desired state. + NotInDesiredState = Repository '{0}' is not in the desired state. + RepositoryExist = Updating the properties of the repository '{0}'. + RepositoryDoesNotExist = Creating the repository '{0}'. + RemoveExistingRepository = Removing the repository '{0}'. +'@ diff --git a/DSC/Examples/README.md b/DSC/Examples/README.md index 4db26804..a027a549 100644 --- a/DSC/Examples/README.md +++ b/DSC/Examples/README.md @@ -5,3 +5,4 @@ These are the links to the examples for each individual resource. - [PSModule](Resources/PSModule) +- [PSRepository](Resources/PSRepository) diff --git a/DSC/Examples/Resources/PSRepository/1-PSRepository_AddRepositoryConfig.ps1 b/DSC/Examples/Resources/PSRepository/1-PSRepository_AddRepositoryConfig.ps1 new file mode 100644 index 00000000..9a22a4f1 --- /dev/null +++ b/DSC/Examples/Resources/PSRepository/1-PSRepository_AddRepositoryConfig.ps1 @@ -0,0 +1,78 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID a1f8ee59-31b6-49be-9175-f7a49b5e03f1 +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/PowerShellGet/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/PowerShellGet +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module PowerShellGet + +<# + .SYNOPSIS + Configuration that installs a module. + + .DESCRIPTION + Configuration that installs a module. + + .PARAMETER NodeName + The names of one or more nodes to compile a configuration for. + Defaults to 'localhost'. + + .PARAMETER RepositoryName + The name of the repository that will be added. + + .EXAMPLE + PSRepository_AddRepositoryConfig -RepositoryName 'PSTestGallery' + + Compiles a configuration that downloads and installs the module 'PSLogging'. + + .EXAMPLE + $configurationParameters = @{ + RepositoryName = 'PSTestGallery' + } + Start-AzureRmAutomationDscCompilationJob -ResourceGroupName '' -AutomationAccountName '' -ConfigurationName 'PSRepository_AddRepositoryConfig' -Parameters $configurationParameters + + Compiles a configuration in Azure Automation that downloads and installs + the module 'PSLogging'. + + Replace the and with correct values. +#> +configuration PSRepository_AddRepositoryConfig +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $RepositoryName + ) + + Import-DscResource -ModuleName 'PowerShellGet' + + Node $nodeName + { + PSRepository 'AddRepository' + { + Name = $RepositoryName + SourceLocation = 'https://www.poshtestgallery.com/api/v2/' + PublishLocation = 'https://www.poshtestgallery.com/api/v2/package/' + ScriptSourceLocation = 'https://www.poshtestgallery.com/api/v2/items/psscript/' + ScriptPublishLocation = 'https://www.poshtestgallery.com/api/v2/package/' + InstallationPolicy = 'Trusted' + } + } +} diff --git a/DSC/Examples/Resources/PSRepository/2-PSRepository_RemoveRepositoryConfig.ps1 b/DSC/Examples/Resources/PSRepository/2-PSRepository_RemoveRepositoryConfig.ps1 new file mode 100644 index 00000000..3dc9abfd --- /dev/null +++ b/DSC/Examples/Resources/PSRepository/2-PSRepository_RemoveRepositoryConfig.ps1 @@ -0,0 +1,74 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID c9a8b46f-12a6-46e1-8c6b-946ac9995aad +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/PowerShellGet/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/PowerShellGet +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module PowerShellGet + +<# + .SYNOPSIS + Configuration that installs a module. + + .DESCRIPTION + Configuration that installs a module. + + .PARAMETER NodeName + The names of one or more nodes to compile a configuration for. + Defaults to 'localhost'. + + .PARAMETER RepositoryName + The name of the repository that will be added. + + .EXAMPLE + PSRepository_RemoveRepositoryConfig -RepositoryName 'PSTestGallery' + + Compiles a configuration that downloads and installs the module 'PSLogging'. + + .EXAMPLE + $configurationParameters = @{ + RepositoryName = 'PSTestGallery' + } + Start-AzureRmAutomationDscCompilationJob -ResourceGroupName '' -AutomationAccountName '' -ConfigurationName 'PSRepository_RemoveRepositoryConfig' -Parameters $configurationParameters + + Compiles a configuration in Azure Automation that downloads and installs + the module 'PSLogging'. + + Replace the and with correct values. +#> +configuration PSRepository_RemoveRepositoryConfig +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $RepositoryName + ) + + Import-DscResource -ModuleName 'PowerShellGet' + + Node $nodeName + { + PSRepository 'AddRepository' + { + Ensure = 'Absent' + Name = $RepositoryName + } + } +} diff --git a/DSC/Modules/PowerShellGet.ResourceHelper/PowerShellGet.ResourceHelper.psm1 b/DSC/Modules/PowerShellGet.ResourceHelper/PowerShellGet.ResourceHelper.psm1 index c56fdefc..e79eaf34 100644 --- a/DSC/Modules/PowerShellGet.ResourceHelper/PowerShellGet.ResourceHelper.psm1 +++ b/DSC/Modules/PowerShellGet.ResourceHelper/PowerShellGet.ResourceHelper.psm1 @@ -232,3 +232,146 @@ function Get-InstallationPolicy { return $repositoryObject.IsTrusted } } + +<# + .SYNOPSIS + This method is used to compare current and desired values for any DSC resource. + + .PARAMETER CurrentValues + This is hash table of the current values that are applied to the resource. + + .PARAMETER DesiredValues + This is a PSBoundParametersDictionary of the desired values for the resource. + + .PARAMETER ValuesToCheck + This is a list of which properties in the desired values list should be checked. + If this is empty then all values in DesiredValues are checked. +#> +function Test-DscParameterState { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $CurrentValues, + + [Parameter(Mandatory = $true)] + [System.Object] + $DesiredValues, + + [Parameter()] + [System.Array] + $ValuesToCheck + ) + + $returnValue = $true + + if (($DesiredValues.GetType().Name -ne 'HashTable') ` + -and ($DesiredValues.GetType().Name -ne 'CimInstance') ` + -and ($DesiredValues.GetType().Name -ne 'PSBoundParametersDictionary')) { + $errorMessage = $script:localizedData.PropertyTypeInvalidForDesiredValues -f $($DesiredValues.GetType().Name) + New-InvalidArgumentException -ArgumentName 'DesiredValues' -Message $errorMessage + } + + if (($DesiredValues.GetType().Name -eq 'CimInstance') -and ($null -eq $ValuesToCheck)) { + $errorMessage = $script:localizedData.PropertyTypeInvalidForValuesToCheck + New-InvalidArgumentException -ArgumentName 'ValuesToCheck' -Message $errorMessage + } + + if (($null -eq $ValuesToCheck) -or ($ValuesToCheck.Count -lt 1)) { + $keyList = $DesiredValues.Keys + } + else { + $keyList = $ValuesToCheck + } + + $keyList | ForEach-Object -Process { + if (($_ -ne 'Verbose')) { + if (($CurrentValues.ContainsKey($_) -eq $false) ` + -or ($CurrentValues.$_ -ne $DesiredValues.$_) ` + -or (($DesiredValues.GetType().Name -ne 'CimInstance' -and $DesiredValues.ContainsKey($_) -eq $true) -and ($null -ne $DesiredValues.$_ -and $DesiredValues.$_.GetType().IsArray))) { + if ($DesiredValues.GetType().Name -eq 'HashTable' -or ` + $DesiredValues.GetType().Name -eq 'PSBoundParametersDictionary') { + $checkDesiredValue = $DesiredValues.ContainsKey($_) + } + else { + # If DesiredValue is a CimInstance. + $checkDesiredValue = $false + if (([System.Boolean]($DesiredValues.PSObject.Properties.Name -contains $_)) -eq $true) { + if ($null -ne $DesiredValues.$_) { + $checkDesiredValue = $true + } + } + } + + if ($checkDesiredValue) { + $desiredType = $DesiredValues.$_.GetType() + $fieldName = $_ + if ($desiredType.IsArray -eq $true) { + if (($CurrentValues.ContainsKey($fieldName) -eq $false) ` + -or ($null -eq $CurrentValues.$fieldName)) { + Write-Verbose -Message ($script:localizedData.PropertyValidationError -f $fieldName) -Verbose + + $returnValue = $false + } + else { + $arrayCompare = Compare-Object -ReferenceObject $CurrentValues.$fieldName ` + -DifferenceObject $DesiredValues.$fieldName + if ($null -ne $arrayCompare) { + Write-Verbose -Message ($script:localizedData.PropertiesDoesNotMatch -f $fieldName) -Verbose + + $arrayCompare | ForEach-Object -Process { + Write-Verbose -Message ($script:localizedData.PropertyThatDoesNotMatch -f $_.InputObject, $_.SideIndicator) -Verbose + } + + $returnValue = $false + } + } + } + else { + switch ($desiredType.Name) { + 'String' { + if (-not [System.String]::IsNullOrEmpty($CurrentValues.$fieldName) -or ` + -not [System.String]::IsNullOrEmpty($DesiredValues.$fieldName)) { + Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` + -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose + + $returnValue = $false + } + } + + 'Int32' { + if (-not ($DesiredValues.$fieldName -eq 0) -or ` + -not ($null -eq $CurrentValues.$fieldName)) { + Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` + -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose + + $returnValue = $false + } + } + + { $_ -eq 'Int16' -or $_ -eq 'UInt16'} { + if (-not ($DesiredValues.$fieldName -eq 0) -or ` + -not ($null -eq $CurrentValues.$fieldName)) { + Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` + -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose + + $returnValue = $false + } + } + + default { + Write-Warning -Message ($script:localizedData.UnableToCompareProperty ` + -f $fieldName, $desiredType.Name) + + $returnValue = $false + } + } + } + } + } + } + } + + return $returnValue +} diff --git a/DSC/Modules/PowerShellGet.ResourceHelper/en-US/PowerShellGet.ResourceHelper.strings.psd1 b/DSC/Modules/PowerShellGet.ResourceHelper/en-US/PowerShellGet.ResourceHelper.strings.psd1 index ec703201..8e3522fd 100644 --- a/DSC/Modules/PowerShellGet.ResourceHelper/en-US/PowerShellGet.ResourceHelper.strings.psd1 +++ b/DSC/Modules/PowerShellGet.ResourceHelper/en-US/PowerShellGet.ResourceHelper.strings.psd1 @@ -17,6 +17,13 @@ ConvertFrom-StringData -StringData @' VersionError = MinimumVersion should be less than the MaximumVersion. The MinimumVersion or MaximumVersion cannot be used with the RequiredVersion in the same command. UnexpectedArgument = Unexpected argument type: '{0}'. SourceNotFound = Source '{0}' not found. Please make sure you register it. - CallingFunction = "Call a function '{0}'". + CallingFunction = Calling function '{0}'. + PropertyTypeInvalidForDesiredValues = Property 'DesiredValues' must be either a [System.Collections.Hashtable], [CimInstance] or [PSBoundParametersDictionary]. The type detected was {0}. + PropertyTypeInvalidForValuesToCheck = If 'DesiredValues' is a CimInstance, then property 'ValuesToCheck' must contain a value. + PropertyValidationError = Expected to find an array value for property {0} in the current values, but it was either not present or was null. This has caused the test method to return false. + PropertiesDoesNotMatch = Found an array for property {0} in the current values, but this array does not match the desired state. Details of the changes are below. + PropertyThatDoesNotMatch = {0} - {1} + ValueOfTypeDoesNotMatch = {0} value for property {1} does not match. Current state is '{2}' and desired state is '{3}'. + UnableToCompareProperty = Unable to compare property {0} as the type {1} is not handled by the Test-SQLDSCParameterState cmdlet. ###PSLOC '@ diff --git a/DSC/Tests/Integration/MSFT_PSRepository.Integration.Tests.ps1 b/DSC/Tests/Integration/MSFT_PSRepository.Integration.Tests.ps1 new file mode 100644 index 00000000..95c63cc1 --- /dev/null +++ b/DSC/Tests/Integration/MSFT_PSRepository.Integration.Tests.ps1 @@ -0,0 +1,209 @@ +<# + .SYNOPSIS + Integration tests for DSC resource PSModule. + + .NOTES + The header and footer was removed that is usually part of the + DscResource.Tests integration test template. + The header was adding the project folder as a PSModulePath which + collide with the PowerShellGet test framework that copies the + module to the regular PowerShell Module folder. +#> + +$script:dscResourceFriendlyName = 'PSRepository' +$script:dcsResourceName = "MSFT_$($script:dscResourceFriendlyName)" + +#region Integration Tests +$configurationFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dcsResourceName).config.ps1" +. $configurationFile + +Describe "$($script:dcsResourceName)_Integration" { + $configurationName = "$($script:dcsResourceName)_AddRepository_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + 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 "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.Name + $resourceCurrentState.SourceLocation | Should -Be $ConfigurationData.AllNodes.TestSourceLocation + $resourceCurrentState.PublishLocation | Should -Be $ConfigurationData.AllNodes.TestPublishLocation + $resourceCurrentState.ScriptSourceLocation | Should -Be $ConfigurationData.AllNodes.TestScriptSourceLocation + $resourceCurrentState.ScriptPublishLocation | Should -Be $ConfigurationData.AllNodes.TestScriptPublishLocation + $resourceCurrentState.InstallationPolicy | Should -Be 'Trusted' + $resourceCurrentState.PackageManagementProvider | Should -Be 'NuGet' + $resourceCurrentState.Trusted | Should -Be $true + $resourceCurrentState.Registered | Should -Be $true + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be $true + } + } + + $configurationName = "$($script:dcsResourceName)_InstallTestModule_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + } + + $configurationName = "$($script:dcsResourceName)_ChangeRepository_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + 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 "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.Name + $resourceCurrentState.SourceLocation | Should -Be $ConfigurationData.AllNodes.SourceLocation + $resourceCurrentState.PublishLocation | Should -Be $ConfigurationData.AllNodes.PublishLocation + $resourceCurrentState.ScriptSourceLocation | Should -Be $ConfigurationData.AllNodes.ScriptSourceLocation + $resourceCurrentState.ScriptPublishLocation | Should -Be $ConfigurationData.AllNodes.ScriptPublishLocation + $resourceCurrentState.InstallationPolicy | Should -Be 'Untrusted' + $resourceCurrentState.PackageManagementProvider | Should -Be $ConfigurationData.AllNodes.PackageManagementProvider + $resourceCurrentState.Trusted | Should -Be $false + $resourceCurrentState.Registered | Should -Be $true + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be $true + } + } + + $configurationName = "$($script:dcsResourceName)_RemoveRepository_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + 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 "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.Name + $resourceCurrentState.SourceLocation | Should -BeNullOrEmpty + $resourceCurrentState.PublishLocation | Should -BeNullOrEmpty + $resourceCurrentState.ScriptSourceLocation | Should -BeNullOrEmpty + $resourceCurrentState.ScriptPublishLocation | Should -BeNullOrEmpty + $resourceCurrentState.InstallationPolicy | Should -BeNullOrEmpty + $resourceCurrentState.PackageManagementProvider | Should -BeNullOrEmpty + $resourceCurrentState.Trusted | Should -Be $false + $resourceCurrentState.Registered | Should -Be $false + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be $true + } + } +} +#endregion diff --git a/DSC/Tests/Integration/MSFT_PSRepository.config.ps1 b/DSC/Tests/Integration/MSFT_PSRepository.config.ps1 new file mode 100644 index 00000000..de969bcb --- /dev/null +++ b/DSC/Tests/Integration/MSFT_PSRepository.config.ps1 @@ -0,0 +1,128 @@ +#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 +{ + $ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + CertificateFile = $env:DscPublicCertificatePath + + Name = 'PSTestGallery' + + TestSourceLocation = 'https://www.poshtestgallery.com/api/v2/' + TestPublishLocation = 'https://www.poshtestgallery.com/api/v2/package/' + TestScriptSourceLocation = 'https://www.poshtestgallery.com/api/v2/items/psscript/' + TestScriptPublishLocation = 'https://www.poshtestgallery.com/api/v2/package/' + + # Using these URI's to get a stable and accessible site, not to test a real scenario. + SourceLocation = 'https://www.nuget.org/api/v2' + PublishLocation = 'https://www.nuget.org/api/v2/package' + ScriptSourceLocation = 'https://www.nuget.org/api/v2/items/psscript/' + ScriptPublishLocation = 'https://www.nuget.org/api/v2/package' + + <# + Currently there are no default package management providers + that supports the feature 'supports-powershell-modules', so + it is not possible to test switching to another provider. + Note: PowerShellGet provider is not + #> + PackageManagementProvider = 'NuGet' + + TestModuleName = 'ContosoServer' + } + ) + } +} + +<# + .SYNOPSIS + Adds a repository. +#> +Configuration MSFT_PSRepository_AddRepository_Config +{ + Import-DscResource -ModuleName 'PowerShellGet' + + node $AllNodes.NodeName + { + PSRepository 'Integration_Test' + { + Name = $Node.Name + SourceLocation = $Node.TestSourceLocation + PublishLocation = $Node.TestPublishLocation + ScriptSourceLocation = $Node.TestScriptSourceLocation + ScriptPublishLocation = $Node.TestScriptPublishLocation + InstallationPolicy = 'Trusted' + } + } +} + +<# + .SYNOPSIS + Installs a module with default parameters from the new repository. +#> +Configuration MSFT_PSRepository_InstallTestModule_Config +{ + Import-DscResource -ModuleName 'PowerShellGet' + + node $AllNodes.NodeName + { + PSModule 'Integration_Test' + { + Name = $Node.TestModuleName + Repository = $Node.Name + } + } +} + +<# + .SYNOPSIS + Changes the properties of the repository. +#> +Configuration MSFT_PSRepository_ChangeRepository_Config +{ + Import-DscResource -ModuleName 'PowerShellGet' + + node $AllNodes.NodeName + { + PSRepository 'Integration_Test' + { + Name = $Node.Name + SourceLocation = $Node.SourceLocation + PublishLocation = $Node.PublishLocation + ScriptSourceLocation = $Node.ScriptSourceLocation + ScriptPublishLocation = $Node.ScriptPublishLocation + PackageManagementProvider = $Node.PackageManagementProvider + InstallationPolicy = 'Untrusted' + } + } +} + +<# + .SYNOPSIS + Removes the repository. +#> +Configuration MSFT_PSRepository_RemoveRepository_Config +{ + Import-DscResource -ModuleName 'PowerShellGet' + + node $AllNodes.NodeName + { + PSRepository 'Integration_Test' + { + Ensure = 'Absent' + Name = $Node.Name + } + } +} diff --git a/DSC/Tests/Unit/MSFT_PSRepository.Tests.ps1 b/DSC/Tests/Unit/MSFT_PSRepository.Tests.ps1 new file mode 100644 index 00000000..3a9323ba --- /dev/null +++ b/DSC/Tests/Unit/MSFT_PSRepository.Tests.ps1 @@ -0,0 +1,452 @@ +#region HEADER +# This must be same name as the root folder, and module manifest. +$script:DSCModuleName = 'DSC' +$script:DSCResourceName = 'MSFT_PSRepository' + +# Unit Test Template Version: 1.2.4 +$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 ` + -ResourceType 'Mof' ` + -TestType Unit + +#endregion HEADER + +function Invoke-TestSetup { +} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + +# Begin Testing +try { + Invoke-TestSetup + + InModuleScope $script:DSCResourceName { + $mockRepositoryName = 'PSTestGallery' + $mockSourceLocation = 'https://www.poshtestgallery.com/api/v2/' + $mockPublishLocation = 'https://www.poshtestgallery.com/api/v2/package/' + $mockScriptSourceLocation = 'https://www.poshtestgallery.com/api/v2/items/psscript/' + $mockScriptPublishLocation = 'https://www.poshtestgallery.com/api/v2/package/' + $mockPackageManagementProvider = 'NuGet' + $mockInstallationPolicy_Trusted = 'Trusted' + $mockInstallationPolicy_NotTrusted = 'Untrusted' + + $mockRepository = New-Object -TypeName Object | + Add-Member -Name 'Name' -MemberType NoteProperty -Value $mockRepositoryName -PassThru | + Add-Member -Name 'SourceLocation' -MemberType NoteProperty -Value $mockSourceLocation -PassThru | + Add-Member -Name 'ScriptSourceLocation' -MemberType NoteProperty -Value $mockScriptSourceLocation -PassThru | + Add-Member -Name 'PublishLocation' -MemberType NoteProperty -Value $mockPublishLocation -PassThru | + Add-Member -Name 'ScriptPublishLocation' -MemberType NoteProperty -Value $mockScriptPublishLocation -PassThru | + Add-Member -Name 'InstallationPolicy' -MemberType NoteProperty -Value $mockInstallationPolicy_Trusted -PassThru | + Add-Member -Name 'PackageManagementProvider' -MemberType NoteProperty -Value $mockPackageManagementProvider -PassThru | + Add-Member -Name 'Trusted' -MemberType NoteProperty -Value $true -PassThru | + Add-Member -Name 'Registered' -MemberType NoteProperty -Value $true -PassThru -Force + + $mockGetPSRepository = { + return @($mockRepository) + } + + Describe 'MSFT_PSRepository\Get-TargetResource' -Tag 'Get' { + Context 'When the system is in the desired state' { + Context 'When the configuration is present' { + BeforeAll { + Mock -CommandName Get-PSRepository -MockWith $mockGetPSRepository + } + + It 'Should return the same values as passed as parameters' { + $getTargetResourceResult = Get-TargetResource -Name $mockRepositoryName + $getTargetResourceResult.Name | Should -Be $mockRepositoryName + + Assert-MockCalled -CommandName Get-PSRepository -Exactly -Times 1 -Scope It + } + + It 'Should return the correct values for the other properties' { + $getTargetResourceResult = Get-TargetResource -Name $mockRepositoryName + + $getTargetResourceResult.Ensure | Should -Be 'Present' + $getTargetResourceResult.SourceLocation | Should -Be $mockRepository.SourceLocation + $getTargetResourceResult.ScriptSourceLocation | Should -Be $mockRepository.ScriptSourceLocation + $getTargetResourceResult.PublishLocation | Should -Be $mockRepository.PublishLocation + $getTargetResourceResult.ScriptPublishLocation | Should -Be $mockRepository.ScriptPublishLocation + $getTargetResourceResult.InstallationPolicy | Should -Be $mockRepository.InstallationPolicy + $getTargetResourceResult.PackageManagementProvider | Should -Be $mockRepository.PackageManagementProvider + $getTargetResourceResult.Trusted | Should -Be $true + $getTargetResourceResult.Registered | Should -Be $true + + Assert-MockCalled -CommandName Get-PSRepository -Exactly -Times 1 -Scope It + } + } + + Context 'When the configuration is absent' { + BeforeAll { + Mock -CommandName Get-PSRepository + } + + It 'Should return the same values as passed as parameters' { + $getTargetResourceResult = Get-TargetResource -Name $mockRepositoryName + $getTargetResourceResult.Name | Should -Be $mockRepositoryName + + Assert-MockCalled -CommandName Get-PSRepository -Exactly -Times 1 -Scope It + } + + It 'Should return the correct values for the other properties' { + $getTargetResourceResult = Get-TargetResource -Name $mockRepositoryName + + $getTargetResourceResult.Ensure | Should -Be 'Absent' + $getTargetResourceResult.SourceLocation | Should -BeNullOrEmpty + $getTargetResourceResult.ScriptSourceLocation | Should -BeNullOrEmpty + $getTargetResourceResult.PublishLocation | Should -BeNullOrEmpty + $getTargetResourceResult.ScriptPublishLocation | Should -BeNullOrEmpty + $getTargetResourceResult.InstallationPolicy | Should -BeNullOrEmpty + $getTargetResourceResult.PackageManagementProvider | Should -BeNullOrEmpty + $getTargetResourceResult.Trusted | Should -Be $false + $getTargetResourceResult.Registered | Should -Be $false + + Assert-MockCalled -CommandName Get-PSRepository -Exactly -Times 1 -Scope It + } + } + } + } + + Describe 'MSFT_PSRepository\Set-TargetResource' -Tag 'Set' { + Context 'When the system is not in the desired state' { + BeforeAll { + Mock -CommandName Register-PSRepository + Mock -CommandName Unregister-PSRepository + Mock -CommandName Set-PSRepository + } + + Context 'When the configuration should be present' { + Context 'When the repository does not exist' { + BeforeEach { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + Name = $mockRepositoryName + SourceLocation = $null + ScriptSourceLocation = $null + PublishLocation = $null + ScriptPublishLocation = $null + InstallationPolicy = $null + PackageManagementProvider = $null + Trusted = $false + Registered = $false + } + } + } + + It 'Should return call the correct mocks' { + $setTargetResourceParameters = @{ + Name = $mockRepository.Name + SourceLocation = $mockRepository.SourceLocation + ScriptSourceLocation = $mockRepository.ScriptSourceLocation + PublishLocation = $mockRepository.PublishLocation + ScriptPublishLocation = $mockRepository.ScriptPublishLocation + InstallationPolicy = $mockRepository.InstallationPolicy + PackageManagementProvider = $mockRepository.PackageManagementProvider + } + + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Register-PSRepository -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Unregister-PSRepository -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Set-PSRepository -Exactly -Times 0 -Scope It + } + } + + Context 'When the repository do exist but with wrong properties' { + BeforeEach { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Name = $mockRepository.Name + SourceLocation = 'https://www.powershellgallery.com/api/v2/' + ScriptSourceLocation = $mockRepository.ScriptSourceLocation + PublishLocation = $mockRepository.PublishLocation + ScriptPublishLocation = $mockRepository.ScriptPublishLocation + InstallationPolicy = $mockRepository.InstallationPolicy + PackageManagementProvider = $mockRepository.PackageManagementProvider + Trusted = $mockRepository.Trusted + Registered = $mockRepository.Registered + } + } + } + + It 'Should return call the correct mocks' { + $setTargetResourceParameters = @{ + Name = $mockRepository.Name + SourceLocation = $mockRepository.SourceLocation + ScriptSourceLocation = $mockRepository.ScriptSourceLocation + PublishLocation = $mockRepository.PublishLocation + ScriptPublishLocation = $mockRepository.ScriptPublishLocation + InstallationPolicy = $mockRepository.InstallationPolicy + PackageManagementProvider = $mockRepository.PackageManagementProvider + } + + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Register-PSRepository -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Unregister-PSRepository -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Set-PSRepository -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the configuration should be absent' { + Context 'When the repository do exist' { + BeforeEach { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Name = $mockRepository.Name + SourceLocation = $mockRepository.SourceLocation + ScriptSourceLocation = $mockRepository.ScriptSourceLocation + PublishLocation = $mockRepository.PublishLocation + ScriptPublishLocation = $mockRepository.ScriptPublishLocation + InstallationPolicy = $mockRepository.InstallationPolicy + PackageManagementProvider = $mockRepository.PackageManagementProvider + Trusted = $mockRepository.Trusted + Registered = $mockRepository.Registered + } + } + } + + It 'Should return call the correct mocks' { + $setTargetResourceParameters = @{ + Ensure = 'Absent' + Name = $mockRepositoryName + } + + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Register-PSRepository -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Unregister-PSRepository -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Set-PSRepository -Exactly -Times 0 -Scope It + } + } + } + } + } + + Describe 'MSFT_PSRepository\Test-TargetResource' -Tag 'Test' { + Context 'When the system is in the desired state' { + Context 'When the configuration is present' { + BeforeEach { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Name = $mockRepository.Name + SourceLocation = $mockRepository.SourceLocation + ScriptSourceLocation = $mockRepository.ScriptSourceLocation + PublishLocation = $mockRepository.PublishLocation + ScriptPublishLocation = $mockRepository.ScriptPublishLocation + InstallationPolicy = $mockRepository.InstallationPolicy + PackageManagementProvider = $mockRepository.PackageManagementProvider + Trusted = $mockRepository.Trusted + Registered = $mockRepository.Registered + } + } + } + + It 'Should return the state as $true' { + $testTargetResourceResult = Test-TargetResource -Name $mockRepositoryName + $testTargetResourceResult | Should -Be $true + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + + Context 'When the configuration is absent' { + BeforeEach { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + Name = $mockRepositoryName + SourceLocation = $null + ScriptSourceLocation = $null + PublishLocation = $null + ScriptPublishLocation = $null + InstallationPolicy = $null + PackageManagementProvider = $null + Trusted = $false + Registered = $false + } + } + } + + It 'Should return the state as $true' { + $testTargetResourceResult = Test-TargetResource -Ensure 'Absent' -Name $mockRepositoryName + $testTargetResourceResult | Should -Be $true + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When the configuration should be present' { + BeforeEach { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + Name = $mockRepositoryName + SourceLocation = $null + ScriptSourceLocation = $null + PublishLocation = $null + ScriptPublishLocation = $null + InstallationPolicy = $null + PackageManagementProvider = $null + Trusted = $false + Registered = $false + } + } + } + + It 'Should return the state as $false' { + $testTargetResourceParameters = @{ + Name = $mockRepository.Name + SourceLocation = $mockRepository.SourceLocation + ScriptSourceLocation = $mockRepository.ScriptSourceLocation + PublishLocation = $mockRepository.PublishLocation + ScriptPublishLocation = $mockRepository.ScriptPublishLocation + InstallationPolicy = $mockRepository.InstallationPolicy + PackageManagementProvider = $mockRepository.PackageManagementProvider + } + + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -Be $false + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + + Context 'When a property is not in desired state' { + BeforeEach { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Name = $mockRepository.Name + SourceLocation = $mockRepository.SourceLocation + ScriptSourceLocation = $mockRepository.ScriptSourceLocation + PublishLocation = $mockRepository.PublishLocation + ScriptPublishLocation = $mockRepository.ScriptPublishLocation + InstallationPolicy = $mockRepository.InstallationPolicy + PackageManagementProvider = $mockRepository.PackageManagementProvider + Trusted = $mockRepository.Trusted + Registered = $mockRepository.Registered + } + } + } + + $defaultTestCase = @{ + SourceLocation = $mockRepository.SourceLocation + ScriptSourceLocation = $mockRepository.ScriptSourceLocation + PublishLocation = $mockRepository.PublishLocation + ScriptPublishLocation = $mockRepository.ScriptPublishLocation + InstallationPolicy = $mockRepository.InstallationPolicy + PackageManagementProvider = $mockRepository.PackageManagementProvider + } + + $testCaseSourceLocationIsMissing = $defaultTestCase.Clone() + $testCaseSourceLocationIsMissing['TestName'] = 'SourceLocation is missing' + $testCaseSourceLocationIsMissing['SourceLocation'] = 'https://www.powershellgallery.com/api/v2/' + + $testCaseScriptSourceLocationIsMissing = $defaultTestCase.Clone() + $testCaseScriptSourceLocationIsMissing['TestName'] = 'ScriptSourceLocation is missing' + $testCaseScriptSourceLocationIsMissing['ScriptSourceLocation'] = 'https://www.powershellgallery.com/api/v2/items/psscript/' + + $testCasePublishLocationIsMissing = $defaultTestCase.Clone() + $testCasePublishLocationIsMissing['TestName'] = 'PublishLocation is missing' + $testCasePublishLocationIsMissing['PublishLocation'] = 'https://www.powershellgallery.com/api/v2/package/' + + $testCaseScriptPublishLocationIsMissing = $defaultTestCase.Clone() + $testCaseScriptPublishLocationIsMissing['TestName'] = 'ScriptPublishLocation is missing' + $testCaseScriptPublishLocationIsMissing['ScriptPublishLocation'] = 'https://www.powershellgallery.com/api/v2/package/' + + $testCaseInstallationPolicyIsMissing = $defaultTestCase.Clone() + $testCaseInstallationPolicyIsMissing['TestName'] = 'InstallationPolicy is missing' + $testCaseInstallationPolicyIsMissing['InstallationPolicy'] = $mockInstallationPolicy_NotTrusted + + $testCasePackageManagementProviderIsMissing = $defaultTestCase.Clone() + $testCasePackageManagementProviderIsMissing['TestName'] = 'PackageManagementProvider is missing' + $testCasePackageManagementProviderIsMissing['PackageManagementProvider'] = 'PSGallery' + + $testCases = @( + $testCaseSourceLocationIsMissing + $testCaseScriptSourceLocationIsMissing + $testCasePublishLocationIsMissing + $testCaseScriptPublishLocationIsMissing + $testCaseInstallationPolicyIsMissing + $testCasePackageManagementProviderIsMissing + ) + + It 'Should return the state as $false when the correct ' -TestCases $testCases { + param + ( + $SourceLocation, + $ScriptSourceLocation, + $PublishLocation, + $ScriptPublishLocation, + $InstallationPolicy, + $PackageManagementProvider + ) + + $testTargetResourceParameters = @{ + Name = $mockRepositoryName + SourceLocation = $SourceLocation + ScriptSourceLocation = $ScriptSourceLocation + PublishLocation = $PublishLocation + ScriptPublishLocation = $ScriptPublishLocation + InstallationPolicy = $InstallationPolicy + PackageManagementProvider = $PackageManagementProvider + } + + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -Be $false + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + + Context 'When the configuration should be absent' { + BeforeEach { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Name = $mockRepositoryName + SourceLocation = $mockRepository.SourceLocation + ScriptSourceLocation = $mockRepository.ScriptSourceLocation + PublishLocation = $mockRepository.PublishLocation + ScriptPublishLocation = $mockRepository.ScriptPublishLocation + InstallationPolicy = $mockRepository.InstallationPolicy + PackageManagementProvider = $mockRepository.PackageManagementProvider + Trusted = $mockRepository.Trusted + Registered = $mockRepository.Registered + } + } + } + + It 'Should return the state as $false' { + $testTargetResourceResult = Test-TargetResource -Ensure 'Absent' -Name $mockRepositoryName + $testTargetResourceResult | Should -Be $false + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + } + } + } +} +finally { + Invoke-TestCleanup +} diff --git a/DSC/Tests/Unit/PowerShellGet.ResourceHelper.Tests.ps1 b/DSC/Tests/Unit/PowerShellGet.ResourceHelper.Tests.ps1 index f4a5f255..8e0ddf36 100644 --- a/DSC/Tests/Unit/PowerShellGet.ResourceHelper.Tests.ps1 +++ b/DSC/Tests/Unit/PowerShellGet.ResourceHelper.Tests.ps1 @@ -6,281 +6,566 @@ $script:helperModuleName = 'PowerShellGet.ResourceHelper' -Describe "$script:helperModuleName Unit Tests" { - BeforeAll { - $resourceModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent - $dscResourcesFolderFilePath = Join-Path -Path (Join-Path -Path $resourceModuleRoot -ChildPath 'Modules') ` - -ChildPath $script:helperModuleName - - Import-Module -Name (Join-Path -Path $dscResourcesFolderFilePath ` - -ChildPath "$script:helperModuleName.psm1") -Force +$resourceModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$dscResourcesFolderFilePath = Join-Path -Path (Join-Path -Path $resourceModuleRoot -ChildPath 'Modules') ` + -ChildPath $script:helperModuleName + +Import-Module -Name (Join-Path -Path $dscResourcesFolderFilePath ` + -ChildPath "$script:helperModuleName.psm1") -Force + +InModuleScope $script:helperModuleName { + Describe 'New-SplatParameterHashTable' { + Context 'When specific parameters should be returned' { + It 'Should return a hashtable with the correct values' { + $mockPSBoundParameters = @{ + Property1 = '1' + Property2 = '2' + Property3 = '3' + Property4 = '4' + } + + $extractArgumentsResult = New-SplatParameterHashTable ` + -FunctionBoundParameters $mockPSBoundParameters ` + -ArgumentNames @('Property2', 'Property3') + + $extractArgumentsResult | Should -BeOfType [System.Collections.Hashtable] + $extractArgumentsResult.Count | Should -Be 2 + $extractArgumentsResult.ContainsKey('Property2') | Should -BeTrue + $extractArgumentsResult.ContainsKey('Property3') | Should -BeTrue + $extractArgumentsResult.Property2 | Should -Be '2' + $extractArgumentsResult.Property3 | Should -Be '3' + } + } + + Context 'When the specific parameters to be returned does not exist' { + It 'Should return an empty hashtable' { + $mockPSBoundParameters = @{ + Property1 = '1' + } + + $extractArgumentsResult = New-SplatParameterHashTable ` + -FunctionBoundParameters $mockPSBoundParameters ` + -ArgumentNames @('Property2', 'Property3') + + $extractArgumentsResult | Should -BeOfType [System.Collections.Hashtable] + $extractArgumentsResult.Count | Should -Be 0 + } + } + + Context 'When and empty hashtable is passed in the parameter FunctionBoundParameters' { + It 'Should return an empty hashtable' { + $mockPSBoundParameters = @{ + } + + $extractArgumentsResult = New-SplatParameterHashTable ` + -FunctionBoundParameters $mockPSBoundParameters ` + -ArgumentNames @('Property2', 'Property3') + + $extractArgumentsResult | Should -BeOfType [System.Collections.Hashtable] + $extractArgumentsResult.Count | Should -Be 0 + } + } } - InModuleScope $script:helperModuleName { - Describe 'New-SplatParameterHashTable' { - Context 'When specific parameters should be returned' { - It 'Should return a hashtable with the correct values' { - $mockPSBoundParameters = @{ - Property1 = '1' - Property2 = '2' - Property3 = '3' - Property4 = '4' - } + Describe 'Test-ParameterValue' { + BeforeAll { + $mockProviderName = 'PowerShellGet' + } + + Context 'When passing a correct uri as ''Value'' and type is ''SourceUri''' { + It 'Should not throw an error' { + { + Test-ParameterValue ` + -Value 'https://mocked.uri' ` + -Type 'SourceUri' ` + -ProviderName $mockProviderName + } | Should -Not -Throw + } + } + + Context 'When passing an invalid uri as ''Value'' and type is ''SourceUri''' { + It 'Should throw the correct error' { + $mockParameterName = 'mocked.uri' - $extractArgumentsResult = New-SplatParameterHashTable ` - -FunctionBoundParameters $mockPSBoundParameters ` - -ArgumentNames @('Property2', 'Property3') + { + Test-ParameterValue ` + -Value $mockParameterName ` + -Type 'SourceUri' ` + -ProviderName $mockProviderName + } | Should -Throw ($LocalizedData.InValidUri -f $mockParameterName) + } + } + + Context 'When passing a correct path as ''Value'' and type is ''DestinationPath''' { + It 'Should not throw an error' { + { + Test-ParameterValue ` + -Value 'TestDrive:\' ` + -Type 'DestinationPath' ` + -ProviderName $mockProviderName + } | Should -Not -Throw + } + } + + Context 'When passing an invalid path as ''Value'' and type is ''DestinationPath''' { + It 'Should throw the correct error' { + $mockParameterName = 'TestDrive:\NonExistentPath' + + { + Test-ParameterValue ` + -Value $mockParameterName ` + -Type 'DestinationPath' ` + -ProviderName $mockProviderName + } | Should -Throw ($LocalizedData.PathDoesNotExist -f $mockParameterName) + } + } + + Context 'When passing a correct uri as ''Value'' and type is ''PackageSource''' { + It 'Should not throw an error' { + { + Test-ParameterValue ` + -Value 'https://mocked.uri' ` + -Type 'PackageSource' ` + -ProviderName $mockProviderName + } | Should -Not -Throw + } + } - $extractArgumentsResult | Should -BeOfType [System.Collections.Hashtable] - $extractArgumentsResult.Count | Should -Be 2 - $extractArgumentsResult.ContainsKey('Property2') | Should -BeTrue - $extractArgumentsResult.ContainsKey('Property3') | Should -BeTrue - $extractArgumentsResult.Property2 | Should -Be '2' - $extractArgumentsResult.Property3 | Should -Be '3' + Context 'When passing an correct package source as ''Value'' and type is ''PackageSource''' { + BeforeAll { + $mockParameterName = 'PSGallery' + + Mock -CommandName Get-PackageSource -MockWith { + return New-Object -TypeName Object | + Add-Member -Name 'Name' -MemberType NoteProperty -Value $mockParameterName -PassThru | + Add-Member -Name 'ProviderName' -MemberType NoteProperty -Value $mockProviderName -PassThru -Force } } - Context 'When the specific parameters to be returned does not exist' { - It 'Should return an empty hashtable' { - $mockPSBoundParameters = @{ - Property1 = '1' - } + It 'Should not throw an error' { + { + Test-ParameterValue ` + -Value $mockParameterName ` + -Type 'PackageSource' ` + -ProviderName $mockProviderName + } | Should -Not -Throw + + Assert-MockCalled -CommandName Get-PackageSource -Exactly -Times 1 -Scope It + } + } + + Context 'When passing type is ''PackageSource'' and passing a package source that does not exist' { + BeforeAll { + $mockParameterName = 'PSGallery' + + Mock -CommandName Get-PackageSource + } + + It 'Should not throw an error' { + { + Test-ParameterValue ` + -Value $mockParameterName ` + -Type 'PackageSource' ` + -ProviderName $mockProviderName + } | Should -Not -Throw + + Assert-MockCalled -CommandName Get-PackageSource -Exactly -Times 1 -Scope It + } + } + + Context 'When passing invalid type in parameter ''Type''' { + BeforeAll { + $mockType = 'UnknownType' + } - $extractArgumentsResult = New-SplatParameterHashTable ` - -FunctionBoundParameters $mockPSBoundParameters ` - -ArgumentNames @('Property2', 'Property3') + It 'Should throw the correct error' { + { + Test-ParameterValue ` + -Value 'AnyArgument' ` + -Type $mockType ` + -ProviderName $mockProviderName + } | Should -Throw ($LocalizedData.UnexpectedArgument -f $mockType) + } + } + } + + Describe 'Test-VersionParameter' { + Context 'When not passing in any parameters (using default values)' { + It 'Should return true' { + Test-VersionParameter | Should -BeTrue + } + } + + Context 'When only ''RequiredVersion'' are passed' { + It 'Should return true' { + Test-VersionParameter -RequiredVersion '3.0.0.0' | Should -BeTrue + } + } - $extractArgumentsResult | Should -BeOfType [System.Collections.Hashtable] - $extractArgumentsResult.Count | Should -Be 0 + Context 'When ''MinimumVersion'' has a lower version than ''MaximumVersion''' { + It 'Should throw the correct error' { + { + Test-VersionParameter ` + -MinimumVersion '2.0.0.0' ` + -MaximumVersion '1.0.0.0' + } | Should -Throw $LocalizedData.VersionError + } + } + + Context 'When ''MinimumVersion'' has a lower version than ''MaximumVersion''' { + It 'Should throw the correct error' { + { + Test-VersionParameter ` + -MinimumVersion '2.0.0.0' ` + -MaximumVersion '1.0.0.0' + } | Should -Throw $LocalizedData.VersionError + } + } + + Context 'When ''RequiredVersion'', ''MinimumVersion'', and ''MaximumVersion'' are passed' { + It 'Should throw the correct error' { + { + Test-VersionParameter ` + -RequiredVersion '3.0.0.0' ` + -MinimumVersion '2.0.0.0' ` + -MaximumVersion '1.0.0.0' + } | Should -Throw $LocalizedData.VersionError + } + } + } + + Describe 'Get-InstallationPolicy' { + Context 'When the package source exist, and is trusted' { + BeforeAll { + Mock -CommandName Get-PackageSource -MockWith { + return New-Object -TypeName Object | + Add-Member -Name 'IsTrusted' -MemberType NoteProperty -Value $true -PassThru -Force } } - Context 'When and empty hashtable is passed in the parameter FunctionBoundParameters' { - It 'Should return an empty hashtable' { - $mockPSBoundParameters = @{ - } + It 'Should return true' { + Get-InstallationPolicy -RepositoryName 'PSGallery' | Should -BeTrue - $extractArgumentsResult = New-SplatParameterHashTable ` - -FunctionBoundParameters $mockPSBoundParameters ` - -ArgumentNames @('Property2', 'Property3') + Assert-MockCalled -CommandName Get-PackageSource -Exactly -Times 1 -Scope It + } + } - $extractArgumentsResult | Should -BeOfType [System.Collections.Hashtable] - $extractArgumentsResult.Count | Should -Be 0 + Context 'When the package source exist, and is not trusted' { + BeforeAll { + Mock -CommandName Get-PackageSource -MockWith { + return New-Object -TypeName Object | + Add-Member -Name 'IsTrusted' -MemberType NoteProperty -Value $false -PassThru -Force } } + + It 'Should return false' { + + + Get-InstallationPolicy -RepositoryName 'PSGallery' | Should -BeFalse + + Assert-MockCalled -CommandName Get-PackageSource -Exactly -Times 1 -Scope It + } } - Describe 'Test-ParameterValue' { + Context 'When the package source does not exist' { BeforeAll { - $mockProviderName = 'PowerShellGet' + Mock -CommandName Get-PackageSource + } + + It 'Should return $null' { + Get-InstallationPolicy -RepositoryName 'Unknown' | Should -BeNullOrEmpty + + Assert-MockCalled -CommandName Get-PackageSource -Exactly -Times 1 -Scope It } + } + } - Context 'When passing a correct uri as ''Value'' and type is ''SourceUri''' { - It 'Should not throw an error' { - { - Test-ParameterValue ` - -Value 'https://mocked.uri' ` - -Type 'SourceUri' ` - -ProviderName $mockProviderName - } | Should -Not -Throw + Describe 'Testing Test-DscParameterState' -Tag TestDscParameterState { + Context -Name 'When passing values' -Fixture { + It 'Should return true for two identical tables' { + $mockDesiredValues = @{ Example = 'test' } + + $testParameters = @{ + CurrentValues = $mockDesiredValues + DesiredValues = $mockDesiredValues } + + Test-DscParameterState @testParameters | Should -Be $true } - Context 'When passing an invalid uri as ''Value'' and type is ''SourceUri''' { - It 'Should throw the correct error' { - $mockParameterName = 'mocked.uri' + It 'Should return false when a value is different for [System.String]' { + $mockCurrentValues = @{ Example = [System.String]'something' } + $mockDesiredValues = @{ Example = [System.String]'test' } - { - Test-ParameterValue ` - -Value $mockParameterName ` - -Type 'SourceUri' ` - -ProviderName $mockProviderName - } | Should -Throw ($LocalizedData.InValidUri -f $mockParameterName) + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues } + + Test-DscParameterState @testParameters | Should -Be $false } - Context 'When passing a correct path as ''Value'' and type is ''DestinationPath''' { - It 'Should not throw an error' { - { - Test-ParameterValue ` - -Value 'TestDrive:\' ` - -Type 'DestinationPath' ` - -ProviderName $mockProviderName - } | Should -Not -Throw + It 'Should return false when a value is different for [System.Int32]' { + $mockCurrentValues = @{ Example = [System.Int32]1 } + $mockDesiredValues = @{ Example = [System.Int32]2 } + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues } + + Test-DscParameterState @testParameters | Should -Be $false } - Context 'When passing an invalid path as ''Value'' and type is ''DestinationPath''' { - It 'Should throw the correct error' { - $mockParameterName = 'TestDrive:\NonExistentPath' + It 'Should return false when a value is different for [Int16]' { + $mockCurrentValues = @{ Example = [System.Int16]1 } + $mockDesiredValues = @{ Example = [System.Int16]2 } - { - Test-ParameterValue ` - -Value $mockParameterName ` - -Type 'DestinationPath' ` - -ProviderName $mockProviderName - } | Should -Throw ($LocalizedData.PathDoesNotExist -f $mockParameterName) + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues } + + Test-DscParameterState @testParameters | Should -Be $false } - Context 'When passing a correct uri as ''Value'' and type is ''PackageSource''' { - It 'Should not throw an error' { - { - Test-ParameterValue ` - -Value 'https://mocked.uri' ` - -Type 'PackageSource' ` - -ProviderName $mockProviderName - } | Should -Not -Throw + It 'Should return false when a value is different for [UInt16]' { + $mockCurrentValues = @{ Example = [System.UInt16]1 } + $mockDesiredValues = @{ Example = [System.UInt16]2 } + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues } + + Test-DscParameterState @testParameters | Should -Be $false } - Context 'When passing an correct package source as ''Value'' and type is ''PackageSource''' { - BeforeAll { - $mockParameterName = 'PSGallery' + It 'Should return false when a value is missing' { + $mockCurrentValues = @{ } + $mockDesiredValues = @{ Example = 'test' } - Mock -CommandName Get-PackageSource -MockWith { - return New-Object -TypeName Object | - Add-Member -Name 'Name' -MemberType NoteProperty -Value $mockParameterName -PassThru | - Add-Member -Name 'ProviderName' -MemberType NoteProperty -Value $mockProviderName -PassThru -Force - } + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues } - It 'Should not throw an error' { - { - Test-ParameterValue ` - -Value $mockParameterName ` - -Type 'PackageSource' ` - -ProviderName $mockProviderName - } | Should -Not -Throw + Test-DscParameterState @testParameters | Should -Be $false + } + + It 'Should return true when only a specified value matches, but other non-listed values do not' { + $mockCurrentValues = @{ Example = 'test'; SecondExample = 'true' } + $mockDesiredValues = @{ Example = 'test'; SecondExample = 'false' } - Assert-MockCalled -CommandName Get-PackageSource -Exactly -Times 1 -Scope It + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + ValuesToCheck = @('Example') } + + Test-DscParameterState @testParameters | Should -Be $true } - Context 'When passing type is ''PackageSource'' and passing a package source that does not exist' { - BeforeAll { - $mockParameterName = 'PSGallery' + It 'Should return false when only specified values do not match, but other non-listed values do ' { + $mockCurrentValues = @{ Example = 'test'; SecondExample = 'true' } + $mockDesiredValues = @{ Example = 'test'; SecondExample = 'false' } - Mock -CommandName Get-PackageSource + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + ValuesToCheck = @('SecondExample') } - It 'Should not throw an error' { - { - Test-ParameterValue ` - -Value $mockParameterName ` - -Type 'PackageSource' ` - -ProviderName $mockProviderName - } | Should -Not -Throw + Test-DscParameterState @testParameters | Should -Be $false + } + + It 'Should return false when an empty hash table is used in the current values' { + $mockCurrentValues = @{ } + $mockDesiredValues = @{ Example = 'test'; SecondExample = 'false' } - Assert-MockCalled -CommandName Get-PackageSource -Exactly -Times 1 -Scope It + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues } + + Test-DscParameterState @testParameters | Should -Be $false } - Context 'When passing invalid type in parameter ''Type''' { - BeforeAll { - $mockType = 'UnknownType' + It 'Should return true when evaluating a table against a CimInstance' { + $mockCurrentValues = @{ Handle = '0'; ProcessId = '1000' } + + $mockWin32ProcessProperties = @{ + Handle = 0 + ProcessId = 1000 } - It 'Should throw the correct error' { - { - Test-ParameterValue ` - -Value 'AnyArgument' ` - -Type $mockType ` - -ProviderName $mockProviderName - } | Should -Throw ($LocalizedData.UnexpectedArgument -f $mockType) + $mockNewCimInstanceParameters = @{ + ClassName = 'Win32_Process' + Property = $mockWin32ProcessProperties + Key = 'Handle' + ClientOnly = $true } - } - } - Describe 'Test-VersionParameter' { - Context 'When not passing in any parameters (using default values)' { - It 'Should return true' { - Test-VersionParameter | Should -BeTrue + $mockDesiredValues = New-CimInstance @mockNewCimInstanceParameters + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + ValuesToCheck = @('Handle', 'ProcessId') } + + Test-DscParameterState @testParameters | Should -Be $true } - Context 'When only ''RequiredVersion'' are passed' { - It 'Should return true' { - Test-VersionParameter -RequiredVersion '3.0.0.0' | Should -BeTrue + It 'Should return false when evaluating a table against a CimInstance and a value is wrong' { + $mockCurrentValues = @{ Handle = '1'; ProcessId = '1000' } + + $mockWin32ProcessProperties = @{ + Handle = 0 + ProcessId = 1000 + } + + $mockNewCimInstanceParameters = @{ + ClassName = 'Win32_Process' + Property = $mockWin32ProcessProperties + Key = 'Handle' + ClientOnly = $true + } + + $mockDesiredValues = New-CimInstance @mockNewCimInstanceParameters + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + ValuesToCheck = @('Handle', 'ProcessId') } + + Test-DscParameterState @testParameters | Should -Be $false } - Context 'When ''MinimumVersion'' has a lower version than ''MaximumVersion''' { - It 'Should throw the correct error' { - { - Test-VersionParameter ` - -MinimumVersion '2.0.0.0' ` - -MaximumVersion '1.0.0.0' - } | Should -Throw $LocalizedData.VersionError + It 'Should return true when evaluating a hash table containing an array' { + $mockCurrentValues = @{ Example = 'test'; SecondExample = @('1', '2') } + $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1', '2') } + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues } + + Test-DscParameterState @testParameters | Should -Be $true } - Context 'When ''MinimumVersion'' has a lower version than ''MaximumVersion''' { - It 'Should throw the correct error' { - { - Test-VersionParameter ` - -MinimumVersion '2.0.0.0' ` - -MaximumVersion '1.0.0.0' - } | Should -Throw $LocalizedData.VersionError + It 'Should return false when evaluating a hash table containing an array with wrong values' { + $mockCurrentValues = @{ Example = 'test'; SecondExample = @('A', 'B') } + $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1', '2') } + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues } + + Test-DscParameterState @testParameters | Should -Be $false } - Context 'When ''RequiredVersion'', ''MinimumVersion'', and ''MaximumVersion'' are passed' { - It 'Should throw the correct error' { - { - Test-VersionParameter ` - -RequiredVersion '3.0.0.0' ` - -MinimumVersion '2.0.0.0' ` - -MaximumVersion '1.0.0.0' - } | Should -Throw $LocalizedData.VersionError + It 'Should return false when evaluating a hash table containing an array, but the CurrentValues are missing an array' { + $mockCurrentValues = @{ Example = 'test' } + $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1', '2') } + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues } + + Test-DscParameterState @testParameters | Should -Be $false } - } - Describe 'Get-InstallationPolicy' { - Context 'When the package source exist, and is trusted' { - BeforeAll { - Mock -CommandName Get-PackageSource -MockWith { - return New-Object -TypeName Object | - Add-Member -Name 'IsTrusted' -MemberType NoteProperty -Value $true -PassThru -Force - } + It 'Should return false when evaluating a hash table containing an array, but the property i CurrentValues is $null' { + $mockCurrentValues = @{ Example = 'test'; SecondExample = $null } + $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1', '2') } + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues } - It 'Should return true' { - Get-InstallationPolicy -RepositoryName 'PSGallery' | Should -BeTrue + Test-DscParameterState @testParameters | Should -Be $false + } + } + + Context -Name 'When passing invalid types for DesiredValues' -Fixture { + It 'Should throw the correct error when DesiredValues is of wrong type' { + $mockCurrentValues = @{ Example = 'something' } + $mockDesiredValues = 'NotHashTable' - Assert-MockCalled -CommandName Get-PackageSource -Exactly -Times 1 -Scope It + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues } + + $mockCorrectErrorMessage = ($script:localizedData.PropertyTypeInvalidForDesiredValues -f $testParameters.DesiredValues.GetType().Name) + { Test-DscParameterState @testParameters } | Should -Throw $mockCorrectErrorMessage } - Context 'When the package source exist, and is not trusted' { - BeforeAll { - Mock -CommandName Get-PackageSource -MockWith { - return New-Object -TypeName Object | - Add-Member -Name 'IsTrusted' -MemberType NoteProperty -Value $false -PassThru -Force + It 'Should write a warning when DesiredValues contain an unsupported type' { + Mock -CommandName Write-Warning -Verifiable + + # This is a dummy type to test with a type that could never be a correct one. + class MockUnknownType { + [ValidateNotNullOrEmpty()] + [System.String] + $Property1 + + [ValidateNotNullOrEmpty()] + [System.String] + $Property2 + + MockUnknownType() { } } - It 'Should return false' { + $mockCurrentValues = @{ Example = New-Object -TypeName MockUnknownType } + $mockDesiredValues = @{ Example = New-Object -TypeName MockUnknownType } + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } - Get-InstallationPolicy -RepositoryName 'PSGallery' | Should -BeFalse + Test-DscParameterState @testParameters | Should -Be $false - Assert-MockCalled -CommandName Get-PackageSource -Exactly -Times 1 -Scope It - } + Assert-MockCalled -CommandName Write-Warning -Exactly -Times 1 } + } + + Context -Name 'When passing an CimInstance as DesiredValue and ValuesToCheck is $null' -Fixture { + It 'Should throw the correct error' { + $mockCurrentValues = @{ Example = 'something' } - Context 'When the package source does not exist' { - BeforeAll { - Mock -CommandName Get-PackageSource + $mockWin32ProcessProperties = @{ + Handle = 0 + ProcessId = 1000 } - It 'Should return $null' { - Get-InstallationPolicy -RepositoryName 'Unknown' | Should -BeNullOrEmpty + $mockNewCimInstanceParameters = @{ + ClassName = 'Win32_Process' + Property = $mockWin32ProcessProperties + Key = 'Handle' + ClientOnly = $true + } + + $mockDesiredValues = New-CimInstance @mockNewCimInstanceParameters - Assert-MockCalled -CommandName Get-PackageSource -Exactly -Times 1 -Scope It + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + ValuesToCheck = $null } + + $mockCorrectErrorMessage = $script:localizedData.PropertyTypeInvalidForValuesToCheck + { Test-DscParameterState @testParameters } | Should -Throw $mockCorrectErrorMessage } } + + Assert-VerifiableMock } } From 9336fc113848cb5c13ad1fc4ffe9c56a42fdce5b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 17 Feb 2019 10:16:18 +0100 Subject: [PATCH 2/2] Update integration tests for PSModule --- .../Integration/MSFT_PSModule.config.ps1 | 73 +++---------------- 1 file changed, 12 insertions(+), 61 deletions(-) diff --git a/DSC/Tests/Integration/MSFT_PSModule.config.ps1 b/DSC/Tests/Integration/MSFT_PSModule.config.ps1 index e667268d..d2ef24a9 100644 --- a/DSC/Tests/Integration/MSFT_PSModule.config.ps1 +++ b/DSC/Tests/Integration/MSFT_PSModule.config.ps1 @@ -32,47 +32,22 @@ else <# .SYNOPSIS - Changes the package source 'PSGallery' to not trusted. + Changes the repository (package source) 'PSGallery' to not trusted. .NOTES - Since the module is installed by SYSTEM as default, the package - source 'PSGallery' must be trusted for SYSTEM for some of the - tests. + Since the module is installed by SYSTEM as default this is done in + case the PSGallery is already trusted for SYSTEM. #> Configuration MSFT_PSModule_SetPackageSourceAsNotTrusted_Config { - Import-DscResource -ModuleName PSDscResources + Import-DscResource -ModuleName 'PowerShellGet' node $AllNodes.NodeName { - Script 'TrustPackageSourcePSGallery' + PSRepository 'Integration_Test' { - SetScript = { - Write-Verbose -Message 'Setting package source ''PSGallery'' as trusted' - Set-PackageSource -Name 'PSGallery' -Trusted:$false - } - - TestScript = { - Write-Verbose -Message 'Test if the package source ''PSGallery'' is trusted.' - - <# - This takes the string of the $GetScript parameter and creates - a new script block (during runtime in the resource) and then - runs that script block. - #> - $getScriptResult = & ([ScriptBlock]::Create($GetScript)) - - return $getScriptResult.Result -eq $false - } - - GetScript = { - Write-Verbose -Message 'Return the current state of package source ''PSGallery''.' - $trusted = (Get-PackageSource -Name 'PSGallery').IsTrusted - - return @{ - Result = $trusted - } - } + Name = 'PSGallery' + InstallationPolicy = 'Untrusted' } } } @@ -118,7 +93,7 @@ Configuration MSFT_PSModule_UninstallModule1_Config <# .SYNOPSIS - Changes the package source 'PSGallery' to trusted. + Changes the repository (package source) 'PSGallery' to trusted. .NOTES Since the module is installed by SYSTEM as default, the package @@ -127,38 +102,14 @@ Configuration MSFT_PSModule_UninstallModule1_Config #> Configuration MSFT_PSModule_SetPackageSourceAsTrusted_Config { - Import-DscResource -ModuleName PSDscResources + Import-DscResource -ModuleName 'PowerShellGet' node $AllNodes.NodeName { - Script 'TrustPackageSourcePSGallery' + PSRepository 'Integration_Test' { - SetScript = { - Write-Verbose -Message 'Setting package source ''PSGallery'' as trusted' - Set-PackageSource -Name 'PSGallery' -Trusted - } - - TestScript = { - Write-Verbose -Message 'Test if the package source ''PSGallery'' is trusted.' - - <# - This takes the string of the $GetScript parameter and creates - a new script block (during runtime in the resource) and then - runs that script block. - #> - $getScriptResult = & ([ScriptBlock]::Create($GetScript)) - - return $getScriptResult.Result -eq $true - } - - GetScript = { - Write-Verbose -Message 'Return the current state of package source ''PSGallery''.' - $trusted = (Get-PackageSource -Name 'PSGallery').IsTrusted - - return @{ - Result = $trusted - } - } + Name = 'PSGallery' + InstallationPolicy = 'Trusted' } } }