Part of #26. Phase 2 asserted that Get-ArchiveExcludes returns the right list;
this asserts the archive that actually ships. A name can be on the exclude list
and still land in the zip - only opening the zip proves otherwise. Each test
builds a real temp source tree, archives it, and reads back the entries.
66 tests across: TopLevelExclude (files, directories, multiple names, absent
names), recursive junk-directory pruning at top level and any depth, junk
extensions, nested archives, OS junk filenames, script-file inclusion via
-IncludeScriptFiles, ExtraFiles (how zbackup bundles a pg_dump), zip mechanics
(overwrite, destination creation, forward-slash entry paths), and two
end-to-end shapes - a python deploy zip built from the real exclude list, and a
backup of the same tree that must retain .env and uploads.
Two behaviours are pinned deliberately:
- TopLevelExclude matches TOP-LEVEL names only, so a nested backend/.env is
NOT excluded by listing '.env'. That is current behaviour and the reason
deploys rely on server-side preservation; the test exists so changing it is
a decision rather than an accident.
- An all-junk tree throws instead of producing an empty zip - a silent empty
deploy would unpack to nothing on the server.
Verified by mutation testing, which paid for itself immediately: the first run
showed the junk-directory tests were vacuous. Inside a Where-Object, $_ rebinds
to the pipeline item and shadowed Pester's -ForEach value, so the pattern
matched nothing and the tests passed while asserting nothing. Fixed by
capturing the value first; re-run now catches all 8 mutations (disabling
TopLevelExclude, top-level and nested junk pruning, each file filter, and the
empty-archive throw). ZHelpers.ps1 restored byte-for-byte afterwards.
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.
Part of #26. The toolkit had no automated tests at all - including for the
functions that decide what goes into a deploy zip, which is where a dev .env
reached production (#23).
Phase 1 - the seam. Get-ZConfig read a hardcoded $PSScriptRoot\zconfig.json,
so nothing config-dependent could be tested without touching the real config.
Adds Get-ZConfigPath honoring $env:ZCONFIG (the bash port has always had this,
so it also closes a parity gap) and Reset-ZConfigCache to drop the memoised
config between fixtures. Deliberately did NOT convert the exit 1 paths to
throw: that changes observed CLI output, and the pure functions don't need it.
Phase 2 - 61 tests over the functions with no side effects: Get-ArchiveExcludes
(common/python/vite/nextjs lists, deploy.exclude merging, dedupe, array shape),
Get-ZConfig / Get-ZConfigPath / Get-ZProjectKeys / Get-ZProject (dash tolerance,
underscore-key filtering, memoisation), Get-ZEdgeProject, Get-RemoteComposeDir,
Get-Ec2Target / Get-Ec2Home, Get-LabelFromBuildJsonObj, Read-JsonBuildVersion.
The suite is verified by mutation testing rather than assumed useful - six
deliberate regressions were each introduced and confirmed to turn it red,
including reintroducing the exact #23 bug and its inverse (backups silently
dropping .env/uploads, which would produce restore points that cannot restore).
Runs off a fixture config injected via ZCONFIG, so it never reads a real
zconfig.json and passes on a machine that has never been configured.