diff --git a/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md b/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md index 50f58a6d60..c335f5aa08 100644 --- a/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md +++ b/.github/instructions/dsc-community-style-guidelines-powershell.instructions.md @@ -165,7 +165,7 @@ function Get-Something ## File Rules -- End files with a blank line +- End files with only one blank line - Use CR+LF line endings - Maximum two consecutive newlines diff --git a/CHANGELOG.md b/CHANGELOG.md index 7265619754..86aa8e395f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 job-level env declaration. - `Assert-SqlDscLogin` - Added new public command to validate that a specified SQL Server principal - exists as a login, throwing a terminating error if it doesn't exist. + is a login. +- `Enable-SqlDscLogin` + - Added new public command to enable a SQL Server login. +- `Disable-SqlDscLogin` + - Added new public command to disable a SQL Server login. +- `Test-SqlDscIsLoginEnabled` + - Added new public command to test whether a SQL Server login is enabled. + Throws a terminating error if the specified principal does not exist as a login. - Supports pipeline input and provides detailed error messages with localization. - Uses `Test-SqlDscIsLogin` command for login validation following module patterns. - Added `Get-SqlDscLogin`, `Get-SqlDscRole`, `New-SqlDscLogin`, `New-SqlDscRole`, `Remove-SqlDscRole`, and `Remove-SqlDscLogin` commands for retrieving and managing SQL Server logins and roles with support for refresh, pipeline input, and ShouldProcess. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d24f8aef8f..cb39f5d209 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -294,11 +294,13 @@ stages: 'tests/Integration/Commands/Assert-SqlDscLogin.Integration.Tests.ps1' 'tests/Integration/Commands/New-SqlDscLogin.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscLogin.Integration.Tests.ps1' + 'tests/Integration/Commands/Disable-SqlDscLogin.Integration.Tests.ps1' + 'tests/Integration/Commands/Enable-SqlDscLogin.Integration.Tests.ps1' + 'tests/Integration/Commands/Test-SqlDscIsLoginEnabled.Integration.Tests.ps1' 'tests/Integration/Commands/New-SqlDscRole.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscRole.Integration.Tests.ps1' 'tests/Integration/Commands/Remove-SqlDscRole.Integration.Tests.ps1' 'tests/Integration/Commands/Remove-SqlDscLogin.Integration.Tests.ps1' - # Group 9 'tests/Integration/Commands/Uninstall-SqlDscServer.Integration.Tests.ps1' ) diff --git a/source/Public/Disable-SqlDscLogin.ps1 b/source/Public/Disable-SqlDscLogin.ps1 new file mode 100644 index 0000000000..ab40e2cbb9 --- /dev/null +++ b/source/Public/Disable-SqlDscLogin.ps1 @@ -0,0 +1,120 @@ +<# + .SYNOPSIS + Disables a SQL Server login. + + .DESCRIPTION + This command disables a SQL Server login in a SQL Server Database Engine instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER LoginObject + Specifies a login object to disable. + + .PARAMETER Name + Specifies the name of the server login to be disabled. + + .PARAMETER Force + Specifies that the login should be disabled without any confirmation. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s logins should be refreshed before + trying to disable the login object. This is helpful when logins could have + been modified outside of the **ServerObject**, for example through T-SQL. + But on instances with a large amount of logins it might be better to make + sure the **ServerObject** is recent enough, or pass in **LoginObject**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $loginObject = $serverObject | Get-SqlDscLogin -Name 'MyLogin' + $loginObject | Disable-SqlDscLogin + + Disables the login named **MyLogin**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Disable-SqlDscLogin -Name 'MyLogin' + + Disables the login named **MyLogin**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Disable-SqlDscLogin -Name 'MyLogin' -Force + + Disables the login without confirmation using **-Force**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Disable-SqlDscLogin -Name 'MyLogin' -Refresh + + Refreshes the server logins collection before disabling **MyLogin**. + .INPUTS + [Microsoft.SqlServer.Management.Smo.Server] + + Server object accepted from the pipeline (ServerObject parameter set). + + [Microsoft.SqlServer.Management.Smo.Login] + + Login object accepted from the pipeline (LoginObject parameter set). + + .OUTPUTS + None. +#> +function Disable-SqlDscLogin +{ + [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.')] + [OutputType()] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param + ( + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'LoginObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Login] + $LoginObject, + + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter(ParameterSetName = 'ServerObject')] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + if ($PSCmdlet.ParameterSetName -eq 'ServerObject') + { + $getSqlDscLoginParameters = @{ + ServerObject = $ServerObject + Name = $Name + Refresh = $Refresh + ErrorAction = 'Stop' + } + + # If this command does not find the login it will throw an exception. + $LoginObject = Get-SqlDscLogin @getSqlDscLoginParameters + } + + $verboseDescriptionMessage = $script:localizedData.Login_Disable_ShouldProcessVerboseDescription -f $LoginObject.Name, $LoginObject.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.Login_Disable_ShouldProcessVerboseWarning -f $LoginObject.Name + $captionMessage = $script:localizedData.Login_Disable_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + $LoginObject.Disable() + } + } +} diff --git a/source/Public/Enable-SqlDscLogin.ps1 b/source/Public/Enable-SqlDscLogin.ps1 new file mode 100644 index 0000000000..967e63231c --- /dev/null +++ b/source/Public/Enable-SqlDscLogin.ps1 @@ -0,0 +1,107 @@ +<# + .SYNOPSIS + Enables a SQL Server login. + + .DESCRIPTION + This command enables a SQL Server login in a SQL Server Database Engine instance. + + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER LoginObject + Specifies a login object to enable. + + .PARAMETER Name + Specifies the name of the server login to be enabled. + + .PARAMETER Force + Specifies that the login should be enabled without any confirmation. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s logins should be refreshed before + trying to enable the login object. This is helpful when logins could have + been modified outside of the **ServerObject**, for example through T-SQL. + But on instances with a large amount of logins it might be better to make + sure the **ServerObject** is recent enough, or pass in **LoginObject**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $loginObject = $serverObject | Get-SqlDscLogin -Name 'MyLogin' + $loginObject | Enable-SqlDscLogin + + Enables the login named **MyLogin**. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $serverObject | Enable-SqlDscLogin -Name 'MyLogin' + + Enables the login named **MyLogin**. + + .INPUTS + Microsoft.SqlServer.Management.Smo.Server + When using the ServerObject parameter set, a Server object can be piped in. + + Microsoft.SqlServer.Management.Smo.Login + When using the LoginObject parameter set, a Login object can be piped in. + + .OUTPUTS + None. +#> +function Enable-SqlDscLogin +{ + [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.')] + [OutputType()] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + param + ( + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'LoginObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Login] + $LoginObject, + + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force, + + [Parameter(ParameterSetName = 'ServerObject')] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + if ($PSCmdlet.ParameterSetName -eq 'ServerObject') + { + $getSqlDscLoginParameters = @{ + ServerObject = $ServerObject + Name = $Name + Refresh = $Refresh + ErrorAction = 'Stop' + } + + # If this command does not find the login it will throw an exception. + $LoginObject = Get-SqlDscLogin @getSqlDscLoginParameters + } + + $verboseDescriptionMessage = $script:localizedData.Login_Enable_ShouldProcessVerboseDescription -f $LoginObject.Name, $LoginObject.Parent.InstanceName + $verboseWarningMessage = $script:localizedData.Login_Enable_ShouldProcessVerboseWarning -f $LoginObject.Name + $captionMessage = $script:localizedData.Login_Enable_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + $LoginObject.Enable() + } + } +} diff --git a/source/Public/Test-SqlDscIsLoginEnabled.ps1 b/source/Public/Test-SqlDscIsLoginEnabled.ps1 new file mode 100644 index 0000000000..c43cf14459 --- /dev/null +++ b/source/Public/Test-SqlDscIsLoginEnabled.ps1 @@ -0,0 +1,112 @@ +<# + .SYNOPSIS + Returns whether the server login is enabled or disabled. + + .DESCRIPTION + Tests the state of a SQL Server login and returns a Boolean result. + When a Server object is provided, the login is resolved using + Get-SqlDscLogin (optionally refreshing the server logins first). + When a Login object is provided, its current state is evaluated directly. + .PARAMETER ServerObject + Specifies current server connection object. + + .PARAMETER LoginObject + Specifies a login object to test. + + .PARAMETER Name + Specifies the name of the server login to test. + + .PARAMETER Refresh + Specifies that the **ServerObject**'s logins should be refreshed before + trying to test the login object. This is helpful when logins could have + been modified outside of the **ServerObject**, for example through T-SQL. + But on instances with a large amount of logins it might be better to make + sure the **ServerObject** is recent enough, or pass in **LoginObject**. + + .INPUTS + [Microsoft.SqlServer.Management.Smo.Server] + + Server object accepted from the pipeline. + + [Microsoft.SqlServer.Management.Smo.Login] + + Login object accepted from the pipeline. + + .OUTPUTS + [System.Boolean] + + Returns $true if the login is enabled, $false if the login is disabled. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Test-SqlDscIsLoginEnabled -ServerObject $serverObject -Name 'MyLogin' + + Returns $true if the login is enabled, if not $false is returned. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $loginObject = $serverObject | Get-SqlDscLogin -Name 'MyLogin' + Test-SqlDscIsLoginEnabled -LoginObject $loginObject + + Returns $true if the login is enabled, if not $false is returned. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $result = $serverObject | Test-SqlDscIsLoginEnabled -Name 'MyLogin' + + Demonstrates pipeline usage with ServerObject. Returns $true if the login is enabled, if not $false is returned. + + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + $loginObject = $serverObject | Get-SqlDscLogin -Name 'MyLogin' + $result = $loginObject | Test-SqlDscIsLoginEnabled + + Demonstrates pipeline usage with LoginObject. Returns $true if the login is enabled, if not $false is returned. +#> +function Test-SqlDscIsLoginEnabled +{ + [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(ParameterSetName = 'ServerObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject, + + [Parameter(ParameterSetName = 'LoginObject', Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Login] + $LoginObject, + + [Parameter(ParameterSetName = 'ServerObject', Mandatory = $true)] + [System.String] + $Name, + + [Parameter(ParameterSetName = 'ServerObject')] + [System.Management.Automation.SwitchParameter] + $Refresh + ) + + process + { + if ($PSCmdlet.ParameterSetName -eq 'ServerObject') + { + $getSqlDscLoginParameters = @{ + ServerObject = $ServerObject + Name = $Name + Refresh = $Refresh + ErrorAction = 'Stop' + } + + # If this command does not find the login it will throw an exception. + $loginObjectArray = Get-SqlDscLogin @getSqlDscLoginParameters + + # Pick the only object in the array. + $LoginObject = $loginObjectArray + } + + $loginEnabled = -not $LoginObject.IsDisabled + + return $loginEnabled + } +} diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 92edba1917..adb2deb2ff 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -74,6 +74,18 @@ ConvertFrom-StringData @' Login_Remove_ShouldProcessCaption = Remove login on instance Login_Remove_Failed = Removal of the login '{0}' failed. (RSDL0001) + ## Enable-SqlDscLogin + Login_Enable_ShouldProcessVerboseDescription = Enabling the login '{0}' on the instance '{1}'. + Login_Enable_ShouldProcessVerboseWarning = Are you sure you want to enable the login '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Login_Enable_ShouldProcessCaption = Enable login on instance + + ## Disable-SqlDscLogin + Login_Disable_ShouldProcessVerboseDescription = Disabling the login '{0}' on the instance '{1}'. + Login_Disable_ShouldProcessVerboseWarning = Are you sure you want to disable the login '{0}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Login_Disable_ShouldProcessCaption = Disable login on instance + ## Remove-SqlDscAudit Audit_Remove_ShouldProcessVerboseDescription = Removing the audit '{0}' on the instance '{1}'. Audit_Remove_ShouldProcessVerboseWarning = Are you sure you want to remove the audit '{0}'? diff --git a/tests/Integration/Commands/Disable-SqlDscLogin.Integration.Tests.ps1 b/tests/Integration/Commands/Disable-SqlDscLogin.Integration.Tests.ps1 new file mode 100644 index 0000000000..ea9c484616 --- /dev/null +++ b/tests/Integration/Commands/Disable-SqlDscLogin.Integration.Tests.ps1 @@ -0,0 +1,126 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have 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' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have 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 -Force -ErrorAction 'Stop' +} + +Describe 'Disable-SqlDscLogin' -Tag @('Integration_SQL2016', 'Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $script:mockInstanceName = 'DSCSQLTEST' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential + + # Use existing persistent login for testing + $script:testLoginName = 'IntegrationTestSqlLogin' + } + + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + + # Stop the named instance SQL Server service to save memory on the build worker. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + Context 'When disabling a login using ServerObject parameter set' { + BeforeEach { + Enable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + } + + It 'Should disable the specified login' { + # Verify login is initially enabled + $loginBefore = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName + $loginBefore.IsDisabled | Should -BeFalse + + # Disable the login + Disable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Verify login is now disabled + $loginAfter = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Refresh + $loginAfter.IsDisabled | Should -BeTrue + } + + It 'Should disable the login with Refresh parameter' { + # Disable with Refresh parameter + Disable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Refresh -Force -ErrorAction 'Stop' + + # Verify login is disabled + $loginAfter = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Refresh + $loginAfter.IsDisabled | Should -BeTrue + } + + It 'Should accept ServerObject from pipeline' { + # Disable using pipeline + $script:serverObject | Disable-SqlDscLogin -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Verify login is disabled + $loginAfter = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Refresh + $loginAfter.IsDisabled | Should -BeTrue + } + } + + Context 'When disabling a login using LoginObject parameter set' { + BeforeEach { + Enable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + } + + It 'Should disable the specified login object' { + # Get the login object and disable it + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName + Disable-SqlDscLogin -LoginObject $loginObject -Force -ErrorAction 'Stop' + + # Verify login is disabled + $loginAfter = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Refresh + $loginAfter.IsDisabled | Should -BeTrue + } + + It 'Should accept LoginObject from pipeline' { + # Disable using pipeline + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName + $loginObject | Disable-SqlDscLogin -Force -ErrorAction 'Stop' + + # Verify login is disabled + $loginAfter = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Refresh + $loginAfter.IsDisabled | Should -BeTrue + } + } + + Context 'When disabling a non-existent login' { + It 'Should throw an error for non-existent login' { + { Disable-SqlDscLogin -ServerObject $script:serverObject -Name 'NonExistentLogin' -Force -ErrorAction 'Stop' } | + Should -Throw + } + } +} diff --git a/tests/Integration/Commands/Enable-SqlDscLogin.Integration.Tests.ps1 b/tests/Integration/Commands/Enable-SqlDscLogin.Integration.Tests.ps1 new file mode 100644 index 0000000000..d23c3ba1d9 --- /dev/null +++ b/tests/Integration/Commands/Enable-SqlDscLogin.Integration.Tests.ps1 @@ -0,0 +1,126 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have 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' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have 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 -Force -ErrorAction 'Stop' +} + +Describe 'Enable-SqlDscLogin' -Tag @('Integration_SQL2016', 'Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $script:mockInstanceName = 'DSCSQLTEST' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential + + # Use existing persistent login for testing + $script:testLoginName = 'IntegrationTestSqlLogin' + } + + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + + # Stop the named instance SQL Server service to save memory on the build worker. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + Context 'When enabling a login using ServerObject parameter set' { + BeforeEach { + Disable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + } + + It 'Should enable the specified login' { + # Verify login is initially disabled + $loginBefore = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName + $loginBefore.IsDisabled | Should -BeTrue + + # Enable the login + Enable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Verify login is now enabled + $loginAfter = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Refresh + $loginAfter.IsDisabled | Should -BeFalse + } + + It 'Should enable the login with Refresh parameter' { + # Enable with Refresh parameter + Enable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Refresh -Force -ErrorAction 'Stop' + + # Verify login is enabled + $loginAfter = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Refresh + $loginAfter.IsDisabled | Should -BeFalse + } + + It 'Should accept ServerObject from pipeline' { + # Enable using pipeline + $script:serverObject | Enable-SqlDscLogin -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Verify login is enabled + $loginAfter = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Refresh + $loginAfter.IsDisabled | Should -BeFalse + } + } + + Context 'When enabling a login using LoginObject parameter set' { + BeforeEach { + Disable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + } + + It 'Should enable the specified login object' { + # Get the login object and enable it + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName + Enable-SqlDscLogin -LoginObject $loginObject -Force -ErrorAction 'Stop' + + # Verify login is enabled + $loginAfter = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Refresh + $loginAfter.IsDisabled | Should -BeFalse + } + + It 'Should accept LoginObject from pipeline' { + # Enable using pipeline + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName + $loginObject | Enable-SqlDscLogin -Force -ErrorAction 'Stop' + + # Verify login is enabled + $loginAfter = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Refresh + $loginAfter.IsDisabled | Should -BeFalse + } + } + + Context 'When enabling a non-existent login' { + It 'Should throw an error for non-existent login' { + { Enable-SqlDscLogin -ServerObject $script:serverObject -Name 'NonExistentLogin' -Force -ErrorAction 'Stop' } | + Should -Throw + } + } +} diff --git a/tests/Integration/Commands/Test-SqlDscIsLoginEnabled.Integration.Tests.ps1 b/tests/Integration/Commands/Test-SqlDscIsLoginEnabled.Integration.Tests.ps1 new file mode 100644 index 0000000000..48c8b3fddb --- /dev/null +++ b/tests/Integration/Commands/Test-SqlDscIsLoginEnabled.Integration.Tests.ps1 @@ -0,0 +1,179 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have 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' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have 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 -Force -ErrorAction 'Stop' +} + +Describe 'Test-SqlDscIsLoginEnabled' -Tag @('Integration_SQL2016', 'Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + # Starting the named instance SQL Server service prior to running tests. + Start-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + + $script:mockInstanceName = 'DSCSQLTEST' + + $mockSqlAdministratorUserName = 'SqlAdmin' # Using computer name as NetBIOS name throw exception. + $mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force + + $script:mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new($mockSqlAdministratorUserName, $mockSqlAdministratorPassword) + + $script:serverObject = Connect-SqlDscDatabaseEngine -InstanceName $script:mockInstanceName -Credential $script:mockSqlAdminCredential + + # Use existing persistent login for testing + $script:testLoginName = 'IntegrationTestSqlLogin' + } + + AfterAll { + Disconnect-SqlDscDatabaseEngine -ServerObject $script:serverObject + + # Stop the named instance SQL Server service to save memory on the build worker. + Stop-Service -Name 'MSSQL$DSCSQLTEST' -Verbose -ErrorAction 'Stop' + } + + Context 'When testing login state using ServerObject parameter set' { + It 'Should return True when login is enabled' { + # Ensure login is enabled + Enable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Test if login is enabled + $result = Test-SqlDscIsLoginEnabled -ServerObject $script:serverObject -Name $script:testLoginName + + $result | Should -BeOfType [System.Boolean] + $result | Should -BeTrue + } + + It 'Should return False when login is disabled' { + # Ensure login is disabled + Disable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Test if login is enabled + $result = Test-SqlDscIsLoginEnabled -ServerObject $script:serverObject -Name $script:testLoginName + + $result | Should -BeOfType [System.Boolean] + $result | Should -BeFalse + } + + It 'Should work with Refresh parameter when login is enabled' { + # Ensure login is enabled + Enable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Test with Refresh parameter + $result = Test-SqlDscIsLoginEnabled -ServerObject $script:serverObject -Name $script:testLoginName -Refresh + + $result | Should -BeOfType [System.Boolean] + $result | Should -BeTrue + } + + It 'Should work with Refresh parameter when login is disabled' { + # Ensure login is disabled + Disable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Test with Refresh parameter + $result = Test-SqlDscIsLoginEnabled -ServerObject $script:serverObject -Name $script:testLoginName -Refresh + + $result | Should -BeOfType [System.Boolean] + $result | Should -BeFalse + } + + It 'Should accept ServerObject from pipeline' { + # Ensure login is enabled + Enable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Test using pipeline + $result = $script:serverObject | Test-SqlDscIsLoginEnabled -Name $script:testLoginName + + $result | Should -BeOfType [System.Boolean] + $result | Should -BeTrue + } + } + + Context 'When testing login state using LoginObject parameter set' { + It 'Should return True when login object is enabled' { + # Ensure login is enabled + Enable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Get login object and test + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName + $result = Test-SqlDscIsLoginEnabled -LoginObject $loginObject + + $result | Should -BeOfType [System.Boolean] + $result | Should -BeTrue + } + + It 'Should return False when login object is disabled' { + # Ensure login is disabled + Disable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Get login object and test + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName + $result = Test-SqlDscIsLoginEnabled -LoginObject $loginObject + + $result | Should -BeOfType [System.Boolean] + $result | Should -BeFalse + } + + It 'Should accept LoginObject from pipeline when enabled' { + # Ensure login is enabled + Enable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Test using pipeline + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName + $result = $loginObject | Test-SqlDscIsLoginEnabled + + $result | Should -BeOfType [System.Boolean] + $result | Should -BeTrue + } + + It 'Should accept LoginObject from pipeline when disabled' { + # Ensure login is disabled + Disable-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName -Force -ErrorAction 'Stop' + + # Test using pipeline + $loginObject = Get-SqlDscLogin -ServerObject $script:serverObject -Name $script:testLoginName + $result = $loginObject | Test-SqlDscIsLoginEnabled + + $result | Should -BeOfType [System.Boolean] + $result | Should -BeFalse + } + } + + Context 'When testing a non-existent login' { + It 'Should throw an error for non-existent login' { + { Test-SqlDscIsLoginEnabled -ServerObject $script:serverObject -Name 'NonExistentLogin' -ErrorAction 'Stop' } | + Should -Throw -ExpectedMessage 'There is no login with the name ''NonExistentLogin''.' + } + } + + Context 'When testing built-in logins' { + It 'Should return correct state for sa login' { + $result = Test-SqlDscIsLoginEnabled -ServerObject $script:serverObject -Name 'sa' + + $result | Should -BeOfType [System.Boolean] + # We only assert the type here; specific state is environment-dependent. + } + } +} diff --git a/tests/Unit/Public/Disable-SqlDscLogin.Tests.ps1 b/tests/Unit/Public/Disable-SqlDscLogin.Tests.ps1 new file mode 100644 index 0000000000..67b5ea8854 --- /dev/null +++ b/tests/Unit/Public/Disable-SqlDscLogin.Tests.ps1 @@ -0,0 +1,201 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have 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' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have 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' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # 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 + + # Script-scoped variables for tracking method calls + $script:mockMethodDisableWasRun = 0 +} + +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 + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Disable-SqlDscLogin' -Tag 'Public' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'ServerObject' + ExpectedParameters = '-ServerObject -Name [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + }, + @{ + ExpectedParameterSetName = 'LoginObject' + ExpectedParameters = '-LoginObject [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Disable-SqlDscLogin').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + + It 'Should have ServerObject parameter as mandatory in ServerObject parameter set' { + $parameterInfo = (Get-Command -Name 'Disable-SqlDscLogin').Parameters['ServerObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Name parameter as mandatory in ServerObject parameter set' { + $parameterInfo = (Get-Command -Name 'Disable-SqlDscLogin').Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have LoginObject parameter as mandatory in LoginObject parameter set' { + $parameterInfo = (Get-Command -Name 'Disable-SqlDscLogin').Parameters['LoginObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + Context 'When using parameter set ServerObject' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $mockLoginObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList @('TestLogin') + $mockLoginObject.IsDisabled = $false + + Mock -CommandName Get-SqlDscLogin -MockWith { + return @($mockLoginObject) + } + } + + Context 'When the login should be disabled' { + BeforeAll { + $script:mockMethodDisableWasRun = 0 + + $mockLoginObject | Add-Member -MemberType 'ScriptMethod' -Name 'Disable' -Value { + $script:mockMethodDisableWasRun += 1 + } -Force + + # Add parent property + $mockLoginObject | Add-Member -MemberType 'NoteProperty' -Name 'Parent' -Value $mockServerObject -Force + } + + It 'Should call the correct methods' { + Disable-SqlDscLogin -ServerObject $mockServerObject -Name 'TestLogin' -Force + + Should -Invoke -CommandName Get-SqlDscLogin -ParameterFilter { + $ServerObject -eq $mockServerObject -and $Name -eq 'TestLogin' + } -Exactly -Times 1 -Scope It + + $script:mockMethodDisableWasRun | Should -Be 1 + } + + It 'Should not call Disable method when using WhatIf' { + $script:mockMethodDisableWasRun = 0 + + Disable-SqlDscLogin -ServerObject $mockServerObject -Name 'TestLogin' -WhatIf + + $script:mockMethodDisableWasRun | Should -Be 0 + } + } + + Context 'When the login does not exist' { + BeforeAll { + Mock -CommandName Get-SqlDscLogin -MockWith { + return @() + } + } + + It 'Should throw a terminating error when login is not found' { + { Disable-SqlDscLogin -ServerObject $mockServerObject -Name 'NonExistent' -Force } | Should -Throw + + Should -Invoke -CommandName Get-SqlDscLogin -ParameterFilter { + $ServerObject -eq $mockServerObject -and $Name -eq 'NonExistent' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When using Refresh parameter' { + It 'Should pass Refresh parameter to Get-SqlDscLogin' { + Disable-SqlDscLogin -ServerObject $mockServerObject -Name 'TestLogin' -Refresh -Force + + Should -Invoke -CommandName Get-SqlDscLogin -ParameterFilter { + $ServerObject -eq $mockServerObject -and $Name -eq 'TestLogin' -and $Refresh -eq $true + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When using parameter set LoginObject' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $mockLoginObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList @('TestLogin') + $mockLoginObject.IsDisabled = $false + $mockLoginObject | Add-Member -MemberType 'NoteProperty' -Name 'Parent' -Value $mockServerObject -Force + + $script:mockMethodDisableWasRun = 0 + $script:mockGetSqlDscLoginWasRun = 0 + + $mockLoginObject | Add-Member -MemberType 'ScriptMethod' -Name 'Disable' -Value { + $script:mockMethodDisableWasRun += 1 + } -Force + + Mock -CommandName Get-SqlDscLogin -MockWith { + return @($mockLoginObject) + } + } + + It 'Should call the correct methods and not invoke Get-SqlDscLogin' { + $script:mockMethodDisableWasRun = 0 + + Disable-SqlDscLogin -LoginObject $mockLoginObject -Force + + $script:mockMethodDisableWasRun | Should -Be 1 + Should -Invoke -CommandName Get-SqlDscLogin -Exactly -Times 0 -Scope It + } + + It 'Should not call Disable method when using WhatIf' { + $script:mockMethodDisableWasRun = 0 + + Disable-SqlDscLogin -LoginObject $mockLoginObject -WhatIf + + $script:mockMethodDisableWasRun | Should -Be 0 + } + } +} diff --git a/tests/Unit/Public/Enable-SqlDscLogin.Tests.ps1 b/tests/Unit/Public/Enable-SqlDscLogin.Tests.ps1 new file mode 100644 index 0000000000..16e546828f --- /dev/null +++ b/tests/Unit/Public/Enable-SqlDscLogin.Tests.ps1 @@ -0,0 +1,201 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have 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' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have 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' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # 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 + + # Script-scoped variables for tracking method calls + $script:mockMethodEnableWasRun = 0 +} + +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 + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Enable-SqlDscLogin' -Tag 'Public' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'ServerObject' + ExpectedParameters = '-ServerObject -Name [-Force] [-Refresh] [-WhatIf] [-Confirm] []' + }, + @{ + ExpectedParameterSetName = 'LoginObject' + ExpectedParameters = '-LoginObject [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Enable-SqlDscLogin').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + + It 'Should have ServerObject parameter as mandatory in ServerObject parameter set' { + $parameterInfo = (Get-Command -Name 'Enable-SqlDscLogin').Parameters['ServerObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Name parameter as mandatory in ServerObject parameter set' { + $parameterInfo = (Get-Command -Name 'Enable-SqlDscLogin').Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have LoginObject parameter as mandatory in LoginObject parameter set' { + $parameterInfo = (Get-Command -Name 'Enable-SqlDscLogin').Parameters['LoginObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + Context 'When using parameter set ServerObject' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $mockLoginObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList @('TestLogin') + $mockLoginObject.IsDisabled = $true + + Mock -CommandName Get-SqlDscLogin -MockWith { + return @($mockLoginObject) + } + } + + Context 'When the login should be enabled' { + BeforeAll { + $script:mockMethodEnableWasRun = 0 + + $mockLoginObject | Add-Member -MemberType 'ScriptMethod' -Name 'Enable' -Value { + $script:mockMethodEnableWasRun += 1 + } -Force + + # Add parent property + $mockLoginObject | Add-Member -MemberType 'NoteProperty' -Name 'Parent' -Value $mockServerObject -Force + } + + It 'Should call the correct methods' { + Enable-SqlDscLogin -ServerObject $mockServerObject -Name 'TestLogin' -Force + + Should -Invoke -CommandName Get-SqlDscLogin -ParameterFilter { + $ServerObject -eq $mockServerObject -and $Name -eq 'TestLogin' + } -Exactly -Times 1 -Scope It + + $script:mockMethodEnableWasRun | Should -Be 1 + } + + It 'Should not call Enable method when using WhatIf' { + $script:mockMethodEnableWasRun = 0 + + Enable-SqlDscLogin -ServerObject $mockServerObject -Name 'TestLogin' -WhatIf + + $script:mockMethodEnableWasRun | Should -Be 0 + } + } + + Context 'When the login does not exist' { + BeforeAll { + Mock -CommandName Get-SqlDscLogin -MockWith { + return @() + } + } + + It 'Should throw a terminating error when login is not found' { + { Enable-SqlDscLogin -ServerObject $mockServerObject -Name 'NonExistent' -Force } | Should -Throw + + Should -Invoke -CommandName Get-SqlDscLogin -ParameterFilter { + $ServerObject -eq $mockServerObject -and $Name -eq 'NonExistent' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When using Refresh parameter' { + It 'Should pass Refresh parameter to Get-SqlDscLogin' { + Enable-SqlDscLogin -ServerObject $mockServerObject -Name 'TestLogin' -Refresh -Force + + Should -Invoke -CommandName Get-SqlDscLogin -ParameterFilter { + $ServerObject -eq $mockServerObject -and $Name -eq 'TestLogin' -and $Refresh -eq $true + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When using parameter set LoginObject' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + + $mockLoginObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList @('TestLogin') + $mockLoginObject.IsDisabled = $true + $mockLoginObject | Add-Member -MemberType 'NoteProperty' -Name 'Parent' -Value $mockServerObject -Force + + $script:mockMethodEnableWasRun = 0 + $script:mockGetSqlDscLoginWasRun = 0 + + $mockLoginObject | Add-Member -MemberType 'ScriptMethod' -Name 'Enable' -Value { + $script:mockMethodEnableWasRun += 1 + } -Force + + Mock -CommandName Get-SqlDscLogin -MockWith { + return @($mockLoginObject) + } + } + + It 'Should call the correct methods and not invoke Get-SqlDscLogin' { + $script:mockMethodEnableWasRun = 0 + + Enable-SqlDscLogin -LoginObject $mockLoginObject -Force + + $script:mockMethodEnableWasRun | Should -Be 1 + Should -Invoke -CommandName Get-SqlDscLogin -Exactly -Times 0 -Scope It + } + + It 'Should not call Enable method when using WhatIf' { + $script:mockMethodEnableWasRun = 0 + + Enable-SqlDscLogin -LoginObject $mockLoginObject -WhatIf + + $script:mockMethodEnableWasRun | Should -Be 0 + } + } +} diff --git a/tests/Unit/Public/Test-SqlDscIsLoginEnabled.Tests.ps1 b/tests/Unit/Public/Test-SqlDscIsLoginEnabled.Tests.ps1 new file mode 100644 index 0000000000..159b71d6de --- /dev/null +++ b/tests/Unit/Public/Test-SqlDscIsLoginEnabled.Tests.ps1 @@ -0,0 +1,196 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have 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' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have 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' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName -Force -ErrorAction 'Stop' + + # 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 + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Test-SqlDscIsLoginEnabled' -Tag 'Public' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = 'ServerObject' + ExpectedParameters = '-ServerObject -Name [-Refresh] []' + }, + @{ + ExpectedParameterSetName = 'LoginObject' + ExpectedParameters = '-LoginObject []' + } + ) { + $result = (Get-Command -Name 'Test-SqlDscIsLoginEnabled').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + + It 'Should have ServerObject parameter as mandatory in ServerObject parameter set' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscIsLoginEnabled').Parameters['ServerObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have Name parameter as mandatory in ServerObject parameter set' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscIsLoginEnabled').Parameters['Name'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have LoginObject parameter as mandatory in LoginObject parameter set' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscIsLoginEnabled').Parameters['LoginObject'] + $parameterInfo.Attributes.Mandatory | Should -BeTrue + } + + It 'Should have ServerObject parameter accept pipeline input' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscIsLoginEnabled').Parameters['ServerObject'] + ($parameterInfo.Attributes.ValueFromPipeline -or $parameterInfo.Attributes.ValueFromPipelineByPropertyName) | Should -BeTrue + } + + It 'Should have LoginObject parameter accept pipeline input' { + $parameterInfo = (Get-Command -Name 'Test-SqlDscIsLoginEnabled').Parameters['LoginObject'] + ($parameterInfo.Attributes.ValueFromPipeline -or $parameterInfo.Attributes.ValueFromPipelineByPropertyName) | Should -BeTrue + } + + Context 'When using parameter set ServerObject' { + BeforeAll { + $mockServerObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockServerObject.InstanceName = 'TestInstance' + } + + Context 'When the login is enabled' { + BeforeAll { + $mockLoginObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList @('TestLogin') + $mockLoginObject.IsDisabled = $false + + Mock -CommandName Get-SqlDscLogin -MockWith { + return $mockLoginObject + } + } + + It 'Should return $true' { + $result = Test-SqlDscIsLoginEnabled -ServerObject $mockServerObject -Name 'TestLogin' + + $result | Should -BeTrue + + Should -Invoke -CommandName Get-SqlDscLogin -ParameterFilter { + $ServerObject -eq $mockServerObject -and $Name -eq 'TestLogin' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When the login is disabled' { + BeforeAll { + $mockLoginObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList @('TestLogin') + $mockLoginObject.IsDisabled = $true + + Mock -CommandName Get-SqlDscLogin -MockWith { + return $mockLoginObject + } + } + + It 'Should return $false' { + $result = Test-SqlDscIsLoginEnabled -ServerObject $mockServerObject -Name 'TestLogin' + + $result | Should -BeFalse + + Should -Invoke -CommandName Get-SqlDscLogin -ParameterFilter { + $ServerObject -eq $mockServerObject -and $Name -eq 'TestLogin' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When using Refresh parameter' { + BeforeAll { + $mockLoginObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList @('TestLogin') + $mockLoginObject.IsDisabled = $false + + Mock -CommandName Get-SqlDscLogin -MockWith { + return $mockLoginObject + } + } + + It 'Should pass Refresh parameter to Get-SqlDscLogin' { + $result = Test-SqlDscIsLoginEnabled -ServerObject $mockServerObject -Name 'TestLogin' -Refresh + + $result | Should -BeTrue + + Should -Invoke -CommandName Get-SqlDscLogin -ParameterFilter { + $ServerObject -eq $mockServerObject -and $Name -eq 'TestLogin' -and $Refresh -eq $true + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When using parameter set LoginObject' { + Context 'When the login is enabled' { + BeforeAll { + $mockLoginObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList @('TestLogin') + $mockLoginObject.IsDisabled = $false + } + + It 'Should return $true' { + $result = Test-SqlDscIsLoginEnabled -LoginObject $mockLoginObject + + $result | Should -BeTrue + } + } + + Context 'When the login is disabled' { + BeforeAll { + $mockLoginObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Login' -ArgumentList @('TestLogin') + $mockLoginObject.IsDisabled = $true + } + + It 'Should return $false' { + $result = Test-SqlDscIsLoginEnabled -LoginObject $mockLoginObject + + $result | Should -BeFalse + } + } + } +}