feat: add comprehensive Pester and standalone E2E test suite (96 tests) and code audit report

This commit is contained in:
2026-10-22 09:17:22 +09:00
parent 5da8bb951e
commit 10133ea6d7
15 changed files with 1901 additions and 36 deletions
+14 -8
View File
@@ -11,7 +11,7 @@ function Test-ADConnection {
)
try {
$protocol = if ($UseLdaps) { "LDAP://$DomainController:636" } else { "LDAP://$DomainController" }
$protocol = if ($UseLdaps) { "LDAP://${DomainController}:636" } else { "LDAP://$DomainController" }
$path = if ([string]::IsNullOrWhiteSpace($BaseOU)) { "$protocol/RootDSE" } else { "$protocol/$BaseOU" }
$entry = if ([string]::IsNullOrWhiteSpace($Username)) {
@@ -21,7 +21,10 @@ function Test-ADConnection {
}
# 바인딩 강제 테스트
$null = $entry.NativeObject
if ($null -eq $entry.NativeObject) {
throw "Active Directory DC에 바인딩할 수 없습니다. (NativeObject null)"
}
$null = $entry.Guid
return [PSCustomObject]@{
Success = $true
Message = "Active Directory DC ($DomainController) 연결 성공 (RootDSE 응답 확인)"
@@ -52,7 +55,7 @@ function Get-ADUserList {
$pass = $Config.ActiveDirectory.Password
try {
$prefix = if ($useLdaps) { "LDAP://$dc:636" } else { "LDAP://$dc" }
$prefix = if ($useLdaps) { "LDAP://${dc}:636" } else { "LDAP://$dc" }
$rootPath = if ([string]::IsNullOrWhiteSpace($baseOu)) { $prefix } else { "$prefix/$baseOu" }
$rootEntry = if ([string]::IsNullOrWhiteSpace($user)) {
@@ -102,7 +105,9 @@ function Get-ADUserList {
if ($fileTime -gt 0) {
$lastLogonStr = [DateTime]::FromFileTime($fileTime).ToString("yyyy-MM-dd HH:mm:ss")
}
} catch {}
} catch {
Write-Verbose "lastLogonTimestamp 파싱 건너뜀: $($_.Exception.Message)"
}
}
$userList.Add([PSCustomObject]@{
@@ -201,7 +206,7 @@ function Reset-ADUserPassword {
$pass = $Config.ActiveDirectory.Password
try {
$rootPath = if ([string]::IsNullOrWhiteSpace($baseOu)) { "LDAP://$dc:636" } else { "LDAP://$dc:636/$baseOu" }
$rootPath = if ([string]::IsNullOrWhiteSpace($baseOu)) { "LDAP://${dc}:636" } else { "LDAP://${dc}:636/$baseOu" }
$rootEntry = if ([string]::IsNullOrWhiteSpace($user)) {
New-Object System.DirectoryServices.DirectoryEntry($rootPath)
} else {
@@ -367,13 +372,14 @@ function Get-ADSampleUsers {
)
if ([string]::IsNullOrWhiteSpace($Filter)) {
return $users
return ,$users
} else {
return @($users | Where-Object {
$matched = @($users | Where-Object {
$_.SamAccountName -like "*$Filter*" -or
$_.DisplayName -like "*$Filter*" -or
$_.Department -like "*$Filter*"
})
return ,$matched
}
}
@@ -442,7 +448,7 @@ function New-ADSecurityGroupOnTargetDc {
$user = $Config.ActiveDirectory.Username
$pass = $Config.ActiveDirectory.Password
$useLdaps = [bool]$Config.ActiveDirectory.UseLdaps
$proto = if ($useLdaps) { "LDAP://$TargetDc:636" } else { "LDAP://$TargetDc" }
$proto = if ($useLdaps) { "LDAP://${TargetDc}:636" } else { "LDAP://$TargetDc" }
$ouPath = "$proto/$TargetOU"
$ouEntry = if ([string]::IsNullOrWhiteSpace($user)) {
+4 -4
View File
@@ -566,7 +566,7 @@ function New-NetAppQtreeAction {
$body["svm"] = @{ name = $svm }
}
$res = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/storage/qtrees" -Method "POST" -Body $body -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
$null = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/storage/qtrees" -Method "POST" -Body $body -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
return [PSCustomObject]@{
Success = $true
@@ -624,7 +624,7 @@ function New-NetAppShareAction {
$body["svm"] = @{ name = $svm }
}
$res = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/protocols/cifs/shares" -Method "POST" -Body $body -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
$null = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/protocols/cifs/shares" -Method "POST" -Body $body -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
return [PSCustomObject]@{
Success = $true
@@ -711,7 +711,7 @@ function Set-NetAppShareAcl {
return [PSCustomObject]@{
Success = $false
Message = "NetApp Share ACL 반영 실패 ($MaxRetries회 재시도 초과): $lastError"
Message = "NetApp Share ACL 반영 실패 ($($MaxRetries)회 재시도 초과): $lastError"
Attempts = $attempt
IsLive = $true
}
@@ -772,7 +772,7 @@ function Set-NetAppQuotaRule {
elseif ($QuotaType -eq "group") { $body["group"] = @{ name = $Target } }
}
$res = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/storage/quota/rules" -Method "POST" -Body $body -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
$null = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/storage/quota/rules" -Method "POST" -Body $body -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
return [PSCustomObject]@{
Success = $true
+42
View File
@@ -106,6 +106,40 @@ SELECT
'jhbak' AS SamAccountName, '박지훈' AS UserName, '시스템관리팀' AS Department, 1 AS IsAssigned, '2026-10-01' AS AssignedDate;
"@
# 실제 DB 연결 모드일 경우 실행
$useLive = if ($Config -and $Config.MsSql) { [bool]$Config.MsSql.UseLive } else { $false }
if ($useLive) {
try {
$timeout = if ($Config.MsSql.ConnectionTimeout) { [int]$Config.MsSql.ConnectionTimeout } else { 3 }
$connStr = Build-SqlConnectionString -Server $Config.MsSql.Server -AuthType $Config.MsSql.AuthType -Username $Config.MsSql.Username -Password $Config.MsSql.Password -Timeout $timeout
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
$conn.Open()
$cmd = $conn.CreateCommand()
$cmd.CommandText = $customQuery
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
$table = New-Object System.Data.DataTable
$adapter.Fill($table) | Out-Null
$conn.Close()
$list = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($row in $table.Rows) {
$isAssigned = [bool]$row["IsAssigned"]
$list.Add([PSCustomObject]@{
IsSelected = $false
SamAccountName = $row["SamAccountName"]
UserName = $row["UserName"]
Department = $row["Department"]
Status = if ($isAssigned) { "할당됨" } else { "미할당 (해제됨)" }
IsAssigned = $isAssigned
AssignedDate = if ($row["AssignedDate"] -and $row["AssignedDate"] -ne [System.DBNull]::Value) { $row["AssignedDate"].ToString() } else { "-" }
})
}
return $list.ToArray()
} catch {
Write-Warning "DB 사용자 권한 조회 실패: $($_.Exception.Message) (샘플 데이터 반환)"
}
}
# 목업 및 DB 결과 파싱
return @(
[PSCustomObject]@{ IsSelected = $false; SamAccountName = "gdhong"; UserName = "홍길동"; Department = "클라우드인프라팀"; Status = "할당됨"; IsAssigned = $true; AssignedDate = "2026-09-01" },
@@ -184,6 +218,10 @@ function Add-UsersToShareAndGroup {
$logs = [System.Collections.Generic.List[string]]::new()
$logs.Add("[시작] Share [$ShareName] (AD 보안그룹: $ShareName)에 사용자 추가 작업")
$useLive = if ($Config -and $Config.ActiveDirectory) { [bool]$Config.ActiveDirectory.UseLive } else { $false }
if ($useLive) {
$logs.Add(" -> [Live Mode] 실제 AD 및 DB 인프라 반영 모드 활성화됨")
}
foreach ($sam in $SamAccountNames) {
# --- (A) AD 보안그룹에 사용자 객체 추가 로직 ---
@@ -218,6 +256,10 @@ function Remove-UsersFromShareAndGroup {
$logs = [System.Collections.Generic.List[string]]::new()
$logs.Add("[시작] Share [$ShareName] (AD 보안그룹: $ShareName)에서 사용자 제거/해제 작업")
$useLive = if ($Config -and $Config.ActiveDirectory) { [bool]$Config.ActiveDirectory.UseLive } else { $false }
if ($useLive) {
$logs.Add(" -> [Live Mode] 실제 AD 및 DB 인프라 반영 모드 활성화됨")
}
foreach ($sam in $SamAccountNames) {
# --- (A) AD 보안그룹에서 사용자 객체 제거 로직 ---
+5 -4
View File
@@ -46,7 +46,7 @@ public class WinAuthUniversalImpersonator {
}
"@
if (-not ([System.Management.Automation.PSTypeName]"WinAuthUniversalImpersonator").Type) {
if (-not ("WinAuthUniversalImpersonator" -as [type])) {
Add-Type -TypeDefinition $winAuthTypeDef
}
@@ -370,7 +370,7 @@ function Invoke-SqlQuerySnippet {
return [PSCustomObject]@{
Success = $true
RowCount = 10
Output = "(데모 모드 성공: 10개 행 반환됨 - $((Get-Date).ToString('HH:mm:ss')))`n1. tbl_Users (Row: 14,200)`n2. tbl_AuditLogs (Row: 240,119)`n3. tbl_Orders (Row: 82,900)"
Output = "(데모 모드 성공: [$Query] - 10개 행 반환됨 - $((Get-Date).ToString('HH:mm:ss')))`n1. tbl_Users (Row: 14,200)`n2. tbl_AuditLogs (Row: 240,119)`n3. tbl_Orders (Row: 82,900)"
}
}
@@ -431,11 +431,12 @@ function Get-SqlSampleDatabases {
)
if ([string]::IsNullOrWhiteSpace($Filter)) {
return $databases
return ,$databases
} else {
return @($databases | Where-Object {
$matched = @($databases | Where-Object {
$_.DatabaseName -like "*$Filter*" -or
$_.StateDesc -like "*$Filter*"
})
return ,$matched
}
}
+28 -7
View File
@@ -13,12 +13,13 @@ function Parse-MultiUserInput {
param([string]$RawInput)
if ([string]::IsNullOrWhiteSpace($RawInput)) {
return @()
return ,@()
}
# 쉼표, 세미콜론, 줄바꿈, 탭, 공백 등 모든 구분자로 분리
$tokens = $RawInput -split "[\r\n,;\t]+" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
return @($tokens | Select-Object -Unique)
$uniqueTokens = @($tokens | Select-Object -Unique)
return ,$uniqueTokens
}
function Resolve-UsersWithAmbiguityCheck {
@@ -39,11 +40,28 @@ function Resolve-UsersWithAmbiguityCheck {
[PSCustomObject]@{ SamAccountName = "swjung"; DisplayName = "정수원"; Department = "인사기획팀"; Title = "차장"; Mail = "swjung@corp.local"; EmpId = "EMP1006" }
)
$pool = $allPool
if ($Config -and $Config.ActiveDirectory -and [bool]$Config.ActiveDirectory.UseLive -and (Get-Command Get-ADUserList -ErrorAction SilentlyContinue)) {
try {
$liveUsers = Get-ADUserList -Config $Config
if ($liveUsers -and $liveUsers.Count -gt 0) {
$pool = $liveUsers
}
} catch {
Write-Warning "Live AD 사용자 풀 조회 실패, 기본 풀을 사용합니다: $($_.Exception.Message)"
}
}
$resolvedResults = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($token in $Tokens) {
foreach ($rawToken in $Tokens) {
if ([string]::IsNullOrWhiteSpace($rawToken)) {
continue
}
$token = $rawToken.Trim()
# 1. SamAccountName 정확 일치 우선 검색
$matchById = @($allPool | Where-Object { $_.SamAccountName -ieq $token })
$matchById = @($pool | Where-Object { $_.SamAccountName -ieq $token })
if ($matchById.Count -eq 1) {
$u = $matchById[0]
$resolvedResults.Add([PSCustomObject]@{
@@ -64,8 +82,11 @@ function Resolve-UsersWithAmbiguityCheck {
continue
}
# 2. 이름(DisplayName) 일치 검색 (동명이인 판정)
$matchByName = @($allPool | Where-Object { $_.DisplayName -ieq $token -or $_.DisplayName -like "*$token*" })
# 2. 이름(DisplayName) 일치 검색 (동명이인 판정 - 와일드카드 안전 처리)
$matchByName = @($pool | Where-Object {
$_.DisplayName -ieq $token -or
($_.DisplayName -and ($_.DisplayName.IndexOf($token, [System.StringComparison]::OrdinalIgnoreCase) -ge 0))
})
if ($matchByName.Count -eq 1) {
$u = $matchByName[0]
$resolvedResults.Add([PSCustomObject]@{
@@ -124,5 +145,5 @@ function Resolve-UsersWithAmbiguityCheck {
}
}
return $resolvedResults.ToArray()
return ,$resolvedResults.ToArray()
}