# 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 } } }