Test catalogue
This reference lists automated suites, cases, and CI coverage. See Testing for execution and limitations and Testbed for fixture setup. Every fixture is synthetic; no suite connects to a real vCenter.
At a glance
| Tier | Command | CI job | Runs | Simulates |
|---|---|---|---|---|
| Unit and package | go test -race ./... |
build (Linux, macOS, Windows) |
every push and PR | pure logic, persistence, CLI wiring, one in-process simulator per test |
| In-process simulator | same command, tests/ package |
build |
every push and PR | the real CLI against govmomi/simulator, direct and through SOCKS5/HTTP proxies |
| Multi-vCenter integration | go test -tags integration -run '^TestVCSIM' ./tests/... |
integration-vcsim |
every push and PR | several independent vCenter processes, one of which can be killed |
| TUI scenarios | scripts/testbed test |
tui-scenarios (Linux, macOS, Windows) |
every push and PR | the TUI model driven key by key, with render goldens |
| PTY journeys | scripts/testbed pty |
tui-pty (Linux) |
every push and PR | the real testbed binary in a real terminal |
| Fuzzing | seeds: go test ./...; campaigns: go test -fuzz |
seeds in build; campaigns in fuzz.yml |
seeds always; campaigns nightly and on PRs that touch fuzzing | arbitrary keys, messages, files, and expressions |
| Kubernetes end-to-end | scripts/test-kubernetes.sh |
kubernetes-e2e |
weekly; PRs that change backend or deploy files | the shipped CronJob in a kind cluster against two vcsim Services |
| Release snapshot | goreleaser release --snapshot |
release-snapshot |
every push and PR | the full release build without publishing |
| Vulnerability scans | scripts/vulnscan |
Go vulnerabilities; scans in release-snapshot |
every PR; daily rescan | reachable Go vulnerabilities, fixable High/Critical image findings, stale databases, expired exceptions |
| Action pins | scripts/check-action-pins.sh |
Pinned actions |
every PR | an action or reusable workflow referenced by tag or branch, or without a version comment |
| Release pins | scripts/check-release-pins.sh |
build (Linux) |
every push and PR | drift between Dockerfile, goreleaser, docs, and the CronJob |
The build job also runs gofmt, go mod tidy, go vet, the labeler
workflow tests (node --test scripts/test-labeler.cjs) and a coverage report.
staticcheck runs as its own job, and docs.yml builds this site with
mkdocs build --strict.
Unit and package tests
Untagged _test.go files run on Linux, macOS, and Windows with -race.
Packages needing a vCenter use an in-process simulator.
| Package | What it covers | Situations simulated |
|---|---|---|
internal/assessment |
History, capture selectors (ID, label, date), diffs, trends, capacity, timelines, policy, perf, migrations | interruption, writer leases, renamed/moved VMs, missing contexts, failed collections, old schemas |
internal/cli |
Commands, flags, exits, JSON, examples | invalid inputs, offline import/export, no clobbering, add-context credential choices without a keyring |
internal/config |
Load/save validation, thumbprints | future versions, duplicate names, dangling via, a leftover [ssh] table kept and noticed |
internal/contextops |
CLI/TUI context saving | keyring cleanup only after save and when requested; shared keys preserved; cleanup failure cannot undo edit |
internal/credentials |
Keyring, prompt, env, file, exec sources | lookup-only probing, isolated resolvers, unset/empty variables, missing/failing/silent/slow helpers, no unattended prompts, concise failure reasons |
internal/decommission |
The VM decommission check | strict blockers, unknown coverage, ambiguous identity, unresolved dependencies |
internal/demo |
Offline demo estate | referential integrity, event log agrees with stored history, duplicate names, five seeded runs with health findings, welcome opt-out |
internal/health |
Health, readiness, orphan/zombie disks | thresholds, empty/partial/truncated evidence, duplicate datastore names, schema gates, passthrough |
internal/humanize, internal/limiter, internal/uistate |
Formatting, bounded concurrency, persisted TUI state | cancellation while waiting for a slot, corrupt or missing state files, state from older releases |
internal/metareport |
Saved metadata reports | unreadable sources produce undetermined membership; failed collections cannot imply removal |
internal/network |
Cross-vCenter network comparison | VLAN gaps, MTU mismatches, a blind distributed switch, ambiguous cluster names |
internal/perf |
Performance summaries and sizing classification | missing samples that are not zero, too few samples, periodic peaks vs sustained low use, contention |
internal/jsonpath |
-o jsonpath, custom-columns and --sort-by templates |
ranges, filters, slices, missing fields printing nothing, unsupported syntax refused |
internal/query |
--where predicates, -l and --field-selector |
numeric comparisons, type errors, named tag/custom fields, set and existence selectors, unread tags matching nothing |
internal/report |
XLSX/CSV, sharing, licenses, vFileInfo, vSource |
deterministic export, fail-closed pseudonymization, key redaction, old evidence, row limits |
internal/rvimport |
RVTools import | missing/extra fields, duplicate identities, size limits, malformed/repeated imports, no mutation client |
internal/session |
Operation deadlines and streaming idle timeouts | long work that keeps streaming, silent streams, timeout errors naming their stage |
internal/sizing |
Destination sizing (N-1, growth, storage) | partial coverage that is never fit, shared RDMs counted once, missing assumptions reported as unknown |
internal/testbed |
The loopback testbed lab itself | authenticated routes start, and wrong fixture credentials are rejected |
internal/topology |
Topology and blast-radius queries | shared datastores across contexts, same-named objects that must stay distinct, blind contexts that downgrade results |
internal/transport |
Direct, SOCKS5, and HTTP(S) proxy dialers | offline proxies, ambient proxy variables that must be ignored |
internal/tui |
Browse/detail, dashboards, history, datastore browser, prompts, context forms | stale replies, narrow renders, failed reloads, refresh tiers, demo restrictions, SSH destinations and argument safety, unavailable keyring, missing credential diagnosis/edit |
internal/vsphere |
Read-only simulator inventory | paging, failed/denied collections, provenance, perf gaps, key-free license metadata |
cmd/vsfleet-demo |
The demo binary | seeded assessment history is wired in |
cmd/vsfleet-vulngate, internal/vulngate |
Vulnerability gate verdicts and exit codes | synthetic govulncheck and grype reports, reachability levels, exceptions by alias, expiry, stale or missing databases |
In-process simulator tests
The untagged tests/ suite drives the CLI against an in-process
govmomi/simulator in the build job.
| File | Situation | Asserts |
|---|---|---|
cli_test.go |
context lifecycle, get all across vCenters, one vCenter failing |
inventory still lists, failures are isolated, vsfleet with no subcommand opens the UI |
cli_credentials_test.go, cli_keyring_test.go, contextops_test.go |
env:, file:, exec: credentials, keyring storage, config set-context with --password-stdin |
non-interactive misses never read stdin, read-only schemes reject --password-stdin, a failed connection test blocks saving unless overridden |
socks5_test.go, httpproxy_test.go |
SOCKS5 with and without remote DNS, HTTP/HTTPS proxies, basic auth | wrong proxy credentials fail, offline proxies fail clearly, untrusted HTTPS proxy certificates are rejected, TLS thumbprint mismatch and discovery |
timeout_test.go |
a vCenter that stops answering mid-enumeration | --timeout bounds the whole listing |
session_invalidation_test.go |
a context edited or removed while a session is open | the edited context talks to the new vCenter; removal closes the session |
readonly_test.go |
every command run against a SOAP-auditing simulator | no command issues a mutating call, and no mutation-capable govmomi package is imported |
cli_exitcode_test.go |
complete and partial captures | partial exits 0 by default and 3 with --fail-on-partial |
cli_dangling_network_test.go |
a VM NIC still backed by a deleted port group | the capture stays complete and the stale backing is an informational finding, not permission-hidden inventory |
cli_hidden_inventory_test.go |
live get of networks, switches, port groups, hosts and host subresources when VMs or a cluster's host count contradict the listing |
the gap is reported instead of No resources found., the exit stays 0, and a deleted port group is not a gap |
cli_get_test.go, cli_get_output_test.go, cli_get_run_test.go, cli_describe_test.go, cli_snapshot_test.go, cli_network_test.go, cli_topology_test.go, cli_events_test.go, cli_dvswitch_resourcepool_test.go, cli_host_inventory_test.go, cli_host_subresource_test.go |
get argument forms and globs, get output formats, tag and field selectors, sorting and watch, get and describe from a capture with --run while vCenter is down, describe, snapshots, networks, topology and its table layout, events when every event is routine, distributed switches, resource pools, host storage and network sheets |
stable identities in JSON, ambiguity across contexts until narrowed, host-scoped device evidence that never leaks across hosts |
cli_datastore_files_test.go, cli_file_inventory_test.go |
datastore browsing and the opt-in file inventory | a default capture never browses, limit flags do not enable browsing, denial and truncation are visible in human and JSON output, export is byte-identical |
cli_license_test.go, cli_vsource_test.go |
license metadata and vSource attribution |
planted license keys appear nowhere; denied or empty license answers read as unavailable |
Multi-vCenter vcsim integration
Tagged integration. Each loopback endpoint runs in its own
cmd/vsfleet-vcsim process and can be killed mid-test. Fixtures are listed
below.
| Test | Fixture | Situation | Asserts |
|---|---|---|---|
TestVCSIMMultiContextInventoryAndProvenance |
topology |
two vCenters listed together | every row carries its context and vCenter identity |
TestVCSIMDuplicateNamesStayDistinct |
topology |
identical names in two vCenters | rows never merge |
TestVCSIMContextIsolation |
topology |
per-context credentials and sessions | one context never uses another's session |
TestVCSIMCLIJSONAndExitContract |
topology |
the JSON and exit-code contract across processes | stable shapes and exit codes |
TestVCSIMTopologyAncestryAndAttachments |
topology |
hierarchy and attachment queries | exact ancestry and datastore/network attachments |
TestVCSIMBlastRadiusNeverCrossesDuplicateNameContexts |
topology |
blast radius with colliding names | impact never crosses into the other vCenter |
TestVCSIMHistoryDiffVMHistoryAndCapacity |
history |
three captures with power and name changes between them | diff, describe vm --history, and capacity follow the changes |
TestVCSIMDiffTreatsLostContextAsUnknownNotRemoved |
history |
a context missing from the second capture | its VMs are not covered, not removed |
TestVCSIMPartialCoverageSurvivesProcessLoss |
partial-failure |
an endpoint process killed during an assessment | the run is partial and the healthy context's data is kept |
TestVCSIMPerfCollectRecordsProvenanceAndFailures |
basic-multivcenter |
capture perf with one endpoint taken down |
per-context status and provenance; values are not asserted because vcsim samples are random |
TestVCSIMSizingCompleteAndPartialRuns |
basic-multivcenter |
destination sizing over complete and partial runs | partial input is never reported as fit |
TestVCSIMSharedExportPseudonymizesDuplicateNames |
duplicate-names |
a scoped, pseudonymized export | colliding names stay distinguishable and originals never leak |
Fixture catalogue
Deterministic topology flags create endpoints with independent vCenter and VM identities.
| Fixture | Topology |
|---|---|
basic-multivcenter |
vc-prod: two datacenters, one cluster each, two hosts per cluster, three VMs per resource pool, three datastores, three port groups, one vApp per cluster. vc-edge: one datacenter/cluster, two hosts, one VM pool, datastore, port group, and vApp. |
duplicate-names |
Identical one-datacenter estates with colliding VM and datastore names. |
partial-failure |
Healthy and killable endpoints plus a closed-port context. |
topology |
One datacenter/cluster, two hosts, VMs, datastores, and port groups, one vApp. |
history |
Small basic estate, captured three times with power and name changes. |
TUI scenarios
The harness sends keys and resizes to the TUI model and checks its view and
semantic Observation. Golden scenarios compare renders at 60x20,
100x30, and 140x40 with
internal/testbed/scenarios/testdata/golden/. See render contracts
before updating goldens.
| Scenario | Profile | Drives | Asserts | Goldens |
|---|---|---|---|---|
overview |
presentation | load all contexts with R |
inventory renders while the failed site stays visible | yes |
partial-failure |
presentation | load all contexts | the failed dr-site context is still shown |
no |
duplicate-names |
presentation | load all contexts | same-named resources render as context-qualified rows | no |
credential-cancel |
presentation | open the labelled prompt, then Esc | prompt wraps; cancellation closes it and reports the result without fixture secrets | yes |
stale-result |
presentation | initial load | context selected only; reordered replies are covered by fuzz/unit tests | no |
history-coverage-gap |
presentation | H |
History opens; goldens record the gap | yes |
add-context-no-secret |
connected | start with in-memory keyring refs | production backend selects a context; password-free saves are checked by TestContextopsSave* and TestFormPreservesANonInteractiveCredential, not this scenario |
no |
credential-source-missing |
connected | missing fixture env source, then diagnosis | failure names VSFLEET_TESTBED_PASSWORD; diagnosis says set/restart or edit with e; provider never reads real environment |
yes |
datastore-browser |
presentation | open nvme-01 browser |
root /, datastore name, entries, and browser mode at each size |
yes |
resize |
presentation | resize through 60x20, 100x30, 140x40 |
inventory still renders and selection is kept | no |
vm-dashboard |
presentation | pages 0–4, ranges 1h→30d→1h at each size |
bounded frames, whole page tabs, detail mode retained | yes |
vm-timeline |
presentation | web-05 timeline, tabs 1–3 at each size |
bounded frames, tab marks, live events labelled with their vCenter, stored tab never labelled live | yes |
network-switches |
presentation | both DVS-Production pages; DVS-Storage Wiring/vMotion | bounded frames, page tabs, switch and port-group names | yes |
host-network |
presentation | esxi-db-08 Network; move switch cursor; open DVS-Storage and return | bounded frames/tabs, missing switch and unattached NICs shown, Esc returns to host | yes |
vlan-map |
presentation | all-context VLAN map/where panel; pair compute-a on prod-vc and edge-vc | bounded frames, both context columns, lost-connectivity VLANs named | yes |
cluster-workspace |
presentation | compute-a Summary, Hosts & VMs (unfold host), Storage; compute-b | bounded frames/tabs, missing datastore named, low HA reserve flagged | yes |
PTY journeys
Tagged linux && pty. Journeys run cmd/vsfleet-testbed in a pseudo-terminal
with isolated home/XDG directories and loopback simulators. Assertions use
normalized output; see PTY execution and artifacts.
| Journey | Situation | Asserts |
|---|---|---|
| SSH failure restores the TUI | SSH to a host whose ssh fails |
the failure is reported and the TUI is usable afterwards |
| SSH hang cancels and restores the TUI | an ssh that never returns, cancelled with Ctrl-C |
the handoff ends as failed and the TUI comes back |
| cancel credential prompt and keep using UI | Esc on the password prompt | the cancellation is reported; help and navigation still work |
| browse datastore find recursively and navigate back | datastore detail, browse, recursive *.vmdk find, open the result, walk back to the root |
each directory level and the detail pane come back in order |
| switch history panes and return without leaked state | Tab through Changes, Trends, Runs, Health, then leave and reopen | each pane header appears, and inventory state is unchanged after leaving |
| resize narrow to wide without corrupting state | start at 60x20 in History, resize to 140x40, keep navigating |
panes keep working after the resize |
| VM dashboard pages, ranges, resize, and quit mid-query | open a VM against the simulator, switch to the Disk page, step to 24h, resize 60x20→140x40, step to the unloaded 7d range and quit at once |
real performance queries render; the page and range survive both resizes; the process exits 0 with a query very likely still in flight |
| ctrl-c cancels active background work | Ctrl-C while an assessment capture is running | exit status 0 |
Send one key per write in a journey: Bubble Tea reads back-to-back runes as a
single multi-rune key, so "rq" matches neither reload nor quit.
Fuzz targets
See Fuzzing for seeds, campaigns, and regression inputs.
| Target | Package | Input | Invariants |
|---|---|---|---|
FuzzTUIKeyResizeNeverPanics |
internal/tui |
up to 256 arbitrary keys with arbitrary terminal sizes | the model never panics |
FuzzStaleMessagesCannotReplaceNewerState |
internal/tui |
pairs of load generations | a reply from an older generation never paints over newer data |
FuzzRenderIsBoundedAndDeterministic |
internal/tui |
terminal sizes | rendering twice gives the same frame and no line is wider than the terminal |
FuzzParseWorkbook |
internal/rvimport |
a block of arbitrary cells (header row included) written into one worksheet of a real vsfleet-written workbook | Parse returns a result or an error, never a panic, and reports the same thing for the same workbook twice |
FuzzLoadSaveRoundTrip |
internal/config |
arbitrary configuration text | anything Load accepts survives Save and loads back to the same contexts |
FuzzNormalizeThumbprintIsIdempotent |
internal/config |
arbitrary thumbprint text | normalizing twice changes nothing |
FuzzParseAndEvaluate |
internal/query |
arbitrary --where expressions with tags and custom attributes readable or not |
accepted unquoted predicates print back to an identical parse, tag./custom. always carry a name, and Evaluate never contradicts Match |
Kubernetes end-to-end
The kind suite uses deploy/kubernetes/cronjob.yaml with the CI image and two
vcsim Deployments, prod and edge.
| Step | Situation | Asserts |
|---|---|---|
| security contract | the CronJob as shipped | non-root, read-only root filesystem, no privilege escalation, all capabilities dropped, fsGroup 65532 |
vsfleet-complete |
both vCenters up | assessment status complete, 2 of 2 contexts |
vsfleet-history |
capture list on the persistent volume |
the earlier run was persisted |
vsfleet-partial |
edge scaled to zero, --fail-on-partial |
the job fails with exit code 3 and the result is partial, 1 of 2 contexts |
vsfleet-readiness |
analyze readiness |
the verdict is not ready and unresolved items are listed |
This tier checks ConfigMap mounts, Secret-backed file: credentials, PVC
ownership, and Service DNS.
Release snapshot and pins
release-snapshot builds the full release without publishing or signing,
checks every OS/architecture archive and WinGet manifest, runs the Linux
binary and amd64 image, then scans them as described in
vulnerability scanning.
python scripts/test-winget-publication.py checks the WinGet catalog
comparison offline: an absent package, an older version, the released
version, unrelated packages, and failed reads.
scripts/check-release-pins.sh fails when:
DockerfileandDockerfile.vcsimuse different base images, or the base image is not pinned by digest;- the
base.name/base.digestOCI annotations in.goreleaser.yamlstop readingBASE_IMAGE_NAME/BASE_IMAGE_DIGEST, whichscripts/base-image-env.shderives from theDockerfileat release time (Dependabot bumps only the Dockerfiles); - any
ghcr.io/easonliuuuuu/vsfleet:vX.Y.Zpin inREADME.md,docs/, ordeploy/differs from.release-please-manifest.json, or sits in a file that release-please does not bump; - a release-please
extra-filesentry has nox-release-pleasemarker.
What nothing here proves
See validation limits and real-vCenter acceptance.
Keeping this page current
Add a row with each new scenario, journey, fuzz target, vcsim test, or CI job.
TestCatalogueDocumentsEveryScenarioAndFuzzTarget in
internal/testbed/scenarios enforces scenario/fuzz rows and fuzz workflow entries.