- 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
108 lines
3.6 KiB
Python
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")
|