210 lines
9.7 KiB
PowerShell
210 lines
9.7 KiB
PowerShell
|
|
# 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 "실패"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|