# tests/test_deps_emit.py
"""deps_audit.py --emit json — maps audit_findings strings to contract JSON. deps is PRECISE → level=error
(blocks directly via the runner's authorization gate). Hermetic via the pinned audit cell (no network)."""
import importlib.util, json, os, subprocess

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DEPS = os.path.join(ROOT, "domains", "security", "detectors", "deps", "deps_audit.py")
CELLS = os.path.join(ROOT, "domains", "security", "detectors", "deps", "cells")

def _emit(audit_json):
    p = subprocess.run(["python3", DEPS, "--emit", "json", "--audit-json", audit_json],
                       capture_output=True, text=True, timeout=60)
    return json.loads(p.stdout)

def test_red_audit_emits_error_finding_with_module_symbol():
    out = _emit(os.path.join(CELLS, "audit_vuln.json"))
    assert out["detector"] == "deps"
    hits = [f for f in out["findings"] if "url-regex" in f["symbol"].lower()]
    assert hits, f"expected url-regex finding; got {out}"
    assert hits[0]["level"] == "error" and hits[0]["class"] == "S11"

def test_green_audit_emits_no_findings():
    out = _emit(os.path.join(CELLS, "audit_safe.json"))
    assert out["findings"] == [] and out["status"] == "ok", f"clean audit must be empty/ok; got {out}"


# --- commit-scope filter (Mode 2): scope a tree-wide audit to the touched workspace(s) -----------------------
_MULTI = {"advisories": {
    "1": {"severity": "high", "module_name": "esbuild", "vulnerable_versions": "<0.24", "title": "t", "url": "u",
          "findings": [{"version": "0.23", "paths": ["packages/helpdesk > esbuild@0.23"]}]},
    "2": {"severity": "high", "module_name": "axios", "vulnerable_versions": "<1", "title": "t", "url": "u",
          "findings": [{"version": "0.9", "paths": ["apps/consumer > axios@0.9"]}]},
}}


def _emit_scoped(tmp_path, audit, extra):
    f = tmp_path / "audit.json"; f.write_text(json.dumps(audit))
    p = subprocess.run(["python3", DEPS, "--emit", "json", "--audit-json", str(f), *extra],
                       capture_output=True, text=True, timeout=60)
    return json.loads(p.stdout)


def test_commit_scope_keeps_only_in_scope_workspace(tmp_path):
    out = _emit_scoped(tmp_path, _MULTI, ["--commit-scope", "--scope", "packages/helpdesk"])
    assert {f["symbol"] for f in out["findings"]} == {"esbuild"}, f"only in-scope workspace blocks; got {out}"
    assert all(f["level"] == "error" for f in out["findings"])
    assert out["coverage"]["scanned"] == ["packages/helpdesk"], f"scope declared via coverage.scanned; got {out}"


def test_commit_scope_omits_out_of_scope(tmp_path):
    out = _emit_scoped(tmp_path, _MULTI, ["--commit-scope", "--scope", "apps/consumer"])
    assert {f["symbol"] for f in out["findings"]} == {"axios"}, f"out-of-scope esbuild must be OMITTED; got {out}"


def test_commit_scope_zero_scope_blocks_nothing_surfaces_incomplete(tmp_path):
    # lockfile-only: --commit-scope with ZERO --scope → no localizable workspace → block nothing, surface incomplete.
    out = _emit_scoped(tmp_path, _MULTI, ["--commit-scope"])
    assert out["findings"] == [], f"lockfile-only blocks nothing at pre-commit; got {out}"
    assert out["status"] == "degraded", f"lockfile-only must surface incomplete (no-false-clean); got {out}"
    assert out["coverage"]["unresolved"], f"must carry the CI-deferral note; got {out}"


def test_no_commit_scope_is_full_tree_all_error(tmp_path):
    # backward compat: no --commit-scope → full-tree, ALL advisories error (CI / pre-push authority unchanged).
    out = _emit_scoped(tmp_path, _MULTI, [])
    assert {f["symbol"] for f in out["findings"]} == {"esbuild", "axios"}, f"full-tree keeps ALL; got {out}"
