tests/test_quack.ps1
|
# test_quack.ps1 # Validates Quack-related additions (New-DuckDBConnection -QuackToken, # Start-DuckDBQuackServer, Connect-DuckDBQuack) and runs regression checks # on the existing scalar/table-function and core-connection functionality. # Always import from the local repo directory so tests target the version under development Import-Module (Join-Path $PSScriptRoot '..' 'PaperinikDB.psd1') -Force $passed = 0 $failed = 0 $skipped = 0 function Test-Result { param([string]$Name, $Expected, $Actual) if ("$Expected" -eq "$Actual") { Write-Host " PASS: $Name" -ForegroundColor Green $script:passed++ } else { Write-Host " FAIL: $Name | expected='$Expected' actual='$Actual'" -ForegroundColor Red $script:failed++ } } function Test-True { param([string]$Name, [bool]$Result) if ($Result) { Write-Host " PASS: $Name" -ForegroundColor Green $script:passed++ } else { Write-Host " FAIL: $Name | expected=True actual=False" -ForegroundColor Red $script:failed++ } } function Test-Skip { param([string]$Name, [string]$Reason) Write-Host " SKIP: $Name | $Reason" -ForegroundColor Yellow $script:skipped++ } # ───────────────────────────────────────────────────────────────────────────── Write-Host "`n=== REGRESSION: Core connection ===" -ForegroundColor Cyan $conn = New-DuckDBConnection Test-True 'New-DuckDBConnection succeeds' ($null -ne $conn) $r = $conn.sql('SELECT 42 AS answer') Test-Result 'Basic SELECT returns correct value' 42 $r.answer $r = $conn.sql("SELECT 'hello' AS greeting") Test-Result 'String SELECT returns correct value' 'hello' $r.greeting $conn.CloseDB() # ───────────────────────────────────────────────────────────────────────────── Write-Host "`n=== REGRESSION: Scalar UDFs ===" -ForegroundColor Cyan $conn = New-DuckDBConnection $conn.CreateFunction('triple_it', { param([int]$x) $x * 3 }, @([int]), [int]) $r = $conn.sql('SELECT triple_it(14) AS val') Test-Result 'Scalar UDF triple_it(14) = 42' 42 $r.val $conn.CreateFunction('concat_str', { param([string]$a, [string]$b) "$a$b" }, @([string], [string]), [string]) $r = $conn.sql("SELECT concat_str('foo','bar') AS val") Test-Result 'Scalar UDF concat_str = foobar' 'foobar' $r.val $conn.CloseDB() # ───────────────────────────────────────────────────────────────────────────── Write-Host "`n=== REGRESSION: Table UDFs ===" -ForegroundColor Cyan $conn = New-DuckDBConnection $conn.CreateTableFunction( 'two_rows', { @( [PSCustomObject]@{ id = 1; label = 'alpha' } [PSCustomObject]@{ id = 2; label = 'beta' } ) }, @{ id = [int]; label = [string] } ) $r = $conn.sql('SELECT COUNT(*) AS n FROM two_rows()') Test-Result 'Table UDF row count = 2' 2 $r.n $r = $conn.sql('SELECT label FROM two_rows() WHERE id = 2') Test-Result 'Table UDF filtered row = beta' 'beta' $r.label $conn.CloseDB() # ───────────────────────────────────────────────────────────────────────────── Write-Host "`n=== NEW: New-DuckDBConnection -QuackToken ===" -ForegroundColor Cyan try { $conn = New-DuckDBConnection -QuackToken 'regression_test_token' Test-True 'Connection state is Open after -QuackToken' ($conn.State -eq 'Open') # Verify the named secret was registered $secrets = $conn.sql("SELECT name, type FROM duckdb_secrets() WHERE type = 'quack'") Test-True 'Quack secret exists in duckdb_secrets()' ($null -ne $secrets) $conn.CloseDB() } catch { if ($_ -match 'quack|extension|Extension') { Test-Skip 'New-DuckDBConnection -QuackToken' "Quack extension unavailable: $_" } else { $script:failed++ Write-Host " FAIL: New-DuckDBConnection -QuackToken — $_" -ForegroundColor Red } } # ───────────────────────────────────────────────────────────────────────────── Write-Host "`n=== NEW: Start-DuckDBQuackServer / Connect-DuckDBQuack ===" -ForegroundColor Cyan $server = $null $client = $null $quackToken = 'paperinik_' + [System.Guid]::NewGuid().ToString('N').Substring(0, 12) try { # Start the server $server = Start-DuckDBQuackServer -Token $quackToken Test-True 'Start-DuckDBQuackServer returns a handle' ($null -ne $server) Test-Result 'Server handle .Token matches supplied token' $quackToken $server.Token Test-Result 'Server handle .Endpoint is default' 'quack:localhost' $server.Endpoint Test-True 'Server handle exposes Stop() method' (($server | Get-Member -Name 'Stop' -MemberType ScriptMethod | Measure-Object).Count -gt 0) Test-True 'Server handle has Connection property' ($null -ne $server.Connection) # Allow the server runspace a moment to reach its listening state Start-Sleep -Milliseconds 800 # Connect a client $client = Connect-DuckDBQuack -Token $quackToken Test-True 'Connect-DuckDBQuack returns an open connection' ($client.State -eq 'Open') # Create a table on the server side and read it back [void]$client.sql("CREATE TABLE remote.quack_test AS SELECT 99 AS val, 'duckdb' AS name") $r = $client.sql('FROM remote.quack_test') Test-Result 'Remote table roundtrip — val' 99 $r.val Test-Result 'Remote table roundtrip — name' 'duckdb' $r.name $client.CloseDB() $client = $null $server.Stop() $server = $null Test-True 'Server stopped without error' $true } catch { $msg = $_.Exception.Message if ($msg -match 'quack|extension|Extension|9494|port|Port') { Test-Skip 'Quack server/client round-trip' "Quack not available or port busy: $msg" } else { $script:failed++ Write-Host " FAIL: Quack round-trip — $msg" -ForegroundColor Red Write-Host " $($_.ScriptStackTrace)" -ForegroundColor DarkRed } } finally { if ($client) { try { $client.CloseDB() } catch {} } if ($server) { try { $server.Stop() } catch {} } } # ───────────────────────────────────────────────────────────────────────────── Write-Host "`n=== NEW: sql() DDL / DML dispatch ===" -ForegroundColor Cyan $conn = New-DuckDBConnection # DDL — CREATE TABLE should return nothing $ddlResult = $conn.sql('CREATE TABLE items (id INT, name VARCHAR, price DOUBLE)') Test-True 'CREATE TABLE returns nothing' ($null -eq $ddlResult) # DML INSERT — should return nothing (row count → Verbose stream only) $insResult = $conn.sql("INSERT INTO items VALUES (1, 'Apple', 1.50), (2, 'Banana', 0.75), (3, 'Cherry', 3.00)") Test-True 'INSERT returns nothing' ($null -eq $insResult) # SELECT after INSERT — should return rows $rows = $conn.sql('SELECT * FROM items ORDER BY id') Test-True 'SELECT returns rows array' ($rows -is [System.Array] -or $null -ne $rows) Test-Result 'SELECT row count = 3' 3 ($rows | Measure-Object).Count Test-Result 'First row name = Apple' 'Apple' $rows[0].name Test-Result 'Third row price = 3.0' 3.0 $rows[2].price # DML UPDATE $updResult = $conn.sql("UPDATE items SET price = 2.00 WHERE name = 'Apple'") Test-True 'UPDATE returns nothing' ($null -eq $updResult) $r = $conn.sql("SELECT price FROM items WHERE name = 'Apple'") Test-Result 'Price updated to 2.00' 2.0 $r.price # DML DELETE $delResult = $conn.sql("DELETE FROM items WHERE id = 3") Test-True 'DELETE returns nothing' ($null -eq $delResult) $r = $conn.sql('SELECT COUNT(*) AS n FROM items') Test-Result 'Row count after DELETE = 2' 2 $r.n # INSERT … RETURNING — should yield rows $retRows = $conn.sql("INSERT INTO items VALUES (4, 'Date', 5.00) RETURNING *") Test-True 'INSERT RETURNING yields rows' ($null -ne $retRows) Test-Result 'RETURNING row id = 4' 4 $retRows.id Test-Result 'RETURNING row name = Date' 'Date' $retRows.name # SELECT on empty result — should return nothing (not an error) $empty = $conn.sql("SELECT * FROM items WHERE id = 999") Test-True 'SELECT with no results returns nothing' ($null -eq $empty) $conn.CloseDB() # ───────────────────────────────────────────────────────────────────────────── Write-Host "`n=== NEW: Connect-DuckDBQuack alias validation ===" -ForegroundColor Cyan try { Connect-DuckDBQuack -Token 'x' -Alias '123bad' $script:failed++ Write-Host " FAIL: Invalid alias '123bad' should have thrown" -ForegroundColor Red } catch { Test-True 'Invalid SQL alias throws an error' ($_ -match 'valid SQL identifier') } # ───────────────────────────────────────────────────────────────────────────── Write-Host "`n=== NEW: GetAssemblyVersions (AppDomain) ===" -ForegroundColor Cyan $conn = New-DuckDBConnection $conn.Open() # connection must be open so CloseDB() can close it $versions = $conn.GetAssemblyVersions() Test-True 'GetAssemblyVersions returns results' ($null -ne $versions) Test-True 'DuckDB.NET.Data assembly is listed' (($versions | Where-Object { $_.Assembly -like '*DuckDB.NET.Data*' }) -ne $null) Test-True 'DuckDB.NET.Bindings assembly is listed' (($versions | Where-Object { $_.Assembly -like '*DuckDB.NET.Bindings*' }) -ne $null) Test-True 'Each entry has a Version property' (($versions | Where-Object { $null -eq $_.Version }) -eq $null) # Confirm no duplicate entries (LoadFile would have produced duplicates) $dataCount = ($versions | Where-Object { $_.Assembly -like '*DuckDB.NET.Data*' } | Measure-Object).Count Test-Result 'DuckDB.NET.Data appears exactly once' 1 $dataCount $conn.CloseDB() # ───────────────────────────────────────────────────────────────────────────── Write-Host "`n=== NEW: vacuum_rebuild_indexes (ATTACH SQL, v1.5.4+) ===" -ForegroundColor Cyan # vacuum_rebuild_indexes is an ATTACH-level option, not a connection-string key. # Verify the AdditionalOptions doc note is reflected in behaviour: passing it # via connection string should be avoided; using SQL ATTACH is the correct path. $tmpDb = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "pdb_vri_$(Get-Random).duckdb") try { # Create and populate the database, then close it $conn = New-DuckDBConnection -Path $tmpDb $conn.sql('CREATE TABLE t (id INT)') $conn.CloseDB() # Open a separate in-memory connection and ATTACH the file with vacuum_rebuild_indexes. # Correct syntax: alias comes BEFORE the option parentheses. # (A file cannot be ATTACHed while it is already open as the main database.) $conn = New-DuckDBConnection # in-memory main db $conn.sql("ATTACH '$tmpDb' AS vri (vacuum_rebuild_indexes)") Test-True 'ATTACH with vacuum_rebuild_indexes succeeds' ($conn.State -eq 'Open') $r = $conn.sql('SELECT COUNT(*) AS n FROM vri.t') Test-Result 'Attached table accessible after vacuum_rebuild_indexes' 0 $r.n $conn.CloseDB() } catch { $script:failed++ Write-Host " FAIL: vacuum_rebuild_indexes ATTACH — $_" -ForegroundColor Red } finally { if (Test-Path $tmpDb) { Remove-Item $tmpDb -ErrorAction SilentlyContinue } $wdb = $tmpDb -replace '\.duckdb$', '.wal' if (Test-Path $wdb) { Remove-Item $wdb -ErrorAction SilentlyContinue } } # ───────────────────────────────────────────────────────────────────────────── Write-Host "`n=== NEW: Module version ===" -ForegroundColor Cyan $manifest = Import-PowerShellDataFile (Join-Path $PSScriptRoot '..' 'PaperinikDB.psd1') Test-Result 'Module version is 1.5.4' '1.5.4' $manifest.ModuleVersion Test-True 'Tags include Quack' ($manifest.PrivateData.PSData.Tags -contains 'Quack') # ───────────────────────────────────────────────────────────────────────────── Write-Host "`n=== Summary ===" -ForegroundColor Cyan Write-Host "Passed: $passed" -ForegroundColor Green if ($skipped -gt 0) { Write-Host "Skipped: $skipped" -ForegroundColor Yellow } if ($failed -gt 0) { Write-Host "Failed: $failed" -ForegroundColor Red exit 1 } else { Write-Host 'All tests passed!' -ForegroundColor Green } |