# Tests\ADService.Tests.ps1 # Unit & Integration Tests for ADService Module BeforeAll { $scriptDir = Split-Path -Parent $PSScriptRoot . (Join-Path $scriptDir "Modules\ADService.ps1") } Describe "ADService Unit Tests" -Tag "Mock", "Unit" { Context "LDAPS Path Construction & String Interpolation" { It "constructs standard LDAP URI on port 389 when UseLdaps is false" { $res = Test-ADConnection -DomainController "dc01.corp.local" -BaseOU "DC=corp,DC=local" -UseLdaps $false $res.LdapPath | Should -Be "LDAP://dc01.corp.local/DC=corp,DC=local" } It "constructs LDAPS URI on port 636 without losing DC variable expansion" { # Regression test for scope interpolation bug where $dc:636 resulted in LDAP:// $res = Test-ADConnection -DomainController "dc01.corp.local" -BaseOU "DC=corp,DC=local" -UseLdaps $true $res.LdapPath | Should -Be "LDAP://dc01.corp.local:636/DC=corp,DC=local" $res.LdapPath | Should -Not -Be "LDAP:///DC=corp,DC=local" } It "defaults to RootDSE path when BaseOU is empty" { $res = Test-ADConnection -DomainController "dc01.corp.local" -BaseOU "" -UseLdaps $false $res.LdapPath | Should -Be "LDAP://dc01.corp.local/RootDSE" } It "gracefully reports failure when DC is unreachable without throwing unhandled exception" { $res = Test-ADConnection -DomainController "nonexistent-dc.corp.local" -BaseOU "DC=corp,DC=local" $res.Success | Should -BeFalse $res.Message | Should -Match "AD 연결 실패" } } Context "Mock User Directory & Filtering" { It "retrieves all 5 sample users when filter is empty in demo mode" { $users = Get-ADSampleUsers $users.Count | Should -Be 5 $users[0].IsLive | Should -BeFalse } It "filters users by SamAccountName substring" { $matched = Get-ADSampleUsers -Filter "gdhong" @($matched).Count | Should -Be 1 $matched[0].SamAccountName | Should -Be "gdhong" $matched[0].DisplayName | Should -Be "홍길동" } It "filters users by Korean DisplayName" { $matched = Get-ADSampleUsers -Filter "김철수" @($matched).Count | Should -Be 1 $matched[0].SamAccountName | Should -Be "cskim" $matched[0].Department | Should -Be "데이터플랫폼팀" } It "filters users by Department name" { $matched = Get-ADSampleUsers -Filter "보안관제팀" @($matched).Count | Should -Be 1 $matched[0].SamAccountName | Should -Be "yylee" $matched[0].IsLocked | Should -BeTrue $matched[0].StatusText | Should -Be "잠김" } It "returns empty collection when filter does not match any user" { $matched = Get-ADSampleUsers -Filter "NonExistentFilterKeyword999" @($matched).Count | Should -Be 0 } It "verifies complete AD user attributes on sample collection" { $users = Get-ADSampleUsers foreach ($u in $users) { $u.SamAccountName | Should -Not -BeNullOrEmpty $u.DisplayName | Should -Not -BeNullOrEmpty $u.Department | Should -Not -BeNullOrEmpty $u.UserPrincipalName | Should -Match "@" $u.DistinguishedName | Should -Match "^CN=" } } It "invokes Get-ADUserList in demo mode and returns sample users" { $cfg = @{ ActiveDirectory = @{ UseLive = $false } } $users = Get-ADUserList -Filter "" -Config $cfg $users.Count | Should -Be 5 } } Context "Account Operations (Demo Simulation Mode)" { It "simulates account unlock and reports success" { $cfg = @{ ActiveDirectory = @{ UseLive = $false } } $res = Unlock-ADUserAccount -SamAccountName "yylee" -Config $cfg $res.Success | Should -BeTrue $res.Message | Should -Match "yylee" $res.Message | Should -Match "잠금이 해제되었습니다" } It "simulates password reset with auto-generated temporary password" { $cfg = @{ ActiveDirectory = @{ UseLive = $false } } $res = Reset-ADUserPassword -SamAccountName "gdhong" -Config $cfg $res.Success | Should -BeTrue $res.Message | Should -Match "gdhong" $res.Message | Should -Match "Passw0rd!#" } It "simulates password reset with custom password" { $cfg = @{ ActiveDirectory = @{ UseLive = $false } } $customPass = "CustomComplexPass!2026" $res = Reset-ADUserPassword -SamAccountName "cskim" -NewPassword $customPass -Config $cfg $res.Success | Should -BeTrue $res.Message | Should -Match $customPass } It "simulates account state change to enabled" { $cfg = @{ ActiveDirectory = @{ UseLive = $false } } $res = Set-ADUserAccountState -SamAccountName "mschoi" -Enable $true -Config $cfg $res.Success | Should -BeTrue $res.Message | Should -Match "활성화" } It "simulates account state change to disabled" { $cfg = @{ ActiveDirectory = @{ UseLive = $false } } $res = Set-ADUserAccountState -SamAccountName "mschoi" -Enable $false -Config $cfg $res.Success | Should -BeTrue $res.Message | Should -Match "비활성화" } } Context "Multi-DC Architecture & Replication Control" { It "returns all 6 domain controllers across distributed sites" { $cfg = @{ ActiveDirectory = @{ UseLive = $false } } $dcs = Get-ADDomainControllers -Config $cfg $dcs.Count | Should -Be 6 $hostNames = @($dcs | ForEach-Object { $_.HostName }) $hostNames | Should -Contain "dc01.corp.local" $hostNames | Should -Contain "dc02.corp.local" $hostNames | Should -Contain "dc03.corp.local" $hostNames | Should -Contain "dc04.corp.local" $hostNames | Should -Contain "dc05.corp.local" $hostNames | Should -Contain "dc06.corp.local" $sites = @($dcs | ForEach-Object { $_.Site }) $sites | Should -Contain "Seoul-HQ" $sites | Should -Contain "Pangyo-DC" $sites | Should -Contain "Daejeon-DR" $sites | Should -Contain "Azure-KOR" } It "identifies dc01.corp.local as Primary Domain Controller (PDC)" { $cfg = @{ ActiveDirectory = @{ UseLive = $false } } $dcs = Get-ADDomainControllers -Config $cfg $pdc = $dcs | Where-Object { $_.IsPdc -eq $true } $pdc | Should -Not -BeNullOrEmpty $pdc.HostName | Should -Be "dc01.corp.local" } It "directly binds to target DC when creating security group to bypass multi-DC replication lag" { $cfg = @{ ActiveDirectory = @{ UseLive = $false; BaseOU = "OU=SecurityGroups,DC=corp,DC=local" } } $members = @("gdhong", "cskim", "yylee") $res = New-ADSecurityGroupOnTargetDc -GroupName "Finance_Share_SecGroup" -GroupScope "Global" -TargetDc "dc04.corp.local" -Members $members -Config $cfg $res.Success | Should -BeTrue $res.TargetDc | Should -Be "dc04.corp.local" $res.GroupDN | Should -Be "CN=Finance_Share_SecGroup,OU=SecurityGroups,DC=corp,DC=local" $res.AddedMembers.Count | Should -Be 3 $res.Message | Should -Match "Direct DC Binding" } It "simulates targeted single-object replication to destination DC" { $cfg = @{ ActiveDirectory = @{ UseLive = $false } } $res = Sync-ADObjectToTargetDc -ObjectDN "CN=HR_Share,OU=Groups,DC=corp,DC=local" -SourceDc "dc04.corp.local" -DestinationDc "dc01.corp.local" -Config $cfg $res.Success | Should -BeTrue $res.SourceDc | Should -Be "dc04.corp.local" $res.DestinationDc | Should -Be "dc01.corp.local" $res.Method | Should -Match "Targeted Replication" $res.Message | Should -Match "즉시 단방향 복제" } It "verifies multi-DC site awareness and WAN replication delay isolation" { # In a multi-site topology (Seoul, Pangyo, Daejeon, Azure), # Direct binding to the NetApp-active DC guarantees 0-second replication lag $cfg = @{ ActiveDirectory = @{ UseLive = $false } } $dcs = Get-ADDomainControllers -Config $cfg $siteMap = @{} foreach ($dc in $dcs) { if (-not $siteMap.ContainsKey($dc.Site)) { $siteMap[$dc.Site] = @() } $siteMap[$dc.Site] += $dc.HostName } $siteMap.Keys.Count | Should -BeGreaterOrEqual 4 $siteMap["Pangyo-DC"] | Should -Contain "dc04.corp.local" } } Context "Live Infrastructure Path & Connectivity Tests" -Tag "Live" { It "safely handles unreachable live domain controller" { $res = Test-ADConnection -DomainController "127.0.0.1:65532" -BaseOU "DC=corp,DC=local" $res.Success | Should -BeFalse $res.Message | Should -Match "AD 연결 실패" } It "gracefully fails over New-ADSecurityGroupOnTargetDc when live target DC is unreachable" { $cfg = @{ ActiveDirectory = @{ UseLive = $true; Username = "test"; Password = "pwd"; UseLdaps = $false } } $res = New-ADSecurityGroupOnTargetDc -GroupName "TestGroup" -TargetDc "127.0.0.1:65532" -Config $cfg $res.Success | Should -BeFalse $res.IsLive | Should -BeTrue $res.Message | Should -Match "실패" } } }