_TESTS/PSLocalBase.Cmdlets.Tests.ps1
|
# =========================================================================== # PSLocalBase.Cmdlets.Tests.ps1 - Test Suite for Native Cmdlets & Pipeline # =========================================================================== $ErrorActionPreference = 'Stop' Import-Module (Join-Path (Split-Path $PSScriptRoot -Parent) 'PSLocalBase.psd1') -Force $script:failed = 0 $script:passed = 0 function Test-CmdletCase([string]$Name, [scriptblock]$Body) { $testDbPath = Join-Path ([System.IO.Path]::GetTempPath()) "pslocalbase_cmdlet_$([System.Guid]::NewGuid().ToString('N')).db" try { Connect-LocalBase -Path $testDbPath & $Body $testDbPath $script:passed++ Write-Host "[PASS] $Name" } catch { $script:failed++ Write-Host "[FAIL] ${Name}: $($_.Exception.Message)" Write-Host " $($_.ScriptStackTrace)" -ForegroundColor DarkGray } finally { Disconnect-LocalBase [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() if (Test-Path $testDbPath) { try { Remove-LocalBaseDatabase -Path $testDbPath -Confirm:$false } catch { } } } } Write-Host "==========================================================" -ForegroundColor Cyan Write-Host " Running PSLocalBase.Cmdlets Dedicated Test Suite" -ForegroundColor Cyan Write-Host " PowerShell Version: $($PSVersionTable.PSVersion)" -ForegroundColor Cyan Write-Host "==========================================================" -ForegroundColor Cyan # --------------------------------------------------------------------------- # C01: Connection Lifecycle & Session Context # --------------------------------------------------------------------------- Test-CmdletCase 'C01 - Connect-LocalBase and Disconnect-LocalBase lifecycle' { param($dbPath) $conn = Get-LocalBaseConnection if ($null -eq $conn -or $conn.DbHandle -eq [System.IntPtr]::Zero) { throw "Expected active connection handle" } Disconnect-LocalBase $connAfter = Get-LocalBaseConnection if ($null -ne $connAfter) { throw "Expected connection to be null after Disconnect-LocalBase" } # Should throw without active connection $rejected = $false try { Get-LocalBaseDocument -Collection 'test' } catch { $rejected = $true } if (-not $rejected) { throw "Expected operation to throw when no active database connection exists" } # Reconnect for teardown Connect-LocalBase -Path $dbPath } # --------------------------------------------------------------------------- # C02: Document Ingestion & Pipeline Streaming # --------------------------------------------------------------------------- Test-CmdletCase 'C02 - Add-LocalBaseDocument (Single, Explicit Id, Pipeline Streaming)' { param($dbPath) # 1. Single auto-key $k1 = Add-LocalBaseDocument -Collection 'users' -Data @{ name = 'Alice'; role = 'Admin' } if ([string]::IsNullOrWhiteSpace($k1)) { throw "Expected generated key" } # 2. Single explicit Id $k2 = Add-LocalBaseDocument -Collection 'users' -Id 'usr_custom' -Data @{ name = 'Bob'; role = 'User' } if ($k2 -ne 'usr_custom') { throw "Expected custom key 'usr_custom', got '$k2'" } # 3. Pipeline streaming (10 items) $items = 1..10 | ForEach-Object { [PSCustomObject]@{ idx = $_ tag = "tag_$_" even = ($_ % 2 -eq 0) } } $pipeKeys = $items | Add-LocalBaseDocument -Collection 'metrics' if ($pipeKeys.Count -ne 10) { throw "Expected 10 generated keys from pipeline, got $($pipeKeys.Count)" } $countMetrics = Get-LocalBaseDocument -Collection 'metrics' -Count if ($countMetrics -ne 10) { throw "Expected 10 stored metrics, got $countMetrics" } } # --------------------------------------------------------------------------- # C03: Get-LocalBaseDocument (Point, Filter, Order, Projection, Count) # --------------------------------------------------------------------------- Test-CmdletCase 'C03 - Get-LocalBaseDocument (Point, Filter, Exists/Missing, Order, Select)' { param($dbPath) $null = @( @{ name = 'Alice'; age = 30; dept = 'IT'; phone = '555-0101'; score = 95 } @{ name = 'Bob'; age = 25; dept = 'HR'; score = 80 } @{ name = 'Carol'; age = 40; dept = 'IT'; phone = '555-0102'; score = 90 } @{ name = 'Dave'; age = 35; dept = 'HR'; score = 70 } ) | Add-LocalBaseDocument -Collection 'employees' # Point Lookup $id = Add-LocalBaseDocument -Collection 'employees' -Id 'emp_solo' -Data @{ name = 'Solo'; age = 99 } $solo = Get-LocalBaseDocument -Collection 'employees' -Id 'emp_solo' if ($solo.name -ne 'Solo' -or $solo.id -ne 'emp_solo') { throw "Failed Point Lookup" } # Filter by Hashtable $itStaff = Get-LocalBaseDocument -Collection 'employees' -Filter @{ dept = 'IT' } if ($itStaff.Count -ne 2) { throw "Expected 2 IT employees, got $($itStaff.Count)" } # Field Operator $seniors = Get-LocalBaseDocument -Collection 'employees' -Field 'age' -Operator '>=' -Value 35 if ($seniors.Count -ne 3) { throw "Expected 3 seniors (Solo 99, Carol 40, Dave 35), got $($seniors.Count)" } # Exists / Missing $withPhone = Get-LocalBaseDocument -Collection 'employees' -Exists 'phone' if ($withPhone.Count -ne 2) { throw "Expected 2 with phone, got $($withPhone.Count)" } $withoutPhone = Get-LocalBaseDocument -Collection 'employees' -Missing 'phone' # Bob, Dave, Solo = 3 if ($withoutPhone.Count -ne 3) { throw "Expected 3 without phone, got $($withoutPhone.Count)" } # OrderBy, Descending, Skip & First $topScore = Get-LocalBaseDocument -Collection 'employees' -OrderBy 'score' -Descending -First 1 if ($topScore.name -ne 'Alice' -or $topScore.score -ne 95) { throw "Expected Alice with score 95" } # Select Projection $projected = Get-LocalBaseDocument -Collection 'employees' -Id 'emp_solo' -Select 'name' if ($projected.PSObject.Properties['age']) { throw "Field 'age' should have been excluded by projection" } if ($projected.name -ne 'Solo') { throw "Field 'name' should be preserved" } # Distinct $depts = Get-LocalBaseDocument -Collection 'employees' -Distinct 'dept' if ($depts.Count -ne 2 -or -not ($depts -contains 'IT') -or -not ($depts -contains 'HR')) { throw "Expected distinct IT and HR departments" } # Count $total = Get-LocalBaseDocument -Collection 'employees' -Count if ($total -ne 5) { throw "Expected 5 total documents, got $total" } } # --------------------------------------------------------------------------- # C04: Pipeline Chaining (Get-LocalBaseDocument -> Update-LocalBaseDocument) # --------------------------------------------------------------------------- Test-CmdletCase 'C04 - Pipeline Chaining (Get -> Update with Deep Merge)' { param($dbPath) $null = @( @{ name = 'Server1'; status = 'Online'; details = @{ cpu = 20; ram = 40 }; tags = @('prod', 'web') } @{ name = 'Server2'; status = 'Degraded'; details = @{ cpu = 90; ram = 85 }; tags = @('stage') } @{ name = 'Server3'; status = 'Degraded'; details = @{ cpu = 95; ram = 90 }; tags = @('stage', 'legacy') } ) | Add-LocalBaseDocument -Collection 'servers' # Pipeline: Find degraded servers and update their maintenance flag Get-LocalBaseDocument -Collection 'servers' -Filter @{ status = 'Degraded' } | Update-LocalBaseDocument -Data @{ maintenance = $true; details = @{ flagged = $true } } # Verify deep merge preserved existing fields (cpu, ram, name) while merging flagged $degraded = Get-LocalBaseDocument -Collection 'servers' -Filter @{ status = 'Degraded' } if ($degraded.Count -ne 2) { throw "Expected 2 degraded servers" } foreach ($srv in $degraded) { if (-not $srv.maintenance) { throw "Expected maintenance = true" } if (-not $srv.details.flagged) { throw "Expected details.flagged = true" } if (-not $srv.details.cpu) { throw "Expected cpu property to be preserved" } } $online = Get-LocalBaseDocument -Collection 'servers' -Filter @{ status = 'Online' } if ($online.PSObject.Properties['maintenance']) { throw "Server1 should not have been updated" } # Array mutations with ArrayUnion / ArrayRemove via Update-LocalBaseDocument Get-LocalBaseDocument -Collection 'servers' -Filter @{ name = 'Server1' } | Update-LocalBaseDocument -Data @{ tags = [PSLocalBase]::ArrayUnion('monitored') } $s1 = Get-LocalBaseDocument -Collection 'servers' -Filter @{ name = 'Server1' } if (-not (@($s1.tags) -contains 'monitored')) { throw "Expected ArrayUnion to add 'monitored' tag" } # Pipeline into Set-LocalBaseDocument and verify zero metadata leakage $srvToReplace = Get-LocalBaseDocument -Collection 'servers' -Filter @{ name = 'Server2' } $srvToReplace.status = 'Repaired' $srvToReplace | Set-LocalBaseDocument $s2Raw = (Get-LocalBaseConnection).collection('servers').doc($srvToReplace.id).get() if ($s2Raw.PSObject.Properties['_localbase_collection'] -or $s2Raw.PSObject.Properties['_localbase_key']) { throw "Internal metadata note properties leaked into the database!" } if ($s2Raw.status -ne 'Repaired') { throw "Set-LocalBaseDocument failed to update repaired status" } } # --------------------------------------------------------------------------- # C05: Pipeline Chaining (Get-LocalBaseDocument -> Remove-LocalBaseDocument) & WhatIf # --------------------------------------------------------------------------- Test-CmdletCase 'C05 - Pipeline Chaining (Get -> Remove) & -WhatIf Safety' { param($dbPath) $null = 1..5 | ForEach-Object { Add-LocalBaseDocument -Collection 'tasks' -Data @{ id_num = $_; done = ($_ -le 3) } } $beforeCount = Get-LocalBaseDocument -Collection 'tasks' -Count if ($beforeCount -ne 5) { throw "Expected 5 tasks" } # WhatIf should NOT delete anything Get-LocalBaseDocument -Collection 'tasks' -Filter @{ done = $true } | Remove-LocalBaseDocument -WhatIf $afterWhatIf = Get-LocalBaseDocument -Collection 'tasks' -Count if ($afterWhatIf -ne 5) { throw "WhatIf must not delete any documents" } # Real removal without confirmation Get-LocalBaseDocument -Collection 'tasks' -Filter @{ done = $true } | Remove-LocalBaseDocument -Confirm:$false $afterRemove = Get-LocalBaseDocument -Collection 'tasks' -Count if ($afterRemove -ne 2) { throw "Expected 2 remaining tasks after delete, got $afterRemove" } # Remove by property-name pipeline binding directly ([PSCustomObject] with Collection and Id) $remainingTask = Get-LocalBaseDocument -Collection 'tasks' -First 1 [PSCustomObject]@{ Collection = 'tasks'; Id = $remainingTask.id } | Remove-LocalBaseDocument -Confirm:$false $afterObjRemove = Get-LocalBaseDocument -Collection 'tasks' -Count if ($afterObjRemove -ne 1) { throw "Expected 1 task remaining after pipeline property name deletion" } # Remove by Filter parameter directly Remove-LocalBaseDocument -Collection 'tasks' -Filter @{ done = $false } -Confirm:$false $finalCount = Get-LocalBaseDocument -Collection 'tasks' -Count if ($finalCount -ne 0) { throw "Expected 0 tasks after filter deletion, got $finalCount" } # Combined Id and Filter: does NOT delete if filter does not match $null = Add-LocalBaseDocument -Collection 'tasks' -Id 't_cond' -Data @{ role = 'admin' } Remove-LocalBaseDocument -Collection 'tasks' -Id 't_cond' -Filter @{ role = 'guest' } -Confirm:$false $survived = Get-LocalBaseDocument -Collection 'tasks' -Id 't_cond' if ($null -eq $survived) { throw "Remove-LocalBaseDocument with non-matching filter deleted the document!" } # Combined Id and Filter: DOES delete when filter matches Remove-LocalBaseDocument -Collection 'tasks' -Id 't_cond' -Filter @{ role = 'admin' } -Confirm:$false $deleted = Get-LocalBaseDocument -Collection 'tasks' -Id 't_cond' if ($null -ne $deleted) { throw "Remove-LocalBaseDocument with matching filter failed to delete document!" } # Empty filter without Id must be rejected and preserve all documents $null = Add-LocalBaseDocument -Collection 'tasks' -Id 't_preserved1' -Data @{ v = 1 } $null = Add-LocalBaseDocument -Collection 'tasks' -Id 't_preserved2' -Data @{ v = 2 } $preCount = Get-LocalBaseDocument -Collection 'tasks' -Count $threwEmpty = $false try { Remove-LocalBaseDocument -Collection 'tasks' -Filter @{} -Confirm:$false } catch [System.ArgumentException] { if ($_.Exception.Message -match 'Clear-LocalBaseCollection') { $threwEmpty = $true } } if (-not $threwEmpty) { throw "Remove-LocalBaseDocument with empty filter must throw ArgumentException directing to Clear-LocalBaseCollection" } $postCount = Get-LocalBaseDocument -Collection 'tasks' -Count if ($postCount -ne $preCount) { throw "Empty filter rejection must preserve all documents in collection; expected $preCount, got $postCount" } } # --------------------------------------------------------------------------- # C06: Collection & Index Management Cmdlets # --------------------------------------------------------------------------- Test-CmdletCase 'C06 - Collection & Index Lifecycle (New, Get, Clear, Remove, Index)' { param($dbPath) New-LocalBaseCollection -Name 'inventory' $colls = Get-LocalBaseCollection if (-not (@($colls) -contains 'inventory')) { throw "Collection 'inventory' should exist" } # Create Unique Index New-LocalBaseIndex -Collection 'inventory' -Field 'sku' -Unique $null = Add-LocalBaseDocument -Collection 'inventory' -Data @{ sku = 'SKU-001'; name = 'Item A' } # Duplicate SKU must be rejected $duplicateRejected = $false try { $null = Add-LocalBaseDocument -Collection 'inventory' -Data @{ sku = 'SKU-001'; name = 'Item B' } } catch { $duplicateRejected = $true } if (-not $duplicateRejected) { throw "Expected unique constraint violation on duplicate SKU" } # Drop index Remove-LocalBaseIndex -Collection 'inventory' -Field 'sku' # Clear collection (truncate documents, retain table) Clear-LocalBaseCollection -Collection 'inventory' -Confirm:$false $cnt = Get-LocalBaseDocument -Collection 'inventory' -Count if ($cnt -ne 0) { throw "Expected 0 items after Clear-LocalBaseCollection" } $collsAfterClear = Get-LocalBaseCollection if (-not (@($collsAfterClear) -contains 'inventory')) { throw "Table 'inventory' should still exist after clear" } # Remove collection (drop table) Remove-LocalBaseCollection -Collection 'inventory' -Confirm:$false $collsAfterDrop = Get-LocalBaseCollection if (@($collsAfterDrop) -contains 'inventory') { throw "Collection 'inventory' should be dropped" } } # --------------------------------------------------------------------------- # C07: Measure-LocalBaseDocument (Aggregations) # --------------------------------------------------------------------------- Test-CmdletCase 'C07 - Measure-LocalBaseDocument (Sum, Average, Min, Max)' { param($dbPath) $null = @( @{ cat = 'A'; price = 10 } @{ cat = 'A'; price = 20 } @{ cat = 'A'; price = 30 } @{ cat = 'B'; price = 100 } ) | Add-LocalBaseDocument -Collection 'products' $statsA = Measure-LocalBaseDocument -Collection 'products' -Field 'price' -Filter @{ cat = 'A' } -Sum -Average -Minimum -Maximum if ($statsA.Count -ne 3) { throw "Expected count 3, got $($statsA.Count)" } if ($statsA.Sum -ne 60) { throw "Expected sum 60, got $($statsA.Sum)" } if ($statsA.Average -ne 20) { throw "Expected average 20, got $($statsA.Average)" } if ($statsA.Minimum -ne 10) { throw "Expected min 10, got $($statsA.Minimum)" } if ($statsA.Maximum -ne 30) { throw "Expected max 30, got $($statsA.Maximum)" } # Measure on empty collection $emptyStats = Measure-LocalBaseDocument -Collection 'empty_items' -Field 'val' -Sum -Average -Minimum -Maximum if ($emptyStats.Count -ne 0) { throw "Expected count 0 for empty collection" } if ($emptyStats.Sum -ne 0) { throw "Expected sum 0 for empty collection" } } # --------------------------------------------------------------------------- # C08: Atomic Transactions (Invoke-LocalBaseTransaction, Start/Complete/Undo) # --------------------------------------------------------------------------- Test-CmdletCase 'C08 - Atomic Transactions (Commit, Rollback on Error)' { param($dbPath) # 1. Successful commit via Invoke-LocalBaseTransaction Invoke-LocalBaseTransaction { $null = Add-LocalBaseDocument -Collection 'ledger' -Data @{ entry = 1; amount = 100 } $null = Add-LocalBaseDocument -Collection 'ledger' -Data @{ entry = 2; amount = 200 } } $cnt1 = Get-LocalBaseDocument -Collection 'ledger' -Count if ($cnt1 -ne 2) { throw "Expected 2 ledger entries after committed transaction" } # 2. Rollback on failure via Invoke-LocalBaseTransaction $errOccurred = $false try { Invoke-LocalBaseTransaction { $null = Add-LocalBaseDocument -Collection 'ledger' -Data @{ entry = 3; amount = 300 } throw "Simulated network or validation fault" } } catch { $errOccurred = $true } if (-not $errOccurred) { throw "Expected transaction error" } $cnt2 = Get-LocalBaseDocument -Collection 'ledger' -Count if ($cnt2 -ne 2) { throw "Expected count to remain 2 after rolled back transaction, got $cnt2" } # 3. Manual Start / Complete Start-LocalBaseTransaction $null = Add-LocalBaseDocument -Collection 'ledger' -Data @{ entry = 4; amount = 400 } Complete-LocalBaseTransaction $cnt3 = Get-LocalBaseDocument -Collection 'ledger' -Count if ($cnt3 -ne 3) { throw "Expected count 3 after Complete-LocalBaseTransaction, got $cnt3" } # 4. Manual Start / Undo Start-LocalBaseTransaction $null = Add-LocalBaseDocument -Collection 'ledger' -Data @{ entry = 5; amount = 500 } Undo-LocalBaseTransaction $cnt4 = Get-LocalBaseDocument -Collection 'ledger' -Count if ($cnt4 -ne 3) { throw "Expected count 3 after Undo-LocalBaseTransaction, got $cnt4" } } # --------------------------------------------------------------------------- # C09: Multi-Database Routing via -Database Parameter # --------------------------------------------------------------------------- Test-CmdletCase 'C09 - Multi-Database Routing via -Database parameter' { param($dbPath) $dbPath2 = "$dbPath.secondary.db" $db2 = $null try { $db2 = New-LocalBase -Path $dbPath2 # Active default DB gets Doc A $null = Add-LocalBaseDocument -Collection 'shared' -Data @{ db = 'Default' } # Explicit -Database $db2 gets Doc B $null = Add-LocalBaseDocument -Collection 'shared' -Data @{ db = 'Secondary' } -Database $db2 $defDocs = Get-LocalBaseDocument -Collection 'shared' $secDocs = Get-LocalBaseDocument -Collection 'shared' -Database $db2 if (@($defDocs).Count -ne 1 -or @($defDocs)[0].db -ne 'Default') { throw "Default database corrupted or cross-contaminated" } if (@($secDocs).Count -ne 1 -or @($secDocs)[0].db -ne 'Secondary') { throw "Secondary database corrupted or cross-contaminated" } } finally { if ($db2) { $db2.close() } if (Test-Path $dbPath2) { Remove-LocalBaseDatabase -Path $dbPath2 -Confirm:$false } } } # --------------------------------------------------------------------------- # C10: Mixed-Mode Interoperability (Fluent <-> Cmdlets) # --------------------------------------------------------------------------- Test-CmdletCase 'C10 - Mixed-Mode Interoperability (Fluent API + Cmdlet Pipeline)' { param($dbPath) # 1. Ingest via Cmdlet $null = @( @{ id = 'item_A1'; sku = 'A1'; price = 50 } @{ id = 'item_B2'; sku = 'B2'; price = 120 } @{ id = 'item_C3'; sku = 'C3'; price = 200 } ) | Add-LocalBaseDocument -Collection 'items' # 2. Query via Primary Fluent API on active connection $db = Get-LocalBaseConnection $fluentItems = $db.collection('items').where('price', '>', 100).orderBy('price').get() if (@($fluentItems).Count -ne 2) { throw "Fluent query expected 2 items" } # 3. Pipe fluent results back to Cmdlet $fluentItems | Update-LocalBaseDocument -Collection 'items' -Data @{ premium = $true } # 4. Verify via Cmdlet $premiumCount = Get-LocalBaseDocument -Collection 'items' -Filter @{ premium = $true } -Count if ($premiumCount -ne 2) { throw "Expected 2 premium items after mixed update, got $premiumCount" } } # --------------------------------------------------------------------------- # C11: Backup-LocalBaseDatabase (Online VACUUM INTO) # --------------------------------------------------------------------------- Test-CmdletCase 'C11 - Backup-LocalBaseDatabase (Hot Snapshot)' { param($dbPath) $null = Add-LocalBaseDocument -Collection 'vault' -Data @{ secret = 'TopSecretData'; level = 5 } $backupPath = "$dbPath.backup.db" try { Backup-LocalBaseDatabase -DestinationPath $backupPath if (-not (Test-Path $backupPath)) { throw "Backup file was not created" } # Verify backup contents $bDb = New-LocalBase -Path $backupPath $bSecret = $bDb.collection('vault').first() if ($bSecret.secret -ne 'TopSecretData') { throw "Backup content mismatch" } $bDb.close() # Relative destination path test $tempDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.Guid]::NewGuid().ToString('N')) $null = [System.IO.Directory]::CreateDirectory($tempDir) try { Push-Location $tempDir $relBackup = ".\vault_relative.db" Backup-LocalBaseDatabase -DestinationPath $relBackup if (-not (Test-Path $relBackup)) { throw "Relative path backup was not created at current location" } $relDb = New-LocalBase -Path $relBackup if ($relDb.collection('vault').first().secret -ne 'TopSecretData') { throw "Relative backup content mismatch" } $relDb.close() } finally { Pop-Location if (Test-Path $tempDir) { [System.IO.Directory]::Delete($tempDir, $true) } } } finally { if (Test-Path $backupPath) { Remove-LocalBaseDatabase -Path $backupPath -Confirm:$false } } } # --------------------------------------------------------------------------- # C12: Cmdlet Aliases (gld, ald, sld, uld, rld) # --------------------------------------------------------------------------- Test-CmdletCase 'C12 - Cmdlet Aliases (gld, ald, sld, uld, rld)' { param($dbPath) $id = ald 'alias_test' @{ val = 1 } $doc = gld 'alias_test' -Id $id if ($doc.val -ne 1) { throw "gld failed" } sld 'alias_test' $id @{ val = 2 } $doc2 = gld 'alias_test' -Id $id if ($doc2.val -ne 2) { throw "sld failed" } uld 'alias_test' $id @{ val = 3 } $doc3 = gld 'alias_test' -Id $id if ($doc3.val -ne 3) { throw "uld failed" } rld 'alias_test' $id -Confirm:$false $cnt = gld 'alias_test' -Count if ($cnt -ne 0) { throw "rld failed" } } # --------------------------------------------------------------------------- # C13: Empty Results, Missing ID, and Non-Existent Collection # --------------------------------------------------------------------------- Test-CmdletCase 'C13 - Empty Results, Missing ID, and Non-Existent Collection' { param($dbPath) # 1. Non-existent collection returns empty / 0 $emptyList = @(Get-LocalBaseDocument -Collection 'nonexistent') if ($emptyList.Count -ne 0) { throw "Expected 0 results from non-existent collection" } $emptyCount = Get-LocalBaseDocument -Collection 'nonexistent' -Count if ($emptyCount -ne 0) { throw "Expected count 0 from non-existent collection" } # 2. Point lookup for missing ID returns $null New-LocalBaseCollection -Name 'test_empty' $missingDoc = Get-LocalBaseDocument -Collection 'test_empty' -Id 'missing_key' if ($null -ne $missingDoc) { throw "Expected null for missing document ID" } # 3. Query filter with zero matches returns empty array $null = Add-LocalBaseDocument -Collection 'test_empty' -Data @{ status = 'Pending' } $filteredEmpty = @(Get-LocalBaseDocument -Collection 'test_empty' -Filter @{ status = 'Completed' }) if ($filteredEmpty.Count -ne 0) { throw "Expected 0 results for non-matching filter" } } # --------------------------------------------------------------------------- # C14: Boolean, Null, and DataType Filtering # --------------------------------------------------------------------------- Test-CmdletCase 'C14 - Boolean, Null, and DataType Filtering' { param($dbPath) $null = @( @{ name = 'Alpha'; active = $true; note = $null } @{ name = 'Beta'; active = $false; note = 'urgent' } @{ name = 'Gamma'; active = $true; note = 'normal' } ) | Add-LocalBaseDocument -Collection 'items' # Filter by boolean $actives = @(Get-LocalBaseDocument -Collection 'items' -Filter @{ active = $true }) if ($actives.Count -ne 2) { throw "Expected 2 active items, got $($actives.Count)" } $inactives = @(Get-LocalBaseDocument -Collection 'items' -Filter @{ active = $false }) if ($inactives.Count -ne 1) { throw "Expected 1 inactive item, got $($inactives.Count)" } # Filter with -Operator and -Value $falseItems = @(Get-LocalBaseDocument -Collection 'items' -Field 'active' -Operator '==' -Value $false) if ($falseItems.Count -ne 1 -or $falseItems[0].name -ne 'Beta') { throw "Field operator boolean filter failed" } } # --------------------------------------------------------------------------- # C15: Pipeline Update with Array Mutations (Union / Remove) # --------------------------------------------------------------------------- Test-CmdletCase 'C15 - Pipeline Update with Array Mutations (Union / Remove)' { param($dbPath) $id = Add-LocalBaseDocument -Collection 'articles' -Data @{ title = 'Post 1'; tags = @('powershell', 'database') } # ArrayUnion via Update-LocalBaseDocument Update-LocalBaseDocument -Collection 'articles' -Id $id -Data @{ tags = @{ '$arrayUnion' = @('nosql', 'powershell') } } $docAfterUnion = Get-LocalBaseDocument -Collection 'articles' -Id $id if (@($docAfterUnion.tags).Count -ne 3 -or -not (@($docAfterUnion.tags) -contains 'nosql')) { throw "Expected tags to have 3 elements containing 'nosql'" } # ArrayRemove via Update-LocalBaseDocument Update-LocalBaseDocument -Collection 'articles' -Id $id -Data @{ tags = @{ '$arrayRemove' = @('database') } } $docAfterRemove = Get-LocalBaseDocument -Collection 'articles' -Id $id if (@($docAfterRemove.tags).Count -ne 2 -or (@($docAfterRemove.tags) -contains 'database')) { throw "Expected 'database' to be removed from tags" } } # --------------------------------------------------------------------------- # C16: Transaction Lifecycle (Start, Complete, Pipeline Return, State) # --------------------------------------------------------------------------- Test-CmdletCase 'C16 - Transaction Lifecycle (Start, Complete, Pipeline Return, State)' { param($dbPath) $conn = Get-LocalBaseConnection if ($conn.InTransaction) { throw "Should not be in transaction initially" } # Manual Start & Complete Start-LocalBaseTransaction if (-not $conn.InTransaction) { throw "Expected InTransaction = true after Start-LocalBaseTransaction" } $null = Add-LocalBaseDocument -Collection 'tx_test' -Data @{ n = 1 } Complete-LocalBaseTransaction if ($conn.InTransaction) { throw "Expected InTransaction = false after Complete-LocalBaseTransaction" } $count = Get-LocalBaseDocument -Collection 'tx_test' -Count if ($count -ne 1) { throw "Expected 1 document after Complete-LocalBaseTransaction" } # Invoke-LocalBaseTransaction returning output to pipeline $returnedKey = Invoke-LocalBaseTransaction { Add-LocalBaseDocument -Collection 'tx_test' -Data @{ n = 2 } } if ([string]::IsNullOrWhiteSpace($returnedKey)) { throw "Invoke-LocalBaseTransaction should propagate scriptblock output" } $finalCount = Get-LocalBaseDocument -Collection 'tx_test' -Count if ($finalCount -ne 2) { throw "Expected 2 documents after transaction block" } } # --------------------------------------------------------------------------- # C17: Comprehensive -WhatIf Safety # --------------------------------------------------------------------------- Test-CmdletCase 'C17 - Comprehensive -WhatIf Safety' { param($dbPath) $id = Add-LocalBaseDocument -Collection 'safeguard' -Data @{ v = 10 } # 1. Update-LocalBaseDocument -WhatIf Update-LocalBaseDocument -Collection 'safeguard' -Id $id -Data @{ v = 99 } -WhatIf $docAfterWhatIf = Get-LocalBaseDocument -Collection 'safeguard' -Id $id if ($docAfterWhatIf.v -ne 10) { throw "Update -WhatIf must not alter document" } # 2. Clear-LocalBaseCollection -WhatIf Clear-LocalBaseCollection -Collection 'safeguard' -WhatIf $cntAfterClearWhatIf = Get-LocalBaseDocument -Collection 'safeguard' -Count if ($cntAfterClearWhatIf -ne 1) { throw "Clear -WhatIf must not truncate documents" } # 3. Remove-LocalBaseCollection -WhatIf Remove-LocalBaseCollection -Collection 'safeguard' -WhatIf $colls = Get-LocalBaseCollection if (-not (@($colls) -contains 'safeguard')) { throw "Remove-Collection -WhatIf must not drop collection" } } # --------------------------------------------------------------------------- # C18: Nested Projections in Point Lookup & Explicit Connection Disconnect # --------------------------------------------------------------------------- Test-CmdletCase 'C18 - Nested Select Projection and Explicit Session Disconnect' { param($dbPath) $null = Add-LocalBaseDocument -Collection 'profiles' -Id 'p1' -Data @{ name = 'Carlos' contact = @{ email = 'carlos@example.com'; city = 'Miami' } } # Nested projection in ById parameter set $p = Get-LocalBaseDocument -Collection 'profiles' -Id 'p1' -Select 'name', 'contact.city' if ($p.name -ne 'Carlos') { throw "Expected projected name" } if ($p.PSObject.Properties['contact.city'].Value -ne 'Miami') { throw "Expected projected nested contact.city" } # Close the active session explicitly before removing its files. $tempDb = Join-Path ([System.IO.Path]::GetTempPath()) "pslocalbase_removal_$([System.Guid]::NewGuid().ToString('N')).db" try { Connect-LocalBase -Path $tempDb $null = Add-LocalBaseDocument -Collection 'data' -Data @{ ok = $true } Disconnect-LocalBase Remove-LocalBaseDatabase -Path $tempDb -Confirm:$false $conn = Get-LocalBaseConnection if ($null -ne $conn) { throw "Expected active connection to be disconnected upon database removal" } if (Test-Path $tempDb) { throw "Expected database file to be deleted from disk" } } finally { Disconnect-LocalBase if (Test-Path $tempDb) { Remove-Item $tempDb -Force -ErrorAction SilentlyContinue } } } Write-Host "==========================================================" -ForegroundColor Cyan Write-Host " PSLocalBase.Cmdlets Test Suite Completed" -ForegroundColor Cyan Write-Host " Total Passed: $script:passed" -ForegroundColor Green Write-Host " Total Failed: $script:failed" -ForegroundColor $(if ($script:failed -gt 0) { 'Red' } else { 'Green' }) Write-Host "==========================================================" -ForegroundColor Cyan if ($script:failed -gt 0) { exit 1 } |