mirror of
https://github.com/kellymichels/zscripts-token-savers
synced 2026-10-07 07:18:18 +00:00
* chore: write the site name as evomedia.net, lowercase The name is a domain and is written as one. Script headers, the README, CHANGELOG and elevator pitch, their .txt twins, and the site page -- matching the same sweep in the private evo.scripts so the mirror does not drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: refresh CHECKSUMS.txt for the lowercase sweep Every script's header changed, so every hash did. The repo's own Checksums test caught it -- which is what it is for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
234 lines
10 KiB
PowerShell
234 lines
10 KiB
PowerShell
# evomedia.net Token Savers - https://github.com/evomedia-net/evo.zscripts
|
|
# Created by Kelly Michels - dev@evomedia.net
|
|
# Licensed under the MIT License. See LICENSE.
|
|
|
|
# ArgumentParsing.Tests.ps1 - Pester 5 suite for how the scripts read their
|
|
# target arguments.
|
|
#
|
|
# Invoke-Pester .\tests
|
|
#
|
|
# This is the layer that has regressed most often: bare vs dashed keys, 'all'
|
|
# expansion, and whether a bad key fails loudly or silently selects nothing.
|
|
#
|
|
# SAFETY
|
|
# ------
|
|
# These tests run the real scripts as child processes, so they are restricted to
|
|
# code paths that exit BEFORE doing any work:
|
|
#
|
|
# * no arguments -> usage + non-zero exit
|
|
# * unknown project key -> error + non-zero exit (Get-ZProject exits first)
|
|
#
|
|
# Only zkill is exercised with a *valid* target, because with the fixture config
|
|
# it looks for listeners on deliberately unused high ports and finds none. The
|
|
# scripts that SSH, scp, deploy, or start servers (zdeploy, zbackup_ec2, zec2,
|
|
# zec2online, zrepair, zstop, zstart, zbackup) are NEVER invoked with a real
|
|
# target here - that is integration territory and needs a disposable server.
|
|
#
|
|
# Each test runs against an isolated temp installation: the .ps1 files copied to
|
|
# a temp dir alongside a fixture zconfig.json. Nothing touches this repo, and no
|
|
# real zconfig.json is read - so the suite is safe on a configured machine and
|
|
# passes on one that has never been configured.
|
|
|
|
BeforeAll {
|
|
$script:RepoRoot = Split-Path -Parent $PSScriptRoot
|
|
|
|
# Isolated installation: the scripts, plus a fixture config beside them.
|
|
$script:Install = Join-Path ([IO.Path]::GetTempPath()) ("zargs-" + [guid]::NewGuid().ToString("N"))
|
|
New-Item -ItemType Directory -Path $script:Install -Force | Out-Null
|
|
Copy-Item (Join-Path $script:RepoRoot "*.ps1") $script:Install -Force
|
|
|
|
# Fake roots that do not exist and ports nothing listens on, so any script
|
|
# that somehow got past its guard would still find nothing to act on.
|
|
$fixture = @{
|
|
ec2 = @{ ip = "203.0.113.10"; user = "testuser"; pemKey = "C:\fixtures\test.pem"; stackRoot = "/home/testuser/stack" }
|
|
paths = @{
|
|
temp = (Join-Path $script:Install "temp")
|
|
backupsLocal = (Join-Path $script:Install "backups")
|
|
backupsEc2 = (Join-Path $script:Install "backups-ec2")
|
|
scriptsRoot = $script:Install
|
|
oneDriveBackups = ""
|
|
}
|
|
projects = [ordered]@{
|
|
_note = "comment key - must never be treated as a project"
|
|
pyapp = @{
|
|
label = "Fixture Python App"; kind = "python"
|
|
localRoot = (Join-Path $script:Install "nonexistent-pyapp")
|
|
startModule = "pyapp.main"
|
|
ports = @{ dev = 59990 }
|
|
remote = @{ path = "/home/testuser/stack/pyapp" }
|
|
}
|
|
viteapp = @{
|
|
label = "Fixture Vite Site"; kind = "vite"
|
|
localRoot = (Join-Path $script:Install "nonexistent-viteapp")
|
|
ports = @{ dev = 59991 }
|
|
remote = @{ path = "/home/testuser/stack/viteapp" }
|
|
}
|
|
edgeproxy = @{
|
|
label = "Fixture Edge"; kind = "edge"
|
|
localRoot = (Join-Path $script:Install "nonexistent-edge")
|
|
remote = @{ path = "/home/testuser/stack/edge" }
|
|
}
|
|
}
|
|
}
|
|
$fixture | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath (Join-Path $script:Install "zconfig.json") -Encoding UTF8
|
|
|
|
# Run a script in a child process; capture merged output and exit code.
|
|
#
|
|
# Memoised on the exact command. Several checks deliberately assert
|
|
# different things about the SAME invocation - that running bare exits
|
|
# non-zero, that its usage lists the project keys, and that the usage
|
|
# never mentions the underscore comment key are three checks of one run.
|
|
# Starting a Windows PowerShell costs about 1.7 s, so re-running the same
|
|
# command to ask it a second question is the single most expensive thing
|
|
# this file does. The scripts reached here either refuse their input or
|
|
# inspect an unused fixture port, so none of them has a side effect a
|
|
# second run would reveal.
|
|
$script:zRuns = @{}
|
|
|
|
function Invoke-ZScript {
|
|
param([string]$Script, [string[]]$ScriptArgs = @())
|
|
$key = @($Script) + $ScriptArgs -join "`n"
|
|
if ($script:zRuns.ContainsKey($key)) { return $script:zRuns[$key] }
|
|
$path = Join-Path $script:Install $Script
|
|
$out = & powershell -NoProfile -ExecutionPolicy Bypass -File $path @ScriptArgs 2>&1 | ForEach-Object { "$_" }
|
|
$result = [pscustomobject]@{
|
|
ExitCode = $LASTEXITCODE
|
|
Output = ($out -join "`n")
|
|
}
|
|
$script:zRuns[$key] = $result
|
|
return $result
|
|
}
|
|
}
|
|
|
|
AfterAll {
|
|
Remove-Item -LiteralPath $script:Install -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
# Every target-taking script must refuse to guess. Running bare used to mean
|
|
# "do it to everything" for some of these, which is how an unintended full
|
|
# backup / deploy happens.
|
|
Describe "no arguments shows usage and exits non-zero" {
|
|
It "<_> refuses to run bare" -ForEach @(
|
|
"zstart.ps1", "ZKillOnly.ps1", "zdeploy.ps1", "zbackup.ps1",
|
|
"zbackup_ec2.ps1", "zec2.ps1", "zec2online.ps1", "zrepair.ps1",
|
|
"zstop.ps1", "zbackup_and_sync.ps1"
|
|
) {
|
|
$r = Invoke-ZScript -Script $_
|
|
$r.ExitCode | Should -Not -Be 0 -Because "$_ run bare must not silently act on every project"
|
|
$r.Output | Should -Match "(?i)usage"
|
|
}
|
|
|
|
It "<_> lists the available project keys in its usage" -ForEach @(
|
|
"zstart.ps1", "ZKillOnly.ps1", "zdeploy.ps1", "zbackup.ps1"
|
|
) {
|
|
$r = Invoke-ZScript -Script $_
|
|
$r.Output | Should -Match "pyapp"
|
|
$r.Output | Should -Match "viteapp"
|
|
}
|
|
|
|
It "usage never advertises the underscore comment key" -ForEach @(
|
|
"zstart.ps1", "ZKillOnly.ps1", "zdeploy.ps1", "zbackup.ps1"
|
|
) {
|
|
(Invoke-ZScript -Script $_).Output | Should -Not -Match "_note"
|
|
}
|
|
}
|
|
|
|
# An unknown key must fail loudly. Silently selecting nothing and exiting 0 is
|
|
# the dangerous outcome: a typo'd key in a scheduled task would look like a
|
|
# successful run that backed up nothing.
|
|
Describe "unknown project key fails loudly" {
|
|
It "<_> rejects a key that is not in the config" -ForEach @(
|
|
"zstart.ps1", "ZKillOnly.ps1", "zdeploy.ps1", "zbackup.ps1",
|
|
"zbackup_ec2.ps1", "zec2.ps1", "zec2online.ps1", "zrepair.ps1", "zstop.ps1"
|
|
) {
|
|
$r = Invoke-ZScript -Script $_ -ScriptArgs @("definitelynotaproject")
|
|
$r.ExitCode | Should -Not -Be 0 -Because "$_ must not treat an unknown key as success"
|
|
$r.Output | Should -Match "(?i)unknown project key|not.*found|available"
|
|
}
|
|
|
|
It "<_> names the offending key and the valid ones" -ForEach @(
|
|
"zstart.ps1", "ZKillOnly.ps1", "zdeploy.ps1"
|
|
) {
|
|
$r = Invoke-ZScript -Script $_ -ScriptArgs @("definitelynotaproject")
|
|
$r.Output | Should -Match "definitelynotaproject"
|
|
$r.Output | Should -Match "pyapp"
|
|
}
|
|
|
|
It "the underscore comment key is not addressable as a project" {
|
|
$r = Invoke-ZScript -Script "ZKillOnly.ps1" -ScriptArgs @("_note")
|
|
$r.ExitCode | Should -Not -Be 0
|
|
}
|
|
}
|
|
|
|
# zdeploy -myapp == zdeploy myapp. The dash is tolerated everywhere for anyone
|
|
# with switch-style muscle memory; it must be stripped before the lookup, not
|
|
# passed through into the key.
|
|
Describe "leading-dash tolerance" {
|
|
It "<_> strips a leading dash before resolving the key" -ForEach @(
|
|
"zstart.ps1", "ZKillOnly.ps1", "zdeploy.ps1"
|
|
) {
|
|
# A dashed *unknown* key proves the strip happened: the error must name
|
|
# 'definitelynotaproject', not '-definitelynotaproject'. Using an unknown
|
|
# key keeps this safe - it exits before doing any work.
|
|
$r = Invoke-ZScript -Script $_ -ScriptArgs @("-definitelynotaproject")
|
|
$r.Output | Should -Match "'definitelynotaproject'"
|
|
}
|
|
}
|
|
|
|
# zkill is the one script safe to run for real here: the fixture's dev ports are
|
|
# deliberately unused, so it finds no listeners and kills nothing.
|
|
Describe "zkill target resolution (safe: fixture ports are unused)" {
|
|
It "accepts a bare project key" {
|
|
$r = Invoke-ZScript -Script "ZKillOnly.ps1" -ScriptArgs @("pyapp")
|
|
$r.ExitCode | Should -Be 0
|
|
$r.Output | Should -Match "Fixture Python App"
|
|
$r.Output | Should -Match "59990"
|
|
}
|
|
|
|
It "accepts the same key with a leading dash and behaves identically" {
|
|
$bare = Invoke-ZScript -Script "ZKillOnly.ps1" -ScriptArgs @("pyapp")
|
|
$dashed = Invoke-ZScript -Script "ZKillOnly.ps1" -ScriptArgs @("-pyapp")
|
|
$dashed.ExitCode | Should -Be $bare.ExitCode
|
|
$dashed.Output | Should -Match "Fixture Python App"
|
|
}
|
|
|
|
It "accepts several keys at once" {
|
|
$r = Invoke-ZScript -Script "ZKillOnly.ps1" -ScriptArgs @("pyapp", "viteapp")
|
|
$r.ExitCode | Should -Be 0
|
|
$r.Output | Should -Match "Fixture Python App"
|
|
$r.Output | Should -Match "Fixture Vite Site"
|
|
}
|
|
|
|
It "'all' expands to every project that has a dev port" {
|
|
$r = Invoke-ZScript -Script "ZKillOnly.ps1" -ScriptArgs @("all")
|
|
$r.ExitCode | Should -Be 0
|
|
$r.Output | Should -Match "Fixture Python App"
|
|
$r.Output | Should -Match "Fixture Vite Site"
|
|
}
|
|
|
|
It "'all' skips projects with no dev port (edge/docker stacks)" {
|
|
# edgeproxy has no ports.dev - it has no local dev server to stop.
|
|
$r = Invoke-ZScript -Script "ZKillOnly.ps1" -ScriptArgs @("all")
|
|
$r.Output | Should -Not -Match "Fixture Edge"
|
|
}
|
|
|
|
It "'-all' works the same as 'all'" {
|
|
$r = Invoke-ZScript -Script "ZKillOnly.ps1" -ScriptArgs @("-all")
|
|
$r.ExitCode | Should -Be 0
|
|
$r.Output | Should -Match "Fixture Python App"
|
|
}
|
|
|
|
It "reports the port it inspected rather than failing on an idle port" {
|
|
$r = Invoke-ZScript -Script "ZKillOnly.ps1" -ScriptArgs @("viteapp")
|
|
$r.ExitCode | Should -Be 0
|
|
$r.Output | Should -Match "59991"
|
|
}
|
|
|
|
It "-Port overrides the configured dev port" {
|
|
$r = Invoke-ZScript -Script "ZKillOnly.ps1" -ScriptArgs @("pyapp", "-Port", "59999")
|
|
$r.ExitCode | Should -Be 0
|
|
$r.Output | Should -Match "59999"
|
|
$r.Output | Should -Not -Match "59990"
|
|
}
|
|
}
|