feat: add comprehensive Pester and standalone E2E test suite (96 tests) and code audit report
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
# Tests\SqlService.Tests.ps1
|
||||
# Unit & Integration Tests for SqlService Module
|
||||
|
||||
BeforeAll {
|
||||
$scriptDir = Split-Path -Parent $PSScriptRoot
|
||||
. (Join-Path $scriptDir "Modules\SqlService.ps1")
|
||||
}
|
||||
|
||||
Describe "SqlService Unit Tests" -Tag "Mock", "Unit" {
|
||||
Context "Build-SqlConnectionString Builder" {
|
||||
It "builds Integrated Security connection string for 'Integrated' auth mode" {
|
||||
$connStr = Build-SqlConnectionString -Server "sql01.corp.local" -AuthType "Integrated" -DefaultDatabase "master" -Timeout 10
|
||||
$connStr | Should -Match "Data Source=sql01\.corp\.local"
|
||||
$connStr | Should -Match "Integrated Security=True"
|
||||
$connStr | Should -Match "Initial Catalog=master"
|
||||
$connStr | Should -Match "Connect Timeout=10"
|
||||
$connStr | Should -Match "Application Name=EnterpriseAdminStudio"
|
||||
}
|
||||
|
||||
It "builds Integrated Security connection string for 'AdImpersonated' auth mode" {
|
||||
# AdImpersonated uses Windows token impersonation on caller thread; SQL Server sees Windows Integrated Security
|
||||
$connStr = Build-SqlConnectionString -Server "sql01.corp.local" -AuthType "AdImpersonated" -Username "CORP\admin_user" -Password "Secret123!" -DefaultDatabase "ERP_DB"
|
||||
$connStr | Should -Match "Integrated Security=True"
|
||||
$connStr | Should -Match "Initial Catalog=ERP_DB"
|
||||
$connStr | Should -Not -Match "Password="
|
||||
$connStr | Should -Not -Match "User ID="
|
||||
}
|
||||
|
||||
It "builds SQL authentication connection string with User ID and Password for 'Sql' auth mode" {
|
||||
$connStr = Build-SqlConnectionString -Server "sql01.corp.local" -AuthType "Sql" -Username "sa" -Password "SqlPass!2026" -DefaultDatabase "HR_DB"
|
||||
$connStr | Should -Match "Integrated Security=False"
|
||||
$connStr | Should -Match "User ID=sa"
|
||||
$connStr | Should -Match "Password=SqlPass!2026"
|
||||
$connStr | Should -Match "Initial Catalog=HR_DB"
|
||||
}
|
||||
|
||||
It "defaults Initial Catalog to master when DefaultDatabase is empty" {
|
||||
$connStr = Build-SqlConnectionString -Server "localhost" -AuthType "Integrated" -DefaultDatabase ""
|
||||
$connStr | Should -Match "Initial Catalog=master"
|
||||
}
|
||||
}
|
||||
|
||||
Context "Windows Impersonation (LOGON32_LOGON_NEW_CREDENTIALS) Architecture" {
|
||||
It "verifies WinAuthUniversalImpersonator type is compiled and available" {
|
||||
$type = "WinAuthUniversalImpersonator" -as [type]
|
||||
$type | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "verifies LOGON32_LOGON_NEW_CREDENTIALS constant is set to 9 (/netonly outbound network token)" {
|
||||
[WinAuthUniversalImpersonator]::LOGON32_LOGON_NEW_CREDENTIALS | Should -Be 9
|
||||
}
|
||||
|
||||
It "verifies LOGON32_PROVIDER_DEFAULT constant is set to 0" {
|
||||
[WinAuthUniversalImpersonator]::LOGON32_PROVIDER_DEFAULT | Should -Be 0
|
||||
}
|
||||
|
||||
It "executes standard Action directly when AuthType is Integrated" {
|
||||
$state = @{ Executed = $false }
|
||||
$res = Invoke-WithSqlAuthContext -AuthType "Integrated" -Action {
|
||||
$state.Executed = $true
|
||||
return "integrated_result"
|
||||
}
|
||||
$state.Executed | Should -BeTrue
|
||||
$res | Should -Be "integrated_result"
|
||||
}
|
||||
|
||||
It "executes standard Action directly when AuthType is Sql" {
|
||||
$state = @{ Executed = $false }
|
||||
$res = Invoke-WithSqlAuthContext -AuthType "Sql" -Username "sa" -Password "pass" -Action {
|
||||
$state.Executed = $true
|
||||
return "sql_auth_result"
|
||||
}
|
||||
$state.Executed | Should -BeTrue
|
||||
$res | Should -Be "sql_auth_result"
|
||||
}
|
||||
|
||||
It "runs action inside RunImpersonatedAction when AuthType is AdImpersonated" {
|
||||
# LOGON32_LOGON_NEW_CREDENTIALS creates outbound credentials handle locally
|
||||
$res = Invoke-WithSqlAuthContext -AuthType "AdImpersonated" -Domain "CORP" -Username "svc_sql_admin" -Password "SecretPass123!" -Action {
|
||||
return "impersonation_block_success"
|
||||
}
|
||||
$res | Should -Be "impersonation_block_success"
|
||||
}
|
||||
|
||||
It "correctly parses DOMAIN\Username and Username@domain into domain and user parts" {
|
||||
$res1 = Invoke-WithSqlAuthContext -AuthType "AdImpersonated" -Username "CORP\admin_svc" -Password "Pass1!" -Action {
|
||||
return "domain_slash_handled"
|
||||
}
|
||||
$res1 | Should -Be "domain_slash_handled"
|
||||
|
||||
$res2 = Invoke-WithSqlAuthContext -AuthType "AdImpersonated" -Username "admin_svc@corp.local" -Password "Pass1!" -Action {
|
||||
return "upn_handled"
|
||||
}
|
||||
$res2 | Should -Be "upn_handled"
|
||||
}
|
||||
|
||||
It "propagates exceptions thrown inside impersonated action block" {
|
||||
{
|
||||
Invoke-WithSqlAuthContext -AuthType "AdImpersonated" -Domain "CORP" -Username "admin_svc" -Password "Pass1!" -Action {
|
||||
throw "SQL connection aborted inside impersonation context"
|
||||
}
|
||||
} | Should -Throw "*SQL connection aborted*"
|
||||
}
|
||||
}
|
||||
|
||||
Context "Mock Data & Database Inspection" {
|
||||
It "retrieves sample databases with complete schema attributes" {
|
||||
$dbs = Get-SqlSampleDatabases
|
||||
$dbs.Count | Should -Be 5
|
||||
|
||||
$erp = $dbs | Where-Object { $_.DatabaseName -eq "ERP_PRODUCTION" }
|
||||
$erp | Should -Not -BeNullOrEmpty
|
||||
$erp.StateDesc | Should -Be "ONLINE"
|
||||
$erp.RecoveryModel | Should -Be "FULL"
|
||||
$erp.IsLive | Should -BeFalse
|
||||
}
|
||||
|
||||
It "filters sample databases by name" {
|
||||
$filtered = Get-SqlSampleDatabases -Filter "ERP"
|
||||
@($filtered).Count | Should -Be 1
|
||||
$filtered[0].DatabaseName | Should -Be "ERP_PRODUCTION"
|
||||
}
|
||||
|
||||
It "filters sample databases by status" {
|
||||
$readOnly = Get-SqlSampleDatabases -Filter "READ_ONLY"
|
||||
@($readOnly).Count | Should -Be 1
|
||||
$readOnly[0].DatabaseName | Should -Be "ARCHIVE_2025"
|
||||
}
|
||||
|
||||
It "returns fallback sample data via Get-SqlDatabases when UseLive is false" {
|
||||
$cfg = @{ MsSql = @{ UseLive = $false } }
|
||||
$dbs = Get-SqlDatabases -Config $cfg
|
||||
$dbs.Count | Should -Be 5
|
||||
}
|
||||
}
|
||||
|
||||
Context "Database Backup & Query Operations (Mock Mode)" {
|
||||
It "simulates full database backup and returns target backup path with timestamp" {
|
||||
$cfg = @{ MsSql = @{ UseLive = $false } }
|
||||
$res = Invoke-SqlBackupAction -DatabaseName "ERP_PRODUCTION" -BackupType "FULL" -Config $cfg
|
||||
$res.Success | Should -BeTrue
|
||||
$res.Database | Should -Be "ERP_PRODUCTION"
|
||||
$res.BackupPath | Should -Match "ERP_PRODUCTION.*\.bak$"
|
||||
$res.Message | Should -Match "가상 완료"
|
||||
}
|
||||
|
||||
It "simulates query execution and returns formatted table output" {
|
||||
$cfg = @{ MsSql = @{ UseLive = $false } }
|
||||
$res = Invoke-SqlQuerySnippet -Query "SELECT name FROM sys.databases" -Config $cfg
|
||||
$res.Success | Should -BeTrue
|
||||
$res.RowCount | Should -Be 10
|
||||
$res.Output | Should -Match "tbl_Users"
|
||||
}
|
||||
}
|
||||
|
||||
Context "Connection Testing & Resilience (Live Mode)" -Tag "Live" {
|
||||
It "safely handles unreachable server without throwing uncaught exception" {
|
||||
# Use immediate rejection on localhost port to avoid long TCP timeout
|
||||
$res = Test-SqlConnection -Server "127.0.0.1,65530" -AuthType "Integrated" -Timeout 1
|
||||
$res.Success | Should -BeFalse
|
||||
$res.Message | Should -Match "SQL Server 연결 실패"
|
||||
}
|
||||
|
||||
It "falls back to sample databases when live SQL server is unreachable" {
|
||||
$cfg = @{ MsSql = @{ UseLive = $true; Server = "127.0.0.1,65530"; ConnectionTimeout = 1; AuthType = "Integrated" } }
|
||||
$dbs = Get-SqlDatabases -Config $cfg
|
||||
$dbs | Should -Not -BeNullOrEmpty
|
||||
$dbs.Count | Should -Be 5
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user