crafting-table/tests/test_parsers/test_rust.py
Kayos d467b2f5be v0.1 wave 2A (steps 5+6): per-language parsers + findings extraction
- parsers/ package: rust / python / go / typescript / generic
- parser registry with language+recipe -> fallback resolution
- fingerprint hash (kind+file+line+code) for cross-run dedup
- runner.py post-exec hook: parse log, persist findings, count on job row
  (extraction runs before mark_job_finished so callers polling on terminal
  status see findings_count populated atomically)
- db.insert_finding / list_findings / increment_findings_count DAOs already
  shipped in wave 1; wired here
- GET /jobs/{id}/findings now returns real data (server route already
  shipped; was returning empty list because nothing populated the table)
- tests/test_parsers/: 6 modules + 11 fixtures (rust/python/go/typescript)
- tests/test_runner_findings.py: 3 integration tests
- README: tick steps 2-6, add Findings section

Suite: 108 passing (62 wave-1 + 46 new).
Spec: memory/spec-crafting-table.md
2026-04-29 08:36:16 -07:00

108 lines
3.6 KiB
Python

"""RustParser unit tests — driven from fixtures/rust/ samples."""
from __future__ import annotations
from .conftest import load_fixture
from crafting_table.parsers.rust import RustParser
def test_rust_audit_extracts_two_cves():
raw = load_fixture("rust", "cargo_audit.json")
findings = RustParser.parse(raw, exit_code=1, recipe="audit")
assert len(findings) == 2
f1 = findings[0]
assert f1.kind == "cve"
assert f1.code == "RUSTSEC-2024-0123"
assert f1.severity == "high"
assert "openssl" in f1.message
assert "0.10.55" in f1.message
assert "0.10.66" in f1.message
assert f1.suggested_fix is not None
assert "0.10.66" in f1.suggested_fix
assert f1.raw_json is not None
assert f1.extras["package"] == "openssl"
f2 = findings[1]
assert f2.code == "RUSTSEC-2024-0099"
# No patched versions → no suggested_fix
assert f2.suggested_fix is None
def test_rust_audit_clean_log_no_findings():
# No vulnerabilities in the envelope.
raw = '{"vulnerabilities":{"found":false,"count":0,"list":[]}}'
findings = RustParser.parse(raw, exit_code=0, recipe="audit")
assert findings == []
def test_rust_audit_garbage_log_no_findings():
findings = RustParser.parse("not json at all", exit_code=1, recipe="audit")
assert findings == []
def test_rust_clippy_extracts_warning_and_error():
raw = load_fixture("rust", "cargo_clippy.jsonl")
findings = RustParser.parse(raw, exit_code=1, recipe="lint")
# Two compiler-message lines with level in {warning, error}; the "note"
# one should be filtered out.
assert len(findings) == 2
by_code = {f.code: f for f in findings}
assert "unused_variables" in by_code
assert "E0382" in by_code
w = by_code["unused_variables"]
assert w.severity == "warn"
assert w.kind == "lint"
assert w.file == "src/lib.rs"
assert w.line == 12
assert w.suggested_fix is not None
assert "_x" in w.suggested_fix
e = by_code["E0382"]
assert e.severity == "error"
assert e.line == 42
assert e.file == "src/main.rs"
def test_rust_clippy_skips_non_compiler_message_lines():
raw = '{"reason":"build-finished","success":true}\n'
findings = RustParser.parse(raw, exit_code=0, recipe="lint")
assert findings == []
def test_rust_test_parses_failures():
raw = load_fixture("rust", "cargo_test.txt")
findings = RustParser.parse(raw, exit_code=101, recipe="test")
codes = sorted(f.code for f in findings)
assert codes == sorted(["math::tests::adds_negative", "parser::tests::parses_garbage"])
for f in findings:
assert f.kind == "test_fail"
assert f.severity == "error"
def test_rust_test_zero_exit_no_findings():
findings = RustParser.parse("test result: ok. all passed", exit_code=0, recipe="test")
assert findings == []
def test_rust_test_nonzero_no_failed_lines_emits_synthetic():
findings = RustParser.parse("compile error", exit_code=2, recipe="test")
assert len(findings) == 1
assert findings[0].kind == "test_fail"
assert "exit_2" in findings[0].code
def test_rust_build_recipe_falls_through_to_recipe_fail():
findings = RustParser.parse("anything", exit_code=1, recipe="build")
assert len(findings) == 1
assert findings[0].kind == "recipe_fail"
def test_rust_matches_only_rust_recipes():
assert RustParser.matches("rust", "audit")
assert RustParser.matches("rust", "lint")
assert RustParser.matches("rust", "test")
assert RustParser.matches("rust", "build")
assert not RustParser.matches("python", "audit")
assert not RustParser.matches("rust", "deploy")