feat(test): run live Hashview tests against a local docker stack

The live Hashview tests previously could only run against a remote server
configured in config.json, and the CLI ignored env-var overrides, so the
suite always hit prod. Prod runs an older Hashview whose /v1/jobs/add 500s
on a notify_email kwarg, which masked real client/server drift.

Wire the suite up to a local Hashview docker stack:

- main.py: HASHVIEW_URL / HASHVIEW_API_KEY env vars now override config.json,
  so the CLI can be pointed at a local instance without editing config.
- tests/_hashview_local.py + pytest_configure: when HASHVIEW_TEST_LOCAL=1,
  bring up + seed the Hashview compose stack (HASHVIEW_REPO), verify
  authenticated API access, export the HASHVIEW_* env, and tear down
  (HASHVIEW_KEEP=1 keeps it). Runs in configure so collection-time skipif
  on HASHVIEW_TEST_REAL sees the exported env.
- tests/hashview_local_seed.py: seed an admin api_key + non-default password
  (else Hashview's setup guard redirects every request to /setup), a
  customer, a hashfile, and cracked "effective task" data (else /v1/jobs/add
  has no tasks to schedule).
- api.py: download_left_hashes now uses GET /v1/hashfiles/<id>; the old
  /v1/hashfiles/<id>/left route 404s on current servers.
- subprocess tests: assert on the CLI's actual output ("Job ID:") rather than
  the literal "Job created", and strip ambient HASHVIEW_* creds from the
  no-key-configured check so the env override doesn't defeat it.
- README/CLAUDE: document HASHVIEW_TEST_LOCAL and the env overrides.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Justin Bollinger
2026-06-25 10:17:49 -04:00
co-authored by Claude Opus 4.8
parent dcf377c3d4
commit 048ed7cac1
8 changed files with 488 additions and 9 deletions
@@ -105,6 +105,16 @@ def test_hashview_subcommands_require_api_key(tmp_path, args):
path.write_text("dummy\n")
args[idx + 1] = str(path)
# Strip any ambient Hashview credentials (e.g. exported by the local-stack
# fixture) so this exercises the genuine no-key-configured path. The CLI
# honours HASHVIEW_URL / HASHVIEW_API_KEY as overrides, so leaving them set
# would supply a key and defeat the check.
sub_env = {
k: v
for k, v in os.environ.items()
if k not in ("HASHVIEW_URL", "HASHVIEW_API_KEY")
}
sub_env["PYTHONUNBUFFERED"] = "1"
cli_cmd = [sys.executable, HATE_CRACK_SCRIPT] + args
result = subprocess.run(
cli_cmd,
@@ -112,7 +122,7 @@ def test_hashview_subcommands_require_api_key(tmp_path, args):
stderr=subprocess.PIPE,
text=True,
cwd=REPO_ROOT,
env={**os.environ, "PYTHONUNBUFFERED": "1"},
env=sub_env,
)
output = result.stdout + result.stderr
assert "Hashview API key not configured" in output
@@ -215,7 +225,10 @@ def test_hashview_subcommands_live_upload_hashfile_job(tmp_path):
)
assert run.returncode == 0, output
assert ("Hashfile uploaded" in output) or ("Hashfile added" in output)
assert ("Job created" in output) or ("Failed to add job" in output)
# Success surfaces the server's job id ("Job ID: N"); a graceful failure
# surfaces an "Error:" line. The CLI echoes the server's own message ("Job
# added"), so don't assert on the literal "Job created".
assert ("Job ID:" in output) or ("Error:" in output)
if "Job ID:" in output:
job_id = None
for line in output.splitlines():
@@ -305,7 +318,10 @@ def test_hashview_subcommands_live_upload_hashfile_job_pwdump(tmp_path):
)
assert run.returncode == 0, output
assert ("Hashfile uploaded" in output) or ("Hashfile added" in output)
assert ("Job created" in output) or ("Failed to add job" in output)
# Success surfaces the server's job id ("Job ID: N"); a graceful failure
# surfaces an "Error:" line. The CLI echoes the server's own message ("Job
# added"), so don't assert on the literal "Job created".
assert ("Job ID:" in output) or ("Error:" in output)
if "Job ID:" in output:
job_id = None
for line in output.splitlines():
@@ -389,7 +405,10 @@ def test_hashview_subcommands_live_upload_hashfile_job_hashonly(tmp_path):
output = run.stdout + run.stderr
assert run.returncode == 0, output
assert ("Hashfile uploaded" in output) or ("Hashfile added" in output)
assert ("Job created" in output) or ("Failed to add job" in output)
# Success surfaces the server's job id ("Job ID: N"); a graceful failure
# surfaces an "Error:" line. The CLI echoes the server's own message ("Job
# added"), so don't assert on the literal "Job created".
assert ("Job ID:" in output) or ("Error:" in output)
if "Job ID:" in output:
job_id = None
for line in output.splitlines():