Public/Tests/Get-DepartmentalGroupName.Tests.ps1

BeforeAll {
    $env:TCS_CONFIG_ROOT = Join-Path -Path $TestDrive -ChildPath 'config'
    $env:TCS_SKIP_UPDATE_CHECK = '1'
    $env:TCS_TELEMETRY_OPTOUT = '1'
    $ModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
    Import-Module -Name (Join-Path -Path $ModuleRoot -ChildPath 'tcs.azure.psd1') -Force
}

AfterAll {
    Remove-Module -Name tcs.azure -Force -ErrorAction SilentlyContinue
}

Describe 'Get-DepartmentalGroupName' {
    Context 'Division only' {
        It 'Returns <Expected> for division <Division>' -ForEach @(
            @{ Division = 'Finance'; Expected = 'SG-Finance' }
            @{ Division = 'human resources'; Expected = 'SG-HumanResources' }
            @{ Division = 'Sales & Marketing'; Expected = 'SG-SalesAndMarketing' }
            @{ Division = 'Legal, Risk and Compliance '; Expected = 'SG-LegalRiskAndCompliance' }
            @{ Division = 'IT'; Expected = 'SG-IT' }
            @{ Division = 'HR'; Expected = 'SG-HR' }
            @{ Division = 'HR -'; Expected = 'SG-HR' }
            @{ Division = '- HR -'; Expected = 'SG-HR' }
            @{ Division = 'Accounts Payable - UK'; Expected = 'SG-AccountsPayable-UK' }
            @{ Division = 'Accounts Payable -- UK'; Expected = 'SG-AccountsPayable-UK' }
            @{ Division = 'Research (R/D)'; Expected = 'SG-ResearchRD' }
            @{ Division = 'Sales/Marketing'; Expected = 'SG-SalesMarketing' }
            @{ Division = "Children's Services"; Expected = 'SG-ChildrensServices' }
            @{ Division = 'E-Commerce'; Expected = 'SG-E-Commerce' }
        ) {
            Get-DepartmentalGroupName -Prefix 'SG' -Division $Division | Should -BeExactly $Expected
        }

        It 'Appends the suffix' {
            Get-DepartmentalGroupName -Prefix 'SG' -Division 'Finance' -Suffix 'Users' | Should -BeExactly 'SG-Finance-Users'
        }
    }

    Context 'Division and department' {
        It 'Abbreviates the division and PascalCases the department' {
            Get-DepartmentalGroupName -Prefix 'SG' -Division 'Human Resources' -Department 'payroll' | Should -BeExactly 'SG-HR-Payroll'
        }

        It 'Removes a leading division abbreviation from the department' {
            Get-DepartmentalGroupName -Prefix 'SG' -Division 'Human Resources' -Department 'HR - Payroll & Benefits' -Suffix 'Users' |
                Should -BeExactly 'SG-HR-PayrollAndBenefits-Users'
        }

        It 'Does not remove the abbreviation from the middle of the department' {
            Get-DepartmentalGroupName -Prefix 'SG' -Division 'Human Resources' -Department 'Shr - Team' | Should -BeExactly 'SG-HR-Shr-Team'
        }

        It 'Handles repeated and trailing spaces without failing' {
            Get-DepartmentalGroupName -Prefix 'SG' -Division 'Human Resources ' -Department ' Talent Acquisition ' | Should -BeExactly 'SG-HR-TalentAcquisition'
        }

        It 'Treats a whitespace-only department as no department' {
            Get-DepartmentalGroupName -Prefix 'SG' -Division 'Finance' -Department ' ' | Should -BeExactly 'SG-Finance'
        }

        It 'Returns <Expected> for division <Division> and department <Department>' -ForEach @(
            @{ Division = 'Sales & Marketing'; Department = 'Digital'; Expected = 'SG-SM-Digital' }
            @{ Division = 'Department of the Treasury'; Department = 'Audit'; Expected = 'SG-DT-Audit' }
            @{ Division = 'IT'; Department = 'Service Desk'; Expected = 'SG-IT-ServiceDesk' }
            @{ Division = 'IT Services'; Department = 'Service Desk'; Expected = 'SG-ITS-ServiceDesk' }
            @{ Division = 'Human Resources'; Department = 'HR'; Expected = 'SG-HR' }
            @{ Division = 'HR'; Department = 'HR -'; Expected = 'SG-HR' }
            @{ Division = 'Human Resources'; Department = 'Human Resources - Payroll'; Expected = 'SG-HR-Payroll' }
            @{ Division = 'Finance'; Department = 'Accounts Payable - UK'; Expected = 'SG-F-AccountsPayable-UK' }
            @{ Division = 'Finance'; Department = 'Payroll (UK/IE)'; Expected = 'SG-F-PayrollUKIE' }
            @{ Division = 'Finance'; Department = ','; Expected = 'SG-Finance' }
            @{ Division = 'FINANCE'; Department = 'Payroll'; Expected = 'SG-F-Payroll' }
            @{ Division = 'NHS Trust'; Department = 'Estates'; Expected = 'SG-NHST-Estates' }
        ) {
            Get-DepartmentalGroupName -Prefix 'SG' -Division $Division -Department $Department | Should -BeExactly $Expected
        }
    }

    Context 'Invalid division' {
        It 'Rejects division <Division> with a clear error' -ForEach @(
            @{ Division = ',' }
            @{ Division = '&' }
            @{ Division = ' - ' }
            @{ Division = '()' }
        ) {
            { Get-DepartmentalGroupName -Prefix 'SG' -Division $Division -ErrorAction Stop } | Should -Throw '*contains no letters or digits*'
        }

        It 'Writes a non-terminating error with the InvalidDivision id and returns nothing' {
            $result = Get-DepartmentalGroupName -Prefix 'SG' -Division ',' -ErrorVariable errors -ErrorAction SilentlyContinue
            $result | Should -BeNullOrEmpty
            $errors[0].FullyQualifiedErrorId | Should -BeLike 'InvalidDivision*'
        }

        It 'Records failed end telemetry for an invalid division' {
            Mock -ModuleName tcs.azure Invoke-TelemetryCollection { }
            $null = Get-DepartmentalGroupName -Prefix 'SG' -Division '&' -ErrorAction SilentlyContinue
            Should -Invoke -ModuleName tcs.azure Invoke-TelemetryCollection -ParameterFilter { $Stage -eq 'End' -and $Failed } -Times 1 -Exactly
        }
    }

    Context 'Culture' {
        BeforeAll {
            $script:OriginalCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture
        }

        AfterEach {
            [System.Threading.Thread]::CurrentThread.CurrentCulture = $script:OriginalCulture
        }

        It 'Uses invariant casing under tr-TR' {
            [System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo]::GetCultureInfo('tr-TR')
            Get-DepartmentalGroupName -Prefix 'SG' -Division 'information technology' -Department 'identity' | Should -BeExactly 'SG-IT-Identity'
            Get-DepartmentalGroupName -Prefix 'SG' -Division 'internal audit' | Should -BeExactly 'SG-InternalAudit'
        }
    }

    Context 'Pipeline input' {
        It 'Accepts Division, Department, Prefix and Suffix by property name' {
            $rows = @(
                [pscustomobject]@{ Prefix = 'SG'; Division = 'Finance'; Department = 'Payroll'; Suffix = 'Users' }
                [pscustomobject]@{ Prefix = 'DL'; Division = 'Human Resources'; Department = '' }
            )
            $rows | Get-DepartmentalGroupName | Should -BeExactly @('SG-F-Payroll-Users', 'DL-HumanResources')
        }

        It 'Takes the prefix from the command line for piped rows' {
            @([pscustomobject]@{ Division = 'Finance' }, [pscustomobject]@{ Division = 'Legal' }) | Get-DepartmentalGroupName -Prefix 'SG' |
                Should -BeExactly @('SG-Finance', 'SG-Legal')
        }

        It 'Continues with the next row after an invalid division' {
            $result = @([pscustomobject]@{ Division = ',' }, [pscustomobject]@{ Division = 'Legal' }) |
                Get-DepartmentalGroupName -Prefix 'SG' -ErrorAction SilentlyContinue
            $result | Should -BeExactly 'SG-Legal'
        }
    }

    Context 'Telemetry' {
        BeforeEach {
            Mock -ModuleName tcs.azure Invoke-TelemetryCollection { }
        }

        It 'Records start and end telemetry and returns only the name' {
            $result = @(Get-DepartmentalGroupName -Prefix 'SG' -Division 'Finance')
            $result | Should -BeExactly @('SG-Finance')
            Should -Invoke -ModuleName tcs.azure Invoke-TelemetryCollection -ParameterFilter { $Stage -eq 'Start' -and $CommandName -eq 'Get-DepartmentalGroupName' } -Times 1 -Exactly
            Should -Invoke -ModuleName tcs.azure Invoke-TelemetryCollection -ParameterFilter { $Stage -eq 'End' -and -not $Failed } -Times 1 -Exactly
            Should -Invoke -ModuleName tcs.azure Invoke-TelemetryCollection -ParameterFilter { $Failed } -Times 0 -Exactly
        }

        It 'Records one start and one end for a piped batch' {
            $null = @('Finance', 'Legal', 'Sales') | ForEach-Object { [pscustomobject]@{ Division = $_ } } | Get-DepartmentalGroupName -Prefix 'SG'
            Should -Invoke -ModuleName tcs.azure Invoke-TelemetryCollection -ParameterFilter { $Stage -eq 'Start' } -Times 1 -Exactly
            Should -Invoke -ModuleName tcs.azure Invoke-TelemetryCollection -ParameterFilter { $Stage -eq 'End' } -Times 1 -Exactly
        }

        It 'Records end telemetry when a downstream command stops the pipeline early' {
            $result = @('Finance', 'Legal', 'Sales') | ForEach-Object { [pscustomobject]@{ Division = $_ } } |
                Get-DepartmentalGroupName -Prefix 'SG' | Select-Object -First 1
            $result | Should -BeExactly 'SG-Finance'
            Should -Invoke -ModuleName tcs.azure Invoke-TelemetryCollection -ParameterFilter { $Stage -eq 'End' -and -not $Failed } -Times 1 -Exactly
        }
    }

    Context 'Parameter validation' {
        It 'Rejects a prefix containing whitespace' {
            { Get-DepartmentalGroupName -Prefix 'S G' -Division 'Finance' } | Should -Throw
        }

        It 'Rejects a suffix containing whitespace' {
            { Get-DepartmentalGroupName -Prefix 'SG' -Division 'Finance' -Suffix 'All Users' } | Should -Throw
        }

        It 'Rejects an empty division' {
            { Get-DepartmentalGroupName -Prefix 'SG' -Division '' } | Should -Throw
        }

        It 'Returns a string' {
            Get-DepartmentalGroupName -Prefix 'SG' -Division 'Finance' | Should -BeOfType [string]
        }

        It 'Is not flagged as state changing (no ShouldProcess needed)' {
            (Get-Command -Name Get-DepartmentalGroupName).Verb | Should -Be 'Get'
        }
    }

    Context 'New-TcsDepartmentalGroup alias' {
        It 'Is exported as an alias of Get-DepartmentalGroupName' {
            $alias = Get-Command -Name New-TcsDepartmentalGroup
            $alias.CommandType | Should -Be 'Alias'
            $alias.ResolvedCommand.Name | Should -Be 'Get-DepartmentalGroupName'
        }

        It 'Still returns the same names' {
            New-TcsDepartmentalGroup -Prefix 'SG' -Division 'Human Resources' -Department 'Payroll' | Should -BeExactly 'SG-HR-Payroll'
        }
    }
}