mirror of
https://github.com/safishamsi/graphify.git
synced 2026-07-13 02:47:00 +00:00
29e57cd295
Adds support for the XML-based `.slnx` solution format (VS 2022 17.13+ replacement for `.sln`). Extracts project references as `contains` edges and build dependencies as `imports` edges. XXE-protected XML parsing with size cap. Wired into `_DISPATCH` and `CODE_EXTENSIONS`. 6 new tests passing. Co-authored-by: bakgaard <bakgaard@users.noreply.github.com>
161 lines
5.3 KiB
Python
161 lines
5.3 KiB
Python
"""Tests for .NET project file extraction (.sln, .csproj, .razor)."""
|
|
from pathlib import Path
|
|
import tempfile
|
|
import pytest
|
|
from graphify.extract import extract_sln, extract_slnx, extract_csproj, extract_razor
|
|
|
|
FIXTURES = Path(__file__).parent / "fixtures"
|
|
|
|
|
|
def _labels(r):
|
|
return [n["label"] for n in r["nodes"]]
|
|
|
|
|
|
def _relations(r):
|
|
return {e["relation"] for e in r["edges"]}
|
|
|
|
|
|
# ── .sln ─────────────────────────────────────────────────────────────────────
|
|
|
|
def test_sln_extracts_projects():
|
|
r = extract_sln(FIXTURES / "sample.sln")
|
|
assert "error" not in r
|
|
labels = set(_labels(r))
|
|
assert "WebApi" in labels
|
|
assert "Domain" in labels
|
|
assert "Tests" in labels
|
|
|
|
|
|
def test_sln_contains_edges():
|
|
r = extract_sln(FIXTURES / "sample.sln")
|
|
contains = [e for e in r["edges"] if e["relation"] == "contains"]
|
|
assert len(contains) == 3
|
|
|
|
|
|
def test_sln_project_dependency():
|
|
r = extract_sln(FIXTURES / "sample.sln")
|
|
assert "imports" in _relations(r)
|
|
|
|
|
|
# ── .slnx ────────────────────────────────────────────────────────────────────
|
|
|
|
def test_slnx_extracts_projects():
|
|
r = extract_slnx(FIXTURES / "sample.slnx")
|
|
assert "error" not in r
|
|
labels = set(_labels(r))
|
|
assert "WebApi" in labels
|
|
assert "Domain" in labels
|
|
assert "Tests" in labels
|
|
|
|
|
|
def test_slnx_contains_edges():
|
|
r = extract_slnx(FIXTURES / "sample.slnx")
|
|
contains = [e for e in r["edges"] if e["relation"] == "contains"]
|
|
assert len(contains) == 3
|
|
|
|
|
|
def test_slnx_project_dependency():
|
|
r = extract_slnx(FIXTURES / "sample.slnx")
|
|
assert "imports" in _relations(r)
|
|
|
|
|
|
def test_slnx_invalid_xml():
|
|
with tempfile.NamedTemporaryFile(suffix=".slnx", mode="w", delete=False) as f:
|
|
f.write("<Solution><Project></Solution>")
|
|
f.flush()
|
|
r = extract_slnx(Path(f.name))
|
|
assert "error" in r
|
|
|
|
|
|
def test_slnx_missing_file():
|
|
r = extract_slnx(Path("/nonexistent/file.slnx"))
|
|
assert "error" in r
|
|
|
|
|
|
# ── .csproj ──────────────────────────────────────────────────────────────────
|
|
|
|
def test_csproj_packages():
|
|
r = extract_csproj(FIXTURES / "sample.csproj")
|
|
assert "error" not in r
|
|
labels = _labels(r)
|
|
assert any("MediatR" in l for l in labels)
|
|
assert any("FluentValidation" in l for l in labels)
|
|
assert any("Swashbuckle" in l for l in labels)
|
|
|
|
|
|
def test_csproj_project_references():
|
|
r = extract_csproj(FIXTURES / "sample.csproj")
|
|
imports = [e for e in r["edges"] if e["relation"] == "imports"]
|
|
assert len(imports) == 6 # 4 packages + 2 project refs
|
|
|
|
|
|
def test_csproj_target_framework():
|
|
r = extract_csproj(FIXTURES / "sample.csproj")
|
|
assert "net8.0" in _labels(r)
|
|
|
|
|
|
def test_csproj_sdk():
|
|
r = extract_csproj(FIXTURES / "sample.csproj")
|
|
assert "Microsoft.NET.Sdk.Web" in _labels(r)
|
|
|
|
|
|
def test_csproj_invalid_xml():
|
|
with tempfile.NamedTemporaryFile(suffix=".csproj", mode="w", delete=False) as f:
|
|
f.write("<Project><Invalid></Project>")
|
|
f.flush()
|
|
r = extract_csproj(Path(f.name))
|
|
assert "error" in r
|
|
|
|
|
|
# ── .razor ───────────────────────────────────────────────────────────────────
|
|
|
|
def test_razor_using_and_inject():
|
|
r = extract_razor(FIXTURES / "sample.razor")
|
|
assert "error" not in r
|
|
targets = {e["target"] for e in r["edges"] if e["relation"] == "imports"}
|
|
assert any("microsoft" in t for t in targets)
|
|
assert any("counterservice" in t.lower() for t in targets)
|
|
|
|
|
|
def test_razor_components():
|
|
r = extract_razor(FIXTURES / "sample.razor")
|
|
targets = {e["target"] for e in r["edges"] if e["relation"] == "calls"}
|
|
assert any("weatherdisplay" in t for t in targets)
|
|
assert any("datagrid" in t for t in targets)
|
|
|
|
|
|
def test_razor_page_route():
|
|
r = extract_razor(FIXTURES / "sample.razor")
|
|
assert any("/counter" in l for l in _labels(r))
|
|
|
|
|
|
def test_razor_inherits():
|
|
r = extract_razor(FIXTURES / "sample.razor")
|
|
assert "inherits" in _relations(r)
|
|
|
|
|
|
def test_razor_code_methods():
|
|
r = extract_razor(FIXTURES / "sample.razor")
|
|
labels = _labels(r)
|
|
assert "IncrementCount" in labels
|
|
assert "LoadData" in labels
|
|
|
|
|
|
def test_razor_missing_file():
|
|
r = extract_razor(Path("/nonexistent/file.razor"))
|
|
assert "error" in r
|
|
|
|
|
|
# ── dispatch & detect integration ────────────────────────────────────────────
|
|
|
|
def test_dispatch_table():
|
|
from graphify.extract import _get_extractor
|
|
for ext in (".sln", ".slnx", ".csproj", ".fsproj", ".vbproj", ".razor", ".cshtml"):
|
|
assert _get_extractor(Path(f"foo{ext}")) is not None, f"{ext} not in dispatch"
|
|
|
|
|
|
def test_code_extensions():
|
|
from graphify.detect import CODE_EXTENSIONS
|
|
for ext in (".sln", ".slnx", ".csproj", ".fsproj", ".vbproj", ".razor", ".cshtml"):
|
|
assert ext in CODE_EXTENSIONS, f"{ext} missing"
|