mirror of
https://github.com/kellymichels/zscripts-token-savers
synced 2026-10-07 07:18:18 +00:00
test: cover target-argument parsing across the scripts (phase 4)
Part of #26. This layer has regressed more than any other - bare vs dashed keys, 'all' expansion, and whether a bad key fails loudly or quietly selects nothing. 42 tests over three guarantees: - Running bare shows usage and exits non-zero, for all ten target-taking scripts. Some of these used to mean 'do it to everything' when run with no args, which is how an unintended full backup or deploy happens. - An unknown key fails loudly. Exiting 0 having selected nothing is the dangerous outcome: a typo'd key in a scheduled task looks like a successful run that backed up nothing. - A leading dash is stripped before the lookup, so -myapp == myapp. Asserted via a dashed *unknown* key, so the error must name 'x' rather than '-x'. Plus zkill's own resolution: bare key, dashed key, several keys, 'all' and '-all' expanding to projects that have a ports.dev, edge/docker stacks skipped, and -Port overriding the configured port. Safety: the tests run the real scripts as child processes, so they are confined to paths that exit before doing any work. Only zkill runs with a valid target, because the fixture's dev ports (59990/59991) are deliberately unused and its localRoots do not exist - it finds no listeners and kills nothing. zdeploy, zbackup_ec2, zec2, zec2online, zrepair, zstop, zstart and zbackup are never invoked with a real target; that is integration territory needing a disposable server. Each test runs against an isolated temp installation - the .ps1 files copied beside a fixture zconfig.json - so nothing touches this repo, no real config is read, and the suite passes on a machine that has never been configured. That also makes it independent of the ZCONFIG seam in #27, so the PRs can merge in any order. Verified by mutation testing: removing zkill's all-expansion, dash tolerance and no-args guard, making an unknown key exit 0, and letting underscore comment keys leak in as projects each turn the suite red (2/1/2/16/4 tests). Both mutated files confirmed restored byte-for-byte.
This commit is contained in:
parent
4d086bafae
commit
d93a86d836
217
tests/ArgumentParsing.Tests.ps1
Normal file
217
tests/ArgumentParsing.Tests.ps1
Normal file
@ -0,0 +1,217 @@
|
||||
# Evomedia.net Token Savers - https://github.com/kellymichels/zscripts-token-savers
|
||||
# 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.
|
||||
function Invoke-ZScript {
|
||||
param([string]$Script, [string[]]$ScriptArgs = @())
|
||||
$path = Join-Path $script:Install $Script
|
||||
$out = & powershell -NoProfile -ExecutionPolicy Bypass -File $path @ScriptArgs 2>&1 | ForEach-Object { "$_" }
|
||||
return [pscustomobject]@{
|
||||
ExitCode = $LASTEXITCODE
|
||||
Output = ($out -join "`n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user