Tests/Public/Update-SecretRotationAccountPassword.Tests.ps1

BeforeAll {
    Import-Module (Join-Path $PSScriptRoot '../../Posh-SecretRotation.psd1') -Force
}

Describe 'Update-SecretRotationAccountPassword' -Tag Unit {
    BeforeEach {
        Mock -ModuleName Posh-SecretRotation Write-SecretRotationLog {}
        Mock -ModuleName Posh-SecretRotation Export-SecretRotationShareFile {}

        $script:testConfig = [PSCustomObject]@{
            targets = [PSCustomObject]@{
                't1' = [PSCustomObject]@{
                    backend                = 'ActiveDirectory'
                    passwordRepresentation = 'SecureString'
                    entropy                = 128
                    shamir                 = [PSCustomObject]@{
                        groupThreshold = 1
                        groups         = @([PSCustomObject]@{ threshold = 3; count = 5 })
                    }
                    backendConfig          = [PSCustomObject]@{ forest = 'corp.local'; identity = 'svc1' }
                }
            }
        }
        Mock -ModuleName Posh-SecretRotation Read-SecretRotationConfigFile { $script:testConfig }

        $script:fakePassword = New-Object System.Security.SecureString
        Mock -ModuleName Posh-SecretRotation New-SecretSharingPassword { $script:fakePassword }

        $script:fakeShares = @([PSCustomObject]@{ Mnemonic = 'a b c' }, [PSCustomObject]@{ Mnemonic = 'd e f' })
        Mock -ModuleName Posh-SecretRotation Split-SecretSharingSecret { $script:fakeShares }

        Mock -ModuleName Posh-SecretRotation Set-SecretRotationBackendPassword {}
    }

    Context 'happy path' {
        It 'generates a password at the target entropy, sets it on the backend, splits it, and returns the shares' {
            $result = Update-SecretRotationAccountPassword -Target 't1' -Confirm:$false

            Should -Invoke -ModuleName Posh-SecretRotation New-SecretSharingPassword -Times 1 -Exactly -ParameterFilter { $Entropy -eq 128 }
            Should -Invoke -ModuleName Posh-SecretRotation Set-SecretRotationBackendPassword -Times 1 -Exactly -ParameterFilter {
                $Backend -eq 'ActiveDirectory' -and $Identifier -eq 'svc1'
            }
            Should -Invoke -ModuleName Posh-SecretRotation Split-SecretSharingSecret -Times 1 -Exactly

            @($result).Count | Should -Be 2
        }

        It 'merges -BackendParameter over the target backendConfig, caller wins' {
            Update-SecretRotationAccountPassword -Target 't1' -BackendParameter @{ identity = 'override-svc' } -Confirm:$false

            Should -Invoke -ModuleName Posh-SecretRotation Set-SecretRotationBackendPassword -Times 1 -Exactly -ParameterFilter {
                $Identifier -eq 'override-svc'
            }
        }

        It 'does not generate a password, call the backend, or split under -WhatIf' {
            Update-SecretRotationAccountPassword -Target 't1' -WhatIf

            Should -Invoke -ModuleName Posh-SecretRotation New-SecretSharingPassword -Times 0 -Exactly
            Should -Invoke -ModuleName Posh-SecretRotation Set-SecretRotationBackendPassword -Times 0 -Exactly
            Should -Invoke -ModuleName Posh-SecretRotation Split-SecretSharingSecret -Times 0 -Exactly
        }

        It 'exports one file per share when -OutputPath is given' {
            Update-SecretRotationAccountPassword -Target 't1' -OutputPath 'C:\fake\out' -Confirm:$false

            # Export-SecretRotationShareFile takes ValueFromPipeline, so piping the 2-element
            # $fakeShares array invokes the (mocked) function once per share, not once overall.
            Should -Invoke -ModuleName Posh-SecretRotation Export-SecretRotationShareFile -Times $script:fakeShares.Count -Exactly
        }

        It 'does not export share files when -OutputPath is omitted' {
            Update-SecretRotationAccountPassword -Target 't1' -Confirm:$false

            Should -Invoke -ModuleName Posh-SecretRotation Export-SecretRotationShareFile -Times 0 -Exactly
        }

        It 'throws when the resolved identity is empty' {
            $script:testConfig.targets.t1.backendConfig = [PSCustomObject]@{ forest = 'corp.local' }

            { Update-SecretRotationAccountPassword -Target 't1' -Confirm:$false } | Should -Throw '*identity*'
            Should -Invoke -ModuleName Posh-SecretRotation Set-SecretRotationBackendPassword -Times 0 -Exactly
        }
    }

    Context 'backend failure' {
        BeforeEach {
            Mock -ModuleName Posh-SecretRotation Set-SecretRotationBackendPassword { throw 'backend exploded' }
        }

        It 'throws and never calls Split-SecretSharingSecret when the backend call fails' {
            { Update-SecretRotationAccountPassword -Target 't1' -Confirm:$false } | Should -Throw '*backend exploded*'
            Should -Invoke -ModuleName Posh-SecretRotation Split-SecretSharingSecret -Times 0 -Exactly
        }
    }

    Context 'unknown target' {
        It 'throws (via Get-SecretRotationTargetConfig) rather than proceeding' {
            { Update-SecretRotationAccountPassword -Target 'does-not-exist' -Confirm:$false } | Should -Throw '*t1*'
        }
    }
}