Tests/Unit.Tests.ps1
#region import modules $ThisModule = "$($MyInvocation.MyCommand.Path | Split-Path -Parent | Split-Path -Parent)\PSADSync.psd1" $ThisModuleName = (($ThisModule | Split-Path -Leaf) -replace '\.psd1') Get-Module -Name $ThisModuleName -All | Remove-Module -Force Import-Module -Name $ThisModule -Force -ErrorAction Stop #endregion describe 'Module-level tests' { it 'should validate the module manifest' { { Test-ModuleManifest -Path $ThisModule -ErrorAction Stop } | should not throw } it 'should pass all analyzer rules' { $excludedRules = @( 'PSUseShouldProcessForStateChangingFunctions', 'PSUseToExportFieldsInManifest', 'PSAvoidInvokingEmptyMembers', 'PSUsePSCredentialType', 'PSAvoidUsingPlainTextForPassword' 'PSAvoidUsingConvertToSecureStringWithPlainText' ) Invoke-ScriptAnalyzer -Path $PSScriptRoot -ExcludeRule $excludedRules -Severity Error | Select-Object -ExpandProperty RuleName | should benullorempty } } InModuleScope $ThisModuleName { $script:AllAdUsers = 0..9 | ForEach-Object { $i = $_ $adUser = @{} $props = @{ 'Name' = "nameval$i" 'Enabled' = $true 'SamAccountName' = "samval$i" 'GivenName' = "givennameval$i" 'Surname' = "surnameval$i" 'DisplayName' = "displaynameval$i" 'OtherProperty' = "otherval$i" 'EmployeeId' = $i 'Title' = "titleval$i" } $props.GetEnumerator() | ForEach-Object { if ($_.Key -eq 'Enabled') { if ($i % 2) { $adUser.($_.Key) = $false } else { $adUser.($_.Key) = $true } } else { $adUser.($_.Key) = "$($_.Value)$i" } } if ($i -eq 5) { $adUser.samAccountName = $null } if ($i -eq 6) { $adUser.EmployeeId = $null } [pscustomobject]$adUser } $script:AllCsvUsers = 0..15 | ForEach-Object { $i = $_ $output = @{ AD_LOGON = "nameval$i" PERSON_NUM = "1$i" ExcludeCol = 'dontexcludeme' } if ($i -eq (Get-Random -Maximum 9)) { $output.'AD_LOGON' = $null $output.ExcludeCol = 'excludeme' } if ($i -eq (Get-Random -Maximum 9)) { $output.'PERSON_NUM' = $null } [pscustomobject]$output } describe 'Get-CompanyCsvUser' { $commandName = 'Get-CompanyCsvUser' #region Mocks $script:csvUsers = @( [pscustomobject]@{ AD_LOGON = 'foo' PERSON_NUM = 123 OtherAtrrib = 'x' ExcludeCol = 'excludeme' ExcludeCol2 = 'dontexcludeme' } [pscustomobject]@{ AD_LOGON = 'foo2' PERSON_NUM = 1234 OtherAtrrib = 'x' ExcludeCol = 'dontexcludeme' ExcludeCol2 = 'excludeme' } [pscustomobject]@{ AD_LOGON = 'notinAD' PERSON_NUM = 1234 OtherAtrrib = 'x' ExcludeCol = 'dontexcludeme' ExcludeCol2 = 'dontexcludeme' } [pscustomobject]@{ AD_LOGON = $null PERSON_NUM = 12345 OtherAtrrib = 'x' ExcludeCol = 'dontexcludeme' ExcludeCol2 = 'dontexcludeme' } ) mock 'Import-Csv' { $script:csvUsers } mock 'Test-Path' { $true } $script:csvUsersNullConvert = $script:csvUsers | ForEach-Object { if (-not $_.'AD_LOGON') { $_.'AD_LOGON' = 'null' } $_ } #endregion $parameterSets = @( @{ CsvFilePath = 'C:\users.csv' TestName = 'All users' } @{ CsvFilePath = 'C:\users.csv' Exclude = @{ ExcludeCol = 'excludeme' } TestName = 'Exclude 1 col' } @{ CsvFilePath = 'C:\users.csv' Exclude = @{ ExcludeCol = 'excludeme'; ExcludeCol2 = 'excludeme' } TestName = 'Exclude 2 cols' } ) $testCases = @{ All = $parameterSets Exclude = $parameterSets.where({$_.ContainsKey('Exclude')}) Exclude1Col = $parameterSets.where({$_.ContainsKey('Exclude') -and ($_.Exclude.Keys.Count -eq 1)}) Exclude2Cols = $parameterSets.where({$_.ContainsKey('Exclude') -and ($_.Exclude.Keys.Count -eq 2)}) NoExclusions = $parameterSets.where({ -not $_.ContainsKey('Exclude')}) } context 'when at least one column is excluded' { mock 'Where-Object' { [pscustomobject]@{ AD_LOGON = 'foo2' PERSON_NUM = 1234 OtherAtrrib = 'x' ExcludeCol = 'dontexcludeme' ExcludeCol2 = 'excludeme' } [pscustomobject]@{ AD_LOGON = 'notinAD' PERSON_NUM = 1234 OtherAtrrib = 'x' ExcludeCol = 'dontexcludeme' ExcludeCol2 = 'dontexcludeme' } [pscustomobject]@{ AD_LOGON = $null PERSON_NUM = 12345 OtherAtrrib = 'x' ExcludeCol = 'dontexcludeme' ExcludeCol2 = 'dontexcludeme' } } -ParameterFilter { $FilterScript.ToString() -notmatch '\*' } it 'should create the expected where filter: <TestName>' -TestCases $testCases.Exclude { param($CsvFilePath, $Exclude) & $commandName @PSBoundParameters $assMParams = @{ CommandName = 'Where-Object' Times = $script:csvUsers.Count Exactly = $true Scope = 'It' ParameterFilter = { $PSBoundParameters.FilterScript.ToString() -like "(`$_.`'*' -ne '*')*" } } Assert-MockCalled @assMParams } } it 'when excluding no cols, should return all expected users: <TestName>' -TestCases $testCases.NoExclusions { param($CsvFilePath, $Exclude) $result = & $commandName @PSBoundParameters (Compare-Object $script:csvUsersNullConvert.'AD_LOGON' $result.'AD_LOGON').InputObject | should benullorempty } it 'when excluding 1 col, should return all expected users: <TestName>' -TestCases $testCases.Exclude1Col { param($CsvFilePath, $Exclude) $result = & $commandName @PSBoundParameters (Compare-Object @('foo2', 'notinAD', 'null') $result.'AD_LOGON').InputObject | should benullorempty } it 'when excluding 2 cols, should return all expected users: <TestName>' -TestCases $testCases.Exclude2Cols { param($CsvFilePath, $Exclude) $result = & $commandName @PSBoundParameters (Compare-Object @('notinAD', 'null') $result.'AD_LOGON').InputObject | should benullorempty } } describe 'GetCsvColumnHeaders' { #region Mocks mock 'Get-Content' { @( '"Header1","Header2","Header3"' '"Value1","Value2","Value3"' '"Value4","Value5","Value6"' ) } #endregion it 'should return expected headers' { $result = & GetCsvColumnHeaders -CsvFilePath 'foo.csv' Compare-Object $result @('Header1', 'Header2', 'Header3') | should benullorempty } } describe 'TestCsvHeaderExists' { $commandName = 'TestCsvHeaderExists' $script:command = Get-Command -Name $commandName #region Mocks mock 'GetCsvColumnHeaders' { 'nothinghere', 'nope' } -ParameterFilter { $CsvFilePath -eq 'C:\foofail.csv' } mock 'GetCsvColumnHeaders' { 'Header', 'a', 'b', 'c', 'd', 'e' } -ParameterFilter { $CsvFilePath -eq 'C:\foopass.csv' } mock 'ParseScriptBlockHeaders' { 'Header', 'a', 'b', 'c', 'd', 'e' } $testCases = @( @{ Label = 'Single header / no scriptblocks' Parameters = @{ CsvFilePath = 'C:\foofail.csv' Header = 'fail' } Expected = @{ Execution = @{ ParseScriptBlockHeaders = @{ RunTimes = 0 } } Output = @{ ReturnValue = $false ObjectCount = 1 } } } @{ Label = 'Single header / 1 scriptblock' Parameters = @{ CsvFilePath = 'C:\foofail.csv' Header = { if (-not $_.Header) { '2' } else { '3' } } } Expected = @{ Execution = @{ ParseScriptBlockHeaders = @{ RunTimes = 0 } } Output = @{ ReturnValue = $true ## Returning $true because all scriptblocks and nothing checked. ObjectCount = 1 } } } @{ Label = 'Multiple headers / string/scriptblock' Parameters = @{ CsvFilePath = 'C:\foopass.csv' Header = 'a', { if (-not $_.Header) { 'b' } else { 'c' } }, { if (-not $_.Header) { 'd' } else { 'e' } } } Expected = @{ Execution = @{ ParseScriptBlockHeaders = @{ RunTimes = 0 } } Output = @{ ReturnValue = $true ObjectCount = 1 } } } @{ Label = 'Multiple headers / string/scriptblock / ParseScriptBlockHeaders' Parameters = @{ CsvFilePath = 'C:\foopass.csv' ParseScriptBlockHeaders = $true Header = 'a', { if (-not $_.Header) { 'b' } else { 'c' } }, { if (-not $_.Header) { 'd' } else { 'e' } } } Expected = @{ Execution = @{ ParseScriptBlockHeaders = @{ RunTimes = 2 } } Output = @{ ReturnValue = $true ObjectCount = 1 } } } ) foreach ($testCase in $testCases) { $parameters = $testCase.Parameters $expected = $testCase.Expected context $testCase.Label { $result = & $commandName @parameters it "should call ParseScriptBlockHeaders [$($expected.Execution.ParseScriptBlockHeaders.RunTimes)] times" { $assMParams = @{ CommandName = 'ParseScriptBlockHeaders' Times = $expected.Execution.ParseScriptBlockHeaders.RunTimes Exactly = $true } Assert-MockCalled @assMParams } it "should return [$($expected.Output.ReturnValue)]" { $result | should be $expected.Output.ReturnValue } it "should return [$($expected.Output.ObjectCount)] object(s)" { @($result).Count | should be $expected.Output.ObjectCount } it 'should return the same object type in OutputType()' { $result | should beoftype $script:command.OutputType.Name } } } } describe 'Get-CompanyAdUser' { $commandName = 'Get-CompanyAdUser' #region Mocks mock 'Get-AdUser' { $script:allAdUsers } #endregion $testCases = @( @{ Label = 'Single field match and field sync' Parameters = @{ FieldMatchMap = @{ 'PERSON_NUM' = 'employeeId' } FieldSyncMap = @{ 'csvTitle' = 'title' } } Expected = @{ Output = @{ ObjectCount = 9 } } } @{ Label = 'Multiple field match and field sync' Parameters = @{ FieldMatchMap = @{ 'PERSON_NUM' = 'employeeId' 'csvId' = 'samAccountName' } FieldSyncMap = @{ 'csvTitle' = 'title' 'csvotherprop' = 'OtherProperty' } } Expected = @{ Output = @{ ObjectCount = 10 } } } ) foreach ($testCase in $testCases) { $parameters = $testCase.Parameters $expected = $testCase.Expected context $testCase.Label { $result = & $commandName @parameters it "should return [$($expected.Output.ObjectCount)] objects" { $result.Count | should be $expected.Output.ObjectCount } } } } describe 'FindUserMatch' { $commandName = 'FindUserMatch' #region Mocks mock 'Write-Warning' $script:csvUserMatchOnOneIdentifer = @( [pscustomobject]@{ AD_LOGON = 'foo' PERSON_NUM = 'nomatch' } ) $script:csvUserMatchOnAllIdentifers = @( [pscustomobject]@{ AD_LOGON = 'foo' PERSON_NUM = 123 } ) $script:OneblankCsvUserIdentifier = @( [pscustomobject]@{ PERSON_NUM = $null AD_LOGON = 'foo' } ) $script:AllblankCsvUserIdentifier = @( [pscustomobject]@{ AD_LOGON = $null PERSON_NUM = $null } ) $script:noBlankCsvUserIdentifier = @( [pscustomobject]@{ AD_LOGON = 'ffff' PERSON_NUM = '111111' } ) $script:firstLastMatchCsvUserId = @( [pscustomobject]@{ AD_LOGON = 'ffff' PERSON_NUM = '111111' FIRST_NAME = 'adfirstname1' LAST_NAME = 'adlastname1' } ) $script:firstLastNickMatchCsvUserId = @( [pscustomobject]@{ AD_LOGON = 'ffff' PERSON_NUM = '111111' FIRST_NAME = $null LAST_NAME = 'adlastnamex' NICK_NAME = 'adfirstnamex' } ) $script:firstLastNickMatchCsvUserId2 = @( [pscustomobject]@{ AD_LOGON = 'ffff' PERSON_NUM = '111111' FIRST_NAME = 'adfirstnamex' LAST_NAME = 'adlastnamex' NICK_NAME = $null } ) $script:csvUserNoMatch = @( [pscustomobject]@{ AD_LOGON = 'NotInAd' PERSON_NUM = 'nomatch' } ) $script:AdUsers = @( [pscustomobject]@{ samAccountName = 'foo' EmployeeId = 123 givenName = 'adfirstname1' surName = 'adlastname1' } [pscustomobject]@{ samAccountName = 'foo3' EmployeeId = 1234 givenName = 'adfirstname1' surName = 'adlastname5' } [pscustomobject]@{ samAccountName = 'foo2' EmployeeId = 111 givenName = 'adfirstname2' surName = 'adlastname2' } [pscustomobject]@{ samAccountName = 'foo9' EmployeeId = 999 givenName = 'adfirstnamex' surName = 'adlastnamex' } [pscustomobject]@{ samAccountName = 'NotinCSV' EmployeeId = 12345 givenName = 'adfirstname3' surName = 'adlastname3' } ) #endregion $parameterSets = @( @{ AdUsers = $script:AdUsers CsvUser = $script:csvUserMatchOnOneIdentifer FieldMatchMap = @{ 'AD_LOGON' = 'samAccountName' } TestName = 'Match on 1 ID' } @{ AdUsers = $script:AdUsers CsvUser = $script:csvUserMatchOnAllIdentifers FieldMatchMap = @{ 'PERSON_NUM' = 'EmployeeId' } TestName = 'Match on all IDs' } @{ AdUsers = $script:AdUsers CsvUser = $script:csvUserNoMatch FieldMatchMap = @{ 'PERSON_NUM' = 'EmployeeId' } TestName = 'No Match' } @{ AdUsers = $script:AdUsers CsvUser = $script:OneblankCsvUserIdentifier FieldMatchMap = [ordered]@{ 'PERSON_NUM' = 'EmployeeId' 'AD_LOGON' = 'samAccountName' } TestName = 'One Blank ID' } @{ AdUsers = $script:AdUsers CsvUser = $script:AllblankCsvUserIdentifier FieldMatchMap = @{ 'PERSON_NUM' = 'EmployeeId' 'AD_LOGON' = 'samAccountName' } TestName = 'All Blank IDs' } @{ AdUsers = $script:AdUsers CsvUser = $script:firstLastMatchCsvUserId FieldMatchMap = @{ @( 'FIRST_NAME', 'LAST_NAME') = @('givenName', 'surName') } TestName = 'Multi-string' } @{ AdUsers = $script:AdUsers CsvUser = $script:firstLastNickMatchCsvUserId FieldMatchMap = @{ @({ if ($_.'NICK_NAME') { 'NICK_NAME' } else { 'FIRST_NAME'} }, 'LAST_NAME') = @('givenName', 'surName') } TestName = 'Multi-string conditional' } ) $testCases = @{ All = $parameterSets MatchOnOneId = $parameterSets.where({$_.TestName -eq 'Match on 1 ID'}) MatchOnAllIds = $parameterSets.where({$_.TestName -eq 'Match on all IDs'}) MatchOnFirstNameLastName = $parameterSets.where({$_.TestName -eq 'Multi-string'}) MatchOnFirstNameLastNameConditional = $parameterSets.where({$_.TestName -eq 'Multi-string conditional'}) NoMatch = $parameterSets.where({$_.TestName -eq 'No Match'}) OneBlankId = $parameterSets.where({ $_.CsvUser.AD_LOGON -and -not $_.CsvUser.PERSON_NUM }) AllBlankIds = $parameterSets.where({ -not $_.CsvUser.AD_LOGON -and (-not $_.CsvUser.PERSON_NUM) }) } context 'When no matches could be found' { it 'should return the expected number of objects: <TestName>' -TestCases $testCases.NoMatch { param($AdUsers, $CsvUser, $FieldMatchMap) & $commandName @PSBoundParameters | should benullorempty } } context 'When one match can be found' { it 'should return the expected number of objects: <TestName>' -TestCases $testCases.MatchOnOneId { param($AdUsers, $CsvUser, $FieldMatchMap) $result = & $commandName @PSBoundParameters @($result).Count | should be 1 } it 'should find matches as expected and return the expected property values: <TestName>' -TestCases $testCases.MatchOnOneId { param($AdUsers, $CsvUser, $FieldMatchMap) $result = & $commandName @PSBoundParameters $result.MatchedAdUser.EmployeeId | should be 123 $result.CSVAttemptedMatchIds | should be 'AD_LOGON' $result.ADAttemptedMatchIds | should be 'samAccountName' } } context 'when multiple matches on a single attribute are found' { it 'should throw an exception: <TestName>' -TestCases $testCases.MatchOnAllIds { param($AdUsers, $CsvUser, $FieldMatchMap) { & $commandName @parameters } | should throw } } context 'When multiple matches on different attributes are be found' { it 'should return the expected number of objects: <TestName>' -TestCases $testCases.MatchOnAllIds { param($AdUsers, $CsvUser, $FieldMatchMap) $result = & $commandName @PSBoundParameters @($result).Count | should be 1 } it 'should find matches as expected and return the expected property values: <TestName>' -TestCases $testCases.MatchOnAllIds { param($AdUsers, $CsvUser, $FieldMatchMap) $result = & $commandName @PSBoundParameters $result.MatchedAdUser.EmployeeId | should be 123 $result.CSVAttemptedMatchIds | should be 'PERSON_NUM' $result.ADAttemptedMatchIds | should be 'employeeid' } } context 'when a blank identifier is queried before finding a match' { it 'should return the expected object properties: <TestName>' -TestCases $testCases.OneBlankId { param($AdUsers, $CsvUser, $FieldMatchMap) $result = & $commandName @PSBoundParameters $result.MatchedAdUser.samAccountName | should be 'foo' $result.CSVAttemptedMatchIds | should be 'PERSON_NUM,AD_LOGON' $result.ADAttemptedMatchIds | should be 'EmployeeId,samAccountName' } } context 'when all identifiers are valid' { it 'should return the expected object properties: <TestName>' -TestCases $testCases.MatchOnAllIds { param($AdUsers, $CsvUser, $FieldMatchMap) $result = & $commandName @PSBoundParameters @($result.MatchedAdUser).foreach({ $_.PSObject.Properties.Name -contains 'EmployeeId' | should be $true }) $result.CSVAttemptedMatchIds | should be 'PERSON_NUM' $result.ADAttemptedMatchIds | should be 'employeeId' } } context 'when matching on multi-string' { it 'should return a single object: <TestName>' -TestCases $testCases.MatchOnFirstNameLastName { param($AdUsers, $CsvUser, $FieldMatchMap) $result = & $commandName @PSBoundParameters @($result).Count | should be 1 } it 'should return the expected object properties: <TestName>' -TestCases $testCases.MatchOnFirstNameLastName { param($AdUsers, $CsvUser, $FieldMatchMap) $result = & $commandName @PSBoundParameters $result.MatchedAdUser.EmployeeId | should be 123 $result.MatchedAdUser.givenName | should be 'adfirstname1' $result.MatchedAdUser.surName | should be 'adlastname1' $result.CSVAttemptedMatchIds | should be 'FIRST_NAME,LAST_NAME' $result.ADAttemptedMatchIds | should be 'givenName,surName' } } context 'when matching on a conditional multi-string' { it 'should return a single object: <TestName>' -TestCases $testCases.MatchOnFirstNameLastNameConditional { param($AdUsers, $CsvUser, $FieldMatchMap) $result = & $commandName @PSBoundParameters @($result).Count | should be 1 } context 'when a preferred CSV field is null' { it 'should return the expected object properties: <TestName>' -TestCases $testCases.MatchOnFirstNameLastNameConditional { param($AdUsers, $FieldMatchMap) $result = & $commandName @PSBoundParameters -CsvUser $script:firstLastNickMatchCsvUserId2 $result.MatchedAdUser.EmployeeId | should be 999 $result.MatchedAdUser.givenName | should be 'adfirstnamex' $result.MatchedAdUser.surName | should be 'adlastnamex' $result.CSVAttemptedMatchIds | should be 'FIRST_NAME,LAST_NAME' $result.ADAttemptedMatchIds | should be 'givenName,surName' } } context 'when a preferred CSV field is not null' { it 'should return the expected object properties: <TestName>' -TestCases $testCases.MatchOnFirstNameLastNameConditional { param($AdUsers, $FieldMatchMap) $result = & $commandName @PSBoundParameters -CsvUser $script:firstLastNickMatchCsvUserId $result.MatchedAdUser.EmployeeId | should be 999 $result.MatchedAdUser.givenName | should be 'adfirstnamex' $result.MatchedAdUser.surName | should be 'adlastnamex' $result.CSVAttemptedMatchIds | should be 'NICK_NAME,LAST_NAME' $result.ADAttemptedMatchIds | should be 'givenName,surName' } $script:firstLastNickMatchCsvUserId } } } describe 'NewUsername' { $commandName = 'NewUsername' $script:command = Get-Command -Name $commandName #region Mocks #endregion $testCases = @( @{ Label = 'Valid FirstInitialLastName' Parameters = @{ CsvUser = ([pscustomobject]@{ First = 'Test' Last = 'User' Title = 'testtitle' }) Pattern = 'FirstInitialLastName' FieldMap = @{ FirstName = 'First' LastName = 'Last' } } Expected = @{ Output = @{ Value = 'tuser' ObjectCount = 1 } } } @{ Label = 'Valid FirstNameLastName' Parameters = @{ CsvUser = ([pscustomobject]@{ First = 'Test' Last = 'User' Title = 'testtitle' }) Pattern = 'FirstNameLastName' FieldMap = @{ FirstName = 'First' LastName = 'Last' } } Expected = @{ Output = @{ Value = 'testuser' ObjectCount = 1 } } } @{ Label = 'Valid FirstNameDotLastName' Parameters = @{ CsvUser = ([pscustomobject]@{ First = 'Test' Last = 'User' Title = 'testtitle' }) Pattern = 'FirstNameDotLastName' FieldMap = @{ FirstName = 'First' LastName = 'Last' } } Expected = @{ Output = @{ Value = 'test.user' ObjectCount = 1 } } } @{ Label = 'Valid LastNameFirstTwoFirstNameChars' Parameters = @{ CsvUser = ([pscustomobject]@{ First = 'Test' Last = 'User' Title = 'testtitle' }) Pattern = 'LastNameFirstTwoFirstNameChars' FieldMap = @{ FirstName = 'First' LastName = 'Last' } } Expected = @{ Output = @{ Value = 'userte' ObjectCount = 1 } } } @{ Label = 'Invalid: pattern' Parameters = @{ CsvUser = ([pscustomobject]@{ First = 'Test' Last = 'User' Title = 'testtitle' }) Pattern = 'invalidpattern' FieldMap = @{ FirstName = 'First' LastName = 'Last' } } Expected = @{ ExceptionMessage = 'Unrecognized UserNamePattern' } } @{ Label = 'Invalid: CsvUser does not contain all user attribs' Parameters = @{ CsvUser = ([pscustomobject]@{ First = 'Test' Last = 'User' Title = 'testtitle' }) Pattern = 'invalidpattern' FieldMap = @{ FirstName = 'First' } } Expected = @{ ExceptionMessage = 'One or more values in FieldMap parameter are missing' } } ) foreach ($testCase in $testCases) { $parameters = $testCase.Parameters $expected = $testCase.Expected context $testCase.Label { if ($expected.ContainsKey('ExceptionMessage')) { it 'should throw an exception' { { & $commandName @parameters } | should throw $expected.ExceptionMessage } } else { $result = & $commandName @parameters it "should return [$($expected.Output.ObjectCount)] object(s)" { @($result).Count | should be $expected.Output.ObjectCount } it 'should return the same object type in OutputType()' { $result | should beoftype $script:command.OutputType.Name } it "should return [$($expected.Output.Value)]" { $result | should be $expected.Output.Value } } } } } describe 'New-CompanyAdUser' { $commandName = 'New-CompanyAdUser' $script:command = Get-Command -Name $commandName #region Mocks mock 'Set-AdAccountPassword' mock 'NewRandomPassword' { 'randompwhere' } mock 'Get-AdUser' mock 'New-AdUser' { $obj = New-MockObject -Type 'Microsoft.ActiveDirectory.Management.ADUser' $obj | Add-Member -MemberType NoteProperty -Name 'DistinguishedName' -Force -Value 'dnNamehere' -PassThru } #endregion $testCases = @( @{ Label = 'Random password' Parameters = @{ CsvUser = ([pscustomobject]@{ First = 'Test' Last = "O'Leary" Title = 'testtitle' 'PERSON_NUM' = '1234' }) Path = 'useroupath' UsernamePattern = 'FirstInitialLastName' RandomPassword = $true UserMatchMap = @{ FirstName = 'First' LastName = 'Last' } FieldSyncMap = @{ title = 'Title' } FieldMatchMap = @{ 'PERSON_NUM' = 'employeeId' } Confirm = $false } Expected = @{ Execution = @{ 'Set-AdAccountPassword' = @{ Parameters = @{ Identity = 'newuserdn' NewPassword = 'randompwhere' } RunTimes = 1 } 'New-AdUser' = @{ Parameters = @{ Name = 'toleary' GivenName = 'Test' Surname = "O'Leary" Path = 'useroupath' Enabled = $true OtherAttributes = @{ Title = 'testtitle'; employeeId = '1234' } } RunTimes = 1 } 'Get-AdUser' = @{ Parameters = @{ Filter = "samAccountName -eq 'toleary'" } RunTimes = 1 } } } } ) foreach ($testCase in $testCases) { $parameters = $testCase.Parameters $expected = $testCase.Expected context $testCase.Label { context 'when the user to be created already exists' { mock 'Get-AdUser' { [pscustomobject]@{} } it 'should throw an exception' { { & $commandName @parameters } | should throw 'already exists' } } context 'Shared Tests' { $result = & $commandName @parameters it 'should pass the expected parameters to Get-AdUser' { $thisFunc = $expected.Execution.'Get-AdUser' $assMParams = @{ CommandName = 'Get-AdUser' Times = $thisFunc.RunTimes Exactly = $true ExclusiveFilter = { $PSBoundParameters.Filter -eq $thisFunc.Parameters.Filter } } Assert-MockCalled @assMParams } it 'should pass the expected parameters to New-AdUser' { $thisFunc = $expected.Execution.'New-AdUser' $assMParams = @{ CommandName = 'New-AdUser' Times = $thisFunc.RunTimes Exactly = $true ExclusiveFilter = { $PSBoundParameters.Name -eq $thisFunc.Parameters.Name -and $PSBoundParameters.Path -eq $thisFunc.Parameters.Path -and $PSBoundParameters.Enabled -eq $thisFunc.Parameters.Enabled -and $PSBoundParameters.GivenName -eq $thisFunc.Parameters.GivenName -and $PSBoundParameters.Surname -eq $thisFunc.Parameters.SurName -and $PSBoundParameters.OtherAttributes.Title -eq $thisFunc.Parameters.OtherAttributes.Title -and $PSBoundParameters.OtherAttributes.EmployeeId -eq $thisFunc.Parameters.OtherAttributes.EmployeeId } } Assert-MockCalled @assMParams } it 'should pass the expected parameters to Set-AdAccountPassword' -Skip { $thisFunc = $expected.Execution.'Set-AdAccountPassword' $assMParams = @{ CommandName = 'Set-AdAccountPassword' Times = $thisFunc.RunTimes Exactly = $true ParameterFilter = { $check1 = $PSBoundParameters.Identity.samAccountName -eq $thisFunc.Parameters.Identity $cntParams = @{ AsPlainText = $true Force = $true } $check2 = (ConvertTo-SecureString @cntParams -String $PSBoundParameters.NewPassword) -eq (ConvertTo-SecureString @cntParams -String $thisFunc.Parameters.NewPassword) $check1 -and $check2 } } Assert-MockCalled @assMParams } it "should return Microsoft.ActiveDirectory.Management.ADUser" { $result | should beofType 'Microsoft.ActiveDirectory.Management.ADUser' } it 'should return the expected new password' { $result.Password | should be 'randompwhere' } } } } } describe 'TestFieldMapIsValid' { $commandName = 'TestFieldMapIsValid' $script:command = Get-Command -Name $commandName #region Mocks mock 'TestCsvHeaderExists' { $true } mock 'Write-Warning' #endregion $testCases = @( @{ Label = 'FieldSyncMap invalid' Parameters = @{ FieldSyncMap = @{ 'string' = { $null } } CsvFilePath = 'x' } Expected = @{ ShouldReturn = $false } } @{ Label = 'FieldSyncMap valid' Parameters = @{ FieldSyncMap = @{ { $null } = 'string' } CsvFilePath = 'x' } Expected = @{ ShouldReturn = $true } } @{ Label = 'FieldMatchMap invalid' Parameters = @{ FieldMatchMap = @{ 'string' = { $null } } CsvFilePath = 'x' } Expected = @{ ShouldReturn = $false } } @{ Label = 'FieldMatchMap valid' Parameters = @{ FieldMatchMap = @{ { $null } = 'string' } CsvFilePath = 'x' } Expected = @{ ShouldReturn = $true } } @{ Label = 'FieldValueMap invalid' Parameters = @{ FieldValueMap = @{ { $null } = 'string' } CsvFilePath = 'x' } Expected = @{ ShouldReturn = $false } } @{ Label = 'FieldValueMap valid' Parameters = @{ FieldValueMap = @{ 'string' = { $null } } CsvFilePath = 'x' } Expected = @{ ShouldReturn = $true } } ) foreach ($testCase in $testCases) { $parameters = $testCase.Parameters $expected = $testCase.Expected context $testCase.Label { $result = & $commandName @parameters it "should return [$($expected.ShouldReturn)]" { $result | should be $expected.ShouldReturn } } } } describe 'FindAttributeMismatch' { $commandName = 'FindAttributeMismatch' #region Mocks mock 'Write-Verbose' $script:csvUserMisMatch = [pscustomobject]@{ AD_LOGON = 'foo' PERSON_NUM = 123 OtherAttrib = 'x' } $script:csvUserNoMisMatch = [pscustomobject]@{ AD_LOGON = 'foo' PERSON_NUM = 1111 OtherAttrib = 'y' } $script:AdUserMisMatch = New-MockObject -Type 'System.DirectoryServices.AccountManagement.UserPrincipal' $script:AdUserMisMatch | Add-Member -MemberType NoteProperty -Name 'samAccountName' -Force -Value 'foo' $script:AdUserMisMatch | Add-Member -MemberType NoteProperty -Name 'EmployeeId' -Force -Value $null $script:AdUserMisMatch | Add-Member -MemberType NoteProperty -Name 'otherattribmap' -Force -Value $null -PassThru $script:AdUserNoMisMatch = New-MockObject -Type 'System.DirectoryServices.AccountManagement.UserPrincipal' $script:AdUserNoMisMatch | Add-Member -MemberType NoteProperty -Name 'samAccountName' -Force -Value 'foo' $script:AdUserNoMisMatch | Add-Member -MemberType NoteProperty -Name 'EmployeeId' -Force -Value 1111 $script:AdUserNoMisMatch | Add-Member -MemberType NoteProperty -Name 'otherattribmap' -Force -Value 'y' -PassThru mock 'Get-Member' { [pscustomobject]@{ Name = 'samAccountName' } [pscustomobject]@{ Name = 'EmployeeId' } [pscustomobject]@{ Name = 'otherattribmap' } } #endregion $testCases = @( @{ Label = 'Mismatch' Parameters = @{ AdUser = $script:AdUserMisMatch CsvUser = $script:csvUserMisMatch FieldSyncMap = @{ 'OtherAttrib' = 'otherattribmap' } } } @{ Label = 'No mismatch' Parameters = @{ AdUser = $script:AdUserNoMisMatch CsvUser = $script:csvUserNoMisMatch FieldSyncMap = @{ 'OtherAttrib' = 'otherattribmap' } } } @{ Label = 'Null FieldSyncMap key expression' Parameters = @{ AdUser = $script:AdUserNoMisMatch CsvUser = $script:csvUserNoMisMatch FieldSyncMap = @{ { $null } = 'OtherAttrib' } } } ) foreach ($testCase in $testCases) { $parameters = $testCase.Parameters context $testCase.Label { if ($testCase.Label -eq 'Null FieldSyncMap key expression') { context 'when FieldValueMap has a key expression that does not return anything' { it 'should return nothing' { & $commandName @parameters | should benullorempty } } } if ($testCase.Label -eq 'No mismatch') { context 'when no attribute mismatch is found' { $result = & $commandName @parameters it 'should return nothing' { $result | should benullorempty } } } if ($testCase.Label -eq 'Mismatch') { context 'when an attribute mismatch is found' { $result = & $commandName @parameters it 'should return the expected objects' { @($result).Count | should be 1 $result | should beoftype 'hashtable' $result.ActiveDirectoryAttribute.otherattribmap | should benullorempty $result.CSVField.OtherAttrib | should be 'x' $result.ADShouldBe.otherattribmap | should be 'x' } } } } } } describe 'TestIsValidAdAttribute' { $commandName = 'TestIsValidAdAttribute' $testCases = @( @{ Label = 'Mandatory' Parameters = @{ Name = 'attribname' } } ) foreach ($testCase in $testCases) { $parameters = $testCase.Parameters context $testCase.Label { context 'when the attribute exists' { mock 'Get-AvailableAdUserAttribute' { [pscustomobject]@{ 'ValidName' = 'attribName' } } $result = & $commandName @parameters it 'should return $true' { $result | should be $true } } context 'when the attribute does not exist' { mock 'Get-AvailableAdUserAttribute' { @('notinhere') } $result = & $commandName @parameters it 'should return $false' { $result | should be $false } } } } } describe 'ConvertToSchemaAttributeType' { $commandName = 'ConvertToSchemaAttributeType' $script:command = Get-Command -Name $commandName mock 'Get-AvailableCountryCodes' { [pscustomobject]@{ shortName = "Ukraine" fullName = $null activeDirectoryName = "Ukraine" alpha2 = "UA" alpha3 = "UKR" numeric = 804 } [pscustomobject]@{ shortName = "United States of America" fullName = "the United States of America" activeDirectoryName = "United States" alpha2 = "US" alpha3 = "USA" numeric = 840 } } $testCases = @( @{ Label = 'AD accountExpires value / Read' Parameters = @{ AttributeName = 'accountExpires' AttributeValue = '131907060000000000' Action = 'Read' } Expected = @{ Output = @{ ObjectCount = 1 Value = @('12/30/2018 00:00:00', '12/30/2018 05:00:00') } } } @{ Label = 'AD accountExpires value / Set' Parameters = @{ AttributeName = 'accountExpires' AttributeValue = '131907060000000000' Action = 'Set' } Expected = @{ Output = @{ ObjectCount = 1 Value = @('01/02/2019 00:00:00', '01/02/2019 05:00:00') } } } @{ Label = 'AD accountExpires value not set / Read' Parameters = @{ AttributeName = 'accountExpires' AttributeValue = 0 Action = 'Read' } Expected = @{ Output = @{ ObjectCount = 1 Value = 0 } } } @{ Label = 'AD accountExpires value not set / Set' Parameters = @{ AttributeName = 'accountExpires' AttributeValue = 0 Action = 'Set' } Expected = @{ Output = @{ ObjectCount = 1 Value = 0 } } } @{ Label = 'AD accountExpires never set / Read' Parameters = @{ AttributeName = 'accountExpires' AttributeValue = '9223372036854775807' Action = 'Read' } Expected = @{ Output = @{ ObjectCount = 1 Value = 0 } } } @{ Label = 'AD accountExpires value never set / Set' Parameters = @{ AttributeName = 'accountExpires' AttributeValue = '9223372036854775807' Action = 'Set' } Expected = @{ Output = @{ ObjectCount = 1 Value = 0 } } } @{ Label = 'Unrecognized string' Parameters = @{ AttributeName = 'x' AttributeValue = '12/30/18' Action = 'Set' } Expected = @{ Output = @{ ObjectCount = 1 Value = '12/30/18' } } } @{ Label = 'Null attribute value' Parameters = @{ AttributeName = 'x' AttributeValue = $null Action = 'Set' } Expected = @{ Output = @{ ObjectCount = 1 Value = '' } } } ) foreach ($testCase in $testCases) { $parameters = $testCase.Parameters $expected = $testCase.Expected context $testCase.Label { $result = & $commandName @parameters it "should return [$($expected.Output.ObjectCount)] object(s)" { @($result).Count | should be $expected.Output.ObjectCount } it "should return [$($expected.Output.Value)]" { $result | should bein $expected.Output.Value } } } context 'when converting country code' { context 'when the country code is found' { $parameters = @{ AttributeName = 'countryCode' AttributeValue = 'United States' Action = 'Read' } $result = & $commandName @parameters it "should return 1 object" { @($result).Count | should be 1 } it "should return [840]" { $result | should be 840 } } context 'when the country code is not found' { $parameters = @{ AttributeName = 'countryCode' AttributeValue = 'noworkie' Action = 'Read' } it 'should throw an exception when the countryCode could not be found' { { & $commandName @parameters } | should throw 'could not be found' } } } } describe 'GetManagerEmailAddress' { $commandName = 'GetManagerEmailAddress' $command = Get-Command -Name $commandName context 'when the AD user has no manager' { mock 'Get-AdUser' { [pscustomobject]@{ EmailAddress = 'jjones@company.local' } } $parameters = @{ AdUser = [pscustomobject]@{ EmailAddress = 'jjones@company.local' Manager = $null } } $result = & $commandName @parameters it 'should return nothing' { $result | should benullorempty } } context 'when no email address is found' { mock 'Get-AdUser' { [pscustomobject]@{ EmailAddress = $null } } $parameters = @{ AdUser = [pscustomobject]@{ EmailAddress = $null Manager = 'bsmith' } } $result = & $commandName @parameters it 'should return nothing' { $result | should benullorempty } } context 'when an email addreses is found' { mock 'Get-AdUser' { [pscustomobject]@{ EmailAddress = 'bsmith@company.local' } } $parameters = @{ AdUser = [pscustomobject]@{ EmailAddress = 'jjones@company.local' Manager = 'bsmith' } } $result = & $commandName @parameters it 'should return the manager email address' { $result | should be 'bsmith@company.local' } } } describe 'ReadEmailTemplate' { $commandName = 'ReadEmailTemplate' $command = Get-Command -Name $commandName BeforeAll { Add-Content -Path 'TestDrive:\UnusedAccounts.txt' -Value 'template {0}' $templates = Get-ChildItem -Path 'TestDrive:\' $templateContent = Get-Content -Path 'TestDrive:\UnusedAccounts.txt' } #region Mocks mock 'Get-ChildItem' { $templates } -ParameterFilter { $Path -eq "$PSScriptRoot\EmailTemplates" } mock 'Get-Content' { $templateContent } #endregion context 'Single template' { $parameters = @{ Name = 'UnusedAccount' } $result = & $commandName @parameters it 'should return the expected number of objects' { @($result).Count | should be 1 } it 'should return the same object type in OutputType()' { $result | should beoftype $command.OutputType.Name } } context 'when no template exists' { $parameters = @{ Name = 'DoesNotExist' } $result = & $commandName @parameters it 'should return nothing' { $result | should benullorempty } } } describe 'SendStaleAccountEmail' { $commandName = 'SendStaleAccountEmail' context 'when no manager is defined' { $parameters = @{ AdUser = [pscustomobject]@{ Name = 'jjones' Manager = $null } Confirm = $false } it 'should throw an exception' { { & $commandName @parameters } | should throw 'No manager defined' } } context 'when the manager email address cannot be found' { mock 'GetManagerEmailAddress' $parameters = @{ AdUser = [pscustomobject]@{ Name = 'jjones' Manager = 'CN=bsmwith,DC=test,DC=local' } Confirm = $false } it 'should throw an exception' { { & $commandName @parameters } | should throw 'Could not find a manager email address' } } context 'when a manager email is found' { mock 'GetManagerEmailAddress' { 'bsmith@test.local' } mock 'ReadEmailTemplate' { '{0} {1} {2}' } mock 'Send-MailMessage' $parameters = @{ AdUser = [pscustomobject]@{ Name = 'jjones' Manager = 'CN=bsmith,DC=test,DC=local' } Confirm = $false } $result = & $commandName @parameters it 'should send an email with the expected attributes' { $assMParams = @{ CommandName = 'Send-MailMessage' Times = 1 Exactly = $true ExclusiveFilter = { $PSBoundParameters.To -eq 'bsmith@test.local' -and $PSBoundParameters.From -eq 'unusedaccountfromname <unusedaccountfromemail@company.local>' -and $PSBoundParameters.Subject -eq 'unusedaccountsubject' -and $PSBoundParameters.Body -eq 'bsmith@test.local jjones YourCompany' -and $PSBoundParameters.SmtpServer -eq 'yoursmtpserver.company.local' } } Assert-MockCalled @assMParams } it 'should return nothing' { $result | should benullorempty } } } describe 'SetAduser' { $commandName = 'SetAduser' mock 'Set-AdUser' mock 'ConvertToSchemaAttributeType' { '1/1/01' } -ParameterFilter { $AttributeName -eq 'accountExpires' } mock 'ConvertToSchemaAttributeType' { 'empidhere' } -ParameterFilter { $AttributeName -eq 'empidhere' } mock 'ConvertToSchemaAttributeType' { 'displaynamehere' } -ParameterFilter { $AttributeName -eq 'displaynamehere' } mock 'ConvertToSchemaAttributeType' { '1/1/01' } -ParameterFilter { $AttributeName -eq '1/1/01' } $parameterSets = @( @{ Identity = @{ samAccountName = 'samnamehere'} ActiveDirectoryAttributes = @{ employeeId = 'empidhere' } } @{ Identity = @{ employeeId = 'empidhere'} ActiveDirectoryAttributes = @{ displayName = 'displaynamehere' } } @{ Identity = @{ employeeId = 'empidhere'} ActiveDirectoryAttributes = @{ accountExpires = '1/1/01' } } ) $testCases = @{ All = $parameterSets } it 'returns nothing' -TestCases $testCases.All { param($Identity, $ActiveDirectoryAttributes) & $commandName @PSBoundParameters -Confirm:$false | should benullorempty } it 'should set the expected attribute' -TestCases $testCases.All { param($Identity, $ActiveDirectoryAttributes) ## Need to account for the addition of ConvertToSchemaValue & $commandName @PSBoundParameters -Confirm:$false $assMParams = @{ CommandName = 'Set-AdUser' Times = 1 Exactly = $true Scope = 'It' ParameterFilter = { $PSBoundParameters.Replace.Keys -match 'displayName|employeeId|accountexpires' -and $PSBoundParameters.Replace.Values -match 'displayNameHere|empIdHere|1/1/01' } } Assert-MockCalled @assMParams } it 'should set the expected identity' -TestCases $testCases.All { param($Identity, $ActiveDirectoryAttributes) & $commandName @PSBoundParameters -Confirm:$false $assMParams = @{ CommandName = 'Set-AdUser' Times = 1 Exactly = $true Scope = 'It' ParameterFilter = { $PSBoundParameters.Identity -eq $Identity } } Assert-MockCalled @assMParams } } describe 'SyncCompanyUser' { $commandName = 'SyncCompanyUser' $script:csvUser = [pscustomobject]@{ AD_LOGON = 'foo' PERSON_NUM = 123 OtherAtrrib = 'x' } #region Mocks mock 'SetAdUser' #endregion $testCases = @( @{ Label = 'SamAccountName Identifier' Parameters = @{ Identity = 'foo' CsvUser = $script:csvUser ActiveDirectoryAttributes = @{ 'atttribtosync1' = 'attribtosyncval1' } } Expect = @( @{ Type = 'Function parameters' Name = 'SetAdUser' Parameters = @( @{ Identity = 'foo' ActiveDirectoryAttributes = @{ 'atttribtosync1' = 'attribtosyncval1' } } ) } ) } @{ Label = 'EmployeeId Identifier, 2 Attributes hashtables' Parameters = @{ Identity = 'bar' CsvUser = $script:csvUser ActiveDirectoryAttributes = @{ 'atttribtosync1' = 'attribtosyncval1' 'atttribtosync2' = 'attribtosyncval2' } } Expect = @( @{ Type = 'Function parameters' Name = 'SetAdUser' Parameters = @( @{ Identity = 'bar' ActiveDirectoryAttributes = @(@{ 'atttribtosync1' = 'attribtosyncval1' }, @{ 'atttribtosync2' = 'attribtosyncval2' }) } ) } ) } ) foreach ($testCase in $testCases) { context $testcase.Label { $expect = $testcase.Expect $funcParams = $testCase.Parameters $result = & $commandName @funcParams it 'should return nothing' { $result | should benullorempty } it 'should change only those attributes in the Attributes parameter' { $expectedParams = $expect.where({ $_.Name -eq 'SetAdUser'}) $assMParams = @{ CommandName = 'SetAdUser' Times = @($funcParams.ActiveDirectoryAttributes).Count Exactly = $true ParameterFilter = { $expectedkeys = $expectedParams.Parameters.ActiveDirectoryAttributes | ForEach-Object { $_.Keys } $expectedVals = $expectedParams.Parameters.ActiveDirectoryAttributes | ForEach-Object { $_.Values } $actualKeys = $PSBoundParameters.ActiveDirectoryAttributes | ForEach-Object { $_.Keys } $actualValues = $PSBoundParameters.ActiveDirectoryAttributes | ForEach-Object { $_.Values } -not (Compare-Object $expectedkeys $actualKeys) -and -not (Compare-Object $expectedVals $actualValues) } } Assert-MockCalled @assMParams } it 'should change attributes on the expected user account' { $expectedParams = $expect.where({ $_.Name -eq 'SetAdUser'}) $assMParams = @{ CommandName = 'SetAdUser' Times = @($funcParams.Attributes).Count Exactly = $true ParameterFilter = { foreach ($i in $expectedParams.Parameters) { $PSBoundParameters.Identity -in $i.Identity } } } Assert-MockCalled @assMParams } } } } describe 'WriteLog' { $commandName = 'WriteLog' mock 'Get-Date' { 'time' } mock 'Export-Csv' $parameterSets = @( @{ FilePath = 'C:\log.csv' CSVIdentifierValue = 'username' CSVIdentifierField = 'employeeid' Attributes = @{ ADAttributeName = 'EmployeeId' ADAttributeValue = $null CSVAttributeName = 'PERSON_NUM' CSVAttributeValue = 123 } TestName = 'Standard' } ) $testCases = @{ All = $parameterSets } it 'should export a CSV to the expected path: <TestName>' -TestCases $testCases.All { param($FilePath, $CSVIdentifierValue, $CSVIdentifierField, $Attributes) & $commandName @PSBoundParameters $assMParams = @{ CommandName = 'Export-Csv' Times = 1 Exactly = $true Scope = 'It' ParameterFilter = { $PSBoundParameters.Path -eq $FilePath } } Assert-MockCalled @assMParams } it 'should appends to the CSV: <TestName>' -TestCases $testCases.All { param($FilePath, $CSVIdentifierValue, $CSVIdentifierField, $Attributes) & $commandName @PSBoundParameters $assMParams = @{ CommandName = 'Export-Csv' Times = 1 Exactly = $true Scope = 'It' ParameterFilter = { $Append } } Assert-MockCalled @assMParams } it 'should export as CSV with the expected values: <TestName>' -TestCases $testCases.All { param($FilePath, $CSVIdentifierValue, $CSVIdentifierField, $Attributes) & $commandName @PSBoundParameters $assMParams = @{ CommandName = 'Export-Csv' Times = 1 Exactly = $true Scope = 'It' ParameterFilter = { $InputObject.Time -eq 'time' -and $InputObject.CSVIdentifierValue -eq $CSVIdentifierValue -and $InputObject.CSVIdentifierField -eq $CSVIdentifierField -and $InputObject.ADAttributeName -eq 'EmployeeId' -and $InputObject.ADAttributeValue -eq $null -and $InputObject.CSVAttributeName -eq 'PERSON_NUM' -and $InputObject.CSVAttributeValue -eq 123 } } Assert-MockCalled @assMParams } } describe 'Invoke-AdSync' { $commandName = 'Invoke-AdSync' #region Mocks $script:testAdUser = New-MockObject -Type 'System.DirectoryServices.AccountManagement.UserPrincipal' $amParams = @{ MemberType = 'NoteProperty' Force = $true } $props = @{ 'Name' = 'nameval' 'Enabled' = $true 'SamAccountName' = 'samval' 'GivenName' = 'givennameval' 'Surname' = 'surnameval' 'ADDisplayName' = 'displaynameval' 'OtherProperty' = 'otherval' 'EmployeeId' = 1 'ADTitle' = 'titleval' } $props.GetEnumerator() | ForEach-Object { $script:testAdUser | Add-Member @amParams -Name $_.Key -Value $_.Value } $script:testAdUser mock 'WriteLog' mock 'Test-Path' { $true } mock 'TestIsUserTerminationEnabled' { $false } mock 'SyncCompanyUser' mock 'Write-Warning' mock 'TestCsvHeaderExists' { $true } mock 'Get-CompanyAdUser' { $script:testAdUser } mock 'Get-CompanyCsvUser' { [pscustomobject]@{ AD_LOGON = "nameval" PERSON_NUM = "1" SUPERVISOR_ID = '2' SUPERVISOR = 'supervisorhere' NICK_NAME = 'nicknamehere' FIRST_NAME = 'firstnamehere' LAST_NAME = 'lastnamehere' CsvTitle = 'sync1' CsvDisplayName = 'sync2' } } mock 'FindUserMatch' mock 'GetCsvIdField' { [pscustomobject]@{ Field = 'PERSON_NUM' Value = '1' } } mock 'Write-Output' mock 'TestIsValidAdAttribute' { $true } mock 'TestFieldMapIsValid' { $true } mock 'New-CompanyAdUser' { [pscustomobject]@{ Name = 'username' Password = 'passwordhere' } } mock 'Get-AdUser' { [pscustomobject]@{ DistinguishedName = 'CN=manager' } } mock 'WriteProgressHelper' #endregion $parameterSets = @( @{ Label = 'ReportOnly' Parameters = @{ CsvFilePath = 'C:\log.csv' FieldSyncMap = @{ 'CsvTitle' = 'ADTitle' } FieldMatchMap = @{ PERSON_NUM = 'EmployeeId' } ReportOnly = $true } Expect = @{ Execution = @{ TestIsValidAdAttribute = @{ RunTimes = 1 } } } } @{ Label = 'Single sync /single match field' Parameters = @{ CsvFilePath = 'C:\log.csv' FieldSyncMap = @{ 'CsvTitle' = 'ADTitle' } FieldMatchMap = @{ PERSON_NUM = 'EmployeeId' } } Expect = @{ Execution = @{ TestIsValidAdAttribute = @{ RunTimes = 1 } } } } @{ Label = 'Multi sync/Multi match field' Parameters = @{ CsvFilePath = 'C:\log.csv' FieldSyncMap = @{ 'CsvTitle' = 'ADTitle' 'CSVDisplayName' = 'ADDisplayName' ({ if (-not $_.CsvNullField) { 'CsvNonNullField' }}) = 'ADDisplayName3' } FieldMatchMap = @{ PERSON_NUM = 'EmployeeId' AD_LOGON = 'samAcountName' } } Expect = @{ Execution = @{ TestIsValidAdAttribute = @{ RunTimes = 3 } } } } @{ Label = 'Exclude' Parameters = @{ CsvFilePath = 'C:\log.csv' FieldMatchMap = @{ PERSON_NUM = 'EmployeeId' } FieldSyncMap = @{ 'CsvTitle' = 'ADTitle' } Exclude = @{ ExcludeCol = 'excludeme' } } Expect = @{ Execution = @{ TestIsValidAdAttribute = @{ RunTimes = 1 } } } } @{ Label = 'Multi-string match' Parameters = @{ CsvFilePath = 'C:\log.csv' FieldMatchMap = @{ @( 'FIRST_NAME', 'LAST_NAME') = @('givenName', 'surName') } FieldSyncMap = @{ 'CsvTitle' = 'ADTitle' } } Expect = @{ Execution = @{ TestIsValidAdAttribute = @{ RunTimes = 1 } } } } @{ Label = 'Multi-string conditional match' Parameters = @{ CsvFilePath = 'C:\log.csv' FieldMatchMap = @{ @({ if ($_.'NICK_NAME') { 'NICK_NAME' } else { 'FIRST_NAME'} }, 'LAST_NAME') = @('givenName', 'surName') } FieldSyncMap = @{ 'CsvTitle' = 'ADTitle' } } Expect = @{ Execution = @{ TestIsValidAdAttribute = @{ RunTimes = 1 } } } } @{ Label = 'FieldValueMap / value is returned' Parameters = @{ CsvFilePath = 'C:\log.csv' FieldMatchMap = @{ 'PERSON_NUM' = 'employeeId' } FieldSyncMap = @{ 'CsvTitle' = 'ADTitle' } FieldValueMap = @{ 'SUPERVISOR' = { $supId = $_.'SUPERVISOR_ID'; (Get-AdUser -Filter "EmployeeId -eq '$supId'").DistinguishedName }} } Expect = @{ Execution = @{ TestIsValidAdAttribute = @{ RunTimes = 1 } } } } @{ Label = 'FieldValueMap / value is not returned' Parameters = @{ CsvFilePath = 'C:\log.csv' FieldMatchMap = @{ 'PERSON_NUM' = 'employeeId' } FieldSyncMap = @{ 'CsvTitle' = 'ADTitle' } FieldValueMap = @{ 'SUPERVISOR' = { }} } Expect = @{ Execution = @{ TestIsValidAdAttribute = @{ RunTimes = 1 } } } } @{ Label = 'Creating new users' Parameters = @{ CsvFilePath = 'C:\log.csv' FieldSyncMap = @{ 'CsvTitle' = 'ADTitle' } FieldMatchMap = @{ PERSON_NUM = 'EmployeeId' } UserNamePattern = 'FirstInitialLastName' UserMatchMap = @{ FirstName = 'FIRST_NAME' LastName = 'LAST_NAME' } } Expect = @{ Execution = @{ TestIsValidAdAttribute = @{ RunTimes = 1 } } } } ) $testCases = $parameterSets foreach ($testCase in $testCases) { $parameters = $testCase.Parameters $expect = $testCase.Expect context $testCase.Label { if ($parameters.ContainsKey('Exclude')) { context 'when excluding a CSV column' { $null = & $commandName @parameters context 'when a header does not exist' { mock 'TestCsvHeaderExists' { $false } -ParameterFilter { 'excludecol' -in $Header } it 'should throw an exception' { $params = @{} + $parameters { & $commandName @params } | should throw 'One or more CSV headers excluded with -Exclude do not exist in the CSV file' } } context 'when all headers exist' { mock 'TestCsvHeaderExists' { $true } $null = & $commandName @parameters it 'should pass Exclude to Get-CompanyCsvUser' { $assMParams = @{ CommandName = 'Get-CompanyCsvUser' Times = 1 Exactly = $true ParameterFilter = { $PSBoundParameters.Exclude.Keys -eq 'ExcludeCol' -and $PSBoundParameters.Exclude.Values -eq 'excludeme' } } Assert-MockCalled @assMParams } } } } context 'Shared tests' { $null = & $commandName @parameters it 'should only test string AD attributes in FieldSyncMap' { $thisFunc = $expect.Execution.TestIsValidAdAttribute $assMParams = @{ CommandName = 'TestIsValidAdAttribute' Times = $thisFunc.RunTimes Exactly = $true ExclusiveFilter = { $PSBoundParameters.Name -is 'string' } } Assert-MockCalled @assMParams } } context 'when at least one AD attribute in FieldSyncMap is not available' { mock 'TestIsValidAdAttribute' { $false } it 'should throw an exception' { $params = @{} + $parameters { & $commandName @params } | should throw 'One or more AD attributes' } } context 'when an invalid header is found in a field map' { mock 'TestFieldMapIsValid' { $false } it 'should throw an exception' { { & $commandName @parameters } | should throw 'Invalid attribute found' } } context 'when no AD users are found' { mock 'Get-CompanyAdUser' it 'should throw an exception' { $params = @{} + $parameters { & $commandName @params } | should throw 'No AD users found' } } context 'when no CSV users are found' { mock 'Get-CompanyCsvUser' it 'should throw an exception' { $params = @{} + $parameters { & $commandName @params } | should throw 'No CSV users found' } } context 'when at least one AD user and one CSV user is found' { $result = & $commandName @parameters it 'should return nothing' { $result | should benullorempty } context 'when a user match cannot be found' { mock 'FindUserMatch' context 'when no CSV ID fields can be found' { mock 'GetCsvIdField' $null = & $commandName @parameters it 'write a warning' { $assMParams = @{ CommandName = 'Write-Warning' Times = 1 Exactly = $true ExclusiveFilter = { $PSBoundParameters.Message -match 'No CSV id fields were found' } } Assert-MockCalled @assMParams } it 'should pass the CSV row as the CSV id field for WriteLog' { $assMParams = @{ CommandName = 'WriteLog' Times = 1 Exactly = $true ParameterFilter = { $PSBoundParameters.CsvIdentifierValue -eq 'CSV Row: 2' -and $PSBoundParameters.CSVIdentifierField -eq 'CSV Row: 2' } } Assert-MockCalled @assMParams } } context 'when at least one CSV ID field can be found' { context 'when a populated CSV ID field exists' { mock 'GetCsvIdField' { [pscustomobject]@{ Field = $null Value = 'val1' } [pscustomobject]@{ Field = $null Value = 'val2' } [pscustomobject]@{ Field = 'PERSON_NUM' Value = 'val1' } } $null = & $commandName @parameters it 'should pass the ID as the CSV id field for WriteLog' { $assMParams = @{ CommandName = 'WriteLog' Times = 1 Exactly = $true ParameterFilter = { $PSBoundParameters.CSVIdentifierField -eq 'PERSON_NUM' } } Assert-MockCalled @assMParams } if ($parameters.ContainsKey('UserMatchMap')) { context 'when creating new users' { context 'when the user is not excluded' { mock 'TestShouldCreateNewUser' { $true } $null = & $commandName @parameters it 'should create a new user' { $params = @{ CommandName = 'New-CompanyAdUser' Times = 1 Exactly = $true ExclusiveFilter = { $PSBoundParameters.CsvUser.'AD_LOGON' -eq 'nameval' -and $PSBoundParameters.UsernamePattern -eq 'FirstInitialLastName' -and $PSBoundParameters.UserMatchMap.FirstName -eq 'FIRST_NAME' -and $PSBoundParameters.UserMatchMap.LastName -eq 'LAST_NAME' -and $PSBoundParameters.FieldSyncMap.CsvTitle -eq 'ADTitle' -and $PSBoundParameters.FieldMatchMap.'PERSON_NUM' -eq 'employeeId' } } Assert-MockCalled @params } it 'should record the expected attributes to the log file' { $assMParams = @{ CommandName = 'WriteLog' Times = 1 Exactly = $true ParameterFilter = { $PSBoundParameters.Attributes.CSVAttributeName -eq 'NewUserCreated' -and $PSBoundParameters.Attributes.CSVAttributeValue -eq 'NewUserCreated' -and $PSBoundParameters.Attributes.ADAttributeName -eq 'NewUserCreated' -and $PSBoundParameters.Attributes.ADAttributeValue -eq 'NewUserCreated' -and $PSBoundParameters.Attributes.Message -eq 'UserName: [username] - Password: [passwordhere]' } } Assert-MockCalled @assMParams } } context 'when the user is excluded' { mock 'TestShouldCreateNewUser' { $true } it 'should not attempt to create a new user' { $params = @{ CommandName = 'New-CompanyAdUser' Times = 0 Exactly = $true } Assert-MockCalled @params } } } } context 'when no CSV ID fields are populated' { mock 'GetCsvIdField' $null = & $commandName @parameters it 'should pass the CSV row as the CSV id field for WriteLog' { $assMParams = @{ CommandName = 'WriteLog' Times = 1 Exactly = $true ParameterFilter = { $PSBoundParameters.CsvIdentifierValue -eq 'CSV Row: 2' -and $PSBoundParameters.CSVIdentifierField -eq 'CSV Row: 2' } } Assert-MockCalled @assMParams } } } } context 'when a user match can be found' { mock 'FindUserMatch' { [pscustomobject]@{ MatchedAdUser = $script:testAdUser CSVAttemptedMatchIds = 'PERSON_NUM' ADAttemptedMatchIds = 'EmployeeId' } } context 'when an attribute mismatch is found' { mock 'FindAttributeMismatch' { @(@{ CSVField = @{'1' = '2'} ActiveDirectoryAttribute = @{ '3' = '4' } ADShouldBe = @{ '5' = '6' } } @{ CSVField = @{'7' = '8'} ActiveDirectoryAttribute = @{ '9' = '10' } ADShouldBe = @{ '11' = '12' } }) } $null = & $commandName @parameters it 'should record each changed attribute to the log and no more' { $assMParams = @{ CommandName = 'WriteLog' Exactly = $true } $pfilter1 = { $PSBoundParameters.Attributes.CSVAttributeName -eq 'AttributeChange - 1' -and $PSBoundParameters.Attributes.CSVAttributeValue -eq '2' -and $PSBoundParameters.Attributes.ADAttributeName -eq 'AttributeChange - 3' -and $PSBoundParameters.Attributes.ADAttributeValue -eq '4' } Assert-MockCalled @assMParams -Times 1 -ParameterFilter $pfilter1 $pfilter2 = { $PSBoundParameters.Attributes.CSVAttributeName -eq 'AttributeChange - 7' -and $PSBoundParameters.Attributes.CSVAttributeValue -eq '8' -and $PSBoundParameters.Attributes.ADAttributeName -eq 'AttributeChange - 9' -and $PSBoundParameters.Attributes.ADAttributeValue -eq '10' } Assert-MockCalled @assMParams -Times 1 -ParameterFilter $pfilter2 $pfilter3 = { $PSBoundParameters.Attributes.CSVAttributeName -notin 'AttributeChange - 1', 'AttributeChange - 7' -and $PSBoundParameters.Attributes.CSVAttributeValue -notin '2', '8' -and $PSBoundParameters.Attributes.ADAttributeName -notin 'AttributeChange - 3', 'AttributeChange - 9' -and $PSBoundParameters.Attributes.ADAttributeValue -notin '4', '10' } Assert-MockCalled @assMParams -Times 0 -ParameterFilter $pfilter3 } if ($parameters.ContainsKey('ReportOnly')) { context 'when only reporting' { $null = & $commandName @parameters it 'should not attempt to sync the user' { $assMParams = @{ CommandName = 'SyncCompanyUser' Times = 0 Exactly = $true } Assert-MockCalled @assMParams } } } else { context 'when syncing' { $null = & $commandName @parameters it 'should sync the expected user' { $assMParams = @{ CommandName = 'SyncCompanyUser' Times = 1 Exactly = $true ParameterFilter = { $PSBoundParameters.Identity -eq 'samval' -and $PSBoundParameters.CsvUser.'AD_LOGON' -eq 'nameval' -and $PSBoundParameters.CsvUser.'PERSON_NUM' -eq "1" -and $PSBoundParameters.ActiveDirectoryAttributes.5 -eq '6' -and $PSBoundParameters.ActiveDirectoryAttributes.11 -eq '12' } } Assert-MockCalled @assMParams } } if ($parameters.ContainsKey('FieldValueMap')) { if ($testCase.Label -eq 'FieldValueMap / value is returned') { context 'when a value in the FieldValueMap hashtable returns a value' { $null = & $commandName @parameters it 'should attempt to find the value expression value with FindAttributeMismatch' { $assMParams = @{ CommandName = 'FindAttributeMismatch' Times = 1 Exactly = $true ParameterFilter = { $PSBoundParameters.CsvUser.'SUPERVISOR' -eq 'CN=manager' } } Assert-MockCalled @assMParams } } } if ($testCase.Label -eq 'FieldValueMap / value is not returned') { context 'when a value in the FieldValueMap hashtable returns nothing' { $null = & $commandName @parameters it 'should attempt to find the value expression value with FindAttributeMismatch' { $assMParams = @{ CommandName = 'FindAttributeMismatch' Times = 1 Exactly = $true ParameterFilter = { -not $PSBoundParameters.CsvUser.'SUPERVISOR' } } Assert-MockCalled @assMParams } } } } } } context 'when all attributes are in sync' { mock 'FindAttributeMismatch' $null = & $commandName @parameters it 'should pass the expected attributes to WriteLog' { $assMParams = @{ CommandName = 'WriteLog' Times = 1 Exactly = $true ParameterFilter = { $PSBoundParameters.Attributes.CSVAttributeName -eq 'AlreadyInSync' -and $PSBoundParameters.Attributes.CSVAttributeValue -eq 'AlreadyInSync' -and $PSBoundParameters.Attributes.ADAttributeName -eq 'AlreadyInSync' -and $PSBoundParameters.Attributes.ADAttributeValue -eq 'AlreadyInSync' } } Assert-MockCalled @assMParams } } context 'when user termination is enabled' { mock 'TestIsUserTerminationEnabled' { $true } mock 'InvokeUserTermination' context 'when the user should be terminated' { mock 'TestUserTerminated' { $true } $null = & $commandName @parameters if (-not $parameters.ContainsKey('ReportOnly')) { it 'should terminate the AD user' { $params = @{ CommandName = 'InvokeUserTermination' Times = 1 Exactly = $true ExclusiveFilter = { $PSBoundParameters.AdUser.Name -eq 'nameval' } } Assert-MockCalled @params } } if ($parameters.ContainsKey('ReportOnly')) { context 'when only reporting is enabled' { $null = & $commandName @parameters it 'should not terminate the AD user' { $params = @{ CommandName = 'InvokeUserTermination' Times = 0 Exactly = $true } Assert-MockCalled @params } } } } context 'when the user should not be terminated' { mock 'TestUserTerminated' { $false } $null = & $commandName @parameters it 'should not terminate the AD user' { $params = @{ CommandName = 'InvokeUserTermination' Times = 0 Exactly = $true } Assert-MockCalled @params } } } } } } } } describe 'InvokeUserTermination' { $commandName = 'InvokeUserTermination' $command = Get-Command -Name $commandName #region Mocks mock 'Disable-AdAccount' #endregion context 'when the user account needs to be disabled' { $parameters = @{ AdUser = [pscustomobject]@{ Name = 'testname' samAccountName = 'testsamname' } Confirm = $false } $result = & $commandName @parameters it 'should return nothing' { $result | should benullorempty } it 'should disable the AD account' { $params = @{ CommandName = 'Disable-AdAccount' Times = 1 Exactly = $true ExclusiveFilter = { [string]$PSBoundParameters.Identity -eq 'testsamName' } } Assert-MockCalled @params } } } describe 'TestUserTerminated' { $commandName = 'TestUserTerminated' $command = Get-Command -Name $commandName context 'when one field value for the employee matches one user termination field value in the config' { $parameters = @{ CsvUser = [pscustomobject]@{ Status = '0' } } $result = & $commandName @parameters it 'should return the expected number of objects' { @($result).Count | should be 1 } it 'should return the same object type in OutputType()' { $result | should beoftype $command.OutputType.Name } it 'should return $true' { $result | should be $true } } context 'when no field value for the employee matches any user termination field values in the config' { $parameters = @{ CsvUser = [pscustomobject]@{ Status = '1' } } $result = & $commandName @parameters it 'should return the expected number of objects' { @($result).Count | should be 1 } it 'should return the same object type in OutputType()' { $result | should beoftype $command.OutputType.Name } it 'should return $true' { $result | should be $false } } } describe 'TestShouldCreateNewUser' { $commandName = 'TestShouldCreateNewUser' $command = Get-Command -Name $commandName mock 'GetPsAdSyncConfiguration' { @{ NewUserCreation = @{ Exclude = @{ FieldValueSettings = @{ CsvField = 'Status' CsvValue = 0, 2 } } } } } context 'when user termination is enabled' { mock 'TestIsUserTerminationEnabled' { $true } context 'when the user is not terminated' { mock 'TestUserTerminated' { $false } context 'when the employee CSV field does not match any config values' { $parameters = @{ CsvUser = [pscustomobject]@{ Status = 5 } } $result = & $commandName @parameters it 'should return $true' { $result | should be $true } } context 'when the employee CSV field matches at least one config values' { $parameters = @{ CsvUser = [pscustomobject]@{ Status = 0 } } $result = & $commandName @parameters it 'should return $false' { $result | should be $false } } } context 'when the user is terminated' { mock 'TestIsUserTerminationEnabled' { $true } $parameters = @{ CsvUser = [pscustomobject]@{ Status = 0 } } $result = & $commandName @parameters it 'should return the expected number of objects' { @($result).Count | should be 1 } it 'should return the same object type in OutputType()' { $result | should beoftype $command.OutputType.Name } it 'should return $false' { $result | should be $false } } } context 'when user termination is not enabled' { mock 'TestIsUserTerminationEnabled' { $false } context 'when the employee CSV field does not match any config values' { $parameters = @{ CsvUser = [pscustomobject]@{ Status = 5 } } $result = & $commandName @parameters it 'should return $true' { $result | should be $true } } context 'when the employee CSV field matches at least one config values' { $parameters = @{ CsvUser = [pscustomobject]@{ Status = 0 } } $result = & $commandName @parameters it 'should return $false' { $result | should be $false } } } } } } |