From 7096f4f1e0706de14d2dabebe91d112b5072260f Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Thu, 9 Jul 2026 14:25:06 +0100 Subject: [PATCH 1/3] Add scavenge script, scheduler, and Pester tests Introduced `scavenge.ps1` to POST to /admin/scavenge with optional Basic Auth. Added `Scavenge.xml` for scheduled execution. Implemented `Scavenge.Tests.ps1` with Pester tests for URI and auth header logic. Documented test plan in `2026-07-09-scavenge-tests.md`. --- Scripts/Scavenge.xml | Bin 0 -> 3830 bytes Scripts/Tests/Scavenge.Tests.ps1 | 78 ++++++++++++ Scripts/scavenge.ps1 | 28 +++++ .../plans/2026-07-09-scavenge-tests.md | 113 ++++++++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 Scripts/Scavenge.xml create mode 100644 Scripts/Tests/Scavenge.Tests.ps1 create mode 100644 Scripts/scavenge.ps1 create mode 100644 docs/superpowers/plans/2026-07-09-scavenge-tests.md diff --git a/Scripts/Scavenge.xml b/Scripts/Scavenge.xml new file mode 100644 index 0000000000000000000000000000000000000000..71db543131295047eef3b387008f9d91cdc323e8 GIT binary patch literal 3830 zcmbW4-EJC55QXb{rF{qFdoY-d9a)43$N913Y^($)H@@-01S12s7`%j+-`($2Gt4xD z*-3<8hOX*URi~?~y8r#BW3TOpO>JT|``Z$mTWVu_u$jHG18Z5snmlVrW;S4ah;(Kn zmtI@n-dP>3V{~3y*M7I(ti$LnDEH4I9ywyLIzv;I?rg#5(N67MSzm9#z$_RuM`5cV} z?~49{?|0rpOwtYd=dP15E;~m=UiW96_ZE>i?Ikg^?8JXx;AhL_FOk2%-l63PVk<}P z*%Q|9usH|)3s^mSj~{>X`_(?$jr;h7{kh}B`Z3luab19N1I~aMT3M}H;xjX8Hb-7n z=RxZ`hozC@DPOiZdV|G@TO7K#v3{%_SpDLwGw1h=_@D4K%v|l^xdp>o>F}!<_Mv34 z=6t`!%B}t7o({32^?zD3v@I*o7O%?CA#)V#nXT*_&xE`$N0{f%3cfy(S^IS}Rx&15 zFVs_u-El=%(unPKB3;Uy%w~| zNbUY(>VT?)sze=Gi%*mPZDzF57K%ostw>{AWtK157bCtiI9U;|D$o^Cr+k)PGlq_{ zORtRf+4tBbD5?xv`47IMmC>q%JgXQln`UCg<3N9wd2e8ydK0>03novg*n35MiixNk94tnbOHUW8A zs5%$y>rdD_L#im0J7f1Mt}efZ5PP9o_W%pB`JT7HSQYC(H#4@{gWC}G2V$JFFZv#( z`m?I3Vvb|2HY2to4~4#<5-B5RJMyth++oj$o>py6Y$xQiIGd0yVp2J>SnsF%3RZsw zA@)JFMVY)m^Rs>s)?6NE^VF%fyH~ctyY!0I1FAZIx?#LHXPe`FnnhggvCO~yadqH0 z%cy(><-<8%>GW5QuG);)pY48LwbysNE)HQ>S-8!IYGy8rEtWGQV`Lj;pJt5t4LO2UN3bkr<)wOAo%-_NJyLfVb=QPA!f3d0gr0G`j#Rx_@-qL$E?4uj zr%pgR3rPRn2KxY(PPqjv;Alp-&<$$f|8>{V-7B*tE;G66fo_57!8eSSxzJrCzE?ER zOF7FNxCiQhDc)aWQ8&x&`<;4-=1=^-qv~@?w&{%2Mpv1zi@zev#k|Z{G|VjKNswtg IyOnnBKasOqd;kCd literal 0 HcmV?d00001 diff --git a/Scripts/Tests/Scavenge.Tests.ps1 b/Scripts/Tests/Scavenge.Tests.ps1 new file mode 100644 index 0000000..9999070 --- /dev/null +++ b/Scripts/Tests/Scavenge.Tests.ps1 @@ -0,0 +1,78 @@ +Describe 'scavenge.ps1' { + It 'posts to /admin/scavenge after trimming a trailing slash from BaseUrl' { + $scriptUnderTest = (Resolve-Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'scavenge.ps1')).Path + $expectedUri = 'http://localhost:2113/admin/scavenge' + $captured = [pscustomobject]@{ + Count = 0 + Uri = $null + Headers = $null + } + + Mock Invoke-RestMethod { + $captured.Count++ + $captured.Uri = $Uri + $captured.Headers = $Headers + @{ status = 'ok' } + } -ParameterFilter { + $Method -eq 'Post' + } + + & $scriptUnderTest -BaseUrl 'http://localhost:2113/' + + $captured.Count | Should Be 1 + $captured.Uri | Should Be $expectedUri + $captured.Headers.Accept | Should Be 'application/json' + $captured.Headers.'Content-Type' | Should Be 'application/json' + $captured.Headers.ContainsKey('Authorization') | Should Be $false + } + + It 'adds a Basic Authorization header when username and password are both provided' { + $scriptUnderTest = (Resolve-Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'scavenge.ps1')).Path + $expectedUri = 'http://localhost:2113/admin/scavenge' + $expectedAuth = 'Basic ' + [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes('alice:secret')) + $captured = [pscustomobject]@{ + Count = 0 + Uri = $null + Headers = $null + } + + Mock Invoke-RestMethod { + $captured.Count++ + $captured.Uri = $Uri + $captured.Headers = $Headers + @{ status = 'ok' } + } -ParameterFilter { + $Method -eq 'Post' + } + + & $scriptUnderTest -BaseUrl 'http://localhost:2113' -Username 'alice' -Password 'secret' + + $captured.Count | Should Be 1 + $captured.Uri | Should Be $expectedUri + $captured.Headers.Authorization | Should Be $expectedAuth + } + + It 'does not add an Authorization header when only one credential is supplied' { + $scriptUnderTest = (Resolve-Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'scavenge.ps1')).Path + $captured = [pscustomobject]@{ + Count = 0 + Uri = $null + Headers = $null + } + + Mock Invoke-RestMethod { + $captured.Count++ + $captured.Uri = $Uri + $captured.Headers = $Headers + @{ status = 'ok' } + } -ParameterFilter { + $Method -eq 'Post' + } + + & $scriptUnderTest -BaseUrl 'http://localhost:2113' -Username 'alice' + + $captured.Count | Should Be 1 + $captured.Uri | Should Be 'http://localhost:2113/admin/scavenge' + $captured.Headers.ContainsKey('Authorization') | Should Be $false + } +} diff --git a/Scripts/scavenge.ps1 b/Scripts/scavenge.ps1 new file mode 100644 index 0000000..ae5e2db --- /dev/null +++ b/Scripts/scavenge.ps1 @@ -0,0 +1,28 @@ +param( + [string]$BaseUrl = "http://localhost:2113", + [string]$Username, + [string]$Password +) + +$BaseUrl = $BaseUrl.TrimEnd('/') +$uri = "$BaseUrl/admin/scavenge" + +$headers = @{ + Accept = 'application/json' + 'Content-Type' = 'application/json' +} + +if ($Username -and $Password) { + $pair = "$Username`:$Password" + $bytes = [System.Text.Encoding]::ASCII.GetBytes($pair) + $encoded = [Convert]::ToBase64String($bytes) + $headers['Authorization'] = "Basic $encoded" +} + +try { + Invoke-RestMethod -Method Post -Uri $uri -Headers $headers +} +catch { + Write-Error "POST to $uri failed: $($_.Exception.Message)" + exit 1 +} \ No newline at end of file diff --git a/docs/superpowers/plans/2026-07-09-scavenge-tests.md b/docs/superpowers/plans/2026-07-09-scavenge-tests.md new file mode 100644 index 0000000..8635e1f --- /dev/null +++ b/docs/superpowers/plans/2026-07-09-scavenge-tests.md @@ -0,0 +1,113 @@ +# Scavenge Script Tests Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add focused unit tests for `Scripts/scavenge.ps1` that cover request construction and authentication header behavior. + +**Architecture:** Keep the existing script unchanged unless a test exposes a real behavioral gap. Add one Pester test file alongside the existing script tests so the new coverage stays in the same style and location as the current PowerShell tests. + +**Tech Stack:** PowerShell, Pester + +## Global Constraints + +- Keep tests local and deterministic; do not hit the real `http://localhost:2113` service. +- Follow the existing script-test style in `Scripts/Tests/Invoke-DailySettlementProcessing.Tests.ps1`. + +--- + +### Task 1: Add scavenge script tests + +**Files:** +- Create: `Scripts/Tests/Scavenge.Tests.ps1` + +**Interfaces:** +- Consumes: `Scripts/scavenge.ps1` +- Produces: Pester coverage for the POST target URI and auth header branching + +- [ ] **Step 1: Write the test file** + +```powershell +Describe 'scavenge.ps1' { + It 'posts to /admin/scavenge after trimming a trailing slash from BaseUrl' { + $scriptUnderTest = (Resolve-Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'scavenge.ps1')).Path + $expectedUri = 'http://localhost:2113/admin/scavenge' + $captured = [pscustomobject]@{ + Count = 0 + Uri = $null + Headers = $null + } + + Mock Invoke-RestMethod { + $captured.Count++ + $captured.Uri = $Uri + $captured.Headers = $Headers + @{ status = 'ok' } + } -ParameterFilter { + $Method -eq 'Post' + } + + & $scriptUnderTest -BaseUrl 'http://localhost:2113/' + + $captured.Count | Should Be 1 + $captured.Uri | Should Be $expectedUri + $captured.Headers.Accept | Should Be 'application/json' + $captured.Headers.'Content-Type' | Should Be 'application/json' + $captured.Headers.ContainsKey('Authorization') | Should Be $false + } + + It 'adds a Basic Authorization header when username and password are both provided' { + $scriptUnderTest = (Resolve-Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'scavenge.ps1')).Path + $expectedUri = 'http://localhost:2113/admin/scavenge' + $expectedAuth = 'Basic ' + [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes('alice:secret')) + $captured = [pscustomobject]@{ + Count = 0 + Uri = $null + Headers = $null + } + + Mock Invoke-RestMethod { + $captured.Count++ + $captured.Uri = $Uri + $captured.Headers = $Headers + @{ status = 'ok' } + } -ParameterFilter { + $Method -eq 'Post' + } + + & $scriptUnderTest -BaseUrl 'http://localhost:2113' -Username 'alice' -Password 'secret' + + $captured.Count | Should Be 1 + $captured.Uri | Should Be $expectedUri + $captured.Headers.Authorization | Should Be $expectedAuth + } + + It 'does not add an Authorization header when only one credential is supplied' { + $scriptUnderTest = (Resolve-Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'scavenge.ps1')).Path + $captured = [pscustomobject]@{ + Count = 0 + Uri = $null + Headers = $null + } + + Mock Invoke-RestMethod { + $captured.Count++ + $captured.Uri = $Uri + $captured.Headers = $Headers + @{ status = 'ok' } + } -ParameterFilter { + $Method -eq 'Post' + } + + & $scriptUnderTest -BaseUrl 'http://localhost:2113' -Username 'alice' + + $captured.Count | Should Be 1 + $captured.Uri | Should Be 'http://localhost:2113/admin/scavenge' + $captured.Headers.ContainsKey('Authorization') | Should Be $false + } +} +``` + +- [ ] **Step 2: Run the test file** + +Run: `Invoke-Pester Scripts/Tests/Scavenge.Tests.ps1` +Expected: all three tests pass From ac77088f7f43cc784f2c36a44a733c1d259db28b Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Thu, 9 Jul 2026 14:38:08 +0100 Subject: [PATCH 2/3] Update Pester assertions to use -Be and -BeFalse syntax Updated test assertions in Scavenge.Tests.ps1 and 2026-07-09-scavenge-tests.md to use Should -Be and Should -BeFalse for consistency with Pester syntax. No changes to test logic or behavior. --- Scripts/Tests/Scavenge.Tests.ps1 | 22 +++++++++---------- .../plans/2026-07-09-scavenge-tests.md | 22 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Scripts/Tests/Scavenge.Tests.ps1 b/Scripts/Tests/Scavenge.Tests.ps1 index 9999070..47957be 100644 --- a/Scripts/Tests/Scavenge.Tests.ps1 +++ b/Scripts/Tests/Scavenge.Tests.ps1 @@ -19,11 +19,11 @@ Describe 'scavenge.ps1' { & $scriptUnderTest -BaseUrl 'http://localhost:2113/' - $captured.Count | Should Be 1 - $captured.Uri | Should Be $expectedUri - $captured.Headers.Accept | Should Be 'application/json' - $captured.Headers.'Content-Type' | Should Be 'application/json' - $captured.Headers.ContainsKey('Authorization') | Should Be $false + $captured.Count | Should -Be 1 + $captured.Uri | Should -Be $expectedUri + $captured.Headers.Accept | Should -Be 'application/json' + $captured.Headers.'Content-Type' | Should -Be 'application/json' + $captured.Headers.ContainsKey('Authorization') | Should -BeFalse } It 'adds a Basic Authorization header when username and password are both provided' { @@ -47,9 +47,9 @@ Describe 'scavenge.ps1' { & $scriptUnderTest -BaseUrl 'http://localhost:2113' -Username 'alice' -Password 'secret' - $captured.Count | Should Be 1 - $captured.Uri | Should Be $expectedUri - $captured.Headers.Authorization | Should Be $expectedAuth + $captured.Count | Should -Be 1 + $captured.Uri | Should -Be $expectedUri + $captured.Headers.Authorization | Should -Be $expectedAuth } It 'does not add an Authorization header when only one credential is supplied' { @@ -71,8 +71,8 @@ Describe 'scavenge.ps1' { & $scriptUnderTest -BaseUrl 'http://localhost:2113' -Username 'alice' - $captured.Count | Should Be 1 - $captured.Uri | Should Be 'http://localhost:2113/admin/scavenge' - $captured.Headers.ContainsKey('Authorization') | Should Be $false + $captured.Count | Should -Be 1 + $captured.Uri | Should -Be 'http://localhost:2113/admin/scavenge' + $captured.Headers.ContainsKey('Authorization') | Should -BeFalse } } diff --git a/docs/superpowers/plans/2026-07-09-scavenge-tests.md b/docs/superpowers/plans/2026-07-09-scavenge-tests.md index 8635e1f..0cf3c87 100644 --- a/docs/superpowers/plans/2026-07-09-scavenge-tests.md +++ b/docs/superpowers/plans/2026-07-09-scavenge-tests.md @@ -48,11 +48,11 @@ Describe 'scavenge.ps1' { & $scriptUnderTest -BaseUrl 'http://localhost:2113/' - $captured.Count | Should Be 1 - $captured.Uri | Should Be $expectedUri - $captured.Headers.Accept | Should Be 'application/json' - $captured.Headers.'Content-Type' | Should Be 'application/json' - $captured.Headers.ContainsKey('Authorization') | Should Be $false + $captured.Count | Should -Be 1 + $captured.Uri | Should -Be $expectedUri + $captured.Headers.Accept | Should -Be 'application/json' + $captured.Headers.'Content-Type' | Should -Be 'application/json' + $captured.Headers.ContainsKey('Authorization') | Should -BeFalse } It 'adds a Basic Authorization header when username and password are both provided' { @@ -76,9 +76,9 @@ Describe 'scavenge.ps1' { & $scriptUnderTest -BaseUrl 'http://localhost:2113' -Username 'alice' -Password 'secret' - $captured.Count | Should Be 1 - $captured.Uri | Should Be $expectedUri - $captured.Headers.Authorization | Should Be $expectedAuth + $captured.Count | Should -Be 1 + $captured.Uri | Should -Be $expectedUri + $captured.Headers.Authorization | Should -Be $expectedAuth } It 'does not add an Authorization header when only one credential is supplied' { @@ -100,9 +100,9 @@ Describe 'scavenge.ps1' { & $scriptUnderTest -BaseUrl 'http://localhost:2113' -Username 'alice' - $captured.Count | Should Be 1 - $captured.Uri | Should Be 'http://localhost:2113/admin/scavenge' - $captured.Headers.ContainsKey('Authorization') | Should Be $false + $captured.Count | Should -Be 1 + $captured.Uri | Should -Be 'http://localhost:2113/admin/scavenge' + $captured.Headers.ContainsKey('Authorization') | Should -BeFalse } } ``` From e6d27abca45330454a38c2b9d9d438a40d1b1aa6 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Thu, 9 Jul 2026 15:02:15 +0100 Subject: [PATCH 3/3] :| --- Scripts/Sales Reconciliation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/Sales Reconciliation.js b/Scripts/Sales Reconciliation.js index b7bbcaa..16677c7 100644 --- a/Scripts/Sales Reconciliation.js +++ b/Scripts/Sales Reconciliation.js @@ -1,5 +1,5 @@ -select CONCAT(YEAR(transactiondate),'-', FORMAT(transactiondate, 'MM')), +/*select CONCAT(YEAR(transactiondate),'-', FORMAT(transactiondate, 'MM')), count(*) as 'count', SUM(case [transaction].TransactionType WHEN 'Logon' THEN 1 @@ -20,7 +20,7 @@ SUM([transaction].TransactionAmount) as totalamount from [transaction] group by CONCAT(YEAR(transactiondate),'-', FORMAT(transactiondate, 'MM')) order by CONCAT(YEAR(transactiondate),'-', FORMAT(transactiondate, 'MM')) asc - +*/ fromAll() .when({