[CmdletBinding()] param( [string]$PgRoot = 'D:\pgsql17' ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest $repoRoot = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '..')).Path $pgRootResolved = (Resolve-Path -LiteralPath $PgRoot).Path $bin = Join-Path $pgRootResolved 'bin' $initdb = Join-Path $bin 'initdb.exe' $pgCtl = Join-Path $bin 'pg_ctl.exe' $psql = Join-Path $bin 'psql.exe' $createdb = Join-Path $bin 'createdb.exe' foreach ($required in @($initdb, $pgCtl, $psql, $createdb)) { if (-not (Test-Path -LiteralPath $required -PathType Leaf)) { throw "PostgreSQL executable is missing under the selected PgRoot." } } $postgresExe = Join-Path $bin 'postgres.exe' $version = (& $postgresExe --version 2>&1 | Out-String).Trim() if ($LASTEXITCODE -ne 0 -or $version -notmatch 'PostgreSQL\) 17\.10$') { throw 'T-009 integration requires the frozen PostgreSQL 17.10 binaries.' } $existing5432 = @( Get-NetTCPConnection -State Listen -LocalPort 5432 -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique | Sort-Object ) $listener = [System.Net.Sockets.TcpListener]::new( [System.Net.IPAddress]::Loopback, 0 ) $listener.Start() $port = ([System.Net.IPEndPoint]$listener.LocalEndpoint).Port $listener.Stop() $systemTemp = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()) $clusterName = 'yovision-t009-pg-' + [guid]::NewGuid().ToString('N') $dataDir = Join-Path $systemTemp $clusterName $logPath = Join-Path $systemTemp ($clusterName + '.log') $started = $false $stopped = $false $hadSenseDSN = Test-Path Env:YOVISION_TEST_POSTGRES_DSN $previousSenseDSN = if ($hadSenseDSN) { (Get-Item Env:YOVISION_TEST_POSTGRES_DSN).Value } else { $null } $hadAdminDSN = Test-Path Env:YOVISION_TEST_POSTGRES_ADMIN_DSN $previousAdminDSN = if ($hadAdminDSN) { (Get-Item Env:YOVISION_TEST_POSTGRES_ADMIN_DSN).Value } else { $null } function Invoke-Checked { param( [Parameter(Mandatory)] [string]$FilePath, [Parameter(ValueFromRemainingArguments)] [string[]]$Arguments ) & $FilePath @Arguments if ($LASTEXITCODE -ne 0) { throw "Command failed with exit code $LASTEXITCODE." } } try { New-Item -ItemType Directory -Path $dataDir | Out-Null Invoke-Checked $initdb '-D' $dataDir '-U' 'postgres' '-A' 'trust' '--encoding=UTF8' '--no-locale' '--no-sync' $serverOptions = "-h 127.0.0.1 -p $port -c listen_addresses=127.0.0.1" Invoke-Checked $pgCtl '-D' $dataDir '-l' $logPath '-o' $serverOptions '-w' 'start' $started = $true $adminRootDSN = "postgres://postgres@127.0.0.1:$port/postgres?sslmode=disable" $databaseName = 'yovision_t009' $adminDatabaseDSN = "postgres://postgres@127.0.0.1:$port/${databaseName}?sslmode=disable" $senseDSN = "postgres://yovision_t009_sense@127.0.0.1:$port/${databaseName}?sslmode=disable" Invoke-Checked $psql '-X' '-v' 'ON_ERROR_STOP=1' '-d' $adminRootDSN '-f' (Join-Path $repoRoot 'deploy\postgres\001_roles.sql') Invoke-Checked $createdb '-h' '127.0.0.1' '-p' ([string]$port) '-U' 'postgres' $databaseName foreach ($pass in 1..2) { foreach ($name in @('002_bell.sql', '003_sense.sql', '004_privileges.sql')) { Invoke-Checked $psql '-X' '-v' 'ON_ERROR_STOP=1' '-d' $adminDatabaseDSN '-f' (Join-Path $repoRoot "deploy\postgres\$name") } } Invoke-Checked $psql '-X' '-v' 'ON_ERROR_STOP=1' '-d' $adminRootDSN '-c' 'CREATE ROLE yovision_t009_sense LOGIN IN ROLE sense_app' Invoke-Checked $psql '-X' '-v' 'ON_ERROR_STOP=1' '-d' $adminDatabaseDSN '-f' (Join-Path $repoRoot 'deploy\postgres\tests\assertions.sql') $env:YOVISION_TEST_POSTGRES_DSN = $senseDSN $env:YOVISION_TEST_POSTGRES_ADMIN_DSN = $adminDatabaseDSN Invoke-Checked 'go' '-C' (Join-Path $repoRoot 'Sense') 'test' './internal/store' '-run' '^TestPostgres' '-count=1' } finally { if ($started) { & $pgCtl '-D' $dataDir '-m' 'fast' '-w' 'stop' $stopped = $LASTEXITCODE -eq 0 } if (-not $started -or $stopped) { $resolvedData = [IO.Path]::GetFullPath($dataDir) if (-not $resolvedData.StartsWith($systemTemp, [StringComparison]::OrdinalIgnoreCase) -or [IO.Path]::GetFileName($resolvedData) -notlike 'yovision-t009-pg-*') { throw "Refusing to clean unexpected temporary path." } if (Test-Path -LiteralPath $resolvedData) { Remove-Item -LiteralPath $resolvedData -Recurse -Force } if (Test-Path -LiteralPath $logPath) { Remove-Item -LiteralPath $logPath -Force } } else { Write-Warning 'Temporary PostgreSQL did not stop; its data directory was retained for manual recovery.' } if ($hadSenseDSN) { $env:YOVISION_TEST_POSTGRES_DSN = $previousSenseDSN } else { Remove-Item Env:YOVISION_TEST_POSTGRES_DSN -ErrorAction SilentlyContinue } if ($hadAdminDSN) { $env:YOVISION_TEST_POSTGRES_ADMIN_DSN = $previousAdminDSN } else { Remove-Item Env:YOVISION_TEST_POSTGRES_ADMIN_DSN -ErrorAction SilentlyContinue } $after5432 = @( Get-NetTCPConnection -State Listen -LocalPort 5432 -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique | Sort-Object ) if (($existing5432 -join ',') -ne ($after5432 -join ',')) { throw 'The existing PostgreSQL listener on port 5432 changed during the isolated test.' } } Write-Output "PostgreSQL $version isolated integration passed; temporary port $port was cleaned."