fix: handle Hashview create_job error response correctly

When the Hashview server returns HTTP 200 with an error message and no
job_id (due to its internal notify_email bug), the CLI and interactive
paths now:
- exit 1 (not 0) in the CLI path
- print "✗ Error" instead of "✓ Success"
- print a hint to check the Hashview UI before retrying, preventing
  duplicate job creation

Adds test for the error response path in test_cli_flags.py.
This commit is contained in:
Justin Bollinger
2026-03-09 13:17:50 -04:00
parent 903641f285
commit 5b0c119ec0
2 changed files with 116 additions and 12 deletions

View File

@@ -2898,10 +2898,9 @@ def hashview_api():
customer_id,
limit_recovered,
)
print(
f"\n✓ Success: {job_result.get('msg', 'Job created')}"
)
msg = job_result.get("msg", "")
if "job_id" in job_result:
print(f"\n✓ Success: {msg or 'Job created'}")
print(f" Job ID: {job_result['job_id']}")
print(
"\nNote: Job created with automatically assigned tasks based on"
@@ -2928,6 +2927,14 @@ def hashview_api():
print(
f"\n✓ Success: {start_result.get('msg', 'Job started')}"
)
else:
print(
f"\n✗ Error: {msg or 'Job creation failed (no job_id returned)'}"
)
print(
" Note: The Hashview server may have created the job"
" despite this error. Check the Hashview UI before retrying."
)
except Exception as e:
print(f"\n✗ Error creating job: {str(e)}")
except Exception as e:
@@ -3595,11 +3602,6 @@ def main():
action="store_true",
help="Limit to recovered hashes only",
)
hv_upload_hashfile_job.add_argument(
"--no-notify-email",
action="store_true",
help="Disable email notifications",
)
return parser, hashview_parser
# Removed add_common_args(parser) since config items are now only set via config file
@@ -3761,12 +3763,19 @@ def main():
upload_result["hashfile_id"],
args.customer_id,
limit_recovered=args.limit_recovered,
notify_email=not args.no_notify_email,
)
print(f"\n✓ Success: {job_result.get('msg', 'Job created')}")
msg = job_result.get("msg", "")
if "job_id" in job_result:
print(f"\n✓ Success: {msg or 'Job created'}")
print(f" Job ID: {job_result['job_id']}")
sys.exit(0)
else:
print(f"\n✗ Error: {msg or 'Job creation failed (no job_id returned)'}")
print(
" Note: The Hashview server may have created the job despite this error."
" Check the Hashview UI before retrying."
)
sys.exit(1)
print("✗ Error: No hashview subcommand provided.")
hashview_parser.print_help()

View File

@@ -258,7 +258,6 @@ def test_hashview_upload_hashfile_job_flags(monkeypatch, tmp_path, capsys):
"--job-name",
"TestJob",
"--limit-recovered",
"--no-notify-email",
],
)
assert code == 0
@@ -267,6 +266,102 @@ def test_hashview_upload_hashfile_job_flags(monkeypatch, tmp_path, capsys):
assert "Job created" in out
def test_hashview_upload_hashfile_job_no_notify_email_by_default(
monkeypatch, tmp_path, capsys
):
"""CLI must not send notify_email - it causes the server to create the job but
return 'Failed to add job: notify_email is invalid', hiding the created job and
causing a second job to be created on retry."""
captured_kwargs: dict = {}
class TrackingAPI:
def __init__(self, base_url, api_key, debug=False):
pass
def upload_hashfile(
self, file_path, customer_id, hash_type, file_format=5, hashfile_name=None
):
return {"msg": "Hashfile uploaded", "hashfile_id": 456}
def create_job(
self, name, hashfile_id, customer_id, limit_recovered=False, notify_email=None
):
captured_kwargs["notify_email"] = notify_email
return {"msg": "Job created", "job_id": 789}
hashfile = tmp_path / "hashes.txt"
hashfile.write_text("hash1\n")
monkeypatch.setattr(hc_main, "HashviewAPI", TrackingAPI)
monkeypatch.setattr(hc_main, "hashview_api_key", "dummy")
monkeypatch.setattr(hc_main, "hashview_url", "https://hv.example.com")
code = _run_main(
monkeypatch,
[
"hashview",
"upload-hashfile-job",
"--file",
str(hashfile),
"--customer-id",
"1",
"--hash-type",
"1000",
"--job-name",
"TestJob",
],
)
assert code == 0
assert captured_kwargs["notify_email"] is None
def test_hashview_upload_hashfile_job_error_response_exits_nonzero(
monkeypatch, tmp_path, capsys
):
"""When create_job returns an error response (no job_id), exit code must be 1
and output must show ✗ Error with a hint to check the Hashview UI."""
class ErrorJobAPI:
def __init__(self, base_url, api_key, debug=False):
pass
def upload_hashfile(
self, file_path, customer_id, hash_type, file_format=5, hashfile_name=None
):
return {"msg": "Hashfile uploaded", "hashfile_id": 456}
def create_job(
self, name, hashfile_id, customer_id, limit_recovered=False, notify_email=None
):
return {
"msg": "Failed to add job: 'notify_email' is an invalid keyword argument for JobNotifications"
}
hashfile = tmp_path / "hashes.txt"
hashfile.write_text("hash1\n")
monkeypatch.setattr(hc_main, "HashviewAPI", ErrorJobAPI)
monkeypatch.setattr(hc_main, "hashview_api_key", "dummy")
monkeypatch.setattr(hc_main, "hashview_url", "https://hv.example.com")
code = _run_main(
monkeypatch,
[
"hashview",
"upload-hashfile-job",
"--file",
str(hashfile),
"--customer-id",
"1",
"--hash-type",
"1000",
"--job-name",
"TestJob",
],
)
assert code == 1
out = capsys.readouterr().out
assert "✗ Error" in out
assert "Job ID:" not in out
assert "Check the Hashview UI before retrying" in out
# ---------------------------------------------------------------------------
# 8. Argparse error cases (exit 2)
# ---------------------------------------------------------------------------