Files
ssh-netbox-wrapper/testenv
Sebastian UnterschützandClaude Opus 5.5 dbb99bdded test: add a local Docker test environment for all features
testenv/ starts two real NetBox 4.7 instances (v2 and v1 token), SSH
targets on public, mgmt, and prod networks, a jump host, an IPv6-only
host, a static host, and a client with ssh, sftp, scp, tmux, zellij,
bash, zsh, and fish. A seed script fills NetBox through the REST API.

141 end-to-end checks cover cache and refresh filters, connecting,
all resolver strategies, multiple instances, shortcuts and rewrites,
SSH options and rules, static hosts, sftp/scp incl. IPv6 and jump
hosts, tunnels, local notes and NetBox comments, completion and shell
hooks, the TUI (driven through tmux), the configurator, and uninstall.

Local only: testenv/up.sh, test.sh, shell.sh, down.sh. Not part of
CI/CD.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-14 12:30:24 +01:00
..
…
…
…
…
…
…

netssh test environment

A local Docker environment for testing every netssh feature against real services: two NetBox instances, SSH targets on three networks, a jump host, and a client with ssh, sftp, scp, tmux, zellij, bash, zsh, and fish. It is for local use only and is not part of CI/CD.

testenv/up.sh            # build, start, seed NetBox (first start ~6 min, then ~1 min)
testenv/test.sh          # run all tests; testenv/test.sh tunnel runs only matching files
testenv/shell.sh         # interactive shell in the client, netssh configured
testenv/down.sh          # stop and delete everything (volumes, generated state)

test.sh rebuilds the client image first, so the tests always run the current source. NO_BUILD=1 testenv/test.sh skips that. Exit code 1 if any check fails.

Requirements: Docker with Compose v2, about 3 GB RAM, ports 18000/18001 on 127.0.0.1 free.

Topology

public 10.231.0.0/24 + fd00:30::/64   mgmt 10.231.1.0/24           prod 10.231.2.0/24
 client .10 ────────────┬─ bastion .30 ───────────────────────────────┬─ (only via bastion)
 netbox-fsn .20         │                 fsn1-pve1..3 .11-.13 (mgmt0) │  fsn1-pve1..3 .11-.13 (eth0)
 netbox-lab .21         │                 lab-db1 .41, lab-web1 .42    │  web1 .21, web2 .22
 legacy-box .50         client .10 ───────┘
 v6-host fd00:30::60
NetBox Host Kind Primary IP Other Tags
fsn fsn1-pve1..3.hoster.test device, cluster pve-fsn1 eth0 = prod (needs bastion) mgmt0 = mgmt (direct) pve
fsn bastion.hoster.test device 10.231.0.30 bastion
fsn v6-host device fd00:30::60 (IPv6 only) ipv6
fsn web1, web2 VM, cluster pve-fsn1 prod (needs bastion) web
lab lab-db1 device 10.231.1.41 db
lab web1 VM, cluster lab-k8s 10.231.1.42 web
— legacy-box (10.231.0.50) static host
  • fsn uses a v2 token (nbt_…), lab a legacy v1 token; both come from .state/env.
  • web1 exists in both instances (ambiguity, instance/host).
  • Docker DNS resolves the *.hoster.test names on the mgmt network (hostname strategy).
  • Every target runs sshd on ports 22 and 2222 with users ops and admin, and an HTTP server on its own 127.0.0.1:8081 for tunnel tests ("hello from HOST").
  • NetBox UI: http://127.0.0.1:18000 (fsn), http://127.0.0.1:18001 (lab), login admin / admin.

Files

Path Purpose
compose.yaml All services and networks.
client.Dockerfile, client-entrypoint.sh Client image; builds netssh from the repository.
targets/ SSH target image (sshd config, entrypoint).
seed/seed.py Fills both NetBox instances via the REST API (idempotent), writes .state/.
tests/run.sh, tests/lib.sh Test runner and helpers (check, expect_out, expect_eq, expect_fail, wait_for).
tests/NN-*.sh Test files; each runs in a fresh HOME.
tests/drive.py Drives netssh configure in huh's accessible mode.
.keys/, .state/ Generated test key and seed output (git-ignored).

Writing tests

Each tests/NN-name.sh is sourced by run.sh in a subshell with a new HOME, XDG_CONFIG_HOME, and XDG_CACHE_HOME. Write the netssh config with config <<'EOF' … EOF (both NetBox instances plus your YAML) or config_only (no NetBox), then use the helpers:

config <<'EOF'
ssh: {default_user: ops}
EOF
expect_eq   "connect"   "fsn1-pve1" netssh fsn/fsn1-pve1.hoster.test hostname
expect_out  "dry run"   "-J ops@10.231.0.30" netssh --dry-run fsn/web1
expect_fail "ambiguous" "found 2 exact matches" netssh web1

ssh always reads /home/tester/.ssh/config (OpenSSH ignores $HOME): it uses the test key, BatchMode, StrictHostKeyChecking accept-new, and LogLevel ERROR. Tests that need their own known_hosts set ssh.options.UserKnownHostsFile. The TUI test runs netssh inside tmux and reads the screen with tmux capture-pane.

Not covered

  • The installer (install.sh) downloads release assets from Gitea; it is tested by the Go tests with local fixtures.
  • macOS paths and behaviour.
  • netssh uninstall runs only with --dry-run, so the client keeps its binary.