diff --git a/CHANGELOG.md b/CHANGELOG.md index 7424b14d..0f4b1b26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,11 @@ per-file diff commands. ### Fixed +- **`job_key.py --audit` reports a state file that is not an object of entries** (`tools/job_key.py`, + `tests/test_job_key.py`) - a top-level JSON array, or an entry that is a string or number, crashed the + audit with an `AttributeError` traceback on `.get`, although the function already had a one-line error + for an unusable `seen` value. Both now print a one-line message naming the problem and exit 1. + - **`freehire-search` emits the contract's `YYYY-MM-DD` date instead of the API's ISO timestamp** (`.agents/skills/freehire-search/cli/src/helpers.ts`, `tests/parsing.test.ts`, `tests/commands.test.ts`, `SKILL.md`) - `toResult` passed `posted_at` through verbatim, so diff --git a/tests/test_job_key.py b/tests/test_job_key.py index 16f5a862..543bc70b 100644 --- a/tests/test_job_key.py +++ b/tests/test_job_key.py @@ -256,6 +256,28 @@ class AuditCLI(unittest.TestCase): self.assertEqual(code, 0) self.assertIn("acme_soc-analyst_athens", report["legacy_three_part_keys"]) + def run_audit_raw(self, document) -> subprocess.CompletedProcess: + import tempfile + + with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fh: + json.dump(document, fh) + path = fh.name + return subprocess.run( + [sys.executable, str(TOOL), "--audit", path], capture_output=True, text=True, encoding="utf-8" + ) + + def test_non_object_state_file_is_reported_not_a_traceback(self): + proc = self.run_audit_raw([]) + self.assertEqual(proc.returncode, 1) + self.assertIn("expected an object of job entries", proc.stderr) + self.assertNotIn("Traceback", proc.stderr) + + def test_non_object_entry_is_reported_not_a_traceback(self): + proc = self.run_audit_raw({"seen": {"acme_soc-analyst": "oops"}}) + self.assertEqual(proc.returncode, 1) + self.assertIn("entry is not an object: acme_soc-analyst", proc.stderr) + self.assertNotIn("Traceback", proc.stderr) + if __name__ == "__main__": unittest.main() diff --git a/tools/job_key.py b/tools/job_key.py index 64964113..377c5fe9 100644 --- a/tools/job_key.py +++ b/tools/job_key.py @@ -183,10 +183,14 @@ def audit(path: Path) -> int: except (OSError, json.JSONDecodeError) as exc: print(f"cannot read {path}: {exc}", file=sys.stderr) return 1 - seen = doc.get("seen", doc) + seen = doc.get("seen", doc) if isinstance(doc, dict) else doc if not isinstance(seen, dict): print(f"{path}: expected an object of job entries", file=sys.stderr) return 1 + not_objects = [k for k, v in seen.items() if not isinstance(v, dict)] + if not_objects: + print(f"{path}: entry is not an object: {not_objects[0]}", file=sys.stderr) + return 1 malformed = [k for k in seen if not is_canonical(k) and not is_legacy_shape(k)] legacy = [k for k in seen if is_legacy_shape(k)]