diff --git a/.vscode/analyzersettings.psd1 b/.vscode/analyzersettings.psd1 index f7c2f4486..7ac1eb88e 100644 --- a/.vscode/analyzersettings.psd1 +++ b/.vscode/analyzersettings.psd1 @@ -7,48 +7,49 @@ IncludeDefaultRules = $true IncludeRules = @( # DSC Community style guideline rules from the module ScriptAnalyzer. - 'PSAvoidDefaultValueForMandatoryParameter', - 'PSAvoidDefaultValueSwitchParameter', - 'PSAvoidInvokingEmptyMembers', - 'PSAvoidNullOrEmptyHelpMessageAttribute', - 'PSAvoidUsingCmdletAliases', - 'PSAvoidUsingComputerNameHardcoded', - 'PSAvoidUsingDeprecatedManifestFields', - 'PSAvoidUsingEmptyCatchBlock', - 'PSAvoidUsingInvokeExpression', - 'PSAvoidUsingPositionalParameters', - 'PSAvoidShouldContinueWithoutForce', - 'PSAvoidUsingWMICmdlet', - 'PSAvoidUsingWriteHost', - 'PSDSCReturnCorrectTypesForDSCFunctions', - 'PSDSCStandardDSCFunctionsInResource', - 'PSDSCUseIdenticalMandatoryParametersForDSC', - 'PSDSCUseIdenticalParametersForDSC', - 'PSMisleadingBacktick', - 'PSMissingModuleManifestField', - 'PSPossibleIncorrectComparisonWithNull', - 'PSProvideCommentHelp', - 'PSReservedCmdletChar', - 'PSReservedParams', - 'PSUseApprovedVerbs', - 'PSUseCmdletCorrectly', - 'PSUseOutputTypeCorrectly', - 'PSAvoidGlobalVars', - 'PSAvoidUsingConvertToSecureStringWithPlainText', - 'PSAvoidUsingPlainTextForPassword', - 'PSAvoidUsingUsernameAndPasswordParams', - 'PSDSCUseVerboseMessageInDSCResource', - 'PSShouldProcess', - 'PSUseDeclaredVarsMoreThanAssignments', - 'PSUsePSCredentialType', + 'PSAvoidDefaultValueForMandatoryParameter' + 'PSAvoidDefaultValueSwitchParameter' + 'PSAvoidInvokingEmptyMembers' + 'PSAvoidNullOrEmptyHelpMessageAttribute' + 'PSAvoidUsingCmdletAliases' + 'PSAvoidUsingComputerNameHardcoded' + 'PSAvoidUsingDeprecatedManifestFields' + 'PSAvoidUsingEmptyCatchBlock' + 'PSAvoidUsingInvokeExpression' + 'PSAvoidUsingPositionalParameters' + 'PSAvoidShouldContinueWithoutForce' + 'PSAvoidUsingWMICmdlet' + 'PSAvoidUsingWriteHost' + 'PSDSCReturnCorrectTypesForDSCFunctions' + 'PSDSCStandardDSCFunctionsInResource' + 'PSDSCUseIdenticalMandatoryParametersForDSC' + 'PSDSCUseIdenticalParametersForDSC' + 'PSMisleadingBacktick' + 'PSMissingModuleManifestField' + 'PSPossibleIncorrectComparisonWithNull' + 'PSProvideCommentHelp' + 'PSReservedCmdletChar' + 'PSReservedParams' + 'PSUseApprovedVerbs' + 'PSUseCmdletCorrectly' + 'PSUseOutputTypeCorrectly' + 'PSAvoidGlobalVars' + 'PSAvoidUsingConvertToSecureStringWithPlainText' + 'PSAvoidUsingPlainTextForPassword' + 'PSAvoidUsingUsernameAndPasswordParams' + 'PSDSCUseVerboseMessageInDSCResource' + 'PSShouldProcess' + 'PSUseDeclaredVarsMoreThanAssignments' + 'PSUsePSCredentialType' # Additional rules from the module ScriptAnalyzer - 'PSUseConsistentWhitespace', - 'UseCorrectCasing', - 'PSPlaceOpenBrace', - 'PSPlaceCloseBrace', - 'AlignAssignmentStatement', - 'AvoidUsingDoubleQuotesForConstantString', + 'PSUseConsistentWhitespace' + 'UseCorrectCasing' + 'PSPlaceOpenBrace' + 'PSPlaceCloseBrace' + 'AlignAssignmentStatement' + 'AvoidUsingDoubleQuotesForConstantString' + 'UseShouldProcessForStateChangingFunctions' # Rules from the modules DscResource.AnalyzerRules and SqlServerDsc.AnalyzerRules 'Measure-*' @@ -70,6 +71,11 @@ 'UseSyntacticallyCorrectExamples' ) + # TODO: This is not excluded correctly, see test QA/ScriptAnalyzer.Tests.ps1 for more information. + ExcludeRules = @( + 'TypeNotFound' + ) + Rules = @{ PSUseConsistentWhitespace = @{ Enable = $true @@ -80,7 +86,7 @@ CheckSeparator = $true CheckPipe = $true CheckPipeForRedundantWhitespace = $true - CheckParameter = $true + CheckParameter = $false } PSPlaceOpenBrace = @{ diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e96e5db5..34d312b52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 the existing DSC Community rules and style guideline were added. - Added the Visual Studio Code extension _Code Spell Checker_ to the list of recommended Visual Studio Code extensions. + - Added a file `prefix.ps1` which content is placed first in the built module + (.psm1). This file imports dependent modules, and imports localized strings + used by private and public commands. + - The following classes were added to the module: + - `DatabasePermission` - complex type for the DSC resource SqlDatabasePermission. + - `Ensure` - Enum to be used for the property `Ensure` in class-based + resources. + - `Reason` - Used by method `Get()` to return the reason a property is not + in desired state. + - `ResourceBase` - class that can be inherited by class-based resource and + provides functionality meant simplify the creating of class-based resource. + - The following private functions were added to the module (see comment-based + help for more information): + - `ConvertFrom-CompareResult` + - `ConvertTo-Reason` + - `Get-ClassName` + - `Get-DscProperty` + - `Get-LocalizedDataRecursive` + - `Test-ResourceHasDscProperty` + - `Test-ResourceDscPropertyIsAssigned` + - The following public functions were added to the module (see comment-based + help for more information): + - `Connect-SqlDscDatabaseEngine` + - `ConvertFrom-SqlDscDatabasePermission` + - `ConvertTo-SqlDscDatabasePermission` + - `Get-SqlDscDatabasePermission` + - `Set-SqlDscDatabasePermission` + - `Test-SqlDscIsDatabasePrincipal` - CommonTestHelper - `Import-SqlModuleStub` - Added the optional parameter **PasThru** that, if used, will return the @@ -59,8 +87,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bumped Stale task to v5 in the GitHub workflow. - Make it possible to publish code coverage on failed test runs, and when re-run a fail job. + - Exclude Script Analyzer rule **TypeNotFound** in the file `.vscode/analyzersettings.psd1`. + - Update CONTRIBUTING.md describing error handling in commands and class-based + resources. + - The QA tests are now run in Windows PowerShell due to a bug in PowerShell 7 + that makes class-based resource using inheritance to not work. + - The QA test are excluding the rule **TypeNotFound** because it cannot + run on the source files (there is a new issue that is tracking so this + rule is only run on the built module). + - The Pester code coverage has been switched to use the older functionality + that uses breakpoints to calculate coverage. Newer functionality sometimes + throw an exception when used in conjunction with class-based resources.ยจ + - The SMO stubs (used in the unit tests) was updated to remove a bug related + to the type `DatabasePermissionInfo` when used with the type `DatabasePermissionSet`. + The stubs suggested that the property `PermissionType` (of type `DatabasePermissionSet`) + in `DatabasePermissionInfo` should have been a array `DatabasePermissionSet[]`. + This conflicted with real SMO as it does not pass an array, but instead + a single `DatabasePermissionSet`. The stubs was modified to mimic the + real SMO. At the same time some old mock code in the SMO stubs was removed + as it was no longer in use. - Wiki - add introduction and links to DSC technology +- SqlServerDsc.Common + - The parameter `SetupCredential` of the function `Connect-SQL` was renamed + to `Credential` and the parameter name `SetupCredential` was made a + parameter alias. - SqlLogin - BREAKING CHANGE: The parameters `LoginMustChangePassword`, `LoginPasswordExpirationEnabled`, and `LoginPasswordPolicyEnforced` no longer have a default value of `$true`. @@ -128,6 +179,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - BREAKING CHANGE: The parameter `IsHadrEnabled` is no longer returned by `Get-TargetResource`. The `Ensure` parameter now returns `Present` if Always On HADR is enabled and `Absent` if it is disabled. +- SqlDatabasePermission + - BREAKING CHANGE: The resource has been refactored. The parameters + `ParameterState` and `Permissions` has been replaced by parameters + `Permission`, `PermissionToInclude`, and `PermissionToExclude`. These + permissions parameters are now an instance of the type `DatabasePermission`. + The type `DatabasePermission` contains two properties; `State` and + `Permission`. + - The resource was refactored into a class-based resource. ### Fixed @@ -203,6 +262,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 step for each iteration. A line of code was moved outside of the loop. - The `SourcePath` parameter is now mandatory for all `*-TargetResource` ([issue #1755](https://github.com/dsccommunity/SqlServerDsc/issues/1755)). +- SqlDatabasePermission + - It is no longer possible to have one permission that has two different + states in the same configuration, e.g. denying and granting `update` + in the same configuration. ## [15.2.0] - 2021-09-01 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30c7b5d82..2d29d410c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -217,7 +217,7 @@ class DSC_SqlConfiguration : OMI_BaseResource ### Composite or class-based resource Any composite (with a Configuration) or class-based resources should be prefixed -with just 'Sql' +with 'Sql' ### Localization @@ -277,3 +277,103 @@ SQL Server 2016, SQL Server 2017 and SQL Server 2019. When sending in a Pull Request (PR) all example files will be tested so they can be compiled to a .mof file. If the tests find any errors the build will fail. + +### Class-based DSC resource + +#### Terminating Error + +A terminating error is an error that prevents the resource to continue further. +If a DSC resource shall throw an terminating error the commands of the module +**DscResource.Common** shall be used primarily; [`New-InvalidArgumentException`](https://github.com/dsccommunity/DscResource.Common#new-invalidargumentexception), +[`New-InvalidDataExcpetion`](https://github.com/dsccommunity/DscResource.Common#new-invaliddataexception), +[`New-InvalidOperationException`](https://github.com/dsccommunity/DscResource.Common#new-invalidoperationexception), +[`New-InvalidResultException`](https://github.com/dsccommunity/DscResource.Common#new-invalidresultexception), +or [`New-NotImplementedException`](https://github.com/dsccommunity/DscResource.Common#new-notimplementedexception). +If neither of those commands works in the scenarion then `throw` shall be used. + +### Commands + +Commands are publicly exported commands from the module, and the source for +commands are located in the folder `./source/Public`. + +#### Non-Terminating Error + +A non-terminating error should only be used when a command shall be able to +handle (ignoring) an error and continue processing and still give the user +an expected outcome. + +With a non-terminating error the user is able to decide whether the command +should throw or continue processing on error. The user can pass the +parameter and value `-ErrorAction 'SilentlyContinue'` to the command to +ignore the error and allowing the command to continue, for example the +command could then return `$null`. But if the user passes the parameter +and value `-ErrorAction 'Stop'` the same error will throw a terminating +error telling the user the expected outcome could not be achieved. + +The below example checks to see if a database exist, if it doesn't a +non-terminating error are called. The user is able to either ignore the +error or have it throw depending on what value the user specifies +in parameter `ErrorAction` (or `$ErrorActionPreference`). + +```powershell +if (-not $databaseExist) +{ + $errorMessage = $script:localizedData.MissingDatabase -f $DatabaseName + + Write-Error -Message $errorMessage -Category 'InvalidOperation' -ErrorId 'GS0001' -TargetObject $DatabaseName +} +``` + +#### Terminating Error + +A terminating error is an error that the user are not able to ignore by +passing a parameter to the command (like for non-terminating errors). + +If a command shall throw an terminating error the statement `throw` shall +not be used, neither shall the command `Write-Error` be used with the parameter +`-ErrorAction `Stop``. Instead the method `$PSCmdlet.ThrowTerminatingError()` +shall be used to throw a terminating error. + + +>**NOTE:** Below output assumes `$ErrorView` is set to `'NormalView'` in the +>PowerShell session. + +When using `throw` it will fail on the line with the throw statement +making it look like it is that statement inside the function that failed, +which is not correct since it is either a previous command or evaluation +that failed resulting in the line with the `throw` being called. This is +an example when using `throw`: + +```plaintext +Exception: +Line | + 2 | throw 'My error' + | ~~~~~~~~~~~~~~~~ + | My error +``` + +When instead using `$PSCmdlet.ThrowTerminatingError()`: + +```powershell +$PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + 'MyError', + 'GS0001', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + 'MyObjectOrValue' + ) +) +``` + +The result from `$PSCmdlet.ThrowTerminatingError()` shows that the command +failed (in this example `Get-Something`) and returns a clear category and +error code. + +```plaintext +Get-Something : My Error +At line:1 char:1 ++ Get-Something ++ ~~~~~~~~~~~~~ ++ CategoryInfo : InvalidOperation: (MyObjectOrValue:String) [Get-Something], Exception ++ FullyQualifiedErrorId : GS0001,Get-Something +``` diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 29f821f4d..957347fd8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -68,7 +68,11 @@ stages: buildType: 'current' artifactName: $(buildArtifactName) targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' - - pwsh: | + # This task need to use Windows PowerShell due to a bug in PS7 that cannot + # find/use class-based DSC resources that uses inheritance, which result in + # the examples cannot compile. See the following issue for more information: + # https://github.com/dsccommunity/DnsServerDsc/issues/268#issuecomment-918505230 + - powershell: | # Workaround for issue https://github.com/dsccommunity/DscResource.Test/issues/100 ./build.ps1 -Task noop @@ -78,7 +82,7 @@ stages: $pesterConfig.Output.Verbosity = 'Detailed' Invoke-Pester -Configuration $pesterConfig - name: qualitytest + name: qualityTest displayName: 'Run SqlServerDsc QA Test' - task: PowerShell@2 name: test diff --git a/build.yaml b/build.yaml index aefbee919..40e6d57a3 100644 --- a/build.yaml +++ b/build.yaml @@ -38,6 +38,7 @@ CopyPaths: - DSCResources - en-US - Modules +Prefix: prefix.ps1 Encoding: UTF8 VersionedOutputDirectory: true @@ -88,7 +89,9 @@ Pester: CoveragePercentTarget: 85 OutputPath: JaCoCo_coverage.xml OutputEncoding: ascii - UseBreakpoints: false + # There is a bug in Pester when running unit tests for classes when 'UseBreakpoints' is turned off. + # See error in gist: https://gist.github.com/johlju/c16dfd9587c7e066e8825fc54b33a703 + UseBreakpoints: true TestResult: OutputFormat: NUnitXML OutputEncoding: ascii @@ -116,6 +119,8 @@ DscTest: - output ExcludeModuleFile: - Modules/DscResource.Common + # Must exclude built module file because it should not be tested like MOF-based resources + - SqlServerDsc.psm1 MainGitBranch: main #################################################### diff --git a/source/Classes/002.DatabasePermission.ps1 b/source/Classes/002.DatabasePermission.ps1 new file mode 100644 index 000000000..bd0b40529 --- /dev/null +++ b/source/Classes/002.DatabasePermission.ps1 @@ -0,0 +1,205 @@ +<# + .SYNOPSIS + The possible database permission states. + + .PARAMETER State + The state of the permission. + + .PARAMETER Permission + The permissions to be granted or denied for the user in the database. + + .NOTES + The DSC properties specifies the attribute Mandatory but State was meant + to be attribute Key, but those attributes are not honored correctly during + compilation in the current implementation of PowerShell DSC. If the + attribute would have been left as Key then it would not have been possible + to add an identical instance of DatabasePermission in two separate DSC + resource instances in a DSC configuration. The Key property only works + on the top level DSC properties. E.g. two resources instances of + SqlDatabasePermission in a DSC configuration trying to grant the database + permission 'connect' in two separate databases would have failed compilation + as a the property State would have been seen as "duplicate resource". + + Since it is not possible to use the attribute Key the State property is + evaluate during runtime so that no two states are enforcing the same + permission. + + The method Equals() returns $false if type is not [DatabasePermission] + on both sides of the comparison. There was a though to throw an exception + if the object being compared was of another type, but since there was issues + with using [DatabasePermission[]] it was left out. This can be the correct + way since if moving [DatabasePermission[]] to the left side and the + [DatabasePermission] to the right side, then the left side array is filtered + with the matching values on the right side. +#> +class DatabasePermission : IComparable, System.IEquatable[Object] +{ + [DscProperty(Mandatory)] + [ValidateSet('Grant', 'GrantWithGrant', 'Deny')] + [System.String] + $State + + [DscProperty(Mandatory)] + [AllowEmptyCollection()] + [ValidateSet( + 'Alter', + 'AlterAnyAsymmetricKey', + 'AlterAnyApplicationRole', + 'AlterAnyAssembly', + 'AlterAnyCertificate', + 'AlterAnyDatabaseAudit', + 'AlterAnyDataspace', + 'AlterAnyDatabaseEventNotification', + 'AlterAnyExternalDataSource', + 'AlterAnyExternalFileFormat', + 'AlterAnyFulltextCatalog', + 'AlterAnyMask', + 'AlterAnyMessageType', + 'AlterAnyRole', + 'AlterAnyRoute', + 'AlterAnyRemoteServiceBinding', + 'AlterAnyContract', + 'AlterAnySymmetricKey', + 'AlterAnySchema', + 'AlterAnySecurityPolicy', + 'AlterAnyService', + 'AlterAnyDatabaseDdlTrigger', + 'AlterAnyUser', + 'Authenticate', + 'BackupDatabase', + 'BackupLog', + 'Control', + 'Connect', + 'ConnectReplication', + 'Checkpoint', + 'CreateAggregate', + 'CreateAsymmetricKey', + 'CreateAssembly', + 'CreateCertificate', + 'CreateDatabase', + 'CreateDefault', + 'CreateDatabaseDdlEventNotification', + 'CreateFunction', + 'CreateFulltextCatalog', + 'CreateMessageType', + 'CreateProcedure', + 'CreateQueue', + 'CreateRole', + 'CreateRoute', + 'CreateRule', + 'CreateRemoteServiceBinding', + 'CreateContract', + 'CreateSymmetricKey', + 'CreateSchema', + 'CreateSynonym', + 'CreateService', + 'CreateTable', + 'CreateType', + 'CreateView', + 'CreateXmlSchemaCollection', + 'Delete', + 'Execute', + 'Insert', + 'References', + 'Select', + 'Showplan', + 'SubscribeQueryNotifications', + 'TakeOwnership', + 'Unmask', + 'Update', + 'ViewDefinition', + 'ViewDatabaseState' + )] + [System.String[]] + $Permission + + [System.Boolean] Equals([System.Object] $object) + { + $isEqual = $false + + if ($object -is $this.GetType()) + { + if ($this.Grant -eq $object.Grant) + { + if (-not (Compare-Object -ReferenceObject $this.Permission -DifferenceObject $object.Permission)) + { + $isEqual = $true + } + } + } + + return $isEqual + } + + + [System.Int32] CompareTo([Object] $object) + { + [System.Int32] $returnValue = 0 + + if ($null -eq $object) + { + return 1 + } + + if ($object -is $this.GetType()) + { + <# + Less than zero - The current instance precedes the object specified by the CompareTo + method in the sort order. + Zero - This current instance occurs in the same position in the sort order + as the object specified by the CompareTo method. + Greater than zero - This current instance follows the object specified by the CompareTo + method in the sort order. + #> + $returnValue = 0 + + # Order objects in the order 'Grant', 'GrantWithGrant', 'Deny'. + switch ($this.State) + { + 'Grant' + { + if ($object.State -in @('GrantWithGrant', 'Deny')) + { + # This current instance precedes $object + $returnValue = -1 + } + } + + 'GrantWithGrant' + { + if ($object.State -in @('Grant')) + { + # This current instance follows $object + $returnValue = 1 + } + + if ($object.State -in @('Deny')) + { + # This current instance precedes $object + $returnValue = -1 + } + } + + 'Deny' + { + if ($object.State -in @('Grant', 'GrantWithGrant')) + { + # This current instance follows $object + $returnValue = 1 + } + } + } + } + else + { + $errorMessage = $script:localizedData.InvalidTypeForCompare -f @( + $this.GetType().FullName, + $object.GetType().FullName + ) + + New-InvalidArgumentException -ArgumentName 'Object' -Message $errorMessage + } + + return $returnValue + } +} diff --git a/source/Classes/002.Reason.ps1 b/source/Classes/002.Reason.ps1 new file mode 100644 index 000000000..086cee3dd --- /dev/null +++ b/source/Classes/002.Reason.ps1 @@ -0,0 +1,10 @@ +class Reason +{ + [DscProperty()] + [System.String] + $Code + + [DscProperty()] + [System.String] + $Phrase +} diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 new file mode 100644 index 000000000..b0a730ccf --- /dev/null +++ b/source/Classes/010.ResourceBase.ps1 @@ -0,0 +1,301 @@ +<# + .SYNOPSIS + A class with methods that are equal for all class-based resources. + + .DESCRIPTION + A class with methods that are equal for all class-based resources. + + .NOTES + This class should be able to be inherited by all DSC resources. This class + shall not contain any DSC properties, neither shall it contain anything + specific to only a single resource. +#> + +class ResourceBase +{ + # Property for holding localization strings + hidden [System.Collections.Hashtable] $localizedData = @{} + + # Property for derived class to set properties that should not be enforced. + hidden [System.String[]] $notEnforcedProperties = @() + + # Default constructor + ResourceBase() + { + <# + TODO: When this fails, for example when the localized string file is missing + the LCM returns the error 'Failed to create an object of PowerShell + class SqlDatabasePermission' instead of the actual error that occurred. + #> + $this.localizedData = Get-LocalizedDataRecursive -ClassName ($this | Get-ClassName -Recurse) + } + + [ResourceBase] Get() + { + $this.Assert() + + # Get all key properties. + $keyProperty = $this | Get-DscProperty -Type 'Key' + + Write-Verbose -Message ($this.localizedData.GetCurrentState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + + $getCurrentStateResult = $this.GetCurrentState($keyProperty) + + $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) + + # Set values returned from the derived class' GetCurrentState(). + foreach ($propertyName in $this.PSObject.Properties.Name) + { + if ($propertyName -in @($getCurrentStateResult.Keys)) + { + $dscResourceObject.$propertyName = $getCurrentStateResult.$propertyName + } + } + + # Set key property values unless it was returned from the derived class' GetCurrentState(). + foreach ($propertyName in $keyProperty.Keys) + { + if ($propertyName -notin @($getCurrentStateResult.Keys)) + { + # Add the key value to the instance to be returned. + $dscResourceObject.$propertyName = $this.$propertyName + } + } + + $ignoreProperty = @() + + <# + TODO: This need to be re-evaluated for a resource that is using Ensure + property. How Ensure is handled might need to be refactored, or + removed altogether from this base class. + + If the derived DSC resource has a Ensure property and it was not returned + by GetCurrentState(), then the property Ensure is removed from the + comparison (when calling Compare()). The property Ensure is ignored + since the method GetCurrentState() did not return it, and the current + state for property Ensure cannot be determined until the method Compare() + has run to determined if other properties are not in desired state. + #> + if (($this | Test-ResourceHasDscProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) + { + $ignoreProperty += 'Ensure' + } + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + $propertiesNotInDesiredState = $this.Compare($getCurrentStateResult, $ignoreProperty) + + <# + Return the correct values for Ensure property if the derived DSC resource + has such property and it hasn't been already set by GetCurrentState(). + #> + if (($this | Test-ResourceHasDscProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) + { + if ($propertiesNotInDesiredState) + { + <# + Get all the key properties that might not be in desired state. + This will return $null if all key properties are in desired state. + #> + $keyPropertiesNotInDesiredState = $this | Get-DscProperty -Name $propertiesNotInDesiredState.Property -Type 'Key' + + if ($keyPropertiesNotInDesiredState) + { + <# + The compare come back with at least one key property that was + not in desired state. That only happens if the object does not + exist on the node, so the Ensure value is set to Absent since + the object does not exist. + #> + $dscResourceObject.Ensure = [Ensure]::Absent + } + else + { + <# + The compare come back with all key properties in desired state. + That only happens if the object exist on the node, so the Ensure + value is set to Present since the object exist. + #> + $dscResourceObject.Ensure = [Ensure]::Present + } + } + else + { + <# + The compare come back with $null, meaning that all key properties + match. That only happens if the object exist on the node, so the + Ensure value is set to Present since the object exist. + #> + $dscResourceObject.Ensure = [Ensure]::Present + } + } + + <# + Return the correct values for Reasons property if the derived DSC resource + has such property and it hasn't been already set by GetCurrentState(). + #> + if (($this | Test-ResourceHasDscProperty -Name 'Reasons') -and -not $getCurrentStateResult.ContainsKey('Reasons')) + { + # Always return an empty array if all properties are in desired state. + $dscResourceObject.Reasons = $propertiesNotInDesiredState | + ConvertTo-Reason -ResourceName $this.GetType() + } + + # Return properties. + return $dscResourceObject + } + + [void] Set() + { + # Get all key properties. + $keyProperty = $this | Get-DscProperty -Type 'Key' + + Write-Verbose -Message ($this.localizedData.SetDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + + $this.Assert() + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + $propertiesNotInDesiredState = $this.Compare() + + if ($propertiesNotInDesiredState) + { + $propertiesToModify = $propertiesNotInDesiredState | ConvertFrom-CompareResult + + $propertiesToModify.Keys | + ForEach-Object -Process { + Write-Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_) + } + + <# + Call the Modify() method with the properties that should be enforced + and was not in desired state. + #> + $this.Modify($propertiesToModify) + } + else + { + Write-Verbose -Message $this.localizedData.NoPropertiesToSet + } + } + + [System.Boolean] Test() + { + # Get all key properties. + $keyProperty = $this | Get-DscProperty -Type 'Key' + + Write-Verbose -Message ($this.localizedData.TestDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + + $this.Assert() + + $isInDesiredState = $true + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + $propertiesNotInDesiredState = $this.Compare() + + if ($propertiesNotInDesiredState) + { + $isInDesiredState = $false + } + + if ($isInDesiredState) + { + Write-Verbose $this.localizedData.InDesiredState + } + else + { + Write-Verbose $this.localizedData.NotInDesiredState + } + + return $isInDesiredState + } + + <# + Returns a hashtable containing all properties that should be enforced and + are not in desired state, or $null if all enforced properties are in + desired state. + + This method should normally not be overridden. + #> + hidden [System.Collections.Hashtable[]] Compare() + { + # Get the current state, all properties except Read properties . + $currentState = $this.Get() | Get-DscProperty -Type @('Key', 'Mandatory', 'Optional') + + return $this.Compare($currentState, @()) + } + + <# + Returns a hashtable containing all properties that should be enforced and + are not in desired state, or $null if all enforced properties are in + desired state. + + This method should normally not be overridden. + #> + hidden [System.Collections.Hashtable[]] Compare([System.Collections.Hashtable] $currentState, [System.String[]] $excludeProperties) + { + # Get the desired state, all assigned properties that has an non-null value. + $desiredState = $this | Get-DscProperty -Type @('Key', 'Mandatory', 'Optional') -HasValue + + $CompareDscParameterState = @{ + CurrentValues = $currentState + DesiredValues = $desiredState + Properties = $desiredState.Keys + ExcludeProperties = ($excludeProperties + $this.notEnforcedProperties) | Select-Object -Unique + IncludeValue = $true + # This is needed to sort complex types. + SortArrayValues = $true + } + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + return (Compare-DscParameterState @CompareDscParameterState) + } + + # This method should normally not be overridden. + hidden [void] Assert() + { + # Get the properties that has a non-null value and is not of type Read. + $desiredState = $this | Get-DscProperty -Type @('Key', 'Mandatory', 'Optional') -HasValue + + $this.AssertProperties($desiredState) + } + + <# + This method can be overridden if resource specific property asserts are + needed. The parameter properties will contain the properties that was + assigned a value. + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidEmptyNamedBlocks', '')] + hidden [void] AssertProperties([System.Collections.Hashtable] $properties) + { + } + + <# + This method must be overridden by a resource. The parameter properties will + contain the properties that should be enforced and that are not in desired + state. + #> + hidden [void] Modify([System.Collections.Hashtable] $properties) + { + throw $this.localizedData.ModifyMethodNotImplemented + } + + <# + This method must be overridden by a resource. The parameter properties will + contain the key properties. + #> + hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + throw $this.localizedData.GetCurrentStateMethodNotImplemented + } +} diff --git a/source/Classes/020.SqlDatabasePermission.ps1 b/source/Classes/020.SqlDatabasePermission.ps1 new file mode 100644 index 000000000..02bfa5092 --- /dev/null +++ b/source/Classes/020.SqlDatabasePermission.ps1 @@ -0,0 +1,722 @@ +<# + .SYNOPSIS + The `SqlDatabasePermission` DSC resource is used to grant, deny or revoke + permissions for a user in a database + + .DESCRIPTION + The `SqlDatabasePermission` DSC resource is used to grant, deny or revoke + permissions for a user in a database. For more information about permissions, + please read the article [Permissions (Database Engine)](https://docs.microsoft.com/en-us/sql/relational-databases/security/permissions-database-engine). + + >**Note:** When revoking permission with PermissionState 'GrantWithGrant', both the + >grantee and _all the other users the grantee has granted the same permission to_, + >will also get their permission revoked. + + ## Requirements + + * Target machine must be running Windows Server 2012 or later. + * Target machine must be running SQL Server Database Engine 2012 or later. + + ## Known issues + + All issues are not listed here, see [here for all open issues](https://github.com/dsccommunity/SqlServerDsc/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+SqlDatabasePermission). + + ### `PSDscRunAsCredential` not supported + + The built-in property `PSDscRunAsCredential` does not work with class-based + resources that using advanced type like the parameter `Permission` does. + Use the parameter `Credential` instead of `PSDscRunAsCredential`. + + ### Using `Credential` property. + + SQL Authentication and Group Managed Service Accounts is not supported as + impersonation credentials. Currently only Windows Integrated Security is + supported to use as credentials. + + For Windows Authentication the username must either be provided with the User + Principal Name (UPN), e.g. 'username@domain.local' or if using non-domain + (for example a local Windows Server account) account the username must be + provided without the NetBIOS name, e.g. 'username'. The format 'DOMAIN\username' + will not work. + + See more information in [Credential Overview](https://github.com/dsccommunity/SqlServerDsc/wiki/CredentialOverview). + + ### Invalid values during compilation + + The parameter Permission is of type `[DatabasePermission]`. If a property + in the type is set to an invalid value an error will occur, correct the + values in the properties to valid values. + This happens when the values are validated against the `[ValidateSet()]` + of the resource. When there is an invalid value the following error will + be thrown when the configuration is run (it will not show during compilation): + + ```plaintext + Failed to create an object of PowerShell class SqlDatabasePermission. + + CategoryInfo : InvalidOperation: (root/Microsoft/...ConfigurationManager:String) [], CimException + + FullyQualifiedErrorId : InstantiatePSClassObjectFailed + + PSComputerName : localhost + ``` + + .PARAMETER InstanceName + The name of the SQL Server instance to be configured. Default value is + 'MSSQLSERVER'. + + .PARAMETER DatabaseName + The name of the database. + + .PARAMETER Name + The name of the user that should be granted or denied the permission. + + .PARAMETER ServerName + The host name of the SQL Server to be configured. Default value is the + current computer name. + + .PARAMETER Permission + An array of database permissions to enforce. Any permission that is not + part of the desired state will be revoked. + + Must provide all permission states (`Grant`, `Deny`, `GrantWithGrant`) with + at least an empty string array for the advanced type `DatabasePermission`'s + property `Permission`. + + Valid permission names can be found in the article [DatabasePermissionSet Class properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.databasepermissionset#properties). + + This is an array of CIM instances of advanced type `DatabasePermission` from + the namespace `root/Microsoft/Windows/DesiredStateConfiguration`. + + .PARAMETER PermissionToInclude + An array of database permissions to include to the current state. The + current state will not be affected unless the current state contradict the + desired state. For example if the desired state specifies a deny permissions + but in the current state that permission is granted, that permission will + be changed to be denied. + + Valid permission names can be found in the article [DatabasePermissionSet Class properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.databasepermissionset#properties). + + This is an array of CIM instances of advanced type `DatabasePermission` from + the namespace `root/Microsoft/Windows/DesiredStateConfiguration`. + + .PARAMETER PermissionToExclude + An array of database permissions to exclude (revoke) from the current state. + + Valid permission names can be found in the article [DatabasePermissionSet Class properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.databasepermissionset#properties). + + This is an array of CIM instances of advanced type `DatabasePermission` from + the namespace `root/Microsoft/Windows/DesiredStateConfiguration`. + + .PARAMETER Credential + Specifies the credential to use to connect to the _SQL Server_ instance. + The username of the credentials must be in the format `user@domain`, e.g. + `MySqlUser@company.local`. + + If parameter **Credential'* is not provided then the resource instance is + run using the credential that runs the configuration. + + .PARAMETER Ensure + If the permission should be granted ('Present') or revoked ('Absent'). + + .PARAMETER Reasons + Returns the reason a property is not in desired state. + + .EXAMPLE + Invoke-DscResource -ModuleName SqlServerDsc -Name SqlDatabasePermission -Method Get -Property @{ + Ensure = 'Present' + ServerName = 'localhost' + InstanceName = 'SQL2017' + DatabaseName = 'AdventureWorks' + Credential = (Get-Credential -UserName 'myuser@company.local' -Message 'Password:') + Name = 'INSTANCE\SqlUser' + Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'Grant' + Permission = @('select') + } + ) + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'GrantWithGrant' + Permission = [System.String[]] @() + } + ) + ( + New-CimInstance -ClientOnly -Namespace root/Microsoft/Windows/DesiredStateConfiguration -ClassName DatabasePermission -Property @{ + State = 'Deny' + Permission = [System.String[]] @() + } + ) + ) + } + + This example shows how to call the resource using Invoke-DscResource. + + .NOTES + The built-in property `PsDscRunAsCredential` is not supported on this DSC + resource as it uses a complex type (another class as the type for a DSC + property). If the property `PsDscRunAsCredential` would be used, then the + complex type will not return any values from Get(). This is most likely an + issue (bug) with _PowerShell DSC_. Instead (as a workaround) the property + `Credential` must be used to specify how to connect to the SQL Server + instance. + +#> + +[DscResource(RunAsCredential = 'NotSupported')] +class SqlDatabasePermission : ResourceBase +{ + <# + Property for holding the server connection object. + This should be an object of type [Microsoft.SqlServer.Management.Smo.Server] + but using that type fails the build process currently. + See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. + #> + hidden [System.Object] $sqlServerObject = $null + + [DscProperty(Key)] + [System.String] + $InstanceName + + [DscProperty(Key)] + [System.String] + $DatabaseName + + [DscProperty(Key)] + [System.String] + $Name + + [DscProperty()] + [System.String] + $ServerName = (Get-ComputerName) + + [DscProperty()] + [DatabasePermission[]] + $Permission + + [DscProperty()] + [DatabasePermission[]] + $PermissionToInclude + + [DscProperty()] + [DatabasePermission[]] + $PermissionToExclude + + [DscProperty()] + [PSCredential] + $Credential + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + SqlDatabasePermission() : base () + { + # These properties will not be enforced. + $this.notEnforcedProperties = @( + 'ServerName' + 'InstanceName' + 'DatabaseName' + 'Name' + 'Credential' + ) + } + + [SqlDatabasePermission] Get() + { + # Call the base method to return the properties. + return ([ResourceBase] $this).Get() + } + + [System.Boolean] Test() + { + # Call the base method to test all of the properties that should be enforced. + return ([ResourceBase] $this).Test() + } + + [void] Set() + { + # Call the base method to enforce the properties. + ([ResourceBase] $this).Set() + } + + <# + Returns and reuses the server connection object. If the server connection + object does not exist a connection to the SQL Server instance will occur. + + This should return an object of type [Microsoft.SqlServer.Management.Smo.Server] + but using that type fails the build process currently. + See issue https://github.com/dsccommunity/DscResource.DocGenerator/issues/121. + #> + hidden [System.Object] GetServerObject() + { + if (-not $this.sqlServerObject) + { + $connectSqlDscDatabaseEngineParameters = @{ + ServerName = $this.ServerName + InstanceName = $this.InstanceName + } + + if ($this.Credential) + { + $connectSqlDscDatabaseEngineParameters.Credential = $this.Credential + } + + $this.sqlServerObject = Connect-SqlDscDatabaseEngine @connectSqlDscDatabaseEngineParameters + } + + return $this.sqlServerObject + } + + <# + Base method Get() call this method to get the current state as a hashtable. + The parameter properties will contain the key properties. + #> + hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + $currentStateCredential = $null + + if ($this.Credential) + { + <# + This does not work, even if username is set, the method Get() will + return an empty PSCredential-object. Kept it here so it at least + return a Credential object. + #> + $currentStateCredential = [PSCredential]::new( + $this.Credential.UserName, + [SecureString]::new() + ) + } + + # The property Ensure and key properties will be handled by the base class. + $currentState = @{ + Credential = $currentStateCredential + Permission = [DatabasePermission[]] @() + } + + Write-Verbose -Message ( + $this.localizedData.EvaluateDatabasePermissionForPrincipal -f @( + $properties.Name, + $properties.DatabaseName, + $properties.InstanceName + ) + ) + + $serverObject = $this.GetServerObject() + + $databasePermissionInfo = $serverObject | + Get-SqlDscDatabasePermission -DatabaseName $this.DatabaseName -Name $this.Name -ErrorAction 'SilentlyContinue' + + # If permissions was returned, build the current permission array of [DatabasePermission]. + if ($databasePermissionInfo) + { + [DatabasePermission[]] $currentState.Permission = $databasePermissionInfo | ConvertTo-SqlDscDatabasePermission + } + + # Always return all State; 'Grant', 'GrantWithGrant', and 'Deny'. + foreach ($currentPermissionState in @('Grant', 'GrantWithGrant', 'Deny')) + { + if ($currentState.Permission.State -notcontains $currentPermissionState) + { + [DatabasePermission[]] $currentState.Permission += [DatabasePermission] @{ + State = $currentPermissionState + Permission = @() + } + } + } + + $isPropertyPermissionToIncludeAssigned = $this | Test-ResourceDscPropertyIsAssigned -Name 'PermissionToInclude' + + if ($isPropertyPermissionToIncludeAssigned) + { + $currentState.PermissionToInclude = [DatabasePermission[]] @() + + # Evaluate so that the desired state is present in the current state. + foreach ($desiredIncludePermission in $this.PermissionToInclude) + { + <# + Current state will always have all possible states, so this + will always return one item. + #> + $currentStatePermissionForState = $currentState.Permission | + Where-Object -FilterScript { + $_.State -eq $desiredIncludePermission.State + } + + $currentStatePermissionToInclude = [DatabasePermission] @{ + State = $desiredIncludePermission.State + Permission = @() + } + + foreach ($desiredIncludePermissionName in $desiredIncludePermission.Permission) + { + if ($currentStatePermissionForState.Permission -contains $desiredIncludePermissionName) + { + <# + If the permission exist in the current state, add the + permission to $currentState.PermissionToInclude so that + the base class's method Compare() sees the property as + being in desired state (when the property PermissionToInclude + in the current state and desired state are equal). + #> + $currentStatePermissionToInclude.Permission += $desiredIncludePermissionName + } + else + { + Write-Verbose -Message ( + $this.localizedData.DesiredPermissionAreAbsent -f @( + $desiredIncludePermissionName + ) + ) + } + } + + [DatabasePermission[]] $currentState.PermissionToInclude += $currentStatePermissionToInclude + } + } + + $isPropertyPermissionToExcludeAssigned = $this | Test-ResourceDscPropertyIsAssigned -Name 'PermissionToExclude' + + if ($isPropertyPermissionToExcludeAssigned) + { + $currentState.PermissionToExclude = [DatabasePermission[]] @() + + # Evaluate so that the desired state is missing from the current state. + foreach ($desiredExcludePermission in $this.PermissionToExclude) + { + <# + Current state will always have all possible states, so this + will always return one item. + #> + $currentStatePermissionForState = $currentState.Permission | + Where-Object -FilterScript { + $_.State -eq $desiredExcludePermission.State + } + + $currentStatePermissionToExclude = [DatabasePermission] @{ + State = $desiredExcludePermission.State + Permission = @() + } + + foreach ($desiredExcludedPermissionName in $desiredExcludePermission.Permission) + { + if ($currentStatePermissionForState.Permission -contains $desiredExcludedPermissionName) + { + Write-Verbose -Message ( + $this.localizedData.DesiredAbsentPermissionArePresent -f @( + $desiredExcludedPermissionName + ) + ) + } + else + { + <# + If the permission does _not_ exist in the current state, add + the permission to $currentState.PermissionToExclude so that + the base class's method Compare() sees the property as being + in desired state (when the property PermissionToExclude in + the current state and desired state are equal). + #> + $currentStatePermissionToExclude.Permission += $desiredExcludedPermissionName + } + } + + [DatabasePermission[]] $currentState.PermissionToExclude += $currentStatePermissionToExclude + } + } + + return $currentState + } + + <# + Base method Set() call this method with the properties that should be + enforced are not in desired state. It is not called if all properties + are in desired state. The variable $properties contain the properties + that are not in desired state. + #> + hidden [void] Modify([System.Collections.Hashtable] $properties) + { + $serverObject = $this.GetServerObject() + + $testSqlDscIsDatabasePrincipalParameters = @{ + ServerObject = $serverObject + DatabaseName = $this.DatabaseName + Name = $this.Name + ExcludeFixedRoles = $true + } + + # This will test wether the database and the principal exist. + $isDatabasePrincipal = Test-SqlDscIsDatabasePrincipal @testSqlDscIsDatabasePrincipalParameters + + if (-not $isDatabasePrincipal) + { + $missingPrincipalMessage = $this.localizedData.NameIsMissing -f @( + $this.Name, + $this.DatabaseName, + $this.InstanceName + ) + + New-InvalidOperationException -Message $missingPrincipalMessage + } + + # This holds each state and their permissions to be revoked. + [DatabasePermission[]] $permissionsToRevoke = @() + [DatabasePermission[]] $permissionsToGrantOrDeny = @() + + if ($properties.ContainsKey('Permission')) + { + $keyProperty = $this | Get-DscProperty -Type 'Key' + + $currentState = $this.GetCurrentState($keyProperty) + + <# + Evaluate if there are any permissions that should be revoked + from the current state. + #> + foreach ($currentDesiredPermissionState in $properties.Permission) + { + $currentPermissionsForState = $currentState.Permission | + Where-Object -FilterScript { + $_.State -eq $currentDesiredPermissionState.State + } + + foreach ($permissionName in $currentPermissionsForState.Permission) + { + if ($permissionName -notin $currentDesiredPermissionState.Permission) + { + # Look for an existing object in the array. + $updatePermissionToRevoke = $permissionsToRevoke | + Where-Object -FilterScript { + $_.State -eq $currentDesiredPermissionState.State + } + + # Update the existing object in the array, or create a new object + if ($updatePermissionToRevoke) + { + $updatePermissionToRevoke.Permission += $permissionName + } + else + { + [DatabasePermission[]] $permissionsToRevoke += [DatabasePermission] @{ + State = $currentPermissionsForState.State + Permission = $permissionName + } + } + } + } + } + + <# + At least one permission were missing or should have not be present + in the current state. Grant or Deny all permission assigned to the + property Permission regardless if they were already present or not. + #> + [DatabasePermission[]] $permissionsToGrantOrDeny = $properties.Permission + } + + if ($properties.ContainsKey('PermissionToExclude')) + { + <# + At least one permission were present in the current state. Revoke + all permission assigned to the property PermissionToExclude + regardless if they were already revoked or not. + #> + [DatabasePermission[]] $permissionsToRevoke = $properties.PermissionToExclude + } + + if ($properties.ContainsKey('PermissionToInclude')) + { + <# + At least one permission were missing or should have not be present + in the current state. Grant or Deny all permission assigned to the + property Permission regardless if they were already present or not. + #> + [DatabasePermission[]] $permissionsToGrantOrDeny = $properties.PermissionToInclude + } + + # Revoke all the permissions set in $permissionsToRevoke + if ($permissionsToRevoke) + { + foreach ($currentStateToRevoke in $permissionsToRevoke) + { + $revokePermissionSet = $currentStateToRevoke | ConvertFrom-SqlDscDatabasePermission + + $setSqlDscDatabasePermissionParameters = @{ + ServerObject = $serverObject + DatabaseName = $this.DatabaseName + Name = $this.Name + Permission = $revokePermissionSet + State = 'Revoke' + Force = $true + } + + if ($currentStateToRevoke.State -eq 'GrantWithGrant') + { + $setSqlDscDatabasePermissionParameters.WithGrant = $true + } + + try + { + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters + } + catch + { + $errorMessage = $this.localizedData.FailedToRevokePermissionFromCurrentState -f @( + $this.Name, + $this.DatabaseName + ) + + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + } + + if ($permissionsToGrantOrDeny) + { + foreach ($currentDesiredPermissionState in $permissionsToGrantOrDeny) + { + # If there is not an empty array, change permissions. + if (-not [System.String]::IsNullOrEmpty($currentDesiredPermissionState.Permission)) + { + $permissionSet = $currentDesiredPermissionState | ConvertFrom-SqlDscDatabasePermission + + $setSqlDscDatabasePermissionParameters = @{ + ServerObject = $serverObject + DatabaseName = $this.DatabaseName + Name = $this.Name + Permission = $permissionSet + Force = $true + } + + try + { + switch ($currentDesiredPermissionState.State) + { + 'GrantWithGrant' + { + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State 'Grant' -WithGrant + } + + default + { + Set-SqlDscDatabasePermission @setSqlDscDatabasePermissionParameters -State $currentDesiredPermissionState.State + } + } + } + catch + { + $errorMessage = $this.localizedData.FailedToSetPermission -f @( + $this.Name, + $this.DatabaseName + ) + + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + } + } + } + + <# + Base method Assert() call this method with the properties that was assigned + a value. + #> + hidden [void] AssertProperties([System.Collections.Hashtable] $properties) + { + # PermissionToInclude and PermissionToExclude should be mutually exclusive from Permission + $assertBoundParameterParameters = @{ + BoundParameterList = $properties + MutuallyExclusiveList1 = @( + 'Permission' + ) + MutuallyExclusiveList2 = @( + 'PermissionToInclude' + 'PermissionToExclude' + ) + } + + Assert-BoundParameter @assertBoundParameterParameters + + # Get all assigned permission properties. + $assignedPermissionProperty = $properties.Keys.Where({ + $_ -in @( + 'Permission', + 'PermissionToInclude', + 'PermissionToExclude' + ) + }) + + # Must include either of the permission properties. + if ([System.String]::IsNullOrEmpty($assignedPermissionProperty)) + { + $errorMessage = $this.localizedData.MustAssignOnePermissionProperty + + New-InvalidArgumentException -ArgumentName 'Permission, PermissionToInclude, PermissionToExclude' -Message $errorMessage + } + + foreach ($currentAssignedPermissionProperty in $assignedPermissionProperty) + { + # One State cannot exist several times in the same resource instance. + $permissionStateGroupCount = @( + $properties.$currentAssignedPermissionProperty | + Group-Object -NoElement -Property 'State' -CaseSensitive:$false | + Select-Object -ExpandProperty 'Count' + ) + + if ($permissionStateGroupCount -gt 1) + { + $errorMessage = $this.localizedData.DuplicatePermissionState + + New-InvalidArgumentException -ArgumentName $currentAssignedPermissionProperty -Message $errorMessage + } + + # A specific permission must only exist in one permission state. + $permissionGroupCount = $properties.$currentAssignedPermissionProperty.Permission | + Group-Object -NoElement -CaseSensitive:$false | + Select-Object -ExpandProperty 'Count' + + if ($permissionGroupCount -gt 1) + { + $errorMessage = $this.localizedData.DuplicatePermissionBetweenState + + New-InvalidArgumentException -ArgumentName $currentAssignedPermissionProperty -Message $errorMessage + } + } + + if ($properties.Keys -contains 'Permission') + { + # Each State must exist once. + $missingPermissionState = ( + $properties.Permission.State -notcontains 'Grant' -or + $properties.Permission.State -notcontains 'GrantWithGrant' -or + $properties.Permission.State -notcontains 'Deny' + ) + + if ($missingPermissionState) + { + $errorMessage = $this.localizedData.MissingPermissionState + + New-InvalidArgumentException -ArgumentName 'Permission' -Message $errorMessage + } + } + + <# + Each permission state in the properties PermissionToInclude and PermissionToExclude + must have specified at minimum one permission. + #> + foreach ($currentAssignedPermissionProperty in @('PermissionToInclude', 'PermissionToExclude')) + { + if ($properties.Keys -contains $currentAssignedPermissionProperty) + { + foreach ($currentDatabasePermission in $properties.$currentAssignedPermissionProperty) + { + if ($currentDatabasePermission.Permission.Count -eq 0) + { + $errorMessage = $this.localizedData.MustHaveMinimumOnePermissionInState -f $currentAssignedPermissionProperty + + New-InvalidArgumentException -ArgumentName $currentAssignedPermissionProperty -Message $errorMessage + } + } + } + } + } +} diff --git a/source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.psm1 b/source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.psm1 deleted file mode 100644 index 625f070a1..000000000 --- a/source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.psm1 +++ /dev/null @@ -1,396 +0,0 @@ -$script:sqlServerDscHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\SqlServerDsc.Common' -$script:resourceHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common' - -Import-Module -Name $script:sqlServerDscHelperModulePath -Import-Module -Name $script:resourceHelperModulePath - -$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' - -<# - .SYNOPSIS - Returns the current permissions for the user in the database. - - .PARAMETER DatabaseName - This is the SQL database - - .PARAMETER Name - This is the name of the SQL login for the permission set. - - .PARAMETER PermissionState - This is the state of permission set. Valid values are 'Grant' or 'Deny'. - - .PARAMETER Permissions - This is a list that represents a SQL Server set of database permissions. - - .PARAMETER ServerName - This is the SQL Server for the database. Default value is the current - computer name. - - .PARAMETER InstanceName - This is the SQL instance for the database. -#> -function Get-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $DatabaseName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter(Mandatory = $true)] - [ValidateSet('Grant', 'Deny', 'GrantWithGrant')] - [System.String] - $PermissionState, - - [Parameter(Mandatory = $true)] - [System.String[]] - $Permissions, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ServerName = (Get-ComputerName), - - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName - ) - - Write-Verbose -Message ( - $script:localizedData.GetDatabasePermission -f $Name, $DatabaseName, $InstanceName - ) - - $returnValue = @{ - Ensure = 'Absent' - ServerName = $ServerName - InstanceName = $InstanceName - DatabaseName = $DatabaseName - Name = $Name - PermissionState = $PermissionState - Permissions = @() - } - - $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName - - if ($sqlServerObject) - { - if ($sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName]) - { - $databasePermissionInfo = $sqlDatabaseObject.EnumDatabasePermissions($Name) | - Where-Object -FilterScript { - $_.PermissionState -eq $PermissionState - } - - if ($databasePermissionInfo) - { - # Initialize variable permission - [System.String[]] $getSqlDatabasePermissionResult = @() - - foreach ($currentDatabasePermissionInfo in $databasePermissionInfo) - { - $permissionProperty = ($currentDatabasePermissionInfo.PermissionType | - Get-Member -MemberType Property).Name - - foreach ($currentPermissionProperty in $permissionProperty) - { - if ($currentDatabasePermissionInfo.PermissionType."$currentPermissionProperty") - { - $getSqlDatabasePermissionResult += $currentPermissionProperty - } - } - - # Remove any duplicate permissions. - $getSqlDatabasePermissionResult = @( - $getSqlDatabasePermissionResult | - Sort-Object -Unique - ) - } - - if ($getSqlDatabasePermissionResult) - { - $returnValue['Permissions'] = $getSqlDatabasePermissionResult - - $compareObjectParameters = @{ - ReferenceObject = $Permissions - DifferenceObject = $getSqlDatabasePermissionResult - } - - $resultOfPermissionCompare = Compare-Object @compareObjectParameters | - Where-Object -FilterScript { - $_.SideIndicator -eq '<=' - } - - # If there are no missing permission then return 'Ensure' state as 'Present'. - if ($null -eq $resultOfPermissionCompare) - { - $returnValue['Ensure'] = 'Present' - } - } - } - } - } - - return $returnValue -} - -<# - .SYNOPSIS - Sets the permissions for the user in the database. - - .PARAMETER Ensure - This is The Ensure if the permission should be granted (Present) or - revoked (Absent). - - .PARAMETER DatabaseName - This is the SQL database - - .PARAMETER Name - This is the name of the SQL login for the permission set. - - .PARAMETER PermissionState - This is the state of permission set. Valid values are 'Grant' or 'Deny'. - - .PARAMETER Permissions - This is a list that represents a SQL Server set of database permissions. - - .PARAMETER ServerName - This is the SQL Server for the database. Default value is the current - computer name. - - .PARAMETER InstanceName - This is the SQL instance for the database. -#> -function Set-TargetResource -{ - [CmdletBinding()] - param - ( - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure, - - [Parameter(Mandatory = $true)] - [System.String] - $DatabaseName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter(Mandatory = $true)] - [ValidateSet('Grant', 'Deny', 'GrantWithGrant')] - [System.String] - $PermissionState, - - [Parameter(Mandatory = $true)] - [System.String[]] - $Permissions, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ServerName = (Get-ComputerName), - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName = 'MSSQLSERVER' - ) - - $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName - if ($sqlServerObject) - { - Write-Verbose -Message ( - $script:localizedData.ChangePermissionForUser -f $Name, $DatabaseName, $InstanceName - ) - - if ($sqlDatabaseObject = $sqlServerObject.Databases[$DatabaseName]) - { - $nameExist = $sqlDatabaseObject.Users[$Name] ` - -or ( - <# - Skip fixed roles like db_datareader as it is not possible to set - permissions on those. - #> - $sqlDatabaseObject.Roles | Where-Object -FilterScript { - -not $_.IsFixedRole -and $_.Name -eq $Name - } - ) ` - -or $sqlDatabaseObject.ApplicationRoles[$Name] - - if ($nameExist) - { - try - { - $permissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' - - foreach ($permission in $permissions) - { - $permissionSet."$permission" = $true - } - - switch ($Ensure) - { - 'Present' - { - Write-Verbose -Message ( - $script:localizedData.AddPermission -f $PermissionState, ($Permissions -join ','), $DatabaseName - ) - - switch ($PermissionState) - { - 'GrantWithGrant' - { - $sqlDatabaseObject.Grant($permissionSet, $Name, $true) - } - - 'Grant' - { - $sqlDatabaseObject.Grant($permissionSet, $Name) - } - - 'Deny' - { - $sqlDatabaseObject.Deny($permissionSet, $Name) - } - } - } - - 'Absent' - { - Write-Verbose -Message ( - $script:localizedData.DropPermission -f $PermissionState, ($Permissions -join ','), $DatabaseName - ) - - if ($PermissionState -eq 'GrantWithGrant') - { - $sqlDatabaseObject.Revoke($permissionSet, $Name, $false, $true) - } - else - { - $sqlDatabaseObject.Revoke($permissionSet, $Name) - } - } - } - } - catch - { - $errorMessage = $script:localizedData.FailedToSetPermissionDatabase -f $Name, $DatabaseName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - } - else - { - $errorMessage = $script:localizedData.NameIsMissing -f $Name, $DatabaseName - - New-InvalidOperationException -Message $errorMessage - } - } - else - { - $errorMessage = $script:localizedData.DatabaseNotFound -f $DatabaseName - New-ObjectNotFoundException -Message $errorMessage - } - } -} - -<# - .SYNOPSIS - Tests if the permissions is set for the user in the database. - - .PARAMETER Ensure - This is The Ensure if the permission should be granted (Present) or - revoked (Absent). - - .PARAMETER DatabaseName - This is the SQL database - - .PARAMETER Name - This is the name of the SQL login for the permission set. - - .PARAMETER PermissionState - This is the state of permission set. Valid values are 'Grant' or 'Deny'. - - .PARAMETER Permissions - This is a list that represents a SQL Server set of database permissions. - - .PARAMETER ServerName - This is the SQL Server for the database. Default value is the current - computer name. - - .PARAMETER InstanceName - This is the SQL instance for the database. -#> -function Test-TargetResource -{ - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Connect-Sql is called when Get-TargetResource is called')] - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure, - - [Parameter(Mandatory = $true)] - [System.String] - $DatabaseName, - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter(Mandatory = $true)] - [ValidateSet('Grant', 'Deny', 'GrantWithGrant')] - [System.String] - $PermissionState, - - [Parameter(Mandatory = $true)] - [System.String[]] - $Permissions, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ServerName = (Get-ComputerName), - - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName = 'MSSQLSERVER' - ) - - Write-Verbose -Message ( - $script:localizedData.TestingConfiguration -f $Name, $DatabaseName, $InstanceName - ) - - $getTargetResourceParameters = @{ - InstanceName = $PSBoundParameters.InstanceName - ServerName = $ServerName - DatabaseName = $PSBoundParameters.DatabaseName - Name = $PSBoundParameters.Name - PermissionState = $PSBoundParameters.PermissionState - Permissions = $PSBoundParameters.Permissions - } - - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - - <# - There is no need to evaluate the parameter Permissions here. - In the Get-TargetResource function there is a test to verify if Permissions is in - desired state. If the permissions are correct, then Get-TargetResource will return - the value 'Present' for the Ensure parameter, otherwise Ensure will have the value - 'Absent'. - #> - return Test-DscParameterState -CurrentValues $getTargetResourceResult ` - -DesiredValues $PSBoundParameters ` - -ValuesToCheck @('Name', 'Ensure', 'PermissionState') ` - -TurnOffTypeChecking -} diff --git a/source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.schema.mof b/source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.schema.mof deleted file mode 100644 index 122d072b6..000000000 --- a/source/DSCResources/DSC_SqlDatabasePermission/DSC_SqlDatabasePermission.schema.mof +++ /dev/null @@ -1,11 +0,0 @@ -[ClassVersion("1.0.0.0"), FriendlyName("SqlDatabasePermission")] -class DSC_SqlDatabasePermission : OMI_BaseResource -{ - [Key, Description("The name of the database.")] String DatabaseName; - [Key, Description("The name of the user that should be granted or denied the permission.")] String Name; - [Key, Description("The state of the permission."), ValueMap{"Grant","Deny","GrantWithGrant"}, Values{"Grant","Deny","GrantWithGrant"}] String PermissionState; - [Key, Description("The name of the _SQL Server_ instance to be configured. Default value is `'MSSQLSERVER'`.")] String InstanceName; - [Required, Description("The permissions to be granted or denied for the user in the database.")] String Permissions[]; - [Write, Description("If the permission should be granted (`'Present'`) or revoked (`'Absent'`)."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Write, Description("The host name of the _SQL Server_ to be configured. Default value is the current computer name.")] String ServerName; -}; diff --git a/source/DSCResources/DSC_SqlDatabasePermission/README.md b/source/DSCResources/DSC_SqlDatabasePermission/README.md deleted file mode 100644 index d18357526..000000000 --- a/source/DSCResources/DSC_SqlDatabasePermission/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Description - -The `SqlDatabasePermission` DSC resource is used to grant, deny or revoke -permissions for a user in a database. For more information about permissions, -please read the article [Permissions (Database Engine)](https://docs.microsoft.com/en-us/sql/relational-databases/security/permissions-database-engine). - ->**Note:** When revoking permission with PermissionState 'GrantWithGrant', both the ->grantee and _all the other users the grantee has granted the same permission to_, ->will also get their permission revoked. - -Valid permission names can be found in the article [DatabasePermissionSet Class properties](https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.smo.databasepermissionset#properties). - -## Requirements - -* Target machine must be running Windows Server 2012 or later. -* Target machine must be running SQL Server Database Engine 2012 or later. - -## Known issues - -All issues are not listed here, see [here for all open issues](https://github.com/dsccommunity/SqlServerDsc/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+SqlDatabasePermission). diff --git a/source/DSCResources/DSC_SqlDatabasePermission/en-US/DSC_SqlDatabasePermission.strings.psd1 b/source/DSCResources/DSC_SqlDatabasePermission/en-US/DSC_SqlDatabasePermission.strings.psd1 deleted file mode 100644 index 014c04d97..000000000 --- a/source/DSCResources/DSC_SqlDatabasePermission/en-US/DSC_SqlDatabasePermission.strings.psd1 +++ /dev/null @@ -1,10 +0,0 @@ -ConvertFrom-StringData @' - GetDatabasePermission = Get permissions for the user '{0}' in the database '{1}' on the instance '{2}'. - DatabaseNotFound = The database '{0}' does not exist. - ChangePermissionForUser = Changing the permission for the user '{0}' in the database '{1}' on the instance '{2}'. - NameIsMissing = The name '{0}' is neither a database user, database role (user-defined), or database application role in the database '{1}'. - AddPermission = {0} the permissions '{1}' to the database '{2}'. - DropPermission = Revoking the {0} permissions '{1}' from the database '{2}'. - FailedToSetPermissionDatabase = Failed to set the permissions for the login '{0}' in the database '{1}'. - TestingConfiguration = Determines if the user '{0}' has the correct permissions in the database '{1}' on the instance '{2}'. -'@ diff --git a/source/Enum/1.Ensure.ps1 b/source/Enum/1.Ensure.ps1 new file mode 100644 index 000000000..8f0c9e022 --- /dev/null +++ b/source/Enum/1.Ensure.ps1 @@ -0,0 +1,10 @@ +<# + .SYNOPSIS + The possible states for the DSC resource parameter Ensure. +#> + +enum Ensure +{ + Present + Absent +} diff --git a/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 deleted file mode 100644 index 78cf1488c..000000000 --- a/source/Examples/Resources/SqlDatabasePermission/1-GrantDatabasePermissions.ps1 +++ /dev/null @@ -1,59 +0,0 @@ -<# - .DESCRIPTION - This example shows how to ensure that the user account CONTOSO\SQLAdmin - has "Connect" and "Update" SQL Permissions for database "AdventureWorks". -#> - -Configuration Example -{ - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName 'SqlServerDsc' - - node localhost - { - SqlDatabasePermission 'Grant_SqlDatabasePermissions_SQLAdmin_Db01' - { - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorks' - PermissionState = 'Grant' - Permissions = @('Connect', 'Update') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential - } - - SqlDatabasePermission 'Grant_SqlDatabasePermissions_SQLUser_Db01' - { - Ensure = 'Present' - Name = 'CONTOSO\SQLUser' - DatabaseName = 'AdventureWorks' - PermissionState = 'Grant' - Permissions = @('Connect', 'Update') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential - } - - SqlDatabasePermission 'Grant_SqlDatabasePermissions_SQLAdmin_Db02' - { - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorksLT' - PermissionState = 'Grant' - Permissions = @('Connect', 'Update') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential - } - } -} diff --git a/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermission.ps1 b/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermission.ps1 new file mode 100644 index 000000000..6dc783bb5 --- /dev/null +++ b/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermission.ps1 @@ -0,0 +1,100 @@ +<# + .DESCRIPTION + This example shows how to ensure that the user account CONTOSO\SQLAdmin + is granted "Connect" and "Update" permissions for the databases DB01 and + DB02. It also shows that the user account CONTOSO\SQLUser has is granted + "Connect" and "Update" permissions, but also how it is denied the permission + "Delete" for the database DB01. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost + { + SqlDatabasePermission 'Set_Database_Permissions_SQLAdmin_DB01' + { + ServerName = 'sql01.company.local' + InstanceName = 'DSC' + DatabaseName = 'DB01' + Name = 'CONTOSO\SQLAdmin' + Credential = $SqlAdministratorCredential + Permission = @( + DatabasePermission + { + State = 'Grant' + Permission = @('Connect', 'Update') + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Deny' + Permission = @() + } + ) + } + + SqlDatabasePermission 'Set_Database_Permissions_SQLAdmin_DB02' + { + ServerName = 'sql01.company.local' + InstanceName = 'DSC' + DatabaseName = 'DB02' + Name = 'CONTOSO\SQLAdmin' + Credential = $SqlAdministratorCredential + Permission = @( + DatabasePermission + { + State = 'Grant' + Permission = @('Connect', 'Update') + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Deny' + Permission = @() + } + ) + } + + SqlDatabasePermission 'Set_Database_Permissions_SQLUser_DB01' + { + ServerName = 'sql01.company.local' + InstanceName = 'DSC' + DatabaseName = 'DB01' + Name = 'CONTOSO\SQLUser' + Credential = $SqlAdministratorCredential + Permission = @( + DatabasePermission + { + State = 'Grant' + Permission = @('Connect', 'Update') + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Deny' + Permission = @('Delete') + } + ) + } + } +} diff --git a/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToExclude.ps1 b/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToExclude.ps1 new file mode 100644 index 000000000..661abc103 --- /dev/null +++ b/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToExclude.ps1 @@ -0,0 +1,38 @@ +<# + .DESCRIPTION + This example shows how to enforce that if the user account CONTOSO\SQLAdmin + in the databases DB01 would be granted the permission "Delete" outside of + DSC, it is revoked. + Any other existing permissions in the states Grant, Deny, and GrantWithGrant + will not be changed. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost + { + SqlDatabasePermission 'Set_Database_Permissions_SQLAdmin_DB01' + { + ServerName = 'sql01.company.local' + InstanceName = 'DSC' + DatabaseName = 'DB01' + Name = 'CONTOSO\SQLAdmin' + Credential = $SqlAdministratorCredential + PermissionToExclude = @( + DatabasePermission + { + State = 'Grant' + Permission = @('Delete') + } + ) + } + } +} diff --git a/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToInclude.ps1 b/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToInclude.ps1 new file mode 100644 index 000000000..9a013d1cc --- /dev/null +++ b/source/Examples/Resources/SqlDatabasePermission/1-UseParameterPermissionToInclude.ps1 @@ -0,0 +1,37 @@ +<# + .DESCRIPTION + This example shows how to ensure that the user account CONTOSO\SQLAdmin + is granted "Connect" and "Update" permissions for the databases DB01. + Any existing permissions in the states Grant, Deny, and GrantWithGrant will + not be changed (unless the contradict with the desired state). +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SqlAdministratorCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost + { + SqlDatabasePermission 'Set_Database_Permissions_SQLAdmin_DB01' + { + ServerName = 'sql01.company.local' + InstanceName = 'DSC' + DatabaseName = 'DB01' + Name = 'CONTOSO\SQLAdmin' + Credential = $SqlAdministratorCredential + PermissionToInclude = @( + DatabasePermission + { + State = 'Grant' + Permission = @('Connect', 'Update') + } + ) + } + } +} diff --git a/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 deleted file mode 100644 index 888e2e887..000000000 --- a/source/Examples/Resources/SqlDatabasePermission/2-RevokeDatabasePermissions.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -<# - .DESCRIPTION - This example shows how to ensure that the user account CONTOSO\SQLAdmin - hasn't "Select" and "Create Table" SQL Permissions for database "AdventureWorks". -#> - -Configuration Example -{ - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName 'SqlServerDsc' - - node localhost - { - SqlDatabasePermission 'RevokeGrant_SqlDatabasePermissions_SQLAdmin' - { - Ensure = 'Absent' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorks' - PermissionState = 'Grant' - Permissions = @('Connect', 'Update') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential - } - - SqlDatabasePermission 'RevokeDeny_SqlDatabasePermissions_SQLAdmin' - { - Ensure = 'Absent' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorks' - PermissionState = 'Deny' - Permissions = @('Select', 'CreateTable') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential - } - } -} diff --git a/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 b/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 deleted file mode 100644 index 6eaddb016..000000000 --- a/source/Examples/Resources/SqlDatabasePermission/3-DenyDatabasePermissions.ps1 +++ /dev/null @@ -1,59 +0,0 @@ -<# - .DESCRIPTION - This example shows how to ensure that the user account CONTOSO\SQLAdmin - has "Connect" and "Update" SQL Permissions for database "AdventureWorks". -#> - -Configuration Example -{ - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName 'SqlServerDsc' - - node localhost - { - SqlDatabasePermission 'Deny_SqlDatabasePermissions_SQLAdmin_Db01' - { - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorks' - PermissionState = 'Deny' - Permissions = @('Select', 'CreateTable') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential - } - - SqlDatabasePermission 'Deny_SqlDatabasePermissions_SQLUser_Db01' - { - Ensure = 'Present' - Name = 'CONTOSO\SQLUser' - DatabaseName = 'AdventureWorks' - PermissionState = 'Deny' - Permissions = @('Select', 'CreateTable') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential - } - - SqlDatabasePermission 'Deny_SqlDatabasePermissions_SQLAdmin_Db02' - { - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - DatabaseName = 'AdventureWorksLT' - PermissionState = 'Deny' - Permissions = @('Select', 'CreateTable') - ServerName = 'sqltest.company.local' - InstanceName = 'DSC' - - PsDscRunAsCredential = $SqlAdministratorCredential - } - } -} diff --git a/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 b/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 index d69b74806..d170b445d 100644 --- a/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 +++ b/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 @@ -481,9 +481,9 @@ function Connect-SQL [Parameter(ParameterSetName = 'SqlServerWithCredential', Mandatory = $true)] [ValidateNotNull()] - [Alias('DatabaseCredential')] + [Alias('SetupCredential', 'DatabaseCredential')] [System.Management.Automation.PSCredential] - $SetupCredential, + $Credential, [Parameter(ParameterSetName = 'SqlServerWithCredential')] [ValidateSet('WindowsUser', 'SqlLogin')] @@ -527,7 +527,7 @@ function Connect-SQL } else { - $connectUserName = $SetupCredential.UserName + $connectUserName = $Credential.UserName Write-Verbose -Message ( $script:localizedData.ConnectingUsingImpersonation -f $connectUsername, $LoginType @@ -537,7 +537,7 @@ function Connect-SQL { $sqlConnectionContext.LoginSecure = $false $sqlConnectionContext.Login = $connectUserName - $sqlConnectionContext.SecurePassword = $SetupCredential.Password + $sqlConnectionContext.SecurePassword = $Credential.Password } if ($LoginType -eq 'WindowsUser') @@ -545,7 +545,7 @@ function Connect-SQL $sqlConnectionContext.LoginSecure = $true $sqlConnectionContext.ConnectAsUser = $true $sqlConnectionContext.ConnectAsUserName = $connectUserName - $sqlConnectionContext.ConnectAsUserPassword = $SetupCredential.GetNetworkCredential().Password + $sqlConnectionContext.ConnectAsUserPassword = $Credential.GetNetworkCredential().Password } } diff --git a/source/Private/ConvertFrom-CompareResult.ps1 b/source/Private/ConvertFrom-CompareResult.ps1 new file mode 100644 index 000000000..a917e70b9 --- /dev/null +++ b/source/Private/ConvertFrom-CompareResult.ps1 @@ -0,0 +1,44 @@ +<# + .SYNOPSIS + Returns a hashtable with property name and their expected value. + + .PARAMETER CompareResult + The result from Compare-DscParameterState. + + .EXAMPLE + ConvertFrom-CompareResult -CompareResult (Compare-DscParameterState) + + Returns a hashtable that contain all the properties not in desired state + and their expected value. + + .OUTPUTS + [System.Collections.Hashtable] +#> +function ConvertFrom-CompareResult +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Collections.Hashtable[]] + $CompareResult + ) + + begin + { + $returnHashtable = @{} + } + + process + { + $CompareResult | ForEach-Object -Process { + $returnHashtable[$_.Property] = $_.ExpectedValue + } + } + + end + { + return $returnHashtable + } +} diff --git a/source/Private/ConvertTo-Reason.ps1 b/source/Private/ConvertTo-Reason.ps1 new file mode 100644 index 000000000..fe5c2e0cb --- /dev/null +++ b/source/Private/ConvertTo-Reason.ps1 @@ -0,0 +1,86 @@ +<# + .SYNOPSIS + Returns a array of the type `[Reason]`. + + .DESCRIPTION + This command converts the array of properties that is returned by the command + `Compare-DscParameterState`. The result is an array of the type `[Reason]` that + can be returned in a DSC resource's property **Reasons**. + + .PARAMETER Property + The result from the command Compare-DscParameterState. + + .PARAMETER ResourceName + The name of the resource. Will be used to populate the property Code with + the correct value. + + .EXAMPLE + ConvertTo-Reason -Property (Compare-DscParameterState) -ResourceName 'MyResource' + + Returns an array of `[Reason]` that contain all the properties not in desired + state and why a specific property is not in desired state. + + .OUTPUTS + [Reason[]] +#> +function ConvertTo-Reason +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when the output type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [CmdletBinding()] + [OutputType([Reason[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [AllowEmptyCollection()] + [AllowNull()] + [System.Collections.Hashtable[]] + $Property, + + [Parameter(Mandatory = $true)] + [System.String] + $ResourceName + ) + + begin + { + # Always return an empty array if there are no properties to add. + $reasons = [Reason[]] @() + } + + process + { + foreach ($currentProperty in $Property) + { + if ($currentProperty.ExpectedValue -is [System.Enum]) + { + # Return the string representation of the value (instead of the numeric value). + $propertyExpectedValue = $currentProperty.ExpectedValue.ToString() + } + else + { + $propertyExpectedValue = $currentProperty.ExpectedValue + } + + if ($property.ActualValue -is [System.Enum]) + { + # Return the string representation of the value so that conversion to json is correct. + $propertyActualValue = $currentProperty.ActualValue.ToString() + } + else + { + $propertyActualValue = $currentProperty.ActualValue + } + + $reasons += [Reason] @{ + Code = '{0}:{0}:{1}' -f $ResourceName, $currentProperty.Property + # Convert the object to JSON to handle complex types. + Phrase = 'The property {0} should be {1}, but was {2}' -f $currentProperty.Property, ($propertyExpectedValue | ConvertTo-Json -Compress), ($propertyActualValue | ConvertTo-Json -Compress) + } + } + } + + end + { + return $reasons + } +} diff --git a/source/Private/Get-ClassName.ps1 b/source/Private/Get-ClassName.ps1 new file mode 100644 index 000000000..06745e026 --- /dev/null +++ b/source/Private/Get-ClassName.ps1 @@ -0,0 +1,55 @@ +<# + .SYNOPSIS + Get the class name of the passed object, and optional an array with + all inherited classes. + + .PARAMETER InputObject + The object to be evaluated. + + .PARAMETER Recurse + Specifies if the class name of inherited classes shall be returned. The + recursive stops when the first object of the type `[System.Object]` is + found. + + .EXAMPLE + Get-ClassName -InputObject $this -Recurse + + Get the class name of the current instance and all the inherited (parent) + classes. + + .OUTPUTS + [System.String[]] +#> +function Get-ClassName +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because the rule does not understands that the command returns [System.String[]] when using , (comma) in the return statement')] + [CmdletBinding()] + [OutputType([System.String[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Recurse + ) + + # Create a list of the inherited class names + $class = @($InputObject.GetType().FullName) + + if ($Recurse.IsPresent) + { + $parentClass = $InputObject.GetType().BaseType + + while ($parentClass -ne [System.Object]) + { + $class += $parentClass.FullName + + $parentClass = $parentClass.BaseType + } + } + + return , [System.String[]] $class +} diff --git a/source/Private/Get-DscProperty.ps1 b/source/Private/Get-DscProperty.ps1 new file mode 100644 index 000000000..b77ffa9dd --- /dev/null +++ b/source/Private/Get-DscProperty.ps1 @@ -0,0 +1,143 @@ + +<# + .SYNOPSIS + Returns DSC resource properties that is part of a class-based DSC resource. + + .DESCRIPTION + Returns DSC resource properties that is part of a class-based DSC resource. + The properties can be filtered using name, type, or has been assigned a value. + + .PARAMETER InputObject + The object that contain one or more key properties. + + .PARAMETER Name + Specifies one or more property names to return. If left out all properties + are returned. + + .PARAMETER Type + Specifies one or more property types to return. If left out all property + types are returned. + + .PARAMETER HasValue + Specifies to return only properties that has been assigned a non-null value. + If left out all properties are returned regardless if there is a value + assigned or not. + + .EXAMPLE + Get-DscProperty -InputObject $this + + Returns all DSC resource properties of the DSC resource. + + .EXAMPLE + Get-DscProperty -InputObject $this -Name @('MyProperty1', 'MyProperty2') + + Returns the specified DSC resource properties names of the DSC resource. + + .EXAMPLE + Get-DscProperty -InputObject $this -Type @('Mandatory', 'Optional') + + Returns the specified DSC resource property types of the DSC resource. + + .EXAMPLE + Get-DscProperty -InputObject $this -Type @('Optional') -HasValue + + Returns the specified DSC resource property types of the DSC resource, + but only those properties that has been assigned a non-null value. + + .OUTPUTS + [System.Collections.Hashtable] +#> +function Get-DscProperty +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject, + + [Parameter()] + [System.String[]] + $Name, + + [Parameter()] + [ValidateSet('Key', 'Mandatory', 'NotConfigurable', 'Optional')] + [System.String[]] + $Type, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $HasValue + ) + + $property = $InputObject.PSObject.Properties.Name | + Where-Object -FilterScript { + <# + Return all properties if $Name is not assigned, or if assigned + just those properties. + #> + (-not $Name -or $_ -in $Name) -and + + # Only return the property if it is a DSC property. + $InputObject.GetType().GetMember($_).CustomAttributes.Where( + { + $_.AttributeType.Name -eq 'DscPropertyAttribute' + } + ) + } + + if (-not [System.String]::IsNullOrEmpty($property)) + { + if ($PSBoundParameters.ContainsKey('Type')) + { + $propertiesOfType = @() + + $propertiesOfType += $property | Where-Object -FilterScript { + $InputObject.GetType().GetMember($_).CustomAttributes.Where( + { + <# + To simplify the code, ignoring that this will compare + MemberNAme against type 'Optional' which does not exist. + #> + $_.NamedArguments.MemberName -in $Type + } + ).NamedArguments.TypedValue.Value -eq $true + } + + # Include all optional parameter if it was requested. + if ($Type -contains 'Optional') + { + $propertiesOfType += $property | Where-Object -FilterScript { + $InputObject.GetType().GetMember($_).CustomAttributes.Where( + { + $_.NamedArguments.MemberName -notin @('Key', 'Mandatory', 'NotConfigurable') + } + ) + } + } + + $property = $propertiesOfType + } + } + + # Return a hashtable containing each key property and its value. + $getPropertyResult = @{} + + foreach ($currentProperty in $property) + { + if ($HasValue.IsPresent) + { + $isAssigned = Test-ResourceDscPropertyIsAssigned -Name $currentProperty -InputObject $InputObject + + if (-not $isAssigned) + { + continue + } + } + + $getPropertyResult.$currentProperty = $InputObject.$currentProperty + } + + return $getPropertyResult +} diff --git a/source/Private/Get-LocalizedDataRecursive.ps1 b/source/Private/Get-LocalizedDataRecursive.ps1 new file mode 100644 index 000000000..b32e18aea --- /dev/null +++ b/source/Private/Get-LocalizedDataRecursive.ps1 @@ -0,0 +1,87 @@ +<# + .SYNOPSIS + Get the localization strings data from one or more localization string files. + This can be used in classes to be able to inherit localization strings + from one or more parent (base) classes. + + The order of class names passed to parameter `ClassName` determines the order + of importing localization string files. First entry's localization string file + will be imported first, then next entry's localization string file, and so on. + If the second (or any consecutive) entry's localization string file contain a + localization string key that existed in a previous imported localization string + file that localization string key will be ignored. Making it possible for a + child class to override localization strings from one or more parent (base) + classes. + + .PARAMETER ClassName + An array of class names, normally provided by `Get-ClassName -Recurse`. + + .EXAMPLE + Get-LocalizedDataRecursive -ClassName $InputObject.GetType().FullName + + Returns a hashtable containing all the localized strings for the current + instance. + + .EXAMPLE + Get-LocalizedDataRecursive -ClassName (Get-ClassNamn -InputObject $this -Recurse) + + Returns a hashtable containing all the localized strings for the current + instance and any inherited (parent) classes. + + .OUTPUTS + [System.Collections.Hashtable] +#> +function Get-LocalizedDataRecursive +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.String[]] + $ClassName + ) + + begin + { + $localizedData = @{} + } + + process + { + foreach ($name in $ClassName) + { + if ($name -match '\.psd1') + { + # Assume we got full file name. + $localizationFileName = $name + } + else + { + # Assume we only got class name. + $localizationFileName = '{0}.strings.psd1' -f $name + } + + Write-Debug -Message ('Importing localization data from {0}' -f $localizationFileName) + + # Get localized data for the class + $classLocalizationStrings = Get-LocalizedData -DefaultUICulture 'en-US' -FileName $localizationFileName -ErrorAction 'Stop' + + # Append only previously unspecified keys in the localization data + foreach ($key in $classLocalizationStrings.Keys) + { + if (-not $localizedData.ContainsKey($key)) + { + $localizedData[$key] = $classLocalizationStrings[$key] + } + } + } + } + + end + { + Write-Debug -Message ('Localization data: {0}' -f ($localizedData | ConvertTo-JSON)) + + return $localizedData + } +} diff --git a/source/Private/Test-ResourceDscPropertyIsAssigned.ps1 b/source/Private/Test-ResourceDscPropertyIsAssigned.ps1 new file mode 100644 index 000000000..5be5685df --- /dev/null +++ b/source/Private/Test-ResourceDscPropertyIsAssigned.ps1 @@ -0,0 +1,40 @@ +<# + .SYNOPSIS + Tests whether the class-based resource property is assigned a non-null value. + + .DESCRIPTION + Tests whether the class-based resource property is assigned a non-null value. + + .PARAMETER InputObject + Specifies the object that contain the property. + + .PARAMETER Name + Specifies the name of the property. + + .EXAMPLE + Test-ResourceDscPropertyIsAssigned -InputObject $this -Name 'MyDscProperty' + + Returns $true or $false whether the property is assigned or not. + + .OUTPUTS + [System.Boolean] +#> +function Test-ResourceDscPropertyIsAssigned +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $isAssigned = -not ($null -eq $InputObject.$Name) + + return $isAssigned +} diff --git a/source/Private/Test-ResourceHasDscProperty.ps1 b/source/Private/Test-ResourceHasDscProperty.ps1 new file mode 100644 index 000000000..775582cfb --- /dev/null +++ b/source/Private/Test-ResourceHasDscProperty.ps1 @@ -0,0 +1,62 @@ +<# + .SYNOPSIS + Tests whether the class-based resource has the specified property. + + .DESCRIPTION + Tests whether the class-based resource has the specified property. + + .PARAMETER InputObject + Specifies the object that should be tested for existens of the specified + property. + + .PARAMETER Name + Specifies the name of the property. + + .PARAMETER HasValue + Specifies if the property should be evaluated to have a non-value. If + the property exist but is assigned `$null` the command returns `$false`. + + .EXAMPLE + Test-ResourceHasDscProperty -InputObject $this -Name 'MyDscProperty' + + Returns $true or $false whether the property exist or not. + + .EXAMPLE + Test-ResourceHasDscProperty -InputObject $this -Name 'MyDscProperty' -HasValue + + Returns $true if the property exist and is assigned a non-null value, if not + $false is returned. + + .OUTPUTS + [System.Boolean] +#> +function Test-ResourceHasDscProperty +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject, + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $HasValue + ) + + $hasProperty = $false + + $isDscProperty = (Get-DscProperty @PSBoundParameters).ContainsKey($Name) + + if ($isDscProperty) + { + $hasProperty = $true + } + + return $hasProperty +} diff --git a/source/Public/Connect-SqlDscDatabaseEngine.ps1 b/source/Public/Connect-SqlDscDatabaseEngine.ps1 new file mode 100644 index 000000000..866ff2799 --- /dev/null +++ b/source/Public/Connect-SqlDscDatabaseEngine.ps1 @@ -0,0 +1,84 @@ +<# + .SYNOPSIS + Connect to a SQL Server Database Engine and return the server object. + + .PARAMETER ServerName + String containing the host name of the SQL Server to connect to. + Default value is the current computer name. + + .PARAMETER InstanceName + String containing the SQL Server Database Engine instance to connect to. + Default value is 'MSSQLSERVER'. + + .PARAMETER Credential + The credentials to use to impersonate a user when connecting to the + SQL Server Database Engine instance. If this parameter is left out, then + the current user will be used to connect to the SQL Server Database Engine + instance using Windows Integrated authentication. + + .PARAMETER LoginType + Specifies which type of logon credential should be used. The valid types + are 'WindowsUser' or 'SqlLogin'. Default value is 'WindowsUser' + If set to 'WindowsUser' then the it will impersonate using the Windows + login specified in the parameter Credential. + If set to 'WindowsUser' then the it will impersonate using the native SQL + login specified in the parameter Credential. + + .PARAMETER StatementTimeout + Set the query StatementTimeout in seconds. Default 600 seconds (10 minutes). + + .EXAMPLE + Connect-SqlDscDatabaseEngine + + Connects to the default instance on the local server. + + .EXAMPLE + Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + + Connects to the instance 'MyInstance' on the local server. + + .EXAMPLE + Connect-SqlDscDatabaseEngine -ServerName 'sql.company.local' -InstanceName 'MyInstance' + + Connects to the instance 'MyInstance' on the server 'sql.company.local'. + + .OUTPUTS + None. +#> +function Connect-SqlDscDatabaseEngine +{ + [CmdletBinding(DefaultParameterSetName = 'SqlServer')] + param + ( + [Parameter(ParameterSetName = 'SqlServer')] + [Parameter(ParameterSetName = 'SqlServerWithCredential')] + [ValidateNotNull()] + [System.String] + $ServerName = (Get-ComputerName), + + [Parameter(ParameterSetName = 'SqlServer')] + [Parameter(ParameterSetName = 'SqlServerWithCredential')] + [ValidateNotNull()] + [System.String] + $InstanceName = 'MSSQLSERVER', + + [Parameter(ParameterSetName = 'SqlServerWithCredential', Mandatory = $true)] + [ValidateNotNull()] + [Alias('SetupCredential', 'DatabaseCredential')] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter(ParameterSetName = 'SqlServerWithCredential')] + [ValidateSet('WindowsUser', 'SqlLogin')] + [System.String] + $LoginType = 'WindowsUser', + + [Parameter()] + [ValidateNotNull()] + [System.Int32] + $StatementTimeout = 600 + ) + + # Call the private function. + return (Connect-Sql @PSBoundParameters) +} diff --git a/source/Public/ConvertFrom-SqlDscDatabasePermission.ps1 b/source/Public/ConvertFrom-SqlDscDatabasePermission.ps1 new file mode 100644 index 000000000..ad7b9aa97 --- /dev/null +++ b/source/Public/ConvertFrom-SqlDscDatabasePermission.ps1 @@ -0,0 +1,50 @@ +<# + .SYNOPSIS + Converts a DatabasePermission object into an object of the type + Microsoft.SqlServer.Management.Smo.DatabasePermissionSet. + + .PARAMETER Permission + Specifies a DatabasePermission object. + + .EXAMPLE + [DatabasePermission] @{ + State = 'Grant' + Permission = 'Connect' + } | ConvertFrom-SqlDscDatabasePermission + + Returns an object of `[Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]` + with all the permissions set to $true that was part of the `[DatabasePermission]`. + + .OUTPUTS + [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] +#> +function ConvertFrom-SqlDscDatabasePermission +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when the output type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [CmdletBinding()] + [OutputType([Microsoft.SqlServer.Management.Smo.DatabasePermissionSet])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [DatabasePermission] + $Permission + ) + + begin + { + $permissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + } + + process + { + foreach ($permissionName in $Permission.Permission) + { + $permissionSet.$permissionName = $true + } + } + + end + { + return $permissionSet + } +} diff --git a/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 b/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 new file mode 100644 index 000000000..093b8510c --- /dev/null +++ b/source/Public/ConvertTo-SqlDscDatabasePermission.ps1 @@ -0,0 +1,102 @@ +<# + .SYNOPSIS + Converts a collection of Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo + objects into an array of DatabasePermission objects. + + .PARAMETER DatabasePermissionInfo + Specifies a collection of Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo + objects. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + $databasePermissionInfo = Get-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' + ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $databasePermissionInfo + + Get all permissions for the principal 'MyPrincipal' and converts the permissions + into an array of `[DatabasePermission[]]`. + + .OUTPUTS + [DatabasePermission[]] +#> +function ConvertTo-SqlDscDatabasePermission +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when the output type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [CmdletBinding()] + [OutputType([DatabasePermission[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [AllowEmptyCollection()] + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] + $DatabasePermissionInfo + ) + + begin + { + [DatabasePermission[]] $permissions = @() + } + + process + { + $permissionState = foreach ($currentDatabasePermissionInfo in $DatabasePermissionInfo) + { + # Convert from the type PermissionState to String. + [System.String] $currentDatabasePermissionInfo.PermissionState + } + + $permissionState = $permissionState | Select-Object -Unique + + foreach ($currentPermissionState in $permissionState) + { + $filteredDatabasePermission = $DatabasePermissionInfo | + Where-Object -FilterScript { + $_.PermissionState -eq $currentPermissionState + } + + $databasePermissionStateExist = $permissions.Where({ + $_.State -contains $currentPermissionState + }) | + Select-Object -First 1 + + if ($databasePermissionStateExist) + { + $databasePermission = $databasePermissionStateExist + } + else + { + $databasePermission = [DatabasePermission] @{ + State = $currentPermissionState + Permission = [System.String[]] @() + } + } + + foreach ($currentPermission in $filteredDatabasePermission) + { + # Get the permission names that is set to $true + $permissionProperty = $currentPermission.PermissionType | + Get-Member -MemberType 'Property' | + Select-Object -ExpandProperty 'Name' -Unique | + Where-Object -FilterScript { + $currentPermission.PermissionType.$_ + } + + + foreach ($currentPermissionProperty in $permissionProperty) + { + $databasePermission.Permission += $currentPermissionProperty + } + } + + # Only add the object if it was created. + if (-not $databasePermissionStateExist) + { + $permissions += $databasePermission + } + } + } + + end + { + return $permissions + } +} diff --git a/source/Public/Get-SqlDscDatabasePermission.ps1 b/source/Public/Get-SqlDscDatabasePermission.ps1 new file mode 100644 index 000000000..8f9e3f49d --- /dev/null +++ b/source/Public/Get-SqlDscDatabasePermission.ps1 @@ -0,0 +1,95 @@ +<# + .SYNOPSIS + Returns the current permissions for the database principal. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER DatabaseName + Specifies the database name. + + .PARAMETER Name + Specifies the name of the database principal for which the permissions are + returned. + + .OUTPUTS + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Get-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' + + Get the permissions for the principal 'MyPrincipal'. + + .NOTES + This command excludes fixed roles like _db_datareader_ by default, and will + always return `$null` if a fixed role is specified as **Name**. + + If specifying `-ErrorAction 'SilentlyContinue'` then the command will silently + ignore if the database (parameter **DatabaseName**) is not present or the + database principal is not present. In such case the command will return `$null`. + If specifying `-ErrorAction 'Stop'` the command will throw an error if the + database or database principal is missing. +#> +function Get-SqlDscDatabasePermission +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because the rule does not understands that the command returns [System.String[]] when using , (comma) in the return statement')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] + [CmdletBinding()] + [OutputType([Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $DatabaseName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $getSqlDscDatabasePermissionResult = $null + + $sqlDatabaseObject = $null + + if ($ServerObject.Databases) + { + $sqlDatabaseObject = $ServerObject.Databases[$DatabaseName] + } + + if ($sqlDatabaseObject) + { + $testSqlDscIsDatabasePrincipalParameters = @{ + ServerObject = $ServerObject + DatabaseName = $DatabaseName + Name = $Name + ExcludeFixedRoles = $true + } + + $isDatabasePrincipal = Test-SqlDscIsDatabasePrincipal @testSqlDscIsDatabasePrincipalParameters + + if ($isDatabasePrincipal) + { + $getSqlDscDatabasePermissionResult = $sqlDatabaseObject.EnumDatabasePermissions($Name) + } + else + { + $missingPrincipalMessage = $script:localizedData.DatabasePermission_MissingPrincipal -f $Name, $DatabaseName + + Write-Error -Message $missingPrincipalMessage -Category 'InvalidOperation' -ErrorId 'GSDDP0001' -TargetObject $Name + } + } + else + { + $missingDatabaseMessage = $script:localizedData.DatabasePermission_MissingDatabase -f $DatabaseName + + Write-Error -Message $missingDatabaseMessage -Category 'InvalidOperation' -ErrorId 'GSDDP0002' -TargetObject $DatabaseName + } + + return , [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $getSqlDscDatabasePermissionResult +} diff --git a/source/Public/Set-SqlDscDatabasePermission.ps1 b/source/Public/Set-SqlDscDatabasePermission.ps1 new file mode 100644 index 000000000..99db1ae51 --- /dev/null +++ b/source/Public/Set-SqlDscDatabasePermission.ps1 @@ -0,0 +1,212 @@ +<# + .SYNOPSIS + Returns the current permissions for the database principal. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER DatabaseName + Specifies the database name. + + .PARAMETER Name + Specifies the name of the database principal for which the permissions are + returned. + + .PARAMETER State + Specifies the state of the permission. + + .PARAMETER Permission + Specifies the permissions. + + .PARAMETER WithGrant + Specifies that the principal should also be granted the right to grant + other principals the same permission. This parameter is only valid when + parameter **State** is set to `Grant` or `Revoke`. When the parameter + **State** is set to `Revoke` the right to grant will also be revoked, + and the revocation will cascade. + + .PARAMETER Force + Specifies that the permissions should be set with out any confirmation. + + .OUTPUTS + None. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + + $setPermission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + Update = $true + } + + Set-SqlDscDatabasePermission -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' -State 'Grant' -Permission $setPermission + + Sets the permissions for the principal 'MyPrincipal'. + + .NOTES + This command excludes fixed roles like _db_datareader_ by default, and will + always throw a non-terminating error if a fixed role is specified as **Name**. + + If specifying `-ErrorAction 'SilentlyContinue'` then the command will silently + ignore if the database (parameter **DatabaseName**) is not present or the + database principal is not present. If specifying `-ErrorAction 'Stop'` the + command will throw an error if the database or database principal is missing. +#> +function Set-SqlDscDatabasePermission +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidThrowOutsideOfTry', '', Justification = 'Because the code throws based on an prior expression')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [OutputType()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $DatabaseName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ValidateSet('Grant', 'Deny', 'Revoke')] + [System.String] + $State, + + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] + $Permission, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $WithGrant, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + if ($State -eq 'Deny' -and $WithGrant.IsPresent) + { + Write-Warning -Message $script:localizedData.DatabasePermission_IgnoreWithGrantForStateDeny + } + + if ($Force.IsPresent) + { + $ConfirmPreference = 'None' + } + + $sqlDatabaseObject = $null + + if ($ServerObject.Databases) + { + $sqlDatabaseObject = $ServerObject.Databases[$DatabaseName] + } + + if ($sqlDatabaseObject) + { + $testSqlDscIsDatabasePrincipalParameters = @{ + ServerObject = $ServerObject + DatabaseName = $DatabaseName + Name = $Name + ExcludeFixedRoles = $true + } + + $isDatabasePrincipal = Test-SqlDscIsDatabasePrincipal @testSqlDscIsDatabasePrincipalParameters + + if ($isDatabasePrincipal) + { + # Get the permissions names that are set to $true in the DatabasePermissionSet. + $permissionName = $Permission | + Get-Member -MemberType 'Property' | + Select-Object -ExpandProperty 'Name' | + Where-Object -FilterScript { + $Permission.$_ + } + + $changePermissionShouldProcessVerboseDescriptionMessage = $script:localizedData.DatabasePermission_ChangePermissionShouldProcessVerboseDescription -f $Name, $DatabaseName, $ServerObject.InstanceName + $changePermissionShouldProcessVerboseWarningMessage = $script:localizedData.DatabasePermission_ChangePermissionShouldProcessVerboseWarning -f $Name + $changePermissionShouldProcessCaptionMessage = $script:localizedData.DatabasePermission_ChangePermissionShouldProcessCaption + + if (-not $PSCmdlet.ShouldProcess($changePermissionShouldProcessVerboseDescriptionMessage, $changePermissionShouldProcessVerboseWarningMessage, $changePermissionShouldProcessCaptionMessage)) + { + # Return without doing anything if the user did not want to continue processing. + return + } + + switch ($State) + { + 'Grant' + { + Write-Verbose -Message ( + $script:localizedData.DatabasePermission_GrantPermission -f ($permissionName -join ','), $Name + ) + + if ($WithGrant.IsPresent) + { + $sqlDatabaseObject.Grant($Permission, $Name, $true) + } + else + { + $sqlDatabaseObject.Grant($Permission, $Name) + } + } + + 'Deny' + { + Write-Verbose -Message ( + $script:localizedData.DatabasePermission_DenyPermission -f ($permissionName -join ','), $Name + ) + + $sqlDatabaseObject.Deny($Permission, $Name) + } + + 'Revoke' + { + Write-Verbose -Message ( + $script:localizedData.DatabasePermission_RevokePermission -f ($permissionName -join ','), $Name + ) + + if ($WithGrant.IsPresent) + { + $sqlDatabaseObject.Revoke($Permission, $Name, $false, $true) + } + else + { + $sqlDatabaseObject.Revoke($Permission, $Name) + } + } + } + } + else + { + $missingPrincipalMessage = $script:localizedData.DatabasePermission_MissingPrincipal -f $Name, $DatabaseName + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $missingPrincipalMessage, + 'GSDDP0001', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $Name + ) + ) + } + } + else + { + $missingDatabaseMessage = $script:localizedData.DatabasePermission_MissingDatabase -f $DatabaseName + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + $missingDatabaseMessage, + 'GSDDP0002', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $DatabaseName + ) + ) + } +} diff --git a/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 b/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 new file mode 100644 index 000000000..bba2d89a7 --- /dev/null +++ b/source/Public/Test-SqlDscIsDatabasePrincipal.ps1 @@ -0,0 +1,144 @@ +<# + .SYNOPSIS + Returns whether the database principal exist. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER DatabaseName + Specifies the SQL database name. + + .PARAMETER Name + Specifies the name of the database principal. + + .PARAMETER ExcludeUsers + Specifies that database users should not be evaluated. + + .PARAMETER ExcludeRoles + Specifies that database roles should not be evaluated for the specified + name. This will also exclude fixed roles. + + .PARAMETER ExcludeFixedRoles + Specifies that fixed roles should not be evaluated for the specified name. + + .PARAMETER ExcludeApplicationRoles + Specifies that fixed application roles should not be evaluated for the + specified name. + + .OUTPUTS + [System.Boolean] + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Test-SqlDscIsDatabasePrincipal -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' + + Returns $true if the principal exist in the database, if not $false is returned. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Test-SqlDscIsDatabasePrincipal -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' -ExcludeUsers + + Returns $true if the principal exist in the database and is not a user, if not $false is returned. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Test-SqlDscIsDatabasePrincipal -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' -ExcludeRoles + + Returns $true if the principal exist in the database and is not a role, if not $false is returned. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Test-SqlDscIsDatabasePrincipal -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' -ExcludeFixedRoles + + Returns $true if the principal exist in the database and is not a fixed role, if not $false is returned. + + .EXAMPLE + $serverInstance = Connect-SqlDscDatabaseEngine + Test-SqlDscIsDatabasePrincipal -ServerObject $serverInstance -DatabaseName 'MyDatabase' -Name 'MyPrincipal' -ExcludeApplicationRoles + + Returns $true if the principal exist in the database and is not a application role, if not $false is returned. +#> +function Test-SqlDscIsDatabasePrincipal +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when a parameter type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(Mandatory = $true)] + [System.String] + $DatabaseName, + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ExcludeUsers, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ExcludeRoles, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ExcludeFixedRoles, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ExcludeApplicationRoles + ) + + $principalExist = $false + + $sqlDatabaseObject = $ServerObject.Databases[$DatabaseName] + + if (-not $sqlDatabaseObject) + { + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + ($script:localizedData.IsDatabasePrincipal_DatabaseMissing -f $DatabaseName), + 'TSDISO0001', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $DatabaseName + ) + ) + } + + if (-not $ExcludeUsers.IsPresent -and $sqlDatabaseObject.Users[$Name]) + { + $principalExist = $true + } + + if (-not $ExcludeRoles.IsPresent) + { + $userDefinedRole = if ($ExcludeFixedRoles.IsPresent) + { + # Skip fixed roles like db_datareader. + $sqlDatabaseObject.Roles | Where-Object -FilterScript { + -not $_.IsFixedRole -and $_.Name -eq $Name + } + } + else + { + $sqlDatabaseObject.Roles[$Name] + } + + if ($userDefinedRole) + { + $principalExist = $true + } + } + + if (-not $ExcludeApplicationRoles.IsPresent -and $sqlDatabaseObject.ApplicationRoles[$Name]) + { + $principalExist = $true + } + + return $principalExist +} diff --git a/source/SqlServerDsc.psd1 b/source/SqlServerDsc.psd1 index bf33384fb..20f77d19d 100644 --- a/source/SqlServerDsc.psd1 +++ b/source/SqlServerDsc.psd1 @@ -1,46 +1,49 @@ @{ + # Script module or binary module file associated with this manifest. + RootModule = 'SqlServerDsc.psm1' + # Version number of this module. - moduleVersion = '0.0.1' + ModuleVersion = '0.0.1' # ID used to uniquely identify this module - GUID = '693ee082-ed36-45a7-b490-88b07c86b42f' + GUID = '693ee082-ed36-45a7-b490-88b07c86b42f' # Author of this module - Author = 'DSC Community' + Author = 'DSC Community' # Company or vendor of this module - CompanyName = 'DSC Community' + CompanyName = 'DSC Community' # Copyright statement for this module - Copyright = 'Copyright the DSC Community contributors. All rights reserved.' + Copyright = 'Copyright the DSC Community contributors. All rights reserved.' # Description of the functionality provided by this module - Description = 'Module with DSC resources for deployment and configuration of Microsoft SQL Server.' + Description = 'Module with DSC resources for deployment and configuration of Microsoft SQL Server.' # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '5.0' + PowerShellVersion = '5.0' # Minimum version of the common language runtime (CLR) required by this module - CLRVersion = '4.0' + CLRVersion = '4.0' # Functions to export from this module - FunctionsToExport = @() + FunctionsToExport = @() # Cmdlets to export from this module - CmdletsToExport = @() + CmdletsToExport = @() # Variables to export from this module - VariablesToExport = @() + VariablesToExport = @() # Aliases to export from this module - AliasesToExport = @() + AliasesToExport = @() DscResourcesToExport = @() - RequiredAssemblies = @() + RequiredAssemblies = @() # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. - PrivateData = @{ + PrivateData = @{ PSData = @{ # Set to a prerelease string value if the release should be a prerelease. diff --git a/source/en-US/ResourceBase.strings.psd1 b/source/en-US/ResourceBase.strings.psd1 new file mode 100644 index 000000000..05f4ff47a --- /dev/null +++ b/source/en-US/ResourceBase.strings.psd1 @@ -0,0 +1,17 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + class ResourceBase. +#> + +ConvertFrom-StringData @' + GetCurrentState = Getting the current state for resource '{0}' using the key property '{1}'. (RB0001) + TestDesiredState = Determining the current state for resource '{0}' using the key property '{1}'. (RB0002) + SetDesiredState = Setting the desired state for resource '{0}' using the key property '{1}'. (RB0003) + NotInDesiredState = The current state is not the desired state. (RB0004) + InDesiredState = The current state is the desired state. (RB0005) + SetProperty = The property '{0}' will be set to '{1}'. (RB0006) + NoPropertiesToSet = All properties are in desired state. (RB0007) + ModifyMethodNotImplemented = An override for the method Modify() is not implemented in the resource. (RB0008) + GetCurrentStateMethodNotImplemented = An override for the method GetCurrentState() is not implemented in the resource. (RB0009) +'@ diff --git a/source/en-US/SqlDatabasePermission.strings.psd1 b/source/en-US/SqlDatabasePermission.strings.psd1 new file mode 100644 index 000000000..f2cb010d5 --- /dev/null +++ b/source/en-US/SqlDatabasePermission.strings.psd1 @@ -0,0 +1,23 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource SqlDatabasePermission. +#> + +ConvertFrom-StringData @' + # Strings overrides for the ResourceBase's default strings. + # None + + # Strings directly used by the derived class SqlDatabasePermission. + EvaluateDatabasePermissionForPrincipal = Evaluate the current permissions for the user '{0}' in the database '{1}' on the instance '{2}'. (SDP0001) + NameIsMissing = The name '{0}' is neither a database user, database role (user-defined), or database application role in the database '{1}', or the database '{1}' does not exist on the instance '{2}'. (SDP0004) + DesiredAbsentPermissionArePresent = The desired permission '{0}' that shall be absent are present. (SDP0003) + DesiredPermissionAreAbsent = The desired permission '{0}' that shall be present are absent. (SDP0003) + FailedToRevokePermissionFromCurrentState = Failed to revoke the permissions from the current state for the user '{0}' in the database '{1}'. (SDP0007) + FailedToSetPermission = Failed to set the desired permissions for the user '{0}' in the database '{1}'. (SDP0007) + DuplicatePermissionState = One or more permission states was added more than once. It is only allowed to specify one of each permission state. (SDP0009) + MissingPermissionState = One or more permission states was missing. One of each permission state must be provided. (SDP0009) + MustAssignOnePermissionProperty = At least one of the properties 'Permission', 'PermissionToInclude', or 'PermissionToExclude' must be specified. (SDP0010) + DuplicatePermissionBetweenState = One or more permission state specifies the same permission. It is only allowed to specify a specific permission in one permission state. (SDP0011) + MustHaveMinimumOnePermissionInState = At least one state does not specify a permission in the property '{0}'. +'@ diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 new file mode 100644 index 000000000..236d903e1 --- /dev/null +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -0,0 +1,28 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource DnsServerDsc module. This file should only contain + localized strings for private and public functions. +#> + +ConvertFrom-StringData @' + # Get-SqlDscDatabasePermission, Set-SqlDscDatabasePermission + DatabasePermission_MissingPrincipal = The database principal '{0}' is neither a user, database role (user-defined), or database application role in the database '{1}'. + DatabasePermission_MissingDatabase = The database '{0}' cannot be found. + + # Set-SqlDscDatabasePermission + DatabasePermission_GrantPermission = Grant the permissions '{0}' for the principal '{1}'. + DatabasePermission_DenyPermission = Deny the permissions '{0}' for the principal '{1}'. + DatabasePermission_RevokePermission = Revoke the permissions '{0}' for the principal '{1}'. + DatabasePermission_IgnoreWithGrantForStateDeny = The parameter WithGrant cannot be used together with the state Deny, the parameter WithGrant is ignored. + DatabasePermission_ChangePermissionShouldProcessVerboseDescription = Changing the permission for the principal '{0}' in the database '{1}' on the instance '{2}'. + DatabasePermission_ChangePermissionShouldProcessVerboseWarning = Are you sure you want you change the permission for the principal '{0}'? + ## This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + DatabasePermission_ChangePermissionShouldProcessCaption = Change permission on principal + + # Test-SqlDscIsDatabasePrincipal + IsDatabasePrincipal_DatabaseMissing = The database '{0}' cannot be found. + + # Class DatabasePermission + InvalidTypeForCompare = Invalid type in comparison. Expected type [{0}], but the type was [{1}]. (DP0001) +'@ diff --git a/source/prefix.ps1 b/source/prefix.ps1 new file mode 100644 index 000000000..34b428632 --- /dev/null +++ b/source/prefix.ps1 @@ -0,0 +1,8 @@ +$script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/DscResource.Common' +Import-Module -Name $script:dscResourceCommonModulePath + +# TODO: The goal would be to remove this, when no classes and public or private functions need it. +$script:sqlServerDscCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/SqlServerDsc.Common' +Import-Module -Name $script:sqlServerDscCommonModulePath + +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' diff --git a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 index 021502e03..66454bdef 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1 @@ -1,3 +1,7 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'because ConvertTo-SecureString is used to simplify the tests.')] +param () + BeforeDiscovery { try { @@ -87,14 +91,19 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name - $resourceCurrentState.PermissionState | Should -Be 'Grant' - 'Select' | Should -BeIn $resourceCurrentState.Permissions - 'CreateTable' | Should -BeIn $resourceCurrentState.Permissions + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 3 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Select' + $grantState.Permission | Should -Contain 'CreateTable' } It 'Should return $true when Test-DscConfiguration is run' { @@ -148,14 +157,19 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Absent' $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name - $resourceCurrentState.PermissionState | Should -Be 'Grant' - 'Select' | Should -Not -BeIn $resourceCurrentState.Permissions - 'CreateTable' | Should -Not -BeIn $resourceCurrentState.Permissions + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Not -Contain 'Select' + $grantState.Permission | Should -Not -Contain 'CreateTable' } It 'Should return $true when Test-DscConfiguration is run' { @@ -209,14 +223,24 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name - $resourceCurrentState.PermissionState | Should -Be 'Deny' - 'Select' | Should -BeIn $resourceCurrentState.Permissions - 'CreateTable' | Should -BeIn $resourceCurrentState.Permissions + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 2 + $denyState.Permission | Should -Contain 'Select' + $denyState.Permission | Should -Contain 'CreateTable' } It 'Should return $true when Test-DscConfiguration is run' { @@ -270,14 +294,28 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Absent' $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name - $resourceCurrentState.PermissionState | Should -Be 'Deny' - 'Select' | Should -Not -BeIn $resourceCurrentState.Permissions - 'CreateTable' | Should -Not -BeIn $resourceCurrentState.Permissions + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState.State | Should -Be 'Deny' + <# + Using '-HaveCount 0' does not work as it returns the error + 'Expected an empty collection, but got collection with size 1 @($null).' + even though the array is empty (does not contain oen item that is $null). + Probably due to issue: https://github.com/pester/Pester/issues/1000 + #> + $denyState.Permission | Should -BeNullOrEmpty } It 'Should return $true when Test-DscConfiguration is run' { @@ -331,13 +369,18 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Present' $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be 'guest' - $resourceCurrentState.PermissionState | Should -Be 'Grant' - 'Select' | Should -BeIn $resourceCurrentState.Permissions + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 2 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Select' } It 'Should return $true when Test-DscConfiguration is run' { @@ -391,17 +434,1335 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', -and $_.ResourceId -eq $resourceId } - $resourceCurrentState.Ensure | Should -Be 'Absent' $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName $resourceCurrentState.Name | Should -Be 'guest' - $resourceCurrentState.PermissionState | Should -Be 'Grant' - 'Select' | Should -Not -BeIn $resourceCurrentState.Permissions + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Not -Contain 'Select' + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_GrantPublic_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + 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.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be 'public' + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 2 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Select' } It 'Should return $true when Test-DscConfiguration is run' { Test-DscConfiguration -Verbose | Should -Be 'True' } } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_RemoveGrantPublic_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + 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.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be 'public' + $resourceCurrentState.Permission | Should -HaveCount 3 + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Not -Contain 'Select' + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + <# + These tests assumes that only permission left for test user 'User1' is + a grant for permission 'Connect'. + #> + Context 'When using Invoke-DscResource' { + BeforeAll { + <# + Clear any configuration that was applied by a previous test so that + the configuration is not enforced by LCM while the tests using + Invoke-DscResource are run. + #> + Clear-DscLcmConfiguration + + $mockDefaultInvokeDscResourceParameters = @{ + ModuleName = $script:dscModuleName + Name = $script:dscResourceFriendlyName + Verbose = $true + } + + $mockSqlCredential = [System.Management.Automation.PSCredential]::new( + $ConfigurationData.AllNodes.UserName, + ($ConfigurationData.AllNodes.Password | ConvertTo-SecureString -AsPlainText -Force) + ) + + $mockDefaultInvokeDscResourceProperty = @{ + ServerName = $ConfigurationData.AllNodes.ServerName + InstanceName = $ConfigurationData.AllNodes.InstanceName + DatabaseName = $ConfigurationData.AllNodes.DatabaseName + Name = $ConfigurationData.AllNodes.User1_Name + Credential = $mockSqlCredential + } + + $mockDefaultNewCimInstanceParameters = @{ + ClientOnly = $true + Namespace = 'root/Microsoft/Windows/DesiredStateConfiguration' + ClassName = 'DatabasePermission' + } + } + + AfterEach { + Wait-ForIdleLcm + } + + <# + The tests will leave Connect as granted permission. + + Test 1: Assigning the permissions Connect, Update, and Alter for the state + Grant. + Testing: Adding permission for state Grant, and testing to handle + the permission Connect that already exist from previous tests. + + Test 2: Assigning permission Delete and Update for the state Deny, and + permission Connect for the state Grant. + Testing: Adding permission for state Deny. From previous test + the permission Alter will be revoked, and the permission Update + will be moved from Grant to Deny. + + Test 3: Assigning permission Select for the state GrantWithGrant, and + permission Connect for the state Grant. + Testing: Adding permission for state GrantWithGrant. From previous + test the permission Delete and Update will be revoked. + + Test 4: Assigning permission Connect for the state Grant. + Testing: From previous test the permission Select will be revoked. + #> + Context 'When assigning parameter Permission' { + <# + Test 1: Assigning the permissions Connect, Update, and Alter for the state + Grant. + Testing: Adding permission for state Grant, and testing to handle + the permission Connect that already exist from previous tests. + #> + Context 'When only specifying permissions for state Grant' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect', + 'update', + 'alter' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = [System.String[]] @() + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = [System.String[]] @() + }) + ) + } + + Context 'When the system is not in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect","update","alter"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["Connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + } + + It 'Should run method Test() and return the state as $false' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeFalse + } + + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + + Context 'When the system is in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 3 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + $grantState.Permission | Should -Contain 'Alter' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + $resourceCurrentState.Reasons | Should -BeNullOrEmpty + } + + It 'Should run method Test() and return the state as $true' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeTrue + } + + <# + This test is meant to validate that method Set() also evaluates + the current state against the desired state, and if they match + the Set() method returns without calling Set-SqlDscDatabasePermission + to change permissions. + + It is not possible to validate that Set-SqlDscDatabasePermission + is not call since it is not possible to mock the command in + the session when LCM runs (which Invoke-DscResource invokes). + There are no other indications that can be caught to validate + this, unless looking for the verbose output that says that + all properties are in desired state. + #> + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + } + + <# + Test 2: Assigning permission Delete and Update for the state Deny, and + permission Connect for the state Grant. + Testing: Adding permission for state Deny. From previous test + the permission Alter will be revoked, and the permission Update + will be moved from Grant to Deny. + #> + Context 'When only specifying permissions for state Deny' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = [System.String[]] @() + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = @( + 'delete', + 'update' + ) + }) + ) + } + + Context 'When the system is not in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 3 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + $grantState.Permission | Should -Contain 'Alter' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":["delete","update"]}], but was [{"State":"Grant","Permission":["alter","connect","update"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + } + + It 'Should run method Test() and return the state as $false' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeFalse + } + + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + + Context 'When the system is in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 2 + $denyState.Permission | Should -Contain 'Delete' + $denyState.Permission | Should -Contain 'Update' + + $resourceCurrentState.Reasons | Should -BeNullOrEmpty + } + + It 'Should run method Test() and return the state as $true' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeTrue + } + + <# + This test is meant to validate that method Set() also evaluates + the current state against the desired state, and if they match + the Set() method returns without calling Set-SqlDscDatabasePermission + to change permissions. + + It is not possible to validate that Set-SqlDscDatabasePermission + is not call since it is not possible to mock the command in + the session when LCM runs (which Invoke-DscResource invokes). + There are no other indications that can be caught to validate + this, unless looking for the verbose output that says that + all properties are in desired state. + #> + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + } + + <# + Test 3: Assigning permission Select for the state GrantWithGrant, and + permission Connect for the state Grant. + Testing: Adding permission for state GrantWithGrant. From previous + test the permission Delete and Update will be revoked. + #> + Context 'When only specifying permissions for state GrantWithGrant' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = @( + 'select' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = [System.String[]] @() + }) + ) + } + + Context 'When the system is not in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 2 + $denyState.Permission | Should -Contain 'Delete' + $denyState.Permission | Should -Contain 'Update' + + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]},{"State":"Deny","Permission":["delete","update"]},{"State":"GrantWithGrant","Permission":[]}]' + } + + It 'Should run method Test() and return the state as $false' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeFalse + } + + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + + Context 'When the system is in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -HaveCount 1 + $grantWithGrantState.Permission | Should -Contain 'Select' + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + $resourceCurrentState.Reasons | Should -BeNullOrEmpty + } + + It 'Should run method Test() and return the state as $true' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeTrue + } + + <# + This test is meant to validate that method Set() also evaluates + the current state against the desired state, and if they match + the Set() method returns without calling Set-SqlDscDatabasePermission + to change permissions. + + It is not possible to validate that Set-SqlDscDatabasePermission + is not call since it is not possible to mock the command in + the session when LCM runs (which Invoke-DscResource invokes). + There are no other indications that can be caught to validate + this, unless looking for the verbose output that says that + all properties are in desired state. + #> + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + } + + <# + Test 4: Assigning permission Connect for the state Grant. + Testing: From previous test the permission Select will be revoked. + #> + Context 'When only specifying permissions for state Grant to revoke permission in state GrantWithGrant' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.Permission = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'connect' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = [System.String[]] @() + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = [System.String[]] @() + }) + ) + } + + Context 'When the system is not in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -HaveCount 1 + $grantWithGrantState.Permission | Should -Contain 'Select' + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["connect"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":[]}]' + } + + It 'Should run method Test() and return the state as $false' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeFalse + } + + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + + Context 'When the system is in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + $resourceCurrentState.Reasons | Should -BeNullOrEmpty + } + + It 'Should run method Test() and return the state as $true' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeTrue + } + + <# + This test is meant to validate that method Set() also evaluates + the current state against the desired state, and if they match + the Set() method returns without calling Set-SqlDscDatabasePermission + to change permissions. + + It is not possible to validate that Set-SqlDscDatabasePermission + is not call since it is not possible to mock the command in + the session when LCM runs (which Invoke-DscResource invokes). + There are no other indications that can be caught to validate + this, unless looking for the verbose output that says that + all properties are in desired state. + #> + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + } + } + + <# + The tests for PermissionToExclude after this Context-block is dependent + on that this test leaves the expected permission. If these tests for + PermissionToInclude is changed, make sure the tests for PermissionToExclude + is updated as necessary as well. + + Test 1: Assigning the permission Update to state Grant, and the + permission Select to the state GrantWithGrant, and the + permission Delete to the state Deny. + Testing: Adding permission to all states, and testing to handle + the permission Connect that already exist from previous tests. + #> + Context 'When assigning parameter PermissionToInclude' { + # Test 1 + Context 'When only specifying permissions for state Grant' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.PermissionToInclude = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'update' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = @( + 'select' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = @( + 'delete' + ) + }) + ) + } + + Context 'When the system is not in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + # Property Permission + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + # Property PermissionToInclude + $grantState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -BeNullOrEmpty + + $grantWithGrantState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + # Property Reasons + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:PermissionToInclude' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property PermissionToInclude should be [{"State":"Grant","Permission":["update"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":["delete"]}], but was [{"State":"Grant","Permission":[]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + } + + It 'Should run method Test() and return the state as $false' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeFalse + } + + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + + Context 'When the system is in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToExclude | Should -BeNullOrEmpty + + # Property Permission + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 2 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -HaveCount 1 + $grantWithGrantState.Permission | Should -Contain 'Select' + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 1 + $denyState.Permission | Should -Contain 'Delete' + + # Property PermissionToInclude + $grantState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -HaveCount 1 + $grantWithGrantState.Permission | Should -Contain 'Select' + + $denyState = $resourceCurrentState.PermissionToInclude.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 1 + $denyState.Permission | Should -Contain 'Delete' + + # Property Reasons + $resourceCurrentState.Reasons | Should -BeNullOrEmpty + } + + It 'Should run method Test() and return the state as $true' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeTrue + } + + <# + This test is meant to validate that method Set() also evaluates + the current state against the desired state, and if they match + the Set() method returns without calling Set-SqlDscDatabasePermission + to change permissions. + + It is not possible to validate that Set-SqlDscDatabasePermission + is not call since it is not possible to mock the command in + the session when LCM runs (which Invoke-DscResource invokes). + There are no other indications that can be caught to validate + this, unless looking for the verbose output that says that + all properties are in desired state. + #> + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + } + } + + <# + The tests will leave Connect as granted permission for the principal. + + Test 1: Revoking the permission Update for state Grant, and the + permission Select for the state GrantWithGrant, and the + permission Delete for the state Deny. + Testing: Revoking permission to all states, and testing that + the permission Connect is left in state Grant. + #> + Context 'When assigning parameter PermissionToExclude' { + # Test 1 + Context 'When only specifying permissions for state Grant' { + BeforeAll { + $mockInvokeDscResourceProperty = $mockDefaultInvokeDscResourceProperty.Clone() + + $mockInvokeDscResourceProperty.PermissionToExclude = [Microsoft.Management.Infrastructure.CimInstance[]] @( + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Grant' + Permission = @( + 'update' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'GrantWithGrant' + Permission = @( + 'select' + ) + }) + (New-CimInstance @mockDefaultNewCimInstanceParameters -Property @{ + State = 'Deny' + Permission = @( + 'delete' + ) + }) + ) + } + + Context 'When the system is not in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + + # Property Permission + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 2 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -HaveCount 1 + $grantWithGrantState.Permission | Should -Contain 'Select' + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 1 + $denyState.Permission | Should -Contain 'Delete' + + # Property PermissionToExclude + $grantState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -BeNullOrEmpty + + $grantWithGrantState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + # Property Reasons + $resourceCurrentState.Reasons | Should -HaveCount 1 + $resourceCurrentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:PermissionToExclude' + $resourceCurrentState.Reasons[0].Phrase | Should -Be 'The property PermissionToExclude should be [{"State":"Grant","Permission":["update"]},{"State":"GrantWithGrant","Permission":["select"]},{"State":"Deny","Permission":["delete"]}], but was [{"State":"Grant","Permission":[]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + } + + It 'Should run method Test() and return the state as $false' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeFalse + } + + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + + Context 'When the system is in the desired state' { + It 'Should run method Get() and return the correct values' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Get' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.ServerName | Should -Be $ConfigurationData.AllNodes.ServerName + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.DatabaseName | Should -Be $ConfigurationData.AllNodes.DatabaseName + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.User1_Name + $resourceCurrentState.Permission | Should -HaveCount 3 + $resourceCurrentState.PermissionToInclude | Should -BeNullOrEmpty + + # Property Permission + $grantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Connect' + + $grantWithGrantState = $resourceCurrentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $resourceCurrentState.Permission.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + + # Property PermissionToExclude + $grantState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'Grant' }) + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 1 + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'GrantWithGrant' }) + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -HaveCount 1 + $grantWithGrantState.Permission | Should -Contain 'Select' + + $denyState = $resourceCurrentState.PermissionToExclude.Where({ $_.State -eq 'Deny' }) + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -HaveCount 1 + $denyState.Permission | Should -Contain 'Delete' + + # Property Reasons + $resourceCurrentState.Reasons | Should -BeNullOrEmpty + } + + It 'Should run method Test() and return the state as $true' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Test' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.InDesiredState | Should -BeTrue + } + + <# + This test is meant to validate that method Set() also evaluates + the current state against the desired state, and if they match + the Set() method returns without calling Set-SqlDscDatabasePermission + to change permissions. + + It is not possible to validate that Set-SqlDscDatabasePermission + is not call since it is not possible to mock the command in + the session when LCM runs (which Invoke-DscResource invokes). + There are no other indications that can be caught to validate + this, unless looking for the verbose output that says that + all properties are in desired state. + #> + It 'Should run method Set() without throwing and not require reboot' { + { + $mockInvokeDscResourceParameters = $mockDefaultInvokeDscResourceParameters.Clone() + + $mockInvokeDscResourceParameters.Method = 'Set' + $mockInvokeDscResourceParameters.Property = $mockInvokeDscResourceProperty + + $script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters + } | Should -Not -Throw + + $resourceCurrentState.RebootRequired | Should -BeFalse + } + } + } + } + } } diff --git a/tests/Integration/DSC_SqlDatabasePermission.config.ps1 b/tests/Integration/DSC_SqlDatabasePermission.config.ps1 index 0bde9e020..e9285c876 100644 --- a/tests/Integration/DSC_SqlDatabasePermission.config.ps1 +++ b/tests/Integration/DSC_SqlDatabasePermission.config.ps1 @@ -1,7 +1,13 @@ -#region HEADER -# Integration Test Config Template Version: 1.2.0 -#endregion +<# + .NOTES + There are integration tests in the file DSC_SqlDatabasePermission.Integration.Tests.ps1 + that is using the command Invoke-DscResource to run tests. Those test does + not have a configuration in this file, but do use the $ConfigurationData. + The tests using the command Invoke-DscResource assumes that only permission + left for test user 'User1' after running the configurations in this file is + a grant for permission 'Connect'. +#> $configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') if (Test-Path -Path $configFile) { @@ -19,7 +25,12 @@ else NodeName = 'localhost' CertificateFile = $env:DscPublicCertificatePath - UserName = "$env:COMPUTERNAME\SqlAdmin" + <# + This must be either the UPN username (e.g. username@domain.local) + or the user name without the NetBIOS name (e.g. username). Using + the NetBIOS name (e.g. DOMAIN\username) will not work. + #> + UserName = 'SqlAdmin' Password = 'P@ssw0rd1' ServerName = $env:COMPUTERNAME @@ -47,19 +58,33 @@ Configuration DSC_SqlDatabasePermission_Grant_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Present' - Name = $Node.User1_Name - DatabaseName = $Node.DatabaseName - PermissionState = 'Grant' - Permissions = @( - 'Select' - 'CreateTable' - ) - ServerName = $Node.ServerName InstanceName = $Node.InstanceName + DatabaseName = $Node.DatabaseName + Name = $Node.User1_Name + Permission = @( + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + 'Select' + 'CreateTable' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Deny' + Permission = @() + } + ) - PsDscRunAsCredential = New-Object ` + Credential = New-Object ` -TypeName System.Management.Automation.PSCredential ` -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } @@ -78,21 +103,28 @@ Configuration DSC_SqlDatabasePermission_RemoveGrant_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Absent' - Name = $Node.User1_Name + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Grant' - Permissions = @( - 'Select' - 'CreateTable' + Name = $Node.User1_Name + Permission = @( + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Deny' + Permission = @() + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } @@ -109,21 +141,31 @@ Configuration DSC_SqlDatabasePermission_Deny_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Present' - Name = $Node.User1_Name + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Deny' - Permissions = @( - 'Select' - 'CreateTable' + Name = $Node.User1_Name + Permission = @( + DatabasePermission + { + State = 'Deny' + Permission = @( + 'Select' + 'CreateTable' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + ) + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } @@ -140,21 +182,28 @@ Configuration DSC_SqlDatabasePermission_RemoveDeny_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Absent' - Name = $Node.User1_Name + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Deny' - Permissions = @( - 'Select' - 'CreateTable' + Name = $Node.User1_Name + Permission = @( + DatabasePermission + { + State = 'Deny' + Permission = @() + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + ) + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } @@ -174,20 +223,33 @@ Configuration DSC_SqlDatabasePermission_GrantGuest_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Present' - Name = 'guest' + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Grant' - Permissions = @( - 'Select' + Name = 'guest' + Permission = @( + <# + These are in the order Deny, Grant, and GrantWithGrant on purpose, + to verify the objects are sorted correctly by Compare(). + #> + DatabasePermission + { + State = 'Deny' + Permission = @() + } + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + 'Select' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } @@ -207,20 +269,32 @@ Configuration DSC_SqlDatabasePermission_RemoveGrantGuest_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Absent' - Name = 'guest' + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Grant' - Permissions = @( - 'Select' + Name = 'guest' + Permission = @( + <# + These are in the order Deny, Grant, and GrantWithGrant on purpose, + to verify the objects are sorted correctly by Compare(). + #> + DatabasePermission + { + State = 'Deny' + Permission = @() + } + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } @@ -240,20 +314,33 @@ Configuration DSC_SqlDatabasePermission_GrantPublic_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Present' - Name = 'public' + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Grant' - Permissions = @( - 'Select' + Name = 'public' + Permission = @( + <# + These are in the order Deny, Grant, and GrantWithGrant on purpose, + to verify the objects are sorted correctly by Compare(). + #> + DatabasePermission + { + State = 'Deny' + Permission = @() + } + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + 'Select' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } @@ -273,20 +360,32 @@ Configuration DSC_SqlDatabasePermission_RemoveGrantPublic_Config { SqlDatabasePermission 'Integration_Test' { - Ensure = 'Absent' - Name = 'public' + InstanceName = $Node.InstanceName DatabaseName = $Node.DatabaseName - PermissionState = 'Grant' - Permissions = @( - 'Select' + Name = 'public' + Permission = @( + <# + These are in the order Deny, Grant, and GrantWithGrant on purpose, + to verify the objects are sorted correctly by Compare(). + #> + DatabasePermission + { + State = 'Deny' + Permission = @() + } + DatabasePermission + { + State = 'Grant' + Permission = @( + 'Connect' + ) + } + DatabasePermission + { + State = 'GrantWithGrant' + Permission = @() + } ) - - ServerName = $Node.ServerName - InstanceName = $Node.InstanceName - - PsDscRunAsCredential = New-Object ` - -TypeName System.Management.Automation.PSCredential ` - -ArgumentList @($Node.UserName, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) } } } diff --git a/tests/Integration/DSC_SqlSetup.config.ps1 b/tests/Integration/DSC_SqlSetup.config.ps1 index cc896ba8f..ea1849abc 100644 --- a/tests/Integration/DSC_SqlSetup.config.ps1 +++ b/tests/Integration/DSC_SqlSetup.config.ps1 @@ -275,7 +275,7 @@ Configuration DSC_SqlSetup_CreateDependencies_Config This module might already be installed on the build worker. This is needed to install SQL Server Analysis Services instances. - Thre SqlServer module is purposely not added to 'RequiredModule.psd1' so + The SqlServer module is purposely not added to 'RequiredModule.psd1' so that it does not conflict with the SqlServerStubs module that is used by unit tests. #> diff --git a/tests/Integration/README.md b/tests/Integration/README.md index 611053925..8c53c3bbb 100644 --- a/tests/Integration/README.md +++ b/tests/Integration/README.md @@ -377,7 +377,12 @@ AsymmetricKey1 | RSA_2048 | P@ssw0rd1 **Depends on:** SqlDatabaseUser -The integration test will not leave anything on any instance. +The integration test will leave the following database permission on +principals. + +Principal | State | Permission +--- | --- | --- +User1 | Grant | Connect ## SqlWindowsFirewall diff --git a/tests/QA/ScriptAnalyzer.Tests.ps1 b/tests/QA/ScriptAnalyzer.Tests.ps1 index 136c2c1ba..26c740a48 100644 --- a/tests/QA/ScriptAnalyzer.Tests.ps1 +++ b/tests/QA/ScriptAnalyzer.Tests.ps1 @@ -50,6 +50,16 @@ Describe 'Script Analyzer Rules' { It 'Should pass all PS Script Analyzer rules for file ''''' -ForEach $testCases { $pssaError = Invoke-ScriptAnalyzer -Path $ScriptPath -Settings $scriptAnalyzerSettingsPath + <# + Filter out rule TypeNotFound. + + TODO: The rule "TypeNotFound" is not excluded correctly even if it is + excluded in the file 'analyzersettings.psd1'. This is a workaround + until it is properly excluded for source files, and instead only + ran for the built module script module file (SqlServerDsc.psm1). + #> + $pssaError = $pssaError | Where-Object -FilterScript { $_.RuleName -ne 'TypeNotFound' } + $report = $pssaError | Format-Table -AutoSize | Out-String -Width 200 $pssaError | Should -HaveCount 0 -Because "all script analyzer rules should pass.`r`n`r`n $report`r`n" } diff --git a/tests/Unit/Classes/DatabasePermission.Tests.ps1 b/tests/Unit/Classes/DatabasePermission.Tests.ps1 new file mode 100644 index 000000000..2cbeeaf2e --- /dev/null +++ b/tests/Unit/Classes/DatabasePermission.Tests.ps1 @@ -0,0 +1,357 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'DatabasePermission' -Tag 'DatabasePermission' { + Context 'When instantiating the class' { + It 'Should not throw an error' { + $script:mockDatabasePermissionInstance = InModuleScope -ScriptBlock { + [DatabasePermission]::new() + } + } + + It 'Should be of the correct type' { + $mockDatabasePermissionInstance | Should -Not -BeNullOrEmpty + $mockDatabasePermissionInstance.GetType().Name | Should -Be 'DatabasePermission' + } + } + + Context 'When setting an reading values' { + It 'Should be able to set value in instance' { + $script:mockDatabasePermissionInstance = InModuleScope -ScriptBlock { + $databasPermissionInstance = [DatabasePermission]::new() + + $databasPermissionInstance.State = 'Grant' + $databasPermissionInstance.Permission = 'select' + + return $databasPermissionInstance + } + } + + It 'Should be able read the values from instance' { + $mockDatabasePermissionInstance.State | Should -Be 'Grant' + $mockDatabasePermissionInstance.Permission = 'select' + } + } + + Context 'When comparing two objects using method Equals()' { + Context 'When both objects are equal' { + Context 'When property Permission has a single value' { + It 'Should return $true' { + InModuleScope -ScriptBlock { + $databasPermissionInstance1 = [DatabasePermission]::new() + + $databasPermissionInstance1.State = 'Grant' + $databasPermissionInstance1.Permission = 'select' + + $databasPermissionInstance2 = [DatabasePermission]::new() + + $databasPermissionInstance2.State = 'Grant' + $databasPermissionInstance2.Permission = 'select' + + $databasPermissionInstance1 -eq $databasPermissionInstance2 | Should -BeTrue + } + } + } + + Context 'When property Permission has a multiple values' { + It 'Should return $true' { + InModuleScope -ScriptBlock { + $databasPermissionInstance1 = [DatabasePermission]::new() + + $databasPermissionInstance1.State = 'Grant' + $databasPermissionInstance1.Permission = @('select', 'update') + + $databasPermissionInstance2 = [DatabasePermission]::new() + + $databasPermissionInstance2.State = 'Grant' + $databasPermissionInstance2.Permission = @('select', 'update') + + $databasPermissionInstance1 -eq $databasPermissionInstance2 | Should -BeTrue + } + } + } + } + + Context 'When object has different value for property State' { + It 'Should instantiate two objects' { + $script:mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { + $databasPermissionInstance = [DatabasePermission]::new() + + $databasPermissionInstance.State = 'Deny' + $databasPermissionInstance.Permission = 'select' + + return $databasPermissionInstance + } + + $script:mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { + $databasPermissionInstance = [DatabasePermission]::new() + + $databasPermissionInstance.State = 'Grant' + $databasPermissionInstance.Permission = 'select' + + return $databasPermissionInstance + } + } + + It 'Should return $false' { + $mockDatabasePermissionInstance1 -eq $mockDatabasePermissionInstance2 | Should -BeFalse + } + } + + Context 'When object has different value for property Permission' { + It 'Should instantiate two objects' { + $script:mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { + $databasPermissionInstance = [DatabasePermission]::new() + + $databasPermissionInstance.State = 'Grant' + $databasPermissionInstance.Permission = 'select' + + return $databasPermissionInstance + } + + $script:mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { + $databasPermissionInstance = [DatabasePermission]::new() + + $databasPermissionInstance.State = 'Grant' + $databasPermissionInstance.Permission = 'update' + + return $databasPermissionInstance + } + } + + It 'Should return $false' { + $mockDatabasePermissionInstance1 -eq $mockDatabasePermissionInstance2 | Should -BeFalse + } + } + } + + Context 'When comparing two objects using method CompareTo()' { + Context 'When the instance is compared against an invalid object' { + It 'Should return a value less than zero' { + $mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { + [DatabasePermission] @{ + State = 'Grant' + Permission = 'Select' + } + } + + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.InvalidTypeForCompare + } + + $mockErrorMessage = $mockErrorMessage -f @( + $mockDatabasePermissionInstance1.GetType().FullName + 'System.String' + ) + + $mockErrorMessage += " (Parameter 'Object')" + + # Escape the brackets so Pester can evaluate the string correctly. + $mockErrorMessage = $mockErrorMessage -replace '\[', '`[' + $mockErrorMessage = $mockErrorMessage -replace '\]', '`]' + + { $mockDatabasePermissionInstance1.CompareTo('AnyValue') } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + Context 'When the instance precedes the object being compared' { + Context 'When the instance has the state '''' and object has state ''''' -ForEach @( + @{ + MockInstanceState = 'Grant' + MockObjectState = 'GrantWithGrant' + } + @{ + MockInstanceState = 'Grant' + MockObjectState = 'Deny' + } + @{ + MockInstanceState = 'GrantWithGrant' + MockObjectState = 'Deny' + } + ) { + It 'Should return a value less than zero' { + $mockDatabasePermissionInstance1 = InModuleScope -Parameters $_ -ScriptBlock { + [DatabasePermission] @{ + State = $MockInstanceState + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance2 = InModuleScope -Parameters $_ -ScriptBlock { + [DatabasePermission] @{ + State = $MockObjectState + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance1.CompareTo($mockDatabasePermissionInstance2) | Should -BeLessThan 0 + } + } + } + + Context 'When the instance follows the object being compared' { + Context 'When the instance has the state '''' and object has state ''''' -ForEach @( + @{ + MockInstanceState = 'Deny' + MockObjectState = 'Grant' + } + @{ + MockInstanceState = 'GrantWithGrant' + MockObjectState = 'Grant' + } + @{ + MockInstanceState = 'Deny' + MockObjectState = 'GrantWithGrant' + } + ) { + It 'Should return a value less than zero' { + $mockDatabasePermissionInstance1 = InModuleScope -Parameters $_ -ScriptBlock { + [DatabasePermission] @{ + State = $MockInstanceState + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance2 = InModuleScope -Parameters $_ -ScriptBlock { + [DatabasePermission] @{ + State = $MockObjectState + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance1.CompareTo($mockDatabasePermissionInstance2) | Should -BeGreaterThan 0 + } + } + + Context 'When the instance is compared against an object that is $null' { + It 'Should return a value less than zero' { + $mockDatabasePermissionInstance1 = InModuleScope -ScriptBlock { + [DatabasePermission] @{ + State = 'Grant' + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance1.CompareTo($null) | Should -BeGreaterThan 0 + } + } + } + + Context 'When the instance is in the same position as the object being compared' { + Context 'When the instance has the state '''' and object has state ''''' -ForEach @( + @{ + MockInstanceState = 'Grant' + MockObjectState = 'Grant' + } + @{ + MockInstanceState = 'GrantWithGrant' + MockObjectState = 'GrantWithGrant' + } + @{ + MockInstanceState = 'Deny' + MockObjectState = 'Deny' + } + ) { + It 'Should return a value less than zero' { + $mockDatabasePermissionInstance1 = InModuleScope -Parameters $_ -ScriptBlock { + [DatabasePermission] @{ + State = $MockInstanceState + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance2 = InModuleScope -Parameters $_ -ScriptBlock { + [DatabasePermission] @{ + State = $MockObjectState + Permission = 'Select' + } + } + + $mockDatabasePermissionInstance1.CompareTo($mockDatabasePermissionInstance2) | Should -Be 0 + } + } + } + + Context 'When sorting the instances' { + It 'Should always sort in the correct order' -ForEach @( + @{ + MockState = @('Grant', 'GrantWithGrant', 'Deny') + } + @{ + MockState = @('GrantWithGrant', 'Grant', 'Deny') + } + @{ + MockState = @('GrantWithGrant', 'Deny', 'Grant') + } + @{ + MockState = @('Deny', 'GrantWithGrant', 'Grant') + } + @{ + MockState = @('Grant', 'Deny', 'GrantWithGrant') + } + @{ + MockState = @('Deny', 'Grant', 'GrantWithGrant') + } + ) { + $mockDatabasePermissionArray = @( + InModuleScope -Parameters $_ -ScriptBlock { + foreach ($currentMockState in $MockState) + { + [DatabasePermission] @{ + State = $currentMockState + Permission = 'Select' + } + } + } + ) + + $mockSortedArray = $mockDatabasePermissionArray | Sort-Object + + $mockSortedArray[0].State | Should -Be 'Grant' + $mockSortedArray[1].State | Should -Be 'GrantWithGrant' + $mockSortedArray[2].State | Should -Be 'Deny' + } + } + } +} diff --git a/tests/Unit/Classes/Reason.Tests.ps1 b/tests/Unit/Classes/Reason.Tests.ps1 new file mode 100644 index 000000000..c7676d7b2 --- /dev/null +++ b/tests/Unit/Classes/Reason.Tests.ps1 @@ -0,0 +1,70 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Reason' -Tag 'Reason' { + Context 'When instantiating the class' { + It 'Should not throw an error' { + $script:mockReasonInstance = InModuleScope -ScriptBlock { + [Reason]::new() + } + } + + It 'Should be of the correct type' { + $mockReasonInstance | Should -Not -BeNullOrEmpty + $mockReasonInstance.GetType().Name | Should -Be 'Reason' + } + } + + Context 'When setting an reading values' { + It 'Should be able to set value in instance' { + $mockReasonInstance.Code = '{0}:{0}:Ensure' -f $mockReasonInstance.GetType() + $mockReasonInstance.Phrase = 'It should be absent, but it was present.' + } + + It 'Should be able read the values from instance' { + $mockReasonInstance.Code | Should -Be ('{0}:{0}:Ensure' -f 'Reason') + $mockReasonInstance.Phrase = 'It should be absent, but it was present.' + } + } +} diff --git a/tests/Unit/Classes/ResourceBase.Tests.ps1 b/tests/Unit/Classes/ResourceBase.Tests.ps1 new file mode 100644 index 000000000..9a3379816 --- /dev/null +++ b/tests/Unit/Classes/ResourceBase.Tests.ps1 @@ -0,0 +1,1266 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'ResourceBase\GetCurrentState()' -Tag 'GetCurrentState' { + Context 'When the required methods are not overridden' { + BeforeAll { + $mockResourceBaseInstance = InModuleScope -ScriptBlock { + [ResourceBase]::new() + } + } + + Context 'When there is no override for the method GetCurrentState' { + It 'Should throw the correct error' { + { $mockResourceBaseInstance.GetCurrentState(@{}) } | Should -Throw $mockResourceBaseInstance.GetCurrentStateMethodNotImplemented + } + } + } +} + +Describe 'ResourceBase\Modify()' -Tag 'Modify' { + Context 'When the required methods are not overridden' { + BeforeAll { + $mockResourceBaseInstance = InModuleScope -ScriptBlock { + [ResourceBase]::new() + } + } + + + Context 'When there is no override for the method Modify' { + It 'Should throw the correct error' { + { $mockResourceBaseInstance.Modify(@{}) } | Should -Throw $mockResourceBaseInstance.ModifyMethodNotImplemented + } + } + } +} + +Describe 'ResourceBase\AssertProperties()' -Tag 'AssertProperties' { + BeforeAll { + $mockResourceBaseInstance = InModuleScope -ScriptBlock { + [ResourceBase]::new() + } + } + + + It 'Should not throw' { + $mockDesiredState = @{ + MyProperty1 = 'MyValue1' + } + + { $mockResourceBaseInstance.AssertProperties($mockDesiredState) } | Should -Not -Throw + } +} + +Describe 'ResourceBase\Get()' -Tag 'Get' { + Context 'When the system is in the desired state' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + } + + Context 'When the configuration should be present' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + <# + This will test so that a key value do not need to be enforced, and still + be returned by Get(). + #> + MyMockResource() : base () + { + # These properties will not be enforced. + $this.notEnforcedProperties = @( + 'MyResourceKeyProperty1' + ) + } + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + <# + This does not return the key property that is not being enforce, to let + the base class' method Get() return that value. + #> + return @{ + MyResourceProperty2 = 'MyValue2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + $mockResourceBaseInstance.MyResourceProperty2 = 'MyValue2' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Present) + $getResult.Reasons | Should -BeNullOrEmpty + } + } + } + + Context 'When the configuration should be absent' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + <# + Tests to enforce a key property even if we do not return the key property value + from the method GetCurrentState. + #> + MyMockResource() : base () + { + # Test not to add the key property to the list of properties that are not enforced. + $this.notEnforcedProperties = @() + } + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + MyResourceKeyProperty1 = $null + MyResourceProperty2 = $null + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Ensure = [Ensure]::Absent + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -BeNullOrEmpty + $getResult.MyResourceProperty2 | Should -BeNullOrEmpty + $getResult.Ensure | Should -Be ([Ensure]::Absent) + + $getResult.Reasons | Should -HaveCount 1 + $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceKeyProperty1' + } + } + } + + Context 'When returning Ensure property from method GetCurrentState()' { + Context 'When the configuration should be present' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + Ensure = [Ensure]::Present + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = 'MyValue2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + $mockResourceBaseInstance.MyResourceProperty2 = 'MyValue2' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Present) + $getResult.Reasons | Should -BeNullOrEmpty + } + } + } + + Context 'When the configuration should be absent' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + Ensure = [Ensure]::Absent + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = $null + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Ensure = [Ensure]::Absent + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -BeNullOrEmpty + $getResult.Ensure | Should -Be ([Ensure]::Absent) + $getResult.Reasons | Should -BeNullOrEmpty + } + } + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + } + + Context 'When the configuration should be present' { + Context 'When a non-mandatory parameter is not in desired state' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = 'MyValue2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + $mockResourceBaseInstance.MyResourceProperty2 = 'NewValue2' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Absent) + + $getResult.Reasons | Should -HaveCount 1 + $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceProperty2' + $getResult.Reasons[0].Phrase | Should -Be 'The property MyResourceProperty2 should be "NewValue2", but was "MyValue2"' + } + } + } + + Context 'When a mandatory parameter is not in desired state' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + MyResourceKeyProperty1 = $null + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -BeNullOrEmpty + $getResult.Ensure | Should -Be ([Ensure]::Absent) + + $getResult.Reasons | Should -HaveCount 1 + $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:MyResourceKeyProperty1' + $getResult.Reasons[0].Phrase | Should -Be 'The property MyResourceKeyProperty1 should be "MyValue1", but was null' + } + } + } + } + + Context 'When the configuration should be absent' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = 'MyValue2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Ensure = [Ensure]::Absent + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Present) + + $getResult.Reasons | Should -BeNullOrEmpty + } + } + } + + Context 'When returning Ensure property from method GetCurrentState()' { + Context 'When the configuration should be present' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = ([Ensure]::Present) + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + Ensure = ([Ensure]::Absent) + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = 'MyValue2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + $mockResourceBaseInstance.MyResourceProperty2 = 'NewValue2' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Absent) + + $getResult.Reasons | Should -HaveCount 2 + + # The order in the array was sometimes different so could not use array index ($getResult.Reasons[0]). + $getResult.Reasons.Code | Should -Contain 'MyMockResource:MyMockResource:MyResourceProperty2' + $getResult.Reasons.Code | Should -Contain 'MyMockResource:MyMockResource:Ensure' + $getResult.Reasons.Phrase | Should -Contain 'The property MyResourceProperty2 should be "NewValue2", but was "MyValue2"' + $getResult.Reasons.Phrase | Should -Contain 'The property Ensure should be "Present", but was "Absent"' + } + } + } + + Context 'When the configuration should be absent' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [Reason[]] + $Reasons + + [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + return @{ + Ensure = [Ensure]::Present + MyResourceKeyProperty1 = 'MyValue1' + MyResourceProperty2 = 'MyValue2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return the correct values for the properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Ensure = [Ensure]::Absent + $mockResourceBaseInstance.MyResourceKeyProperty1 = 'MyValue1' + + $getResult = $mockResourceBaseInstance.Get() + + $getResult.MyResourceKeyProperty1 | Should -Be 'MyValue1' + $getResult.MyResourceProperty2 | Should -Be 'MyValue2' + $getResult.Ensure | Should -Be ([Ensure]::Present) + + $getResult.Reasons | Should -HaveCount 1 + + $getResult.Reasons[0].Code | Should -Be 'MyMockResource:MyMockResource:Ensure' + $getResult.Reasons[0].Phrase | Should -Be 'The property Ensure should be "Absent", but was "Present"' + } + } + } + } + } +} + +Describe 'ResourceBase\Test()' -Tag 'Test' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + <# + This will override (mock) the method Compare() that is called by Test(). + Overriding this method is something a derived class normally should not + do, but done here to simplify the tests. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [System.Collections.Hashtable[]] Compare() + { + return $null + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Test() | Should -BeTrue + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + <# + This will override (mock) the method Compare() that is called by Test(). + Overriding this method is something a derived class normally should not + do, but done here to simplify the tests. + #> + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [System.Collections.Hashtable[]] Compare() + { + # Could just return any non-null object, but mocking a real result. + return @{ + Property = 'MyResourceProperty2' + ExpectedValue = '1' + ActualValue = '2' + } + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Test() | Should -BeFalse + } + } + } +} + +Describe 'ResourceBase\Compare()' -Tag 'Compare' { + BeforeAll { + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty + + [ResourceBase] Get() + { + # Creates a new instance of the mock instance MyMockResource. + $currentStateInstance = [System.Activator]::CreateInstance($this.GetType()) + + $currentStateInstance.MyResourceProperty2 = 'MyValue1' + $currentStateInstance.MyResourceReadProperty = 'MyReadValue1' + + return $currentStateInstance + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + Context 'When no properties are enforced' { + It 'Should not return any property to enforce' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Compare() | Should -BeNullOrEmpty + } + } + } + + Context 'When one property are enforced but in desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.MyResourceProperty2 = 'MyValue1' + } + } + + It 'Should not return any property to enforce' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Compare() | Should -BeNullOrEmpty -Because 'no result ($null) means all properties are in desired state' + } + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty()] + [System.String] + $MyResourceProperty3 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty + + [ResourceBase] Get() + { + # Creates a new instance of the mock instance MyMockResource. + $currentStateInstance = [System.Activator]::CreateInstance($this.GetType()) + + $currentStateInstance.MyResourceProperty2 = 'MyValue1' + $currentStateInstance.MyResourceProperty3 = 'MyValue2' + $currentStateInstance.MyResourceReadProperty = 'MyReadValue1' + + return $currentStateInstance + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + Context 'When only enforcing one property' { + BeforeAll { + InModuleScope -ScriptBlock { + # Set desired value for the property that should be enforced. + $mockResourceBaseInstance.MyResourceProperty2 = 'MyNewValue' + } + } + + It 'Should return the correct property that is not in desired state' { + InModuleScope -ScriptBlock { + $compareResult = $mockResourceBaseInstance.Compare() + $compareResult | Should -HaveCount 1 + + $compareResult[0].Property | Should -Be 'MyResourceProperty2' + $compareResult[0].ExpectedValue | Should -Be 'MyNewValue' + $compareResult[0].ActualValue | Should -Be 'MyValue1' + } + } + } + + Context 'When only enforcing two properties' { + BeforeAll { + InModuleScope -ScriptBlock { + # Set desired value for the properties that should be enforced. + $mockResourceBaseInstance.MyResourceProperty2 = 'MyNewValue1' + $mockResourceBaseInstance.MyResourceProperty3 = 'MyNewValue2' + } + } + + It 'Should return the correct property that is not in desired state' { + InModuleScope -ScriptBlock { + <# + The properties that are returned are not [ordered] so they can + come in any order from run to run. The test handle that. + #> + $compareResult = $mockResourceBaseInstance.Compare() + $compareResult | Should -HaveCount 2 + + $compareResult.Property | Should -Contain 'MyResourceProperty2' + $compareResult.Property | Should -Contain 'MyResourceProperty3' + + $compareProperty = $compareResult.Where( { $_.Property -eq 'MyResourceProperty2' }) + $compareProperty.ExpectedValue | Should -Be 'MyNewValue1' + $compareProperty.ActualValue | Should -Be 'MyValue1' + + $compareProperty = $compareResult.Where( { $_.Property -eq 'MyResourceProperty3' }) + $compareProperty.ExpectedValue | Should -Be 'MyNewValue2' + $compareProperty.ActualValue | Should -Be 'MyValue2' + } + } + } + } +} + +Describe 'ResourceBase\Set()' -Tag 'Set' { + BeforeAll { + Mock -CommandName Assert-Module + Mock -CommandName Get-ClassName -MockWith { + # Only return localized strings for this class name. + @('ResourceBase') + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty()] + [System.String] + $MyResourceProperty3 + + # Hidden property to determine whether the method Modify() was called. + hidden [System.Collections.Hashtable] $mockModifyProperties = @{} + + [System.Collections.Hashtable[]] Compare() + { + return $null + } + + [void] Modify([System.Collections.Hashtable] $properties) + { + $this.mockModifyProperties = $properties + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should not set any property' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Set() + + $mockResourceBaseInstance.mockModifyProperties | Should -BeNullOrEmpty + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When setting one property' { + BeforeAll { + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty()] + [System.String] + $MyResourceProperty3 + + # Hidden property to determine whether the method Modify() was called. + hidden [System.Collections.Hashtable] $mockModifyProperties = @{} + + [System.Collections.Hashtable[]] Compare() + { + return @( + @{ + Property = 'MyResourceProperty2' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + } + ) + } + + [void] Modify([System.Collections.Hashtable] $properties) + { + $this.mockModifyProperties = $properties + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should set the correct property' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Set() + + $mockResourceBaseInstance.mockModifyProperties.Keys | Should -HaveCount 1 + $mockResourceBaseInstance.mockModifyProperties.Keys | Should -Contain 'MyResourceProperty2' + + $mockResourceBaseInstance.mockModifyProperties.MyResourceProperty2 | Should -Contain 'MyNewValue1' + } + } + } + + Context 'When setting two properties' { + BeforeAll { + $inModuleScopeScriptBlock = @' +using module SqlServerDsc + +class MyMockResource : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty()] + [System.String] + $MyResourceProperty3 + + # Hidden property to determine whether the method Modify() was called. + hidden [System.Collections.Hashtable] $mockModifyProperties = @{} + + [System.Collections.Hashtable[]] Compare() + { + return @( + @{ + Property = 'MyResourceProperty2' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + }, + @{ + Property = 'MyResourceProperty3' + ExpectedValue = 'MyNewValue2' + ActualValue = 'MyValue2' + } + ) + } + + [void] Modify([System.Collections.Hashtable] $properties) + { + $this.mockModifyProperties = $properties + } +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should have correctly instantiated the resource class' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance | Should -Not -BeNullOrEmpty + $mockResourceBaseInstance.GetType().BaseType.Name | Should -Be 'ResourceBase' + } + } + + It 'Should set the correct properties' { + InModuleScope -ScriptBlock { + $mockResourceBaseInstance.Set() + + $mockResourceBaseInstance.mockModifyProperties.Keys | Should -HaveCount 2 + $mockResourceBaseInstance.mockModifyProperties.Keys | Should -Contain 'MyResourceProperty2' + $mockResourceBaseInstance.mockModifyProperties.Keys | Should -Contain 'MyResourceProperty3' + + $mockResourceBaseInstance.mockModifyProperties.MyResourceProperty2 | Should -Contain 'MyNewValue1' + $mockResourceBaseInstance.mockModifyProperties.MyResourceProperty3 | Should -Contain 'MyNewValue2' + } + } + } + } +} diff --git a/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 new file mode 100644 index 000000000..3fd2edb24 --- /dev/null +++ b/tests/Unit/Classes/SqlDatabasePermission.Tests.ps1 @@ -0,0 +1,1865 @@ +<# + .SYNOPSIS + Unit test for DSC_SqlDatabasePermission DSC resource. +#> + +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../TestHelpers/CommonTestHelper.psm1') + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + # Load the correct SQL Module stub + $script:stubModuleName = Import-SQLModuleStub -PassThru + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + # Unload the stub module. + Remove-SqlModuleStub -Name $script:stubModuleName + + # Remove module common test helper. + Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force +} + +Describe 'SqlDatabasePermission' { + Context 'When class is instantiated' { + It 'Should not throw an exception' { + InModuleScope -ScriptBlock { + { [SqlDatabasePermission]::new() } | Should -Not -Throw + } + } + + It 'Should have a default or empty constructor' { + InModuleScope -ScriptBlock { + $instance = [SqlDatabasePermission]::new() + $instance | Should -Not -BeNullOrEmpty + } + } + + It 'Should be the correct type' { + InModuleScope -ScriptBlock { + $instance = [SqlDatabasePermission]::new() + $instance.GetType().Name | Should -Be 'SqlDatabasePermission' + } + } + } +} + +Describe 'SqlDatabasePermission\Get()' -Tag 'Get' { + Context 'When the system is in the desired state' { + Context 'When the desired permission exist' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.Get() + + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.DatabaseName | Should -Be 'MockDatabaseName' + $currentState.Name | Should -Be 'MockUserName' + $currentState.ServerName | Should -Be (Get-ComputerName) + $currentState.Credential | Should -BeNullOrEmpty + $currentState.Reasons | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + + $currentState.Permission[0].State | Should -Be 'Grant' + $currentState.Permission[0].Permission | Should -Be 'Connect' + } + } + } + + Context 'When the desired permission exist and using parameter Credential' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Credential = [System.Management.Automation.PSCredential]::new( + 'MyCredentialUserName', + [SecureString]::new() + ) + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Credential = $this.Credential + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.Get() + + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.DatabaseName | Should -Be 'MockDatabaseName' + $currentState.Name | Should -Be 'MockUserName' + $currentState.ServerName | Should -Be (Get-ComputerName) + + $currentState.Credential | Should -BeOfType [System.Management.Automation.PSCredential] + + $currentState.Credential.UserName | Should -Be 'MyCredentialUserName' + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + + $currentState.Permission[0].State | Should -Be 'Grant' + $currentState.Permission[0].Permission | Should -Be 'Connect' + + $currentState.Reasons | Should -BeNullOrEmpty + } + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When the desired permission exist' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + <# + This mocks the method GetCurrentState(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect', 'Update') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.Get() + + $currentState.InstanceName | Should -Be 'NamedInstance' + $currentState.DatabaseName | Should -Be 'MockDatabaseName' + $currentState.Name | Should -Be 'MockUserName' + $currentState.ServerName | Should -Be (Get-ComputerName) + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + + $currentState.Permission[0].State | Should -Be 'Grant' + $currentState.Permission[0].Permission | Should -Contain 'Connect' + $currentState.Permission[0].Permission | Should -Contain 'Update' + + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons[0].Code | Should -Be 'SqlDatabasePermission:SqlDatabasePermission:Permission' + $currentState.Reasons[0].Phrase | Should -Be 'The property Permission should be [{"State":"Grant","Permission":["Connect"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}], but was [{"State":"Grant","Permission":["Connect","Update"]},{"State":"GrantWithGrant","Permission":[]},{"State":"Deny","Permission":[]}]' + } + } + } + } +} + +Describe 'SqlDatabasePermission\GetCurrentState()' -Tag 'GetCurrentState' { + Context 'When there are no permission in the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission + } + + It 'Should return empty collections for each state' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -BeNullOrEmpty + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + } + } + + Context 'When using property Credential' { + It 'Should return empty collections for each state' { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance.Credential = [System.Management.Automation.PSCredential]::new( + 'MyCredentialUserName', + [SecureString]::new() + ) + + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeOfType [System.Management.Automation.PSCredential] + + $currentState.Credential.UserName | Should -Be 'MyCredentialUserName' + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -BeNullOrEmpty + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + } + } + } + } + + Context 'When there are permissions for only state Grant' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Update = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Grant' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + return $mockDatabasePermissionInfoCollection + } + } + + It 'Should return correct values for state Grant and empty collections for the two other states' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -HaveCount 2 + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -BeNullOrEmpty + } + } + } + + Context 'When there are permissions for both state Grant and Deny' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Update = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Select = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + return $mockDatabasePermissionInfoCollection + } + } + + It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -Contain 'Select' + } + } + } + + Context 'When using parameter PermissionToInclude' { + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToInclude = [DatabasePermission] @{ + State = 'Grant' + Permission = 'update' + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Update = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Select = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + return $mockDatabasePermissionInfoCollection + } + } + + It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -Contain 'Select' + + $currentState.PermissionToInclude | Should -HaveCount 1 + $currentState.PermissionToInclude[0].State | Should -Be 'Grant' + $currentState.PermissionToInclude[0].Permission | Should -Be 'Update' + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToInclude = [DatabasePermission] @{ + State = 'Grant' + Permission = 'alter' + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Update = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Select = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + return $mockDatabasePermissionInfoCollection + } + } + + It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -Contain 'Select' + + $currentState.PermissionToInclude | Should -HaveCount 1 + $currentState.PermissionToInclude[0].State | Should -Be 'Grant' + $currentState.PermissionToInclude[0].Permission | Should -BeNullOrEmpty + } + } + } + } + + Context 'When using parameter PermissionToExclude' { + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToExclude = [DatabasePermission] @{ + State = 'Grant' + Permission = 'alter' + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Update = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Select = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + return $mockDatabasePermissionInfoCollection + } + } + + It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -Contain 'Select' + + $currentState.PermissionToExclude | Should -HaveCount 1 + $currentState.PermissionToExclude[0].State | Should -Be 'Grant' + $currentState.PermissionToExclude[0].Permission | Should -Be 'Alter' + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToExclude = [DatabasePermission] @{ + State = 'Grant' + Permission = 'update' + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Get-SqlDscDatabasePermission -MockWith { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Update = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Select = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + return $mockDatabasePermissionInfoCollection + } + } + + It 'Should return correct values for the states Grant and Deny and empty collections for the state GrantWithGrant' { + InModuleScope -ScriptBlock { + $currentState = $script:mockSqlDatabasePermissionInstance.GetCurrentState(@{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + }) + + $currentState.Credential | Should -BeNullOrEmpty + + $currentState.Permission.GetType().FullName | Should -Be 'DatabasePermission[]' + $currentState.Permission | Should -HaveCount 3 + + $grantState = $currentState.Permission.Where({ $_.State -eq 'Grant' }) + + $grantState | Should -Not -BeNullOrEmpty + $grantState.State | Should -Be 'Grant' + $grantState.Permission | Should -Contain 'Connect' + $grantState.Permission | Should -Contain 'Update' + + $grantWithGrantState = $currentState.Permission.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantState | Should -Not -BeNullOrEmpty + $grantWithGrantState.State | Should -Be 'GrantWithGrant' + $grantWithGrantState.Permission | Should -BeNullOrEmpty + + $denyState = $currentState.Permission.Where({ $_.State -eq 'Deny' }) + + $denyState | Should -Not -BeNullOrEmpty + $denyState.State | Should -Be 'Deny' + $denyState.Permission | Should -Contain 'Select' + + $currentState.PermissionToExclude | Should -HaveCount 1 + $currentState.PermissionToExclude[0].State | Should -Be 'Grant' + $currentState.PermissionToExclude[0].Permission | Should -BeNullOrEmpty + } + } + } + } +} + +Describe 'SqlDatabasePermission\Set()' -Tag 'Set' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } | + # Mock method Modify which is called by the base method Set(). + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Modify' -Value { + $script:mockMethodModifyCallCount += 1 + } -PassThru + } + } + + BeforeEach { + InModuleScope -ScriptBlock { + $script:mockMethodModifyCallCount = 0 + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } + } + } + + It 'Should not call method Modify()' { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance.Set() + + $script:mockMethodModifyCallCount | Should -Be 0 + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @{ + Property = 'Permission' + ExpectedValue = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect', 'Update') + } + ) + ActualValue = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + ) + } + } + } + } + + It 'Should not call method Modify()' { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance.Set() + + $script:mockMethodModifyCallCount | Should -Be 1 + } + } + } +} + +Describe 'SqlDatabasePermission\Test()' -Tag 'Test' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance.Test() | Should -BeTrue + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @{ + Property = 'Permission' + ExpectedValue = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect', 'Update') + } + ) + ActualValue = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + ) + } + } + } + } + + It 'Should return $false' { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance.Test() | Should -BeFalse + } + } + } +} + +Describe 'SqlDatabasePermission\Modify()' -Tag 'Modify' { + Context 'When the database principal does not exist' { + BeforeAll { + InModuleScope -ScriptBlock { + # This test does not set a desired state as it is not necessary for this test. + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + # Credential is set to increase code coverage. + Credential = [System.Management.Automation.PSCredential]::new( + 'MyCredentialUserName', + [SecureString]::new() + ) + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $false + } + } + + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.NameIsMissing + } + + $mockErrorRecord = Get-InvalidOperationRecord -Message ( + $mockErrorMessage -f @( + 'MockUserName' + 'MockDatabaseName' + 'NamedInstance' + ) + ) + + InModuleScope -ScriptBlock { + { + # This test does not pass any properties to set as it is not necessary for this test. + $mockSqlDatabasePermissionInstance.Modify(@{ + Permission = [DatabasePermission[]] @() + }) + } | Should -Throw -ExpectedMessage $mockErrorRecord + } + } + } + + Context 'When property Permission is not in desired state' { + Context 'When a desired permissions is missing from the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + # This mocks the method GetCurrentState(). + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @() + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + Mock -CommandName Set-SqlDscDatabasePermission + } + + It 'Should call the correct mock with the correct parameter values' { + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.Modify(@{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) + } | Should -Not -Throw + } + + # Grants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Grant' -and $Permission.Connect -eq $true + } -Exactly -Times 1 -Scope It + + # GrantWithGrants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Grant' -and $Permission.Update -eq $true + } -Exactly -Times 1 -Scope It + } + } + + Context 'When a desired permission is missing from the current state and there are four permissions that should not exist in the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + # This mocks the method GetCurrentState(). + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Alter', 'Select') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Delete') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @('CreateDatabase') + } + ) + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + Mock -CommandName Set-SqlDscDatabasePermission + } + + It 'Should call the correct mock with the correct parameter values' { + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.Modify(@{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) + } | Should -Not -Throw + } + + # Revoking Grants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Revoke' -and $Permission.Alter -eq $true -and $Permission.Select -eq $true + } -Exactly -Times 1 -Scope It + + # Revoking GrantWithGrants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Revoke' -and $Permission.Delete -eq $true + } -Exactly -Times 1 -Scope It + + # Revoking Denies + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Revoke' -and $Permission.CreateDatabase -eq $true + } -Exactly -Times 1 -Scope It + + # Adding new Grant + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Grant' -and $Permission.Connect -eq $true + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When property PermissionToInclude is not in desired state' { + Context 'When a desired permissions is missing from the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToInclude = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + # This mocks the method GetCurrentState(). + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @() + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + Mock -CommandName Set-SqlDscDatabasePermission + } + + It 'Should call the correct mock with the correct parameter values' { + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.Modify(@{ + PermissionToInclude = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) + } | Should -Not -Throw + } + + # Grants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Grant' -and $Permission.Connect -eq $true + } -Exactly -Times 1 -Scope It + + # GrantWithGrants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Grant' -and $Permission.Update -eq $true + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When property PermissionToExclude is not in desired state' { + Context 'When a desired permissions is missing from the current state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + PermissionToExclude = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + # This mocks the method GetCurrentState(). + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + Mock -CommandName Set-SqlDscDatabasePermission + } + + It 'Should call the correct mock with the correct parameter values' { + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.Modify(@{ + PermissionToExclude = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) + } | Should -Not -Throw + } + + # Revoking Grants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Revoke' -and $Permission.Connect -eq $true + } -Exactly -Times 1 -Scope It + + # Revoking GrantWithGrants + Should -Invoke -CommandName Set-SqlDscDatabasePermission -ParameterFilter { + $State -eq 'Revoke' -and $Permission.Update -eq $true + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When Set-SqlDscDatabasePermission fails to change permission' { + Context 'When granting permissions' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + # This mocks the method GetCurrentState(). + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @() + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + Mock -CommandName Set-SqlDscDatabasePermission -MockWith { + throw 'Mocked error' + } + } + + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.FailedToSetPermission + } + + $mockErrorRecord = Get-InvalidOperationRecord -Message ( + $mockErrorMessage -f @( + 'MockUserName' + 'MockDatabaseName' + ) + ) + + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.Modify(@{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) + } | Should -Throw -ExpectedMessage $mockErrorRecord + } + } + } + + Context 'When revoking permissions' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{ + Name = 'MockUserName' + DatabaseName = 'MockDatabaseName' + InstanceName = 'NamedInstance' + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + + # This mocks the method GetCurrentState(). + $script:mockSqlDatabasePermissionInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Update') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + } + } + } + + Mock -CommandName Connect-SqlDscDatabaseEngine -MockWith { + return New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + } + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + Mock -CommandName Set-SqlDscDatabasePermission -MockWith { + throw 'Mocked error' + } + } + + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.FailedToRevokePermissionFromCurrentState + } + + $mockErrorRecord = Get-InvalidOperationRecord -Message ( + $mockErrorMessage -f @( + 'MockUserName' + 'MockDatabaseName' + ) + ) + + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.Modify(@{ + Permission = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = @('Connect') + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = @() + } + ) + }) + } | Should -Throw -ExpectedMessage $mockErrorRecord + } + } + } + } +} + +Describe 'SqlDatabasePermission\AssertProperties()' -Tag 'AssertProperties' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockSqlDatabasePermissionInstance = [SqlDatabasePermission] @{} + } + } + + <# + These tests just check for the string localized ID. Since the error is part + of a command outside of SqlServerDsc, a small changes to the localized + string should not fail these tests. + #> + Context 'When passing mutually exclusive parameters' { + Context 'When passing Permission and PermissionToInclude' { + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{ + Permission = [DatabasePermission[]] @([DatabasePermission] @{}) + PermissionToInclude = [DatabasePermission[]] @([DatabasePermission] @{}) + }) + } | Should -Throw -ExpectedMessage '*DRC0010*' + } + } + } + + Context 'When passing Permission and PermissionToExclude' { + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{ + Permission = [DatabasePermission[]] @([DatabasePermission] @{}) + PermissionToExclude = [DatabasePermission[]] @([DatabasePermission] @{}) + }) + } | Should -Throw -ExpectedMessage '*DRC0010*' + } + } + } + } + + Context 'When not passing any permission property' { + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.MustAssignOnePermissionProperty + } + + InModuleScope -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{}) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When a permission Property contain the same State twice' { + It 'Should throw the correct error for property ' -ForEach @( + @{ + MockPropertyName = 'Permission' + } + @{ + MockPropertyName = 'PermissionToInclude' + } + @{ + MockPropertyName = 'PermissionToExclude' + } + ) { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.DuplicatePermissionState + } + + InModuleScope -Parameters $_ -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{ + $MockPropertyName = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + } + [DatabasePermission] @{ + State = 'Grant' + } + ) + }) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When the property Permission is missing a state' { + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.MissingPermissionState + } + + InModuleScope -Parameters $_ -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{ + Permission = [DatabasePermission[]] @( + # Missing state Deny. + [DatabasePermission] @{ + State = 'Grant' + } + [DatabasePermission] @{ + State = 'GrantWithGrant' + } + ) + }) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When a permission Property contain the same permission name twice' { + It 'Should throw the correct error for property ' -ForEach @( + @{ + MockPropertyName = 'Permission' + } + @{ + MockPropertyName = 'PermissionToInclude' + } + @{ + MockPropertyName = 'PermissionToExclude' + } + ) { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.DuplicatePermissionBetweenState + } + + InModuleScope -Parameters $_ -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{ + $MockPropertyName = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + Permission = 'Select' + } + [DatabasePermission] @{ + State = 'Deny' + Permission = 'Select' + } + ) + }) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When a permission Property does not specify any permission name' { + It 'Should throw the correct error for property ' -ForEach @( + @{ + MockPropertyName = 'PermissionToInclude' + } + @{ + MockPropertyName = 'PermissionToExclude' + } + ) { + $mockErrorMessage = InModuleScope -ScriptBlock { + $mockSqlDatabasePermissionInstance.localizedData.MustHaveMinimumOnePermissionInState + } + + InModuleScope -Parameters $_ -ScriptBlock { + { + $mockSqlDatabasePermissionInstance.AssertProperties(@{ + $MockPropertyName = [DatabasePermission[]] @( + [DatabasePermission] @{ + State = 'Grant' + <# + This should not be able to be $null since the property + is mandatory but do allow empty collection. So no need + to test using $null value. + #> + Permission = @() + } + [DatabasePermission] @{ + State = 'Deny' + Permission = 'Select' + } + ) + }) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } +} diff --git a/tests/Unit/DSC_SqlDatabasePermission.Tests.ps1 b/tests/Unit/DSC_SqlDatabasePermission.Tests.ps1 deleted file mode 100644 index 39484f940..000000000 --- a/tests/Unit/DSC_SqlDatabasePermission.Tests.ps1 +++ /dev/null @@ -1,1052 +0,0 @@ -<# - .SYNOPSIS - Unit test for DSC_SqlDatabasePermission DSC resource. -#> - -# Suppressing this rule because Script Analyzer does not understand Pester's syntax. -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] -param () - -BeforeDiscovery { - try - { - if (-not (Get-Module -Name 'DscResource.Test')) - { - # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. - if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) - { - # Redirect all streams to $null, except the error stream (stream 2) - & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null - } - - # If the dependencies has not been resolved, this will throw an error. - Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' - } - } - catch [System.IO.FileNotFoundException] - { - throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' - } -} - -BeforeAll { - $script:dscModuleName = 'SqlServerDsc' - $script:dscResourceName = 'DSC_SqlDatabasePermission' - - $script:testEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:dscModuleName ` - -DSCResourceName $script:dscResourceName ` - -ResourceType 'Mof' ` - -TestType 'Unit' - - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') - - # Loading mocked classes - Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') - - # Load the correct SQL Module stub - $script:stubModuleName = Import-SQLModuleStub -PassThru - - $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscResourceName - $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscResourceName - $PSDefaultParameterValues['Should:ModuleName'] = $script:dscResourceName -} - -AfterAll { - $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') - $PSDefaultParameterValues.Remove('Mock:ModuleName') - $PSDefaultParameterValues.Remove('Should:ModuleName') - - Restore-TestEnvironment -TestEnvironment $script:testEnvironment - - # Unload the module being tested so that it doesn't impact any other tests. - Get-Module -Name $script:dscResourceName -All | Remove-Module -Force - - # Unload the stub module. - Remove-SqlModuleStub -Name $script:stubModuleName - - # Remove module common test helper. - Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force -} - -Describe 'SqlDatabasePermission\Get-TargetResource' -Tag 'Get' { - BeforeAll { - $mockConnectSQL = { - return @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { - return @{ - 'AdventureWorks' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { - return @{ - 'Zebes\SamusAran' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force - ) - ) - } - } -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { - return @{ - 'MyAppRole' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force - ) - ) - } - } -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { - return @{ - 'public' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'public' | - Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force - ) - ) - } - } -PassThru | - Add-Member -MemberType 'ScriptMethod' -Name 'EnumDatabasePermissions' -Value { - param - ( - [Parameter()] - [System.String] - $SqlServerLogin - ) - if ($mockInvalidOperationEnumDatabasePermissions) - { - throw 'Mock EnumDatabasePermissions Method was called with invalid operation.' - } - - if ( $SqlServerLogin -eq 'Zebes\SamusAran' ) - { - $mockEnumDatabasePermissions = @() - $mockEnumDatabasePermissions += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value (New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'Zebes\SamusAran' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - $mockEnumDatabasePermissions += New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name PermissionType -Value $(New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true)) -PassThru | - Add-Member -MemberType NoteProperty -Name PermissionState -Value 'Grant' -PassThru | - Add-Member -MemberType NoteProperty -Name Grantee -Value 'Zebes\SamusAran' -PassThru | - Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | - Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - - $mockEnumDatabasePermissions - } - else - { - return $null - } - } -PassThru -Force - ) - ) - } - } -PassThru -Force - ) - ) - } - - Mock -CommandName Connect-SQL -MockWith $mockConnectSQL - - InModuleScope -ScriptBlock { - # Default parameters that are used for the It-blocks. - $script:mockDefaultParameters = @{ - InstanceName = 'MSSQLSERVER' - ServerName = 'localhost' - } - } - } - - BeforeEach { - InModuleScope -ScriptBlock { - $script:mockGetTargetResourceParameters = $script:mockDefaultParameters.Clone() - } - } - - Context 'When the system is in the desired state' { - Context 'When the desired permission does exist' { - BeforeEach { - InModuleScope -ScriptBlock { - $mockGetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockGetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockGetTargetResourceParameters.PermissionState = 'Grant' - $mockGetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - } - } - - It 'Should return the state as present' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Ensure | Should -Be 'Present' - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - It 'Should return the same values as passed as parameters' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.ServerName | Should -Be $mockGetTargetResourceParameters.ServerName - $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName - $result.ServerRoleName | Should -Be $mockGetTargetResourceParameters.ServerRoleName - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When the desired permission does not exist' { - BeforeEach { - InModuleScope -ScriptBlock { - $mockGetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockGetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockGetTargetResourceParameters.PermissionState = 'Grant' - $mockGetTargetResourceParameters.Permissions = @( 'Connect', 'Update', 'Select' ) - } - } - - It 'Should not return the state as absent' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Ensure | Should -Not -Be 'Present' - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should return the same values as passed as parameters' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.ServerName | Should -Be $mockGetTargetResourceParameters.ServerName - $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName - $result.ServerRoleName | Should -Be $mockGetTargetResourceParameters.ServerRoleName - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - } - - Context 'When the system is not in the desired state' { - Context 'When passing values to parameters and database does not exist' { - It 'Should throw the correct error' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockGetTargetResourceParameters.DatabaseName = 'unknownDatabaseName' - $mockGetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockGetTargetResourceParameters.PermissionState = 'Grant' - $mockGetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Ensure | Should -Be 'Absent' - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When permissions are missing' { - BeforeEach { - InModuleScope -ScriptBlock { - $mockGetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockGetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockGetTargetResourceParameters.PermissionState = 'Grant' - $mockGetTargetResourceParameters.Permissions = @( 'Connect', 'Update', 'Select' ) - } - } - - It 'Should return the state as absent' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Ensure | Should -Be 'Absent' - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should return the same values as passed as parameters' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.ServerName | Should -Be $mockGetTargetResourceParameters.ServerName - $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName - $result.ServerRoleName | Should -Be $mockGetTargetResourceParameters.ServerRoleName - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When the desired permission does not exist' { - BeforeEach { - InModuleScope -ScriptBlock { - $mockGetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockGetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockGetTargetResourceParameters.PermissionState = 'Grant' - $mockGetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - } - } - - It 'Should not return the state as absent' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.Ensure | Should -Not -Be 'Absent' - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should return the same values as passed as parameters' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $result = Get-TargetResource @mockGetTargetResourceParameters - - $result.ServerName | Should -Be $mockGetTargetResourceParameters.ServerName - $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName - $result.ServerRoleName | Should -Be $mockGetTargetResourceParameters.ServerRoleName - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - } -} - -Describe "SqlDatabasePermission\Test-TargetResource" -Tag 'Test' { - BeforeAll { - InModuleScope -ScriptBlock { - # Default parameters that are used for the It-blocks. - $script:mockDefaultParameters = @{ - InstanceName = 'MSSQLSERVER' - ServerName = 'localhost' - } - } - } - - BeforeEach { - InModuleScope -ScriptBlock { - $script:mockTestTargetResourceParameters = $script:mockDefaultParameters.Clone() - } - } - - Context 'When the system is in the desired state' { - Context 'When the desired permission should exist' { - BeforeAll { - Mock -CommandName Get-TargetResource -MockWith { - return @{ - ServerName = 'localhost' - InstanceName = 'MSSQLSERVER' - Name = 'Zebes\SamusAran' - PermissionState = 'Grant' - Permissions = @( 'Connect', 'Update' ) - Ensure = 'Present' - } - } - } - - It 'Should return $true' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockTestTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockTestTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockTestTargetResourceParameters.PermissionState = 'Grant' - $mockTestTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockTestTargetResourceParameters.Ensure = 'Present' - - $result = Test-TargetResource @mockTestTargetResourceParameters -Verbose - - $result | Should -BeTrue - } - - Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It - } - } - - Context 'When the desired permission should not exist' { - BeforeAll { - Mock -CommandName Get-TargetResource -MockWith { - return @{ - ServerName = 'localhost' - InstanceName = 'MSSQLSERVER' - Name = 'Zebes\SamusAran' - PermissionState = 'Grant' - Permissions = @() - Ensure = 'Absent' - } - } - } - - It 'Should return $true' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockTestTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockTestTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockTestTargetResourceParameters.PermissionState = 'Grant' - $mockTestTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockTestTargetResourceParameters.Ensure = 'Absent' - - Test-TargetResource @mockTestTargetResourceParameters | Should -BeTrue - } - - Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It - } - } - } - - Context 'When the system is not in the desired state' { - Context 'When the desired permission are missing' { - BeforeAll { - Mock -CommandName Get-TargetResource -MockWith { - return @{ - ServerName = 'localhost' - InstanceName = 'MSSQLSERVER' - Name = 'Zebes\SamusAran' - PermissionState = 'Grant' - Permissions = @( 'Connect', 'Update' ) - Ensure = 'Absent' - } - } - } - - It 'Should return the state as true ' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockTestTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockTestTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockTestTargetResourceParameters.PermissionState = 'Grant' - $mockTestTargetResourceParameters.Permissions = @( 'Connect', 'Update', 'Select' ) - $mockTestTargetResourceParameters.Ensure = 'Present' - - Test-TargetResource @mockTestTargetResourceParameters | Should -BeFalse - } - - Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It - } - } - - Context 'When there are more permissions than desired' { - BeforeAll { - Mock -CommandName Get-TargetResource -MockWith { - return @{ - ServerName = 'localhost' - InstanceName = 'MSSQLSERVER' - Name = 'Zebes\SamusAran' - PermissionState = 'Grant' - Permissions = @( 'Connect', 'Update' ) - Ensure = 'Absent' - } - } - } - - It 'Should return the state as true ' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockTestTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockTestTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockTestTargetResourceParameters.PermissionState = 'Grant' - $mockTestTargetResourceParameters.Permissions = @( 'Connect' ) - $mockTestTargetResourceParameters.Ensure = 'Present' - - Test-TargetResource @mockTestTargetResourceParameters | Should -BeFalse - } - - Should -Invoke -CommandName Get-TargetResource -Exactly -Times 1 -Scope It - } - } - } -} - -Describe "DSC_SqlDatabasePermission\Set-TargetResource" -Tag 'Set' { - BeforeAll { - $mockConnectSQL = { - return @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { - return @{ - 'AdventureWorks' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { - return @{ - 'Zebes\SamusAran' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force - ) - ) - } - } -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { - return @{ - 'MyAppRole' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force - ) - ) - } - } -PassThru | - Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { - return @{ - 'public' = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'public' | - Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force - ) - ) - } - } -PassThru | - Add-Member -MemberType 'ScriptMethod' -Name 'Grant' -Value { - param - ( - [Parameter()] - [System.Object] - $permissionSet, - - [Parameter()] - [System.String] - $SqlServerLogin - ) - - if ($mockInvalidOperationForGrantMethod) - { - throw 'Mocked InvalidOperationException' - } - - InModuleScope -ScriptBlock { - $script:mockMethodGrantWasRan += 1 - } - - # Make sure the method is called with the expected login - if ( $SqlServerLogin -ne 'Zebes\SamusAran' ) - { - throw "Called mocked Grant() method without setting the right login name. Expected '{0}'. But was '{1}'." ` - -f 'Zebes\SamusAran', $SqlServerLogin - } - } -PassThru | - Add-Member -MemberType 'ScriptMethod' -Name 'Revoke' -Value { - param - ( - [Parameter()] - [System.Object] - $permissionSet, - - [Parameter()] - [System.String] - $SqlServerLogin - ) - - if ($mockInvalidOperationForRevokeMethod) - { - throw 'Mocked InvalidOperationException' - } - - InModuleScope -ScriptBlock { - $script:mockMethodRevokeWasRan += 1 - } - - # Make sure the method is called with the expected login - if ( $SqlServerLogin -ne 'Zebes\SamusAran' ) - { - throw "Called mocked Revoke() method without setting the right login name. Expected '{0}'. But was '{1}'." ` - -f 'Zebes\SamusAran', $SqlServerLogin - } - } -PassThru | - Add-Member -MemberType 'ScriptMethod' -Name 'Deny' -Value { - param - ( - [Parameter()] - [System.Object] - $permissionSet, - - [Parameter()] - [System.String] - $SqlServerLogin - ) - - if ($mockInvalidOperationForDenyMethod) - { - throw 'Mocked InvalidOperationException' - } - - InModuleScope -ScriptBlock { - $script:mockMethodDenyWasRan += 1 - } - - # Make sure the method is called with the expected login - if ( $SqlServerLogin -ne 'Zebes\SamusAran' ) - { - throw "Called mocked Deny() method without setting the right login name. Expected '{0}'. But was '{1}'." ` - -f 'Zebes\SamusAran', $SqlServerLogin - } - } -PassThru -Force - ) - ) - } - } -PassThru -Force - ) - ) - } - - Mock -CommandName Connect-SQL -MockWith $mockConnectSQL - - InModuleScope -ScriptBlock { - # Default parameters that are used for the It-blocks. - $script:mockDefaultParameters = @{ - InstanceName = 'MSSQLSERVER' - ServerName = 'localhost' - } - } - } - - BeforeEach { - InModuleScope -ScriptBlock { - $script:mockSetTargetResourceParameters = $script:mockDefaultParameters.Clone() - - $script:mockMethodGrantWasRan = 0 - $script:mockMethodDenyWasRan = 0 - $script:mockMethodRevokeWasRan = 0 - $script:mockMethodCreateWasRan = 0 - } - } - - Context 'When passing values to parameters and database name does not exist' { - It 'Should throw the correct error' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'unknownDatabaseName' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'Grant' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Present' - - $errorMessage = $script:localizedData.DatabaseNotFound -f $mockSetTargetResourceParameters.DatabaseName - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw ('*' + $errorMessage) - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When passing values to parameters and database user does not exist' { - It 'Should throw the correct error' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'unknownLoginNamen' - $mockSetTargetResourceParameters.PermissionState = 'Grant' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Present' - - $errorMessage = $script:localizedData.NameIsMissing -f $mockSetTargetResourceParameters.Name, 'AdventureWorks' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw ('*' + $errorMessage) - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When the system is not in the desired state' { - Context 'When Ensure is set to Present' { - It 'Should call the method Grant() without throwing' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'Grant' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Present' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw - - $script:mockMethodGrantWasRan | Should -Be 1 - $script:mockMethodDenyWasRan | Should -Be 0 - $script:mockMethodRevokeWasRan | Should -Be 0 - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should call the method Grant() (WithGrant) without throwing' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'GrantWithGrant' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Present' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw - - $script:mockMethodGrantWasRan | Should -Be 1 - $script:mockMethodDenyWasRan | Should -Be 0 - $script:mockMethodRevokeWasRan | Should -Be 0 - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should call the method Deny() without throwing' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'Deny' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Present' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw - - $script:mockMethodGrantWasRan | Should -Be 0 - $script:mockMethodDenyWasRan | Should -Be 1 - $script:mockMethodRevokeWasRan | Should -Be 0 - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When Ensure is set to Absent' { - It 'Should call the method Revoke() for permission state ''Grant'' without throwing' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'Grant' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Absent' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw - - $script:mockMethodGrantWasRan | Should -Be 0 - $script:mockMethodDenyWasRan | Should -Be 0 - $script:mockMethodRevokeWasRan | Should -Be 1 - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should call the method Revoke() for permission state ''GrantWithGrant'' without throwing' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'GrantWithGrant' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Absent' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw - - $script:mockMethodGrantWasRan | Should -Be 0 - $script:mockMethodDenyWasRan | Should -Be 0 - $script:mockMethodRevokeWasRan | Should -Be 1 - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - - It 'Should call the method Revoke() for permission state ''Deny'' without throwing' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = 'Deny' - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = 'Absent' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw - - $script:mockMethodGrantWasRan | Should -Be 0 - $script:mockMethodDenyWasRan | Should -Be 0 - $script:mockMethodRevokeWasRan | Should -Be 1 - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - - Context 'When method fails' -ForEach @( - @{ - MockMethod = 'Grant' - MockVariableName = 'mockInvalidOperationForGrantMethod' - MockEnsure = 'Present' - } - @{ - MockMethod = 'Grant' - MockVariableName = 'mockInvalidOperationForRevokeMethod' - MockEnsure = 'Absent' - } - @{ - MockMethod = 'Deny' - MockVariableName = 'mockInvalidOperationForDenyMethod' - MockEnsure = 'Present' - } - ) { - BeforeAll { - Set-Variable -Name $MockVariableName -Value $true - } - - AfterAll { - Set-Variable -Name $MockVariableName -Value $false - } - - It 'Should throw a mocked invalid operation error' { - InModuleScope -Parameters $_ -ScriptBlock { - Set-StrictMode -Version 1.0 - - $mockSetTargetResourceParameters.DatabaseName = 'AdventureWorks' - $mockSetTargetResourceParameters.Name = 'Zebes\SamusAran' - $mockSetTargetResourceParameters.PermissionState = $MockMethod - $mockSetTargetResourceParameters.Permissions = @( 'Connect', 'Update' ) - $mockSetTargetResourceParameters.Ensure = $MockEnsure - - $errorMessage = $script:localizedData.FailedToSetPermissionDatabas -f 'Zebes\SamusAran', 'AdventureWorks' - - { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw -ExpectedMessage ('*' + $errorMessage + '*') - } - - Should -Invoke -CommandName Connect-SQL -Exactly -Times 1 -Scope It - } - } - } -} - -# try -# { -# InModuleScope $script:dscResourceName { -# $mockServerName = 'localhost' -# $mockInstanceName = 'MSSQLSERVER' -# 'AdventureWorks' = 'AdventureWorks' -# 'Zebes\SamusAran' = 'Zebes\SamusAran' -# 'public' = 'public' -# 'MyAppRole' = 'MyAppRole' -# 'Zebes\SamusAran'Unknown = 'Elysia\Chozo' -# 'WindowsUser' = 'WindowsUser' -# $mockInvalidOperationEnumDatabasePermissions = $false -# $mockInvalidOperationForCreateMethod = $false -# $mockExpectedSqlServerLogin = 'Zebes\SamusAran' -# $mockSqlPermissionState = 'Grant' - -# $mockSqlPermissionType01 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($true, $false) -# $mockSqlPermissionType02 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' -ArgumentList @($false, $true) - -# $script:mockMethodGrantWasRan = $false -# $script:mockMethodDenyWasRan = $false -# $script:mockMethodRevokeWasRan = $false -# $script:mockMethodCreateWasRan = $false - -# # Default parameters that are used for the It-blocks -# $mockDefaultParameters = @{ -# InstanceName = $mockInstanceName -# ServerName = $mockServerName -# } - -# #region Function mocks -# $mockConnectSQL = { -# return @( -# ( -# New-Object -TypeName Object | -# Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { -# return @{ -# 'AdventureWorks' = @( -# ( -# New-Object -TypeName Object | -# Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | -# Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { -# return @{ -# 'Zebes\SamusAran' = @( -# ( -# New-Object -TypeName Object | -# Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force -# ) -# ) -# } -# } -PassThru | -# Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { -# return @{ -# 'MyAppRole' = @( -# ( -# New-Object -TypeName Object | -# Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force -# ) -# ) -# } -# } -PassThru | -# Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { -# return @{ -# 'public' = @( -# ( -# New-Object -TypeName Object | -# Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'public' | -# Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force -# ) -# ) -# } -# } -PassThru | -# Add-Member -MemberType 'ScriptMethod' -Name 'EnumDatabasePermissions' -Value { -# param -# ( -# [Parameter()] -# [System.String] -# $SqlServerLogin -# ) -# if ($mockInvalidOperationEnumDatabasePermissions) -# { -# throw 'Mock EnumDatabasePermissions Method was called with invalid operation.' -# } - -# if ( $SqlServerLogin -eq $mockExpectedSqlServerLogin ) -# { -# $mockEnumDatabasePermissions = @() -# $mockEnumDatabasePermissions += New-Object -TypeName Object | -# Add-Member -MemberType NoteProperty -Name PermissionType -Value $mockSqlPermissionType01 -PassThru | -# Add-Member -MemberType NoteProperty -Name PermissionState -Value $mockSqlPermissionState -PassThru | -# Add-Member -MemberType NoteProperty -Name Grantee -Value $mockExpectedSqlServerLogin -PassThru | -# Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | -# Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | -# Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru -# $mockEnumDatabasePermissions += New-Object -TypeName Object | -# Add-Member -MemberType NoteProperty -Name PermissionType -Value $mockSqlPermissionType02 -PassThru | -# Add-Member -MemberType NoteProperty -Name PermissionState -Value $mockSqlPermissionState -PassThru | -# Add-Member -MemberType NoteProperty -Name Grantee -Value $mockExpectedSqlServerLogin -PassThru | -# Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | -# Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'DatabaseName' -PassThru | -# Add-Member -MemberType NoteProperty -Name ObjectName -Value 'AdventureWorks' -PassThru - -# $mockEnumDatabasePermissions -# } -# else -# { -# return $null -# } -# } -PassThru | -# Add-Member -MemberType 'ScriptMethod' -Name 'Grant' -Value { -# param -# ( -# [Parameter()] -# [System.Object] -# $permissionSet, - -# [Parameter()] -# [System.String] -# $SqlServerLogin -# ) - -# $script:mockMethodGrantWasRan = $true - -# if ( $SqlServerLogin -ne $mockExpectedSqlServerLogin ) -# { -# throw "Called mocked Grant() method without setting the right login name. Expected '{0}'. But was '{1}'." ` -# -f $mockExpectedSqlServerLogin, $SqlServerLogin -# } -# } -PassThru | -# Add-Member -MemberType 'ScriptMethod' -Name 'Revoke' -Value { -# param -# ( -# [Parameter()] -# [System.Object] -# $permissionSet, - -# [Parameter()] -# [System.String] -# $SqlServerLogin -# ) - -# $script:mockMethodRevokeWasRan = $true - -# if ( $SqlServerLogin -ne $mockExpectedSqlServerLogin ) -# { -# throw "Called mocked Revoke() method without setting the right login name. Expected '{0}'. But was '{1}'." ` -# -f $mockExpectedSqlServerLogin, $SqlServerLogin -# } -# } -PassThru | -# Add-Member -MemberType 'ScriptMethod' -Name 'Deny' -Value { -# param -# ( -# [Parameter()] -# [System.Object] -# $permissionSet, - -# [Parameter()] -# [System.String] -# $SqlServerLogin -# ) - -# $script:mockMethodDenyWasRan = $true - -# if ( $SqlServerLogin -ne $mockExpectedSqlServerLogin ) -# { -# throw "Called mocked Deny() method without setting the right login name. Expected '{0}'. But was '{1}'." ` -# -f $mockExpectedSqlServerLogin, $SqlServerLogin -# } -# } -PassThru -Force -# ) -# ) -# } -# } -PassThru -Force -# ) -# ) -# } -# #endregion - -# - - - -# } -# } -# finally -# { -# Invoke-TestCleanup -# } diff --git a/tests/Unit/Private/ConvertFrom-CompareResult.Tests.ps1 b/tests/Unit/Private/ConvertFrom-CompareResult.Tests.ps1 new file mode 100644 index 000000000..f086e3846 --- /dev/null +++ b/tests/Unit/Private/ConvertFrom-CompareResult.Tests.ps1 @@ -0,0 +1,105 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'ConvertFrom-CompareResult' -Tag 'Private' { + Context 'When passing as named parameter' { + It 'Should return the correct values in a hashtable' { + InModuleScope -ScriptBlock { + $mockProperties = @( + @{ + Property = 'MyResourceProperty1' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + }, + @{ + Property = 'MyResourceProperty2' + ExpectedValue = 'MyNewValue2' + ActualValue = 'MyValue2' + } + ) + + $result = ConvertFrom-CompareResult -CompareResult $mockProperties + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'MyResourceProperty1' + $result.Keys | Should -Contain 'MyResourceProperty2' + + $result.MyResourceProperty1 | Should -Be 'MyNewValue1' + $result.MyResourceProperty2 | Should -Be 'MyNewValue2' + } + } + } + + Context 'When passing in the pipeline' { + It 'Should return the correct values in a hashtable' { + InModuleScope -ScriptBlock { + $mockProperties = @( + @{ + Property = 'MyResourceProperty1' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + }, + @{ + Property = 'MyResourceProperty2' + ExpectedValue = 'MyNewValue2' + ActualValue = 'MyValue2' + } + ) + + $result = $mockProperties | ConvertFrom-CompareResult + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'MyResourceProperty1' + $result.Keys | Should -Contain 'MyResourceProperty2' + + $result.MyResourceProperty1 | Should -Be 'MyNewValue1' + $result.MyResourceProperty2 | Should -Be 'MyNewValue2' + } + } + } +} diff --git a/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 b/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 new file mode 100644 index 000000000..7926d5451 --- /dev/null +++ b/tests/Unit/Private/ConvertTo-Reason.Tests.ps1 @@ -0,0 +1,127 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'ConvertTo-Reason' -Tag 'Private' { + Context 'When passing an empty collection' { + It 'Should return an empty collection' { + InModuleScope -ScriptBlock { + $mockProperties = @() + + $result = ConvertTo-Reason -Property $mockProperties -ResourceName 'MyResource' + + $result | Should -HaveCount 0 + } + } + } + + Context 'When passing a null value' { + It 'Should return an empty collection' { + InModuleScope -ScriptBlock { + $mockProperties = @() + + $result = ConvertTo-Reason -Property $null -ResourceName 'MyResource' + + $result | Should -HaveCount 0 + } + } + } + + Context 'When passing as named parameter' { + It 'Should return the correct values in a hashtable' { + InModuleScope -ScriptBlock { + $mockProperties = @( + @{ + Property = 'MyResourceProperty1' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + }, + @{ + Property = 'MyResourceProperty2' + ExpectedValue = @('MyNewValue2', 'MyNewValue3') + ActualValue = @('MyValue2', 'MyValue3') + } + ) + + $result = ConvertTo-Reason -Property $mockProperties -ResourceName 'MyResource' + + $result | Should -HaveCount 2 + + $result.Code | Should -Contain 'MyResource:MyResource:MyResourceProperty1' + $result.Phrase | Should -Contain 'The property MyResourceProperty1 should be "MyNewValue1", but was "MyValue1"' + + $result.Code | Should -Contain 'MyResource:MyResource:MyResourceProperty2' + $result.Phrase | Should -Contain 'The property MyResourceProperty2 should be ["MyNewValue2","MyNewValue3"], but was ["MyValue2","MyValue3"]' + } + } + } + + Context 'When passing in the pipeline' { + It 'Should return the correct values in a hashtable' { + InModuleScope -ScriptBlock { + $mockProperties = @( + @{ + Property = 'MyResourceProperty1' + ExpectedValue = 'MyNewValue1' + ActualValue = 'MyValue1' + }, + @{ + Property = 'MyResourceProperty2' + ExpectedValue = @('MyNewValue2', 'MyNewValue3') + ActualValue = @('MyValue2', 'MyValue3') + } + ) + + $result = $mockProperties | ConvertTo-Reason -ResourceName 'MyResource' + + $result | Should -HaveCount 2 + + $result.Code | Should -Contain 'MyResource:MyResource:MyResourceProperty1' + $result.Phrase | Should -Contain 'The property MyResourceProperty1 should be "MyNewValue1", but was "MyValue1"' + + $result.Code | Should -Contain 'MyResource:MyResource:MyResourceProperty2' + $result.Phrase | Should -Contain 'The property MyResourceProperty2 should be ["MyNewValue2","MyNewValue3"], but was ["MyValue2","MyValue3"]' + } + } + } +} diff --git a/tests/Unit/Private/Get-ClassName.Tests.ps1 b/tests/Unit/Private/Get-ClassName.Tests.ps1 new file mode 100644 index 000000000..fa678b8fd --- /dev/null +++ b/tests/Unit/Private/Get-ClassName.Tests.ps1 @@ -0,0 +1,109 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-ClassName' -Tag 'Private' { + Context 'When getting the class name' { + Context 'When passing value with named parameter' { + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-ClassName -InputObject ([System.UInt32] 3) + + $result.GetType().FullName | Should -Be 'System.String[]' + + $result | Should -HaveCount 1 + $result | Should -Contain 'System.UInt32' + } + } + } + + Context 'When passing value in pipeline' { + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = ([System.UInt32] 3) | Get-ClassName + + $result.GetType().FullName | Should -Be 'System.String[]' + + $result | Should -HaveCount 1 + $result | Should -Contain 'System.UInt32' + } + } + } + } + + Context 'When getting the class name and all inherited class names (base classes)' { + Context 'When passing value with named parameter' { + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-ClassName -InputObject ([System.UInt32] 3) -Recurse + + $result.GetType().FullName | Should -Be 'System.String[]' + + $result | Should -HaveCount 2 + $result | Should -Contain 'System.UInt32' + $result | Should -Contain 'System.ValueType' + + $result[0] | Should -Be 'System.UInt32' + $result[1] | Should -Be 'System.ValueType' + } + } + } + + Context 'When passing value in pipeline' { + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = ([System.UInt32] 3) | Get-ClassName -Recurse + + $result.GetType().FullName | Should -Be 'System.String[]' + + $result | Should -HaveCount 2 + $result | Should -Contain 'System.UInt32' + $result | Should -Contain 'System.ValueType' + + $result[0] | Should -Be 'System.UInt32' + $result[1] | Should -Be 'System.ValueType' + } + } + } + } +} diff --git a/tests/Unit/Private/Get-DscProperty.Tests.ps1 b/tests/Unit/Private/Get-DscProperty.Tests.ps1 new file mode 100644 index 000000000..f4bd4539e --- /dev/null +++ b/tests/Unit/Private/Get-DscProperty.Tests.ps1 @@ -0,0 +1,755 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-DscProperty' -Tag 'Private' { + + Context 'When no property is returned' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [System.String] + $MyResourceKeyProperty1 +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -InputObject $script:mockResourceBaseInstance + + $result | Should -BeNullOrEmpty + } + } + } + + Context 'When getting all DSC properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty + + [System.String] $ClassProperty + + hidden [System.String] $HiddenClassProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -HaveCount 5 + $result.Keys | Should -Contain 'MyResourceKeyProperty1' + $result.Keys | Should -Contain 'MyResourceKeyProperty2' + $result.Keys | Should -Contain 'MyResourceMandatoryProperty' + $result.Keys | Should -Contain 'MyResourceProperty' + $result.Keys | Should -Contain 'MyResourceReadProperty' + + $result.Keys | Should -Not -Contain 'ClassProperty' -Because 'the property is not a DSC property' + $result.Keys | Should -Not -Contain 'HiddenClassProperty' -Because 'the property is not a DSC property' + + $result.MyResourceKeyProperty1 | Should -Be 'MockValue1' + $result.MyResourceKeyProperty2 | Should -Be 'MockValue2' + $result.MyResourceMandatoryProperty | Should -Be 'MockValue3' + $result.MyResourceProperty | Should -Be 'MockValue4' + $result.MyResourceReadProperty | Should -BeNullOrEmpty + } + } + } + + Context 'When using parameter Name' { + Context 'When getting a single property' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty + + [System.String] $ClassProperty + + hidden [System.String] $HiddenClassProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Name 'MyResourceProperty' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -HaveCount 1 + $result.Keys | Should -Contain 'MyResourceProperty' + + $result.MyResourceProperty | Should -Be 'MockValue4' + } + } + } + + Context 'When getting multiple properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty + + [System.String] $ClassProperty + + hidden [System.String] $HiddenClassProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Name @('MyResourceProperty', 'MyResourceMandatoryProperty') -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'MyResourceProperty' + $result.Keys | Should -Contain 'MyResourceMandatoryProperty' + + $result.MyResourceProperty | Should -Be 'MockValue4' + $result.MyResourceMandatoryProperty | Should -Be 'MockValue3' + } + } + } + } + + Context 'When using parameter Type' { + Context 'When getting all key properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Type 'Key' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceProperty' -Because 'optional properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceMandatoryProperty' -Because 'mandatory properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' + + $result.Keys | Should -Contain 'MyResourceKeyProperty1' -Because 'the property is a key property' + $result.Keys | Should -Contain 'MyResourceKeyProperty2' -Because 'the property is a key property' + + $result.MyResourceKeyProperty1 | Should -Be 'MockValue1' + $result.MyResourceKeyProperty2 | Should -Be 'MockValue2' + } + } + } + + Context 'When getting all mandatory properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Type 'Mandatory' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty2' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceProperty' -Because 'optional properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' + + $result.Keys | Should -Contain 'MyResourceMandatoryProperty' -Because 'the property is a mandatory property' + + $result.MyResourceMandatoryProperty | Should -Be 'MockValue3' + } + } + } + + Context 'When getting all optional properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Type 'Optional' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceMandatoryProperty' -Because 'mandatory properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty2' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' + + $result.Keys | Should -Contain 'MyResourceProperty' -Because 'the property is a optional property' + + $result.MyResourceProperty | Should -Be 'MockValue4' + } + } + } + + Context 'When getting all read properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Type 'NotConfigurable' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceProperty' -Because 'optional properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceMandatoryProperty' -Because 'mandatory properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty2' -Because 'key properties should not be part of the collection' + + $result.Keys | Should -Contain 'MyResourceReadProperty' -Because 'the property is a read property' + + $result.MyResourceReadProperty | Should -BeNullOrEmpty + } + } + } + + Context 'When getting all optional and mandatory properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Type @('Mandatory', 'Optional') -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty2' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' + + $result.Keys | Should -Contain 'MyResourceMandatoryProperty' -Because 'the property is a mandatory property' + $result.Keys | Should -Contain 'MyResourceProperty' -Because 'the property is a optional property' + + $result.MyResourceMandatoryProperty | Should -Be 'MockValue3' + $result.MyResourceProperty | Should -Be 'MockValue4' + } + } + } + + Context 'When getting specific property names of a certain type' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty + + [System.String] $ClassProperty + + hidden [System.String] $HiddenClassProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty = 'MockValue4' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Name @('MyResourceProperty', 'MyResourceMandatoryProperty') -Type 'Mandatory' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -HaveCount 1 + $result.Keys | Should -Contain 'MyResourceMandatoryProperty' + + $result.Keys | Should -Not -Contain 'MyResourceProperty' -Because 'it is not a mandatory property' + + $result.MyResourceMandatoryProperty | Should -Be 'MockValue3' + } + } + } + } + + Context 'When using parameter HasValue' { + Context 'When getting all optional properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty(Mandatory)] + [System.String] + $MyResourceMandatoryProperty + + [DscProperty()] + [System.String] + $MyResourceProperty1 + + [DscProperty()] + [System.String] + $MyResourceProperty2 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty2 = 'MockValue5' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Type 'Optional' -HasValue -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceMandatoryProperty' -Because 'mandatory properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty1' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceKeyProperty2' -Because 'key properties should not be part of the collection' + $result.Keys | Should -Not -Contain 'MyResourceReadProperty' -Because 'read properties should not be part of the collection' + + $result.Keys | Should -Not -Contain 'MyResourceProperty1' -Because 'the property has a $null value' + + $result.Keys | Should -Contain 'MyResourceProperty2' -Because 'the property has a non-null value' + + $result.MyResourceProperty2 | Should -Be 'MockValue5' + } + } + } + } + + Context 'When getting specific named properties' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty1 + +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty2 + +[DscProperty(Mandatory)] +[System.String] +$MyResourceMandatoryProperty + +[DscProperty()] +[System.String] +$MyResourceProperty1 + +[DscProperty()] +[System.String] +$MyResourceProperty2 + +[DscProperty(NotConfigurable)] +[System.String] +$MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +$script:mockResourceBaseInstance.MyResourceKeyProperty1 = 'MockValue1' +$script:mockResourceBaseInstance.MyResourceKeyProperty2 = 'MockValue2' +$script:mockResourceBaseInstance.MyResourceMandatoryProperty = 'MockValue3' +$script:mockResourceBaseInstance.MyResourceProperty2 = 'MockValue5' +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Get-DscProperty -Name @('MyResourceProperty1', 'MyResourceProperty2') -HasValue -InputObject $script:mockResourceBaseInstance + + $result | Should -BeOfType [System.Collections.Hashtable] + + $result.Keys | Should -Not -Contain 'MyResourceProperty1' -Because 'the property has a $null value' + + $result.Keys | Should -Contain 'MyResourceProperty2' -Because 'the property has a non-null value' + + $result.MyResourceProperty2 | Should -Be 'MockValue5' + } + } + } +} diff --git a/tests/Unit/Private/Get-LocalizedDataRecursive.Tests.ps1 b/tests/Unit/Private/Get-LocalizedDataRecursive.Tests.ps1 new file mode 100644 index 000000000..b9da388a1 --- /dev/null +++ b/tests/Unit/Private/Get-LocalizedDataRecursive.Tests.ps1 @@ -0,0 +1,161 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-LocalizedDataRecursive' -Tag 'Private' { + BeforeAll { + $getLocalizedData_ParameterFilter_Class = { + $FileName -eq 'MyClassResource.strings.psd1' + } + + $getLocalizedData_ParameterFilter_Base = { + $FileName -eq 'MyBaseClass.strings.psd1' + } + + Mock -CommandName Get-LocalizedData -MockWith { + return @{ + ClassStringKey = 'My class string' + } + } -ParameterFilter $getLocalizedData_ParameterFilter_Class + + Mock -CommandName Get-LocalizedData -MockWith { + return @{ + BaseStringKey = 'My base string' + } + } -ParameterFilter $getLocalizedData_ParameterFilter_Base + } + + Context 'When getting localization string for class name' { + Context 'When passing value with named parameter' { + It 'Should return the correct localization strings' { + InModuleScope -ScriptBlock { + $result = Get-LocalizedDataRecursive -ClassName 'MyClassResource' + + $result.Keys | Should -HaveCount 1 + $result.Keys | Should -Contain 'ClassStringKey' + } + + Should -Invoke -CommandName Get-LocalizedData -ParameterFilter $getLocalizedData_ParameterFilter_Class -Exactly -Times 1 -Scope It + } + } + + Context 'When passing value in pipeline' { + It 'Should return the correct localization strings' { + InModuleScope -ScriptBlock { + $result = 'MyClassResource' | Get-LocalizedDataRecursive + + $result.Keys | Should -HaveCount 1 + $result.Keys | Should -Contain 'ClassStringKey' + } + + Should -Invoke -CommandName Get-LocalizedData -ParameterFilter $getLocalizedData_ParameterFilter_Class -Exactly -Times 1 -Scope It + } + } + } + + Context 'When getting localization string for class and base name' { + Context 'When passing value with named parameter' { + It 'Should return the correct localization strings' { + InModuleScope -ScriptBlock { + $result = Get-LocalizedDataRecursive -ClassName @('MyClassResource', 'MyBaseClass') + + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'ClassStringKey' + $result.Keys | Should -Contain 'BaseStringKey' + } + + Should -Invoke -CommandName Get-LocalizedData -ParameterFilter $getLocalizedData_ParameterFilter_Class -Exactly -Times 1 -Scope It + } + } + + Context 'When passing value in pipeline' { + It 'Should return the correct localization strings' { + InModuleScope -ScriptBlock { + $result = @('MyClassResource', 'MyBaseClass') | Get-LocalizedDataRecursive + + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'ClassStringKey' + $result.Keys | Should -Contain 'BaseStringKey' + } + + Should -Invoke -CommandName Get-LocalizedData -ParameterFilter $getLocalizedData_ParameterFilter_Class -Exactly -Times 1 -Scope It + } + } + } + + Context 'When getting localization string for class and base file name' { + Context 'When passing value with named parameter' { + It 'Should return the correct localization strings' { + InModuleScope -ScriptBlock { + $result = Get-LocalizedDataRecursive -ClassName @( + 'MyClassResource.strings.psd1' + 'MyBaseClass.strings.psd1' + ) + + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'ClassStringKey' + $result.Keys | Should -Contain 'BaseStringKey' + } + + Should -Invoke -CommandName Get-LocalizedData -ParameterFilter $getLocalizedData_ParameterFilter_Class -Exactly -Times 1 -Scope It + } + } + + Context 'When passing value in pipeline' { + It 'Should return the correct localization strings' { + InModuleScope -ScriptBlock { + $result = @( + 'MyClassResource.strings.psd1' + 'MyBaseClass.strings.psd1' + ) | Get-LocalizedDataRecursive + + $result.Keys | Should -HaveCount 2 + $result.Keys | Should -Contain 'ClassStringKey' + $result.Keys | Should -Contain 'BaseStringKey' + } + + Should -Invoke -CommandName Get-LocalizedData -ParameterFilter $getLocalizedData_ParameterFilter_Class -Exactly -Times 1 -Scope It + } + } + } +} diff --git a/tests/Unit/Private/Test-ResourceDscPropertyIsAssigned.Tests.ps1 b/tests/Unit/Private/Test-ResourceDscPropertyIsAssigned.Tests.ps1 new file mode 100644 index 000000000..ffc3fa287 --- /dev/null +++ b/tests/Unit/Private/Test-ResourceDscPropertyIsAssigned.Tests.ps1 @@ -0,0 +1,132 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Test-ResourceDscPropertyIsAssigned' -Tag 'Private' { + Context 'When DSC property has a non-null value' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty()] + [System.String] + $MyProperty3 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource] @{ + MyProperty3 = 'AnyValue' +} +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceDscPropertyIsAssigned -Name 'MyProperty3' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeTrue + } + } + } + + + Context 'When DSC property has a non-null value' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty1 + +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty2 + +[DscProperty()] +[System.String] +$MyProperty3 + +[DscProperty(NotConfigurable)] +[System.String] +$MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource] @{} +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceDscPropertyIsAssigned -Name 'MyProperty3' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeFalse + } + } + } +} diff --git a/tests/Unit/Private/Test-ResourceHasDscProperty.Tests.ps1 b/tests/Unit/Private/Test-ResourceHasDscProperty.Tests.ps1 new file mode 100644 index 000000000..84ed3eb78 --- /dev/null +++ b/tests/Unit/Private/Test-ResourceHasDscProperty.Tests.ps1 @@ -0,0 +1,258 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Test-ResourceHasDscProperty' -Tag 'Private' { + Context 'When resource does not have an Ensure property' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty()] + [System.String] + $MyResourceProperty3 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceHasDscProperty -Name 'Ensure' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeFalse + } + } + } + + Context 'When resource have an Ensure property' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty()] + [System.String] + $Ensure + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceHasDscProperty -Name 'Ensure' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeTrue + } + } + } + + Context 'When resource have an Ensure property, but it is not a DSC property' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [System.String] + $Ensure + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource]::new() +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceHasDscProperty -Name 'Ensure' -InputObject $script:mockResourceBaseInstance + + $result | Should -BeFalse + } + } + } + + Context 'When using parameter HasValue' { + Context 'When DSC property has a non-null value' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty1 + + [DscProperty(Key)] + [System.String] + $MyResourceKeyProperty2 + + [DscProperty()] + [System.String] + $MyProperty3 + + [DscProperty(NotConfigurable)] + [System.String] + $MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource] @{ + MyProperty3 = 'AnyValue' +} +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceHasDscProperty -Name 'MyProperty3' -HasValue -InputObject $script:mockResourceBaseInstance + + $result | Should -BeTrue + } + } + } + + Context 'When DSC property has a null value' { + BeforeAll { + <# + Must use a here-string because we need to pass 'using' which must be + first in a scriptblock, but if it is outside the here-string then PowerShell + PowerShell will fail to parse the test script. + #> + $inModuleScopeScriptBlock = @' +class MyMockResource +{ +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty1 + +[DscProperty(Key)] +[System.String] +$MyResourceKeyProperty2 + +[DscProperty()] +[System.String] +$MyProperty3 + +[DscProperty(NotConfigurable)] +[System.String] +$MyResourceReadProperty +} + +$script:mockResourceBaseInstance = [MyMockResource] @{} +'@ + + InModuleScope -ScriptBlock ([Scriptblock]::Create($inModuleScopeScriptBlock)) + } + + It 'Should return the correct value' { + InModuleScope -ScriptBlock { + $result = Test-ResourceHasDscProperty -Name 'MyProperty3' -HasValue -InputObject $script:mockResourceBaseInstance + + $result | Should -BeFalse + } + } + } + } +} diff --git a/tests/Unit/Public/Connect-SqlDscDatabaseEngine.Tests.ps1 b/tests/Unit/Public/Connect-SqlDscDatabaseEngine.Tests.ps1 new file mode 100644 index 000000000..ea2360776 --- /dev/null +++ b/tests/Unit/Public/Connect-SqlDscDatabaseEngine.Tests.ps1 @@ -0,0 +1,79 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'because ConvertTo-SecureString is used to simplify the tests.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Connect-SqlDscDatabaseEngine' -Tag 'Public' { + BeforeAll { + Mock -CommandName Connect-Sql + + $mockCredentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @( + 'COMPANY\SqlAdmin', + ('dummyPassW0rd' | ConvertTo-SecureString -AsPlainText -Force) + ) + + } + + It 'Should call the correct mock with the expected parameters' { + $mockConnectSqlDscDatabaseEngineParameters = @{ + ServerName = 'MyServer' + InstanceName = 'MyInstance' + Credential = $mockCredentials + LoginType = 'WindowsUser' + StatementTimeout = 800 + } + + Connect-SqlDscDatabaseEngine @mockConnectSqlDscDatabaseEngineParameters + + Should -Invoke -CommandName Connect-Sql -ParameterFilter { + $ServerName -eq 'MyServer' -and + $InstanceName -eq 'MyInstance' -and + $Credential -eq $mockCredentials -and + $LoginType -eq 'WindowsUser' -and + $StatementTimeout -eq 800 + } + } +} diff --git a/tests/Unit/Public/ConvertFrom-SqlDscDatabasePermission.Tests.ps1 b/tests/Unit/Public/ConvertFrom-SqlDscDatabasePermission.Tests.ps1 new file mode 100644 index 000000000..ea83d9b01 --- /dev/null +++ b/tests/Unit/Public/ConvertFrom-SqlDscDatabasePermission.Tests.ps1 @@ -0,0 +1,78 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'ConvertFrom-SqlDscDatabasePermission' -Tag 'Public' { + BeforeAll { + $mockPermission = InModuleScope -ScriptBlock { + [DatabasePermission] @{ + State = 'Grant' + Permission = @( + 'Connect' + 'Alter' + ) + } + } + } + + It 'Should return the correct values' { + $mockResult = ConvertFrom-SqlDscDatabasePermission -Permission $mockPermission + + $mockResult.Connect | Should -BeTrue + $mockResult.Alter | Should -BeTrue + $mockResult.Update | Should -BeFalse + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockPermission | ConvertFrom-SqlDscDatabasePermission + + $mockResult.Connect | Should -BeTrue + $mockResult.Alter | Should -BeTrue + $mockResult.Update | Should -BeFalse + } + } +} diff --git a/tests/Unit/Public/ConvertTo-SqlDscDatabasePermission.Tests.ps1 b/tests/Unit/Public/ConvertTo-SqlDscDatabasePermission.Tests.ps1 new file mode 100644 index 000000000..7c3e3b3e5 --- /dev/null +++ b/tests/Unit/Public/ConvertTo-SqlDscDatabasePermission.Tests.ps1 @@ -0,0 +1,563 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'ConvertTo-SqlDscDatabasePermission' -Tag 'Public' { + Context 'When passing empty collection as PermissionInfo' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 0 + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission + + $mockResult | Should -HaveCount 0 + } + } + } + + Context 'When permission state is only Grant' { + Context 'When the array contain a single PermissionInfo with a single permission' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet.Connect = $true + + $mockDatabasePermissionInfo = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo.PermissionState = 'Grant' + $mockDatabasePermissionInfo.PermissionType = $mockDatabasePermissionSet + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Grant' + $mockResult[0].Permission | Should -Contain 'Connect' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Grant' + $mockResult[0].Permission | Should -Contain 'Connect' + } + } + } + + Context 'When the array contain multiple PermissionInfo with a single permission' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Grant' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Grant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Grant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + } + } + } + + Context 'When the array contain multiple PermissionInfo with a multiple permissions' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Select = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Grant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + $mockDatabasePermissionSet2.Delete = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Grant' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Grant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + $mockResult[0].Permission | Should -Contain 'Select' + $mockResult[0].Permission | Should -Contain 'Delete' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Grant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + $mockResult[0].Permission | Should -Contain 'Select' + $mockResult[0].Permission | Should -Contain 'Delete' + } + } + } + } + + Context 'When permission state is only Deny' { + Context 'When the array contain a single PermissionInfo with a single permission' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet.Connect = $true + + $mockDatabasePermissionInfo = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo.PermissionState = 'Deny' + $mockDatabasePermissionInfo.PermissionType = $mockDatabasePermissionSet + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Deny' + $mockResult[0].Permission | Should -Contain 'Connect' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Deny' + $mockResult[0].Permission | Should -Contain 'Connect' + } + } + } + + Context 'When the array contain multiple PermissionInfo with a single permission' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Deny' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Deny' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Deny' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + } + } + } + + Context 'When the array contain multiple PermissionInfo with a multiple permissions' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Select = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'Deny' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + $mockDatabasePermissionSet2.Delete = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Deny' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Deny' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + $mockResult[0].Permission | Should -Contain 'Select' + $mockResult[0].Permission | Should -Contain 'Delete' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'Deny' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + $mockResult[0].Permission | Should -Contain 'Select' + $mockResult[0].Permission | Should -Contain 'Delete' + } + } + } + } + + Context 'When permission state is only GrantWithGrant' { + Context 'When the array contain a single PermissionInfo with a single permission' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet.Connect = $true + + $mockDatabasePermissionInfo = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo.PermissionState = 'GrantWithGrant' + $mockDatabasePermissionInfo.PermissionType = $mockDatabasePermissionSet + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'GrantWithGrant' + $mockResult[0].Permission | Should -Contain 'Connect' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'GrantWithGrant' + $mockResult[0].Permission | Should -Contain 'Connect' + } + } + } + + Context 'When the array contain multiple PermissionInfo with a single permission' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'GrantWithGrant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'GrantWithGrant' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'GrantWithGrant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'GrantWithGrant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + } + } + } + + Context 'When the array contain multiple PermissionInfo with a multiple permissions' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Select = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'GrantWithGrant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + $mockDatabasePermissionSet2.Delete = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'GrantWithGrant' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'GrantWithGrant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + $mockResult[0].Permission | Should -Contain 'Select' + $mockResult[0].Permission | Should -Contain 'Delete' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission + + $mockResult | Should -HaveCount 1 + + $mockResult[0].State | Should -Be 'GrantWithGrant' + $mockResult[0].Permission | Should -Contain 'Connect' + $mockResult[0].Permission | Should -Contain 'Alter' + $mockResult[0].Permission | Should -Contain 'Select' + $mockResult[0].Permission | Should -Contain 'Delete' + } + } + } + } + + Context 'When permission state have all states Grant, GrantWithGrant, and Deny' { + Context 'When the array contain multiple PermissionInfo with a multiple permissions' { + BeforeAll { + [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] $mockDatabasePermissionInfoCollection = @() + + $mockDatabasePermissionSet1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet1.Connect = $true + $mockDatabasePermissionSet1.Select = $true + + $mockDatabasePermissionInfo1 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo1.PermissionState = 'GrantWithGrant' + $mockDatabasePermissionInfo1.PermissionType = $mockDatabasePermissionSet1 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo1 + + $mockDatabasePermissionSet2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet2.Alter = $true + $mockDatabasePermissionSet2.Delete = $true + + $mockDatabasePermissionInfo2 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo2.PermissionState = 'Grant' + $mockDatabasePermissionInfo2.PermissionType = $mockDatabasePermissionSet2 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo2 + + $mockDatabasePermissionSet3 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionSet' + $mockDatabasePermissionSet3.Update = $true + $mockDatabasePermissionSet3.Insert = $true + + $mockDatabasePermissionInfo3 = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo' + $mockDatabasePermissionInfo3.PermissionState = 'Deny' + $mockDatabasePermissionInfo3.PermissionType = $mockDatabasePermissionSet3 + + $mockDatabasePermissionInfoCollection += $mockDatabasePermissionInfo3 + } + + It 'Should return the correct values' { + $mockResult = ConvertTo-SqlDscDatabasePermission -DatabasePermissionInfo $mockDatabasePermissionInfoCollection + + $mockResult | Should -HaveCount 3 + + $grantPermission = $mockResult.Where({ $_.State -eq 'Grant' }) + + $grantPermission.State | Should -Be 'Grant' + $grantPermission.Permission | Should -Contain 'Alter' + $grantPermission.Permission | Should -Contain 'Delete' + + $grantWithGrantPermission = $mockResult.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantPermission.State | Should -Be 'GrantWithGrant' + $grantWithGrantPermission.Permission | Should -Contain 'Connect' + $grantWithGrantPermission.Permission | Should -Contain 'Select' + + + $denyPermission = $mockResult.Where({ $_.State -eq 'Deny' }) + + $denyPermission.State | Should -Be 'Deny' + $denyPermission.Permission | Should -Contain 'Update' + $denyPermission.Permission | Should -Contain 'Insert' + } + + Context 'When passing DatabasePermissionInfo over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockDatabasePermissionInfoCollection | ConvertTo-SqlDscDatabasePermission + + $mockResult | Should -HaveCount 3 + + $grantPermission = $mockResult.Where({ $_.State -eq 'Grant' }) + + $grantPermission.State | Should -Be 'Grant' + $grantPermission.Permission | Should -Contain 'Alter' + $grantPermission.Permission | Should -Contain 'Delete' + + $grantWithGrantPermission = $mockResult.Where({ $_.State -eq 'GrantWithGrant' }) + + $grantWithGrantPermission.State | Should -Be 'GrantWithGrant' + $grantWithGrantPermission.Permission | Should -Contain 'Connect' + $grantWithGrantPermission.Permission | Should -Contain 'Select' + + + $denyPermission = $mockResult.Where({ $_.State -eq 'Deny' }) + + $denyPermission.State | Should -Be 'Deny' + $denyPermission.Permission | Should -Contain 'Update' + $denyPermission.Permission | Should -Contain 'Insert' + } + } + } + } +} diff --git a/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 b/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 new file mode 100644 index 000000000..9ccc05e41 --- /dev/null +++ b/tests/Unit/Public/Get-SqlDscDatabasePermission.Tests.ps1 @@ -0,0 +1,225 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Get-SqlDscDatabasePermission' -Tag 'Public' { + Context 'When no databases exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return $null + } -PassThru -Force + } + + Context 'When specifying to throw on error' { + BeforeAll { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingDatabase + } + } + + It 'Should throw the correct error' { + { Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'MissingDatabase' -Name 'KnownUser' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') + } + } + + Context 'When ignoring the error' { + It 'Should not throw an exception and return $null' { + Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'MissingDatabase' -Name 'KnownUser' -ErrorAction 'SilentlyContinue' | + Should -BeNullOrEmpty + } + } + } + + Context 'When the specified database does not exist among existing database' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'AdventureWorks' -PassThru -Force + } + } -PassThru -Force + } + + Context 'When specifying to throw on error' { + BeforeAll { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingDatabase + } + } + + It 'Should throw the correct error' { + { Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'MissingDatabase' -Name 'KnownUser' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') + } + } + + Context 'When ignoring the error' { + It 'Should not throw an exception and return $null' { + Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'MissingDatabase' -Name 'KnownUser' -ErrorAction 'SilentlyContinue' | + Should -BeNullOrEmpty + } + } + } + + Context 'When the database principal does not exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'AdventureWorks' -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $false + } + } + + Context 'When specifying to throw on error' { + BeforeAll { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingPrincipal + } + } + + It 'Should throw the correct error' { + { Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UnknownUser' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'UnknownUser', 'AdventureWorks') + } + } + + Context 'When ignoring the error' { + It 'Should not throw an exception and return $null' { + Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UnknownUser' -ErrorAction 'SilentlyContinue' | + Should -BeNullOrEmpty + } + } + } + + Context 'When the database principal exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'EnumDatabasePermissions' -Value { + param + ( + [Parameter()] + [System.String] + $SqlServerLogin + ) + + $mockEnumDatabasePermissions = [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[]] @() + + $mockEnumDatabasePermissions += [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo] @{ + PermissionType = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + PermissionState = 'Grant' + Grantee = 'Zebes\SamusAran' + GrantorType = 'User' + ObjectClass = 'DatabaseName' + ObjectName = 'AdventureWork' + } + + $mockEnumDatabasePermissions += [Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo] @{ + PermissionType = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Update = $true + } + PermissionState = 'Grant' + Grantee = 'Zebes\SamusAran' + GrantorType = 'User' + ObjectClass = 'DatabaseName' + ObjectName = 'AdventureWork' + } + + return $mockEnumDatabasePermissions + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + } + + It 'Should return the correct values' { + $mockResult = Get-SqlDscDatabasePermission -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'Zebes\SamusAran' -ErrorAction 'Stop' + + $mockResult | Should -HaveCount 2 + + $mockResult[0].PermissionState | Should -Be 'Grant' + $mockResult[0].PermissionType.Connect | Should -BeTrue + $mockResult[0].PermissionType.Update | Should -BeFalse + + $mockResult[1].PermissionState | Should -Be 'Grant' + $mockResult[1].PermissionType.Connect | Should -BeFalse + $mockResult[1].PermissionType.Update | Should -BeTrue + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should return the correct values' { + $mockResult = $mockServerObject | Get-SqlDscDatabasePermission -DatabaseName 'AdventureWorks' -Name 'Zebes\SamusAran' -ErrorAction 'Stop' + + $mockResult | Should -HaveCount 2 + + $mockResult[0].PermissionState | Should -Be 'Grant' + $mockResult[0].PermissionType.Connect | Should -BeTrue + $mockResult[0].PermissionType.Update | Should -BeFalse + + $mockResult[1].PermissionState | Should -Be 'Grant' + $mockResult[1].PermissionType.Connect | Should -BeFalse + $mockResult[1].PermissionType.Update | Should -BeTrue + } + } + } +} diff --git a/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 b/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 new file mode 100644 index 000000000..7d77f4d98 --- /dev/null +++ b/tests/Unit/Public/Set-SqlDscDatabasePermission.Tests.ps1 @@ -0,0 +1,557 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Set-SqlDscDatabasePermission' -Tag 'Public' { + Context 'When no databases exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return $null + } -PassThru -Force + + $script:mockDefaultParameters = @{ + DatabaseName = 'MissingDatabase' + Name = 'KnownUser' + State = 'Grant' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]::new() + } + } + + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingDatabase + } + + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') + } + } + + Context 'When the specified database does not exist among existing database' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'AdventureWorks' -PassThru -Force + } + } -PassThru -Force + + $script:mockDefaultParameters = @{ + DatabaseName = 'MissingDatabase' + Name = 'KnownUser' + State = 'Grant' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]::new() + } + } + + + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingDatabase + } + + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') + } + } + + Context 'When the database principal does not exist' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'AdventureWorks' -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $false + } + + $script:mockDefaultParameters = @{ + DatabaseName = 'AdventureWorks' + Name = 'UnknownUser' + State = 'Grant' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet]::new() + } + } + + It 'Should throw the correct error' { + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_MissingPrincipal + } + + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'UnknownUser', 'AdventureWorks') + } + } + + Context 'When the database principal exist' { + Context 'When using parameter Confirm with value $false' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Deny' -Value { + $script:mockMethodDenyCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + Confirm = $false + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Deny' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodDenyCallCount = 0 + } + + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodDenyCallCount | Should -Be 1 + } + } + + Context 'When using parameter Force' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Deny' -Value { + $script:mockMethodDenyCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + Force = $true + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Deny' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodDenyCallCount = 0 + } + + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodDenyCallCount | Should -Be 1 + } + } + + Context 'When using parameter WhatIf' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Deny' -Value { + $script:mockMethodDenyCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + WhatIf = $true + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Deny' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodDenyCallCount = 0 + } + + It 'Should not call the mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodDenyCallCount | Should -Be 0 + } + } + + Context 'When permission should be granted' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Grant' -Value { + $script:mockMethodGrantCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + Confirm = $false + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Grant' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodGrantCallCount = 0 + } + + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodGrantCallCount | Should -Be 1 + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should call the correct mocked method' { + { $mockServerObject | Set-SqlDscDatabasePermission @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodGrantCallCount | Should -Be 1 + } + } + } + + Context 'When permission should be granted and using parameter WithGrant' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Grant' -Value { + param + ( + [Parameter()] + $Permission, + + [Parameter()] + $Name, + + [Parameter()] + $WithGrant + ) + + if ($WithGrant) + { + $script:mockMethodGrantUsingWithGrantCallCount += 1 + } + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + Confirm = $false + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Grant' + WithGrant = $true + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodGrantUsingWithGrantCallCount = 0 + } + + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodGrantUsingWithGrantCallCount | Should -Be 1 + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should call the correct mocked method' { + { $mockServerObject | Set-SqlDscDatabasePermission @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodGrantUsingWithGrantCallCount | Should -Be 1 + } + } + } + + Context 'When permission should be revoked' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Revoke' -Value { + $script:mockMethodRevokeCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + Confirm = $false + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Revoke' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodRevokeCallCount = 0 + } + + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodRevokeCallCount | Should -Be 1 + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should call the correct mocked method' { + { $mockServerObject | Set-SqlDscDatabasePermission @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodRevokeCallCount | Should -Be 1 + } + } + } + + Context 'When permission should be revoked and using parameter WithGrant' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Revoke' -Value { + param + ( + [Parameter()] + $Permission, + + [Parameter()] + $Name, + + [Parameter()] + $RevokeGrant, + + [Parameter()] + $Cascade + ) + + if (-not $RevokeGrant -and $Cascade) + { + $script:mockMethodRevokeUsingWithGrantCallCount += 1 + } + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + Confirm = $false + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Revoke' + WithGrant = $true + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodRevokeUsingWithGrantCallCount = 0 + } + + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodRevokeUsingWithGrantCallCount | Should -Be 1 + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should call the correct mocked method' { + { $mockServerObject | Set-SqlDscDatabasePermission @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodGrantUsingWithGrantCallCount | Should -Be 1 + } + } + } + + Context 'When permission should be denied' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptMethod' -Name 'Deny' -Value { + $script:mockMethodDenyCallCount += 1 + } -PassThru -Force + } + } -PassThru -Force + + Mock -CommandName Test-SqlDscIsDatabasePrincipal -MockWith { + return $true + } + + $script:mockDefaultParameters = @{ + Confirm = $false + DatabaseName = 'AdventureWorks' + Name = 'Zebes\SamusAran' + State = 'Deny' + Permission = [Microsoft.SqlServer.Management.Smo.DatabasePermissionSet] @{ + Connect = $true + } + } + } + + BeforeEach { + $script:mockMethodDenyCallCount = 0 + } + + It 'Should call the correct mocked method' { + { Set-SqlDscDatabasePermission -ServerObject $mockServerObject @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodDenyCallCount | Should -Be 1 + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should call the correct mocked method' { + { $mockServerObject | Set-SqlDscDatabasePermission @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodDenyCallCount | Should -Be 1 + } + } + + Context 'When passing WithGrant' { + BeforeAll { + Mock -CommandName Write-Warning + } + + It 'Should output the correct warning message and return the correct values' { + $mockWarningMessage = InModuleScope -ScriptBlock { + $script:localizedData.DatabasePermission_IgnoreWithGrantForStateDeny + } + + { $mockServerObject | Set-SqlDscDatabasePermission -WithGrant @mockDefaultParameters } | + Should -Not -Throw + + $script:mockMethodDenyCallCount | Should -Be 1 + + Should -Invoke -CommandName 'Write-Warning' -ParameterFilter { + $Message -eq $mockWarningMessage + } + } + } + } + } +} diff --git a/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 b/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 new file mode 100644 index 000000000..1ef851439 --- /dev/null +++ b/tests/Unit/Public/Test-SqlDscIsDatabasePrincipal.Tests.ps1 @@ -0,0 +1,403 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'Test-SqlDscIsDatabasePrincipal' -Tag 'Public' { + Context 'When database does not have the specified principal' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @( + @{ + 'AdventureWorks' = New-Object -TypeName Object + } + ) + } -PassThru -Force + + $mockErrorMessage = InModuleScope -ScriptBlock { + $script:localizedData.IsDatabasePrincipal_DatabaseMissing + } + } + + It 'Should throw the correct error' { + { Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'MissingDatabase' -Name 'KnownUser' } | + Should -Throw -ExpectedMessage ($mockErrorMessage -f 'MissingDatabase') + } + } + + Context 'When database does not have the specified principal' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UnknownUser' + + $result | Should -BeFalse + } + } + + Context 'When database have the specified principal' { + Context 'When the specified principal is a user' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $true' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'Zebes\SamusAran' + + $result | Should -BeTrue + } + + Context 'When passing ServerObject over the pipeline' { + It 'Should return $true' { + $result = $mockServerObject | Test-SqlDscIsDatabasePrincipal -DatabaseName 'AdventureWorks' -Name 'Zebes\SamusAran' + + $result | Should -BeTrue + } + } + + Context 'When users are excluded from evaluation' { + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'Zebes\SamusAran' -ExcludeUsers + + $result | Should -BeFalse + } + } + } + + Context 'When the specified principal is a application role' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $true' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'MyAppRole' + + $result | Should -BeTrue + } + + Context 'When application roles are excluded from evaluation' { + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'MyAppRole' -ExcludeApplicationRoles + + $result | Should -BeFalse + } + } + } + + Context 'When the specified principal is a user defined role' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $true' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UserDefinedRole' + + $result | Should -BeTrue + } + + Context 'When roles are excluded from evaluation' { + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UserDefinedRole' -ExcludeRoles + + $result | Should -BeFalse + } + } + + Context 'When fixed roles are excluded from evaluation' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return ( + @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + ).GetEnumerator() | Select-Object -ExpandProperty Value + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $true' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'UserDefinedRole' -ExcludeFixedRoles + + $result | Should -BeTrue + } + } + } + + Context 'When the specified principal is a fixed role' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $true' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'db_datareader' + + $result | Should -BeTrue + } + + Context 'When roles are excluded from evaluation' { + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'db_datareader' -ExcludeRoles + + $result | Should -BeFalse + } + } + + Context 'When fixed roles are excluded from evaluation' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' | + Add-Member -MemberType 'ScriptProperty' -Name 'Databases' -Value { + return @{ + 'AdventureWorks' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name Name -Value 'AdventureWorks' -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Users' -Value { + return @{ + 'Zebes\SamusAran' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'Zebes\SamusAran' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'ApplicationRoles' -Value { + return @{ + 'MyAppRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'MyAppRole' -PassThru -Force + } + } -PassThru | + Add-Member -MemberType 'ScriptProperty' -Name 'Roles' -Value { + return ( + @{ + 'db_datareader' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'db_datareader' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $true -PassThru -Force + + 'UserDefinedRole' = New-Object -TypeName Object | + Add-Member -MemberType 'NoteProperty' -Name 'Name' -Value 'UserDefinedRole' -PassThru | + Add-Member -MemberType 'NoteProperty' -Name 'IsFixedRole' -Value $false -PassThru -Force + } + ).GetEnumerator() | Select-Object -ExpandProperty Value + } -PassThru -Force + } + } -PassThru -Force + } + + It 'Should return $false' { + $result = Test-SqlDscIsDatabasePrincipal -ServerObject $mockServerObject -DatabaseName 'AdventureWorks' -Name 'db_datareader' -ExcludeFixedRoles + + $result | Should -BeFalse + } + } + } + } +} diff --git a/tests/Unit/Stubs/SMO.cs b/tests/Unit/Stubs/SMO.cs index 3d8dd9c63..7bc18e797 100644 --- a/tests/Unit/Stubs/SMO.cs +++ b/tests/Unit/Stubs/SMO.cs @@ -202,39 +202,44 @@ public ServerPermissionInfo( // BaseType: Microsoft.SqlServer.Management.Smo.PermissionSetBase // Used by: // SqlDatabasePermission.Tests.ps1 + // Get-SqlDscDatabasePermission.Tests.ps1 public class DatabasePermissionSet { public DatabasePermissionSet(){} - public DatabasePermissionSet( bool connect, bool update ) - { - this.Connect = connect; - this.Update = update; - } - public bool Connect = false; public bool Update = false; + public bool Select = false; + public bool Insert = false; + public bool Alter = false; + public bool CreateDatabase = false; + public bool Delete = false; } // TypeName: Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo // BaseType: Microsoft.SqlServer.Management.Smo.PermissionInfo // Used by: // SqlDatabasePermission.Tests.ps1 + // Get-SqlDscDatabasePermission.Tests.ps1 public class DatabasePermissionInfo { public DatabasePermissionInfo() { - Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet = { new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet() }; + Microsoft.SqlServer.Management.Smo.DatabasePermissionSet permissionSet = new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet(); this.PermissionType = permissionSet; } - public DatabasePermissionInfo( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet ) + public DatabasePermissionInfo( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet permissionSet ) { this.PermissionType = permissionSet; } - public Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] PermissionType; - public string PermissionState = "Grant"; + public Microsoft.SqlServer.Management.Smo.DatabasePermissionSet PermissionType; + public string PermissionState; + public string Grantee; + public string GrantorType; + public string ObjectClass; + public string ObjectName; } // TypeName: Microsoft.SqlServer.Management.Smo.Server @@ -554,17 +559,6 @@ public Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[] EnumDatabaseP { List listOfDatabasePermissionInfo = new List(); - if( Globals.GenerateMockData ) { - Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet = { - new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet( true, false ), - new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet( false, true ) - }; - - listOfDatabasePermissionInfo.Add( new Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo( permissionSet ) ); - } else { - listOfDatabasePermissionInfo.Add( new Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo() ); - } - Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[] permissionInfo = listOfDatabasePermissionInfo.ToArray(); return permissionInfo;