mirror of
https://github.com/safishamsi/graphify.git
synced 2026-07-12 18:37:12 +00:00
8bcfffdf62
Adds extract_sln, extract_csproj, and extract_razor extractors. Captures NuGet package refs, project-to-project dependencies, target frameworks, SDK attribute, @using/@inject/@inherits/@model directives, Blazor component refs, and @code methods. Resolves relative project paths to absolute paths so sln/csproj nodes link correctly when the graph is assembled. Closes #515. Co-Authored-By: aksrathore <aksrathore@users.noreply.github.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1133 lines
42 KiB
Python
1133 lines
42 KiB
Python
"""Tests for language extractors: Java, C, C++, Ruby, C#, Kotlin, Scala, PHP, Swift, Go, Julia, Fortran, JS/TS, .NET project files."""
|
|
from __future__ import annotations
|
|
from pathlib import Path
|
|
import pytest
|
|
from graphify.extract import (
|
|
extract_java, extract_c, extract_cpp, extract_ruby,
|
|
extract_csharp, extract_kotlin, extract_scala, extract_php,
|
|
extract_swift, extract_go, extract_julia, extract_js, extract_fortran,
|
|
extract_groovy, extract_sln, 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"]}
|
|
|
|
def _calls(r):
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
return {
|
|
(node_by_id.get(e["source"], e["source"]), node_by_id.get(e["target"], e["target"]))
|
|
for e in r["edges"] if e["relation"] == "calls"
|
|
}
|
|
|
|
|
|
def _references(r):
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
return [
|
|
(
|
|
node_by_id.get(e["source"], e["source"]),
|
|
node_by_id.get(e["target"], e["target"]),
|
|
e,
|
|
)
|
|
for e in r["edges"] if e["relation"] == "references"
|
|
]
|
|
|
|
|
|
def _edges_with_relation(r, *relations):
|
|
return [e for e in r["edges"] if e["relation"] in relations]
|
|
|
|
|
|
def _normalize_symbol_label(label: str) -> str:
|
|
return label.strip("()").lstrip(".")
|
|
|
|
|
|
def _node_by_label(result: dict, label: str) -> dict:
|
|
for node in result["nodes"]:
|
|
if node.get("label") == label or _normalize_symbol_label(node.get("label", "")) == label:
|
|
return node
|
|
raise AssertionError(f"missing node label {label!r}")
|
|
|
|
|
|
def _edge_labels(result: dict, relation: str, context: str | None = None) -> set[tuple[str, str]]:
|
|
labels = {node["id"]: _normalize_symbol_label(node["label"]) for node in result["nodes"]}
|
|
pairs = set()
|
|
for edge in result["edges"]:
|
|
if edge.get("relation") != relation:
|
|
continue
|
|
if context is not None and edge.get("context") != context:
|
|
continue
|
|
pairs.add((labels.get(edge["source"], edge["source"]), labels.get(edge["target"], edge["target"])))
|
|
return pairs
|
|
|
|
|
|
# ── Java ──────────────────────────────────────────────────────────────────────
|
|
|
|
def test_java_no_error():
|
|
r = extract_java(FIXTURES / "sample.java")
|
|
assert "error" not in r
|
|
|
|
def test_java_finds_class():
|
|
r = extract_java(FIXTURES / "sample.java")
|
|
assert any("DataProcessor" in l for l in _labels(r))
|
|
|
|
def test_java_finds_interface():
|
|
r = extract_java(FIXTURES / "sample.java")
|
|
assert any("Processor" in l for l in _labels(r))
|
|
|
|
def test_java_finds_methods():
|
|
r = extract_java(FIXTURES / "sample.java")
|
|
labels = _labels(r)
|
|
assert any("addItem" in l for l in labels)
|
|
assert any("process" in l for l in labels)
|
|
|
|
def test_java_finds_imports():
|
|
r = extract_java(FIXTURES / "sample.java")
|
|
assert "imports" in _relations(r)
|
|
|
|
|
|
def test_java_import_edges_have_import_context():
|
|
r = extract_java(FIXTURES / "sample.java")
|
|
import_edges = _edges_with_relation(r, "imports", "imports_from")
|
|
assert import_edges
|
|
assert all(e.get("context") == "import" for e in import_edges)
|
|
|
|
def test_java_no_dangling_edges():
|
|
r = extract_java(FIXTURES / "sample.java")
|
|
node_ids = {n["id"] for n in r["nodes"]}
|
|
for e in r["edges"]:
|
|
assert e["source"] in node_ids
|
|
|
|
|
|
# ── C ────────────────────────────────────────────────────────────────────────
|
|
|
|
def test_c_no_error():
|
|
r = extract_c(FIXTURES / "sample.c")
|
|
assert "error" not in r
|
|
|
|
def test_c_finds_functions():
|
|
r = extract_c(FIXTURES / "sample.c")
|
|
labels = _labels(r)
|
|
assert any("process" in l for l in labels)
|
|
assert any("main" in l for l in labels)
|
|
|
|
def test_c_finds_includes():
|
|
r = extract_c(FIXTURES / "sample.c")
|
|
assert "imports" in _relations(r)
|
|
|
|
def test_c_emits_calls():
|
|
r = extract_c(FIXTURES / "sample.c")
|
|
assert any(e["relation"] == "calls" for e in r["edges"])
|
|
|
|
def test_c_calls_are_extracted():
|
|
r = extract_c(FIXTURES / "sample.c")
|
|
for e in r["edges"]:
|
|
if e["relation"] == "calls":
|
|
assert e["confidence"] == "EXTRACTED"
|
|
|
|
|
|
def test_c_import_edges_have_import_context():
|
|
r = extract_c(FIXTURES / "sample.c")
|
|
import_edges = _edges_with_relation(r, "imports", "imports_from")
|
|
assert import_edges
|
|
assert all(e.get("context") == "import" for e in import_edges)
|
|
|
|
|
|
def test_c_call_edges_have_call_context():
|
|
r = extract_c(FIXTURES / "sample.c")
|
|
call_edges = _edges_with_relation(r, "calls")
|
|
assert call_edges
|
|
assert all(e.get("context") == "call" for e in call_edges)
|
|
|
|
|
|
# ── C++ ───────────────────────────────────────────────────────────────────────
|
|
|
|
def test_cpp_no_error():
|
|
r = extract_cpp(FIXTURES / "sample.cpp")
|
|
assert "error" not in r
|
|
|
|
def test_cpp_finds_class():
|
|
r = extract_cpp(FIXTURES / "sample.cpp")
|
|
assert any("HttpClient" in l for l in _labels(r))
|
|
|
|
def test_cpp_finds_methods():
|
|
r = extract_cpp(FIXTURES / "sample.cpp")
|
|
labels = _labels(r)
|
|
# C++ extractor captures the constructor and public-visible methods
|
|
assert any("HttpClient" in l for l in labels)
|
|
|
|
def test_cpp_finds_includes():
|
|
r = extract_cpp(FIXTURES / "sample.cpp")
|
|
assert "imports" in _relations(r)
|
|
|
|
|
|
def test_cpp_import_edges_have_import_context():
|
|
r = extract_cpp(FIXTURES / "sample.cpp")
|
|
import_edges = _edges_with_relation(r, "imports", "imports_from")
|
|
assert import_edges
|
|
assert all(e.get("context") == "import" for e in import_edges)
|
|
|
|
|
|
def test_cpp_class_inherits_edge():
|
|
"""Regression for #915: `class Derived : public Base {}` should emit an inherits edge."""
|
|
r = extract_cpp(FIXTURES / "sample.cpp")
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
found = any(
|
|
"AuthedHttpClient" in node_by_id.get(e["source"], "")
|
|
and "HttpClient" in node_by_id.get(e["target"], "")
|
|
for e in r["edges"] if e["relation"] == "inherits"
|
|
)
|
|
assert found, "AuthedHttpClient should have inherits edge to HttpClient"
|
|
|
|
|
|
def test_cpp_struct_inherits_edge():
|
|
"""Structs use the same `: Base` syntax as classes and must also emit inherits."""
|
|
r = extract_cpp(FIXTURES / "sample.cpp")
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
found = any(
|
|
"RetryingHttpClient" in node_by_id.get(e["source"], "")
|
|
and "HttpClient" in node_by_id.get(e["target"], "")
|
|
for e in r["edges"] if e["relation"] == "inherits"
|
|
)
|
|
assert found, "RetryingHttpClient (struct) should have inherits edge to HttpClient"
|
|
|
|
|
|
# ── Ruby ─────────────────────────────────────────────────────────────────────
|
|
|
|
def test_ruby_no_error():
|
|
r = extract_ruby(FIXTURES / "sample.rb")
|
|
assert "error" not in r
|
|
|
|
def test_ruby_finds_class():
|
|
r = extract_ruby(FIXTURES / "sample.rb")
|
|
assert any("ApiClient" in l for l in _labels(r))
|
|
|
|
def test_ruby_finds_methods():
|
|
r = extract_ruby(FIXTURES / "sample.rb")
|
|
labels = _labels(r)
|
|
assert any("get" in l for l in labels)
|
|
assert any("post" in l for l in labels)
|
|
|
|
def test_ruby_finds_function():
|
|
r = extract_ruby(FIXTURES / "sample.rb")
|
|
assert any("parse_response" in l for l in _labels(r))
|
|
|
|
|
|
# ── C# ───────────────────────────────────────────────────────────────────────
|
|
|
|
def test_csharp_no_error():
|
|
r = extract_csharp(FIXTURES / "sample.cs")
|
|
assert "error" not in r
|
|
|
|
def test_csharp_finds_class():
|
|
r = extract_csharp(FIXTURES / "sample.cs")
|
|
assert any("DataProcessor" in l for l in _labels(r))
|
|
|
|
def test_csharp_finds_interface():
|
|
r = extract_csharp(FIXTURES / "sample.cs")
|
|
assert any("IProcessor" in l for l in _labels(r))
|
|
|
|
def test_csharp_finds_methods():
|
|
r = extract_csharp(FIXTURES / "sample.cs")
|
|
labels = _labels(r)
|
|
assert any("Process" in l for l in labels)
|
|
|
|
def test_csharp_finds_usings():
|
|
r = extract_csharp(FIXTURES / "sample.cs")
|
|
assert "imports" in _relations(r)
|
|
|
|
def test_csharp_inherits_edge():
|
|
r = extract_csharp(FIXTURES / "sample.cs")
|
|
inherits = [e for e in r["edges"] if e["relation"] == "inherits"]
|
|
assert len(inherits) >= 1
|
|
|
|
def test_csharp_implements_iprocessor():
|
|
r = extract_csharp(FIXTURES / "sample.cs")
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
found = any(
|
|
"DataProcessor" in node_by_id.get(e["source"], "") and
|
|
"IProcessor" in node_by_id.get(e["target"], "")
|
|
for e in r["edges"] if e["relation"] == "implements"
|
|
)
|
|
assert found, "DataProcessor should have implements edge to IProcessor"
|
|
|
|
|
|
def test_csharp_splits_inherits_and_implements_edges():
|
|
result = extract_csharp(FIXTURES / "sample.cs")
|
|
assert ("DataProcessor", "Processor") in _edge_labels(result, "inherits")
|
|
assert ("DataProcessor", "IProcessor") in _edge_labels(result, "implements")
|
|
|
|
|
|
def test_csharp_parameter_return_and_generic_contexts():
|
|
result = extract_csharp(FIXTURES / "sample.cs")
|
|
assert ("Build", "HttpClient") in _edge_labels(result, "references", "parameter_type")
|
|
assert ("Build", "Result") in _edge_labels(result, "references", "return_type")
|
|
assert ("Build", "DataProcessor") in _edge_labels(result, "references", "generic_arg")
|
|
|
|
|
|
def test_java_normalizes_inherits_and_implements():
|
|
result = extract_java(FIXTURES / "sample.java")
|
|
assert ("DataProcessor", "BaseProcessor") in _edge_labels(result, "inherits")
|
|
assert ("DataProcessor", "Processor") in _edge_labels(result, "implements")
|
|
|
|
|
|
def test_java_parameter_return_generic_and_attribute_contexts():
|
|
result = extract_java(FIXTURES / "sample.java")
|
|
assert ("build", "HttpClient") in _edge_labels(result, "references", "parameter_type")
|
|
assert ("build", "Result") in _edge_labels(result, "references", "return_type")
|
|
assert ("build", "DataProcessor") in _edge_labels(result, "references", "generic_arg")
|
|
assert ("build", "Override") in _edge_labels(result, "references", "attribute")
|
|
|
|
|
|
def test_csharp_field_type_references_have_field_context():
|
|
r = extract_csharp(FIXTURES / "sample.cs")
|
|
refs = _references(r)
|
|
assert any(
|
|
"DataProcessor" in src and "HttpClient" in tgt and edge.get("context") == "field"
|
|
for src, tgt, edge in refs
|
|
), "DataProcessor field declarations should reference HttpClient with field context"
|
|
|
|
|
|
def test_csharp_call_edges_have_call_context():
|
|
r = extract_csharp(FIXTURES / "sample.cs")
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
assert any(
|
|
"Process" in node_by_id.get(e["source"], "")
|
|
and "Validate" in node_by_id.get(e["target"], "")
|
|
and e.get("context") == "call"
|
|
for e in r["edges"] if e["relation"] == "calls"
|
|
), "C# call edges should retain call context"
|
|
|
|
|
|
def test_csharp_import_edges_have_import_context():
|
|
r = extract_csharp(FIXTURES / "sample.cs")
|
|
import_edges = [e for e in r["edges"] if e["relation"] == "imports"]
|
|
assert import_edges
|
|
assert all(e.get("context") == "import" for e in import_edges)
|
|
|
|
|
|
# ── Kotlin ───────────────────────────────────────────────────────────────────
|
|
|
|
def test_kotlin_no_error():
|
|
r = extract_kotlin(FIXTURES / "sample.kt")
|
|
assert "error" not in r
|
|
|
|
def test_kotlin_finds_class():
|
|
r = extract_kotlin(FIXTURES / "sample.kt")
|
|
assert any("HttpClient" in l for l in _labels(r))
|
|
|
|
def test_kotlin_finds_data_class():
|
|
r = extract_kotlin(FIXTURES / "sample.kt")
|
|
assert any("Config" in l for l in _labels(r))
|
|
|
|
def test_kotlin_finds_methods():
|
|
r = extract_kotlin(FIXTURES / "sample.kt")
|
|
labels = _labels(r)
|
|
assert any("get" in l for l in labels)
|
|
assert any("post" in l for l in labels)
|
|
|
|
def test_kotlin_finds_function():
|
|
r = extract_kotlin(FIXTURES / "sample.kt")
|
|
assert any("createClient" in l for l in _labels(r))
|
|
|
|
def test_kotlin_emits_in_file_calls():
|
|
"""Regression test for the call-walker `simple_identifier` /
|
|
`identifier` rename — see graphify-kmp's PythonParityTest."""
|
|
r = extract_kotlin(FIXTURES / "sample.kt")
|
|
calls = _calls(r)
|
|
# In sample.kt: get() and post() both call buildRequest(), and
|
|
# createClient() invokes Config and HttpClient (constructor calls).
|
|
assert (".get()", ".buildRequest()") in calls
|
|
assert (".post()", ".buildRequest()") in calls
|
|
assert ("createClient()", "Config") in calls
|
|
assert ("createClient()", "HttpClient") in calls
|
|
|
|
|
|
# ── Scala ─────────────────────────────────────────────────────────────────────
|
|
|
|
def test_scala_no_error():
|
|
r = extract_scala(FIXTURES / "sample.scala")
|
|
assert "error" not in r
|
|
|
|
def test_scala_finds_class():
|
|
r = extract_scala(FIXTURES / "sample.scala")
|
|
assert any("HttpClient" in l for l in _labels(r))
|
|
|
|
def test_scala_finds_object():
|
|
r = extract_scala(FIXTURES / "sample.scala")
|
|
assert any("HttpClientFactory" in l for l in _labels(r))
|
|
|
|
def test_scala_finds_methods():
|
|
r = extract_scala(FIXTURES / "sample.scala")
|
|
labels = _labels(r)
|
|
assert any("get" in l for l in labels)
|
|
assert any("post" in l for l in labels)
|
|
|
|
|
|
def test_scala_import_edges_have_import_context():
|
|
r = extract_scala(FIXTURES / "sample.scala")
|
|
import_edges = _edges_with_relation(r, "imports", "imports_from")
|
|
assert import_edges
|
|
assert all(e.get("context") == "import" for e in import_edges)
|
|
|
|
|
|
def test_scala_call_edges_have_call_context():
|
|
r = extract_scala(FIXTURES / "sample.scala")
|
|
call_edges = _edges_with_relation(r, "calls")
|
|
assert call_edges
|
|
assert all(e.get("context") == "call" for e in call_edges)
|
|
|
|
|
|
# ── PHP ───────────────────────────────────────────────────────────────────────
|
|
|
|
def test_php_no_error():
|
|
r = extract_php(FIXTURES / "sample.php")
|
|
assert "error" not in r
|
|
|
|
def test_php_finds_class():
|
|
r = extract_php(FIXTURES / "sample.php")
|
|
assert any("ApiClient" in l for l in _labels(r))
|
|
|
|
def test_php_finds_methods():
|
|
r = extract_php(FIXTURES / "sample.php")
|
|
labels = _labels(r)
|
|
assert any("get" in l for l in labels)
|
|
assert any("post" in l for l in labels)
|
|
|
|
def test_php_finds_function():
|
|
r = extract_php(FIXTURES / "sample.php")
|
|
assert any("parseResponse" in l for l in _labels(r))
|
|
|
|
def test_php_finds_imports():
|
|
r = extract_php(FIXTURES / "sample.php")
|
|
assert "imports" in _relations(r)
|
|
|
|
|
|
def test_php_import_edges_have_import_context():
|
|
r = extract_php(FIXTURES / "sample.php")
|
|
import_edges = _edges_with_relation(r, "imports", "imports_from")
|
|
assert import_edges
|
|
assert all(e.get("context") == "import" for e in import_edges)
|
|
|
|
|
|
def test_php_call_edges_have_call_context():
|
|
r = extract_php(FIXTURES / "sample.php")
|
|
call_edges = _edges_with_relation(r, "calls")
|
|
assert call_edges
|
|
assert all(e.get("context") == "call" for e in call_edges)
|
|
|
|
def test_php_finds_static_property_access():
|
|
r = extract_php(FIXTURES / "sample_php_static_prop.php")
|
|
assert "uses_static_prop" in _relations(r)
|
|
|
|
def test_php_static_prop_target_is_holding_class():
|
|
r = extract_php(FIXTURES / "sample_php_static_prop.php")
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
uses_prop = [
|
|
(node_by_id.get(e["source"], e["source"]), node_by_id.get(e["target"], e["target"]))
|
|
for e in r["edges"] if e["relation"] == "uses_static_prop"
|
|
]
|
|
assert any("DefaultPalette" in tgt for _, tgt in uses_prop)
|
|
|
|
def test_php_finds_config_helper_call():
|
|
r = extract_php(FIXTURES / "sample_php_config.php")
|
|
assert "uses_config" in _relations(r)
|
|
|
|
def test_php_config_helper_target_matches_first_segment():
|
|
r = extract_php(FIXTURES / "sample_php_config.php")
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
uses_cfg = [
|
|
(node_by_id.get(e["source"], e["source"]), node_by_id.get(e["target"], e["target"]))
|
|
for e in r["edges"] if e["relation"] == "uses_config"
|
|
]
|
|
assert any("Throttle" in tgt for _, tgt in uses_cfg)
|
|
|
|
def test_php_finds_container_bind():
|
|
r = extract_php(FIXTURES / "sample_php_container.php")
|
|
assert "bound_to" in _relations(r)
|
|
|
|
def test_php_container_bind_links_contract_to_implementation():
|
|
r = extract_php(FIXTURES / "sample_php_container.php")
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
bound = [
|
|
(node_by_id.get(e["source"], e["source"]), node_by_id.get(e["target"], e["target"]))
|
|
for e in r["edges"] if e["relation"] == "bound_to"
|
|
]
|
|
assert any("PaymentGateway" in src and "StripeGateway" in tgt for src, tgt in bound)
|
|
|
|
def test_php_finds_event_listeners():
|
|
r = extract_php(FIXTURES / "sample_php_listen.php")
|
|
assert "listened_by" in _relations(r)
|
|
|
|
def test_php_event_listener_links_event_to_listener():
|
|
r = extract_php(FIXTURES / "sample_php_listen.php")
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
listened = [
|
|
(node_by_id.get(e["source"], e["source"]), node_by_id.get(e["target"], e["target"]))
|
|
for e in r["edges"] if e["relation"] == "listened_by"
|
|
]
|
|
assert any("UserRegistered" in src and "SendWelcomeEmail" in tgt for src, tgt in listened)
|
|
|
|
|
|
# ── Swift ────────────────────────────────────────────────────────────────────
|
|
|
|
def test_swift_no_error():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
assert "error" not in r
|
|
|
|
def test_swift_finds_class():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
assert any("DataProcessor" in l for l in _labels(r))
|
|
|
|
def test_swift_finds_protocol():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
assert any("Processor" in l for l in _labels(r))
|
|
|
|
def test_swift_finds_struct():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
assert any("Config" in l for l in _labels(r))
|
|
|
|
def test_swift_finds_methods():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
labels = _labels(r)
|
|
assert any("addItem" in l for l in labels)
|
|
assert any("process" in l for l in labels)
|
|
|
|
def test_swift_finds_function():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
assert any("createProcessor" in l for l in _labels(r))
|
|
|
|
def test_swift_finds_imports():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
assert "imports" in _relations(r)
|
|
|
|
|
|
def test_swift_import_edges_have_import_context():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
import_edges = _edges_with_relation(r, "imports", "imports_from")
|
|
assert import_edges
|
|
assert all(e.get("context") == "import" for e in import_edges)
|
|
|
|
def test_swift_no_dangling_edges():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
node_ids = {n["id"] for n in r["nodes"]}
|
|
for e in r["edges"]:
|
|
assert e["source"] in node_ids
|
|
|
|
def test_swift_finds_actor():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
assert any("CacheManager" in l for l in _labels(r))
|
|
|
|
def test_swift_finds_enum():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
assert any("NetworkError" in l for l in _labels(r))
|
|
|
|
def test_swift_finds_enum_methods():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
assert any("describe" in l for l in _labels(r))
|
|
|
|
def test_swift_finds_enum_cases():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
labels = _labels(r)
|
|
assert any("timeout" in l for l in labels)
|
|
assert any("connectionFailed" in l for l in labels)
|
|
|
|
def test_swift_enum_cases_have_case_of_edge():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
case_edges = [e for e in r["edges"] if e["relation"] == "case_of"]
|
|
assert len(case_edges) >= 2
|
|
|
|
def test_swift_finds_deinit():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
assert any("deinit" in l for l in _labels(r))
|
|
|
|
def test_swift_finds_subscript():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
assert any("subscript" in l for l in _labels(r))
|
|
|
|
def test_swift_extension_methods_attach_to_type():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
method_edges = [e for e in r["edges"] if e["relation"] == "method"]
|
|
found = False
|
|
for e in method_edges:
|
|
src_label = node_by_id.get(e["source"], "")
|
|
tgt_label = node_by_id.get(e["target"], "")
|
|
if "Config" in src_label and "isValid" in tgt_label:
|
|
found = True
|
|
break
|
|
assert found, "extension method isValid should attach to Config"
|
|
|
|
def test_swift_extension_does_not_duplicate_type_node():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
config_nodes = [n for n in r["nodes"] if n["label"] == "Config"]
|
|
assert len(config_nodes) == 1, f"Config should appear once, got {len(config_nodes)}"
|
|
|
|
def test_swift_conformance_edge():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
inherits_edges = [e for e in r["edges"] if e["relation"] == "inherits"]
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
found = False
|
|
for e in inherits_edges:
|
|
src_label = node_by_id.get(e["source"], "")
|
|
tgt_label = node_by_id.get(e["target"], "")
|
|
if "DataProcessor" in src_label and "Processor" in tgt_label:
|
|
found = True
|
|
break
|
|
assert found, "DataProcessor should have inherits edge to Processor"
|
|
|
|
def test_swift_extension_conformance_edge():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
inherits_edges = [e for e in r["edges"] if e["relation"] == "inherits"]
|
|
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
|
|
found = False
|
|
for e in inherits_edges:
|
|
src_label = node_by_id.get(e["source"], "")
|
|
tgt_label = node_by_id.get(e["target"], "")
|
|
if "DataProcessor" in src_label and "Loggable" in tgt_label:
|
|
found = True
|
|
break
|
|
assert found, "extension should add conformance edge DataProcessor -> Loggable"
|
|
|
|
def test_swift_emits_calls():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
calls = _calls(r)
|
|
assert any("process" in src and "validate" in tgt for src, tgt in calls)
|
|
|
|
def test_swift_call_edges_have_call_context():
|
|
r = extract_swift(FIXTURES / "sample.swift")
|
|
call_edges = _edges_with_relation(r, "calls")
|
|
assert call_edges
|
|
assert all(e.get("context") == "call" for e in call_edges)
|
|
|
|
|
|
def test_swift_extension_across_files_merges_into_canonical_type():
|
|
"""`extension Foo` in a separate file from `class Foo` must resolve to a
|
|
single Foo node. tree-sitter-swift parses both as `class_declaration` and
|
|
node ids carry the file stem, so without a corpus-level merge each file
|
|
would emit its own Foo."""
|
|
from graphify.extract import extract
|
|
paths = sorted((FIXTURES / "swift_cross_file").glob("*.swift"))
|
|
r = extract(paths, cache_root=Path("/tmp/graphify-test-no-cache"))
|
|
foo_nodes = [n for n in r["nodes"] if n["label"] == "Foo"]
|
|
assert len(foo_nodes) == 1, f"Foo should appear once, got {len(foo_nodes)}: {[n['id'] for n in foo_nodes]}"
|
|
foo_id = foo_nodes[0]["id"]
|
|
method_targets = {
|
|
e["target"] for e in r["edges"]
|
|
if e["relation"] == "method" and e["source"] == foo_id
|
|
}
|
|
method_labels = {n["label"] for n in r["nodes"] if n["id"] in method_targets}
|
|
assert any("one" in l for l in method_labels), f"one() should attach to Foo, got {method_labels}"
|
|
assert any("two" in l for l in method_labels), f"extension method two() should attach to Foo, got {method_labels}"
|
|
|
|
|
|
# ── Elixir ────────────────────────────────────────────────────────────────────
|
|
|
|
from graphify.extract import extract_elixir
|
|
|
|
def test_elixir_finds_module():
|
|
r = extract_elixir(FIXTURES / "sample.ex")
|
|
assert "error" not in r
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert any("MyApp.Accounts.User" in l for l in labels)
|
|
|
|
def test_elixir_finds_functions():
|
|
r = extract_elixir(FIXTURES / "sample.ex")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert any("create" in l for l in labels)
|
|
assert any("find" in l for l in labels)
|
|
assert any("validate" in l for l in labels)
|
|
|
|
def test_elixir_finds_imports():
|
|
r = extract_elixir(FIXTURES / "sample.ex")
|
|
import_edges = [e for e in r["edges"] if e["relation"] == "imports"]
|
|
assert len(import_edges) >= 2
|
|
|
|
|
|
def test_elixir_import_edges_have_import_context():
|
|
r = extract_elixir(FIXTURES / "sample.ex")
|
|
import_edges = _edges_with_relation(r, "imports", "imports_from")
|
|
assert import_edges
|
|
assert all(e.get("context") == "import" for e in import_edges)
|
|
|
|
def test_elixir_finds_calls():
|
|
r = extract_elixir(FIXTURES / "sample.ex")
|
|
calls = {(e["source"], e["target"]) for e in r["edges"] if e["relation"] == "calls"}
|
|
labels = {n["id"]: n["label"] for n in r["nodes"]}
|
|
assert any("create" in labels.get(src, "") and "validate" in labels.get(tgt, "") for src, tgt in calls)
|
|
|
|
|
|
def test_elixir_call_edges_have_call_context():
|
|
r = extract_elixir(FIXTURES / "sample.ex")
|
|
call_edges = _edges_with_relation(r, "calls")
|
|
assert call_edges
|
|
assert all(e.get("context") == "call" for e in call_edges)
|
|
|
|
def test_elixir_method_edges():
|
|
r = extract_elixir(FIXTURES / "sample.ex")
|
|
methods = [e for e in r["edges"] if e["relation"] == "method"]
|
|
assert len(methods) >= 3
|
|
|
|
|
|
# ── Objective-C ──────────────────────────────────────────────────────────────
|
|
from graphify.extract import extract_objc
|
|
|
|
|
|
def test_objc_finds_interface():
|
|
r = extract_objc(FIXTURES / "sample.m")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert "Animal" in labels
|
|
|
|
|
|
def test_objc_finds_subclass():
|
|
r = extract_objc(FIXTURES / "sample.m")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert "Dog" in labels
|
|
|
|
|
|
def test_objc_finds_methods():
|
|
r = extract_objc(FIXTURES / "sample.m")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert any("speak" in l or "fetch" in l or "initWithName" in l for l in labels)
|
|
|
|
|
|
def test_objc_finds_imports():
|
|
r = extract_objc(FIXTURES / "sample.m")
|
|
import_edges = [e for e in r["edges"] if e["relation"] == "imports"]
|
|
assert len(import_edges) >= 1
|
|
|
|
|
|
def test_objc_import_edges_have_import_context():
|
|
r = extract_objc(FIXTURES / "sample.m")
|
|
import_edges = _edges_with_relation(r, "imports", "imports_from")
|
|
assert import_edges
|
|
assert all(e.get("context") == "import" for e in import_edges)
|
|
|
|
|
|
def test_objc_inherits_edge():
|
|
r = extract_objc(FIXTURES / "sample.m")
|
|
inherits = [e for e in r["edges"] if e["relation"] == "inherits"]
|
|
assert len(inherits) >= 1
|
|
|
|
|
|
def test_objc_no_dangling_edges():
|
|
r = extract_objc(FIXTURES / "sample.m")
|
|
node_ids = {n["id"] for n in r["nodes"]}
|
|
for e in r["edges"]:
|
|
assert e["source"] in node_ids, f"Dangling source: {e}"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Go
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_go_receiver_methods_share_type_node():
|
|
"""Methods on the same receiver type must share one canonical type node."""
|
|
r = extract_go(FIXTURES / "sample.go")
|
|
server_nodes = [n for n in r["nodes"] if n["label"] == "Server"]
|
|
# Both Start() and Stop() are on *Server — should produce exactly one Server node
|
|
assert len(server_nodes) == 1
|
|
|
|
def test_go_receiver_uses_pkg_scope():
|
|
"""Type node id should be scoped to directory, not file stem."""
|
|
r = extract_go(FIXTURES / "sample.go")
|
|
server_nodes = [n for n in r["nodes"] if n["label"] == "Server"]
|
|
assert server_nodes
|
|
# Should NOT contain the file stem "sample" in the type node id
|
|
assert "sample" not in server_nodes[0]["id"].split(":")[0]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Julia
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_julia_finds_module():
|
|
r = extract_julia(FIXTURES / "sample.jl")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert "Geometry" in labels
|
|
|
|
|
|
def test_julia_finds_structs():
|
|
r = extract_julia(FIXTURES / "sample.jl")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert "Point" in labels
|
|
assert "Circle" in labels
|
|
|
|
|
|
def test_julia_finds_abstract_type():
|
|
r = extract_julia(FIXTURES / "sample.jl")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert "Shape" in labels
|
|
|
|
|
|
def test_julia_finds_functions():
|
|
r = extract_julia(FIXTURES / "sample.jl")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert any("area" in l for l in labels)
|
|
assert any("distance" in l for l in labels)
|
|
|
|
|
|
def test_julia_finds_short_function():
|
|
r = extract_julia(FIXTURES / "sample.jl")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert any("perimeter" in l for l in labels)
|
|
|
|
|
|
def test_julia_finds_imports():
|
|
r = extract_julia(FIXTURES / "sample.jl")
|
|
import_edges = [e for e in r["edges"] if e["relation"] == "imports"]
|
|
assert len(import_edges) >= 1
|
|
|
|
|
|
def test_julia_import_edges_have_import_context():
|
|
r = extract_julia(FIXTURES / "sample.jl")
|
|
import_edges = _edges_with_relation(r, "imports", "imports_from")
|
|
assert import_edges
|
|
assert all(e.get("context") == "import" for e in import_edges)
|
|
|
|
|
|
def test_julia_finds_inherits():
|
|
r = extract_julia(FIXTURES / "sample.jl")
|
|
inherits = [e for e in r["edges"] if e["relation"] == "inherits"]
|
|
assert len(inherits) >= 1
|
|
|
|
|
|
def test_julia_finds_calls():
|
|
r = extract_julia(FIXTURES / "sample.jl")
|
|
call_edges = [e for e in r["edges"] if e["relation"] == "calls"]
|
|
assert len(call_edges) >= 1
|
|
|
|
|
|
def test_julia_call_edges_have_call_context():
|
|
r = extract_julia(FIXTURES / "sample.jl")
|
|
call_edges = _edges_with_relation(r, "calls")
|
|
assert call_edges
|
|
assert all(e.get("context") == "call" for e in call_edges)
|
|
|
|
|
|
def test_julia_no_dangling_edges():
|
|
r = extract_julia(FIXTURES / "sample.jl")
|
|
node_ids = {n["id"] for n in r["nodes"]}
|
|
for e in r["edges"]:
|
|
assert e["source"] in node_ids, f"Dangling source: {e}"
|
|
|
|
|
|
# ── Fortran extractor ────────────────────────────────────────────────────────
|
|
|
|
def test_fortran_finds_module():
|
|
r = extract_fortran(FIXTURES / "sample.f90")
|
|
assert "error" not in r
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert "geometry" in labels
|
|
|
|
|
|
def test_fortran_finds_subroutines():
|
|
r = extract_fortran(FIXTURES / "sample.f90")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert any("circle_area" in l for l in labels)
|
|
assert any("print_area" in l for l in labels)
|
|
|
|
|
|
def test_fortran_finds_function():
|
|
r = extract_fortran(FIXTURES / "sample.f90")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert any("distance" in l for l in labels)
|
|
|
|
|
|
def test_fortran_finds_program():
|
|
r = extract_fortran(FIXTURES / "sample.f90")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert "main" in labels
|
|
|
|
|
|
def test_fortran_finds_use_imports():
|
|
r = extract_fortran(FIXTURES / "sample.f90")
|
|
import_edges = [e for e in r["edges"] if e["relation"] == "imports"]
|
|
assert len(import_edges) >= 2
|
|
|
|
|
|
def test_fortran_use_edges_have_use_context():
|
|
r = extract_fortran(FIXTURES / "sample.f90")
|
|
import_edges = [e for e in r["edges"] if e["relation"] == "imports"]
|
|
assert all(e.get("context") == "use" for e in import_edges)
|
|
|
|
|
|
def test_fortran_finds_calls():
|
|
r = extract_fortran(FIXTURES / "sample.f90")
|
|
call_edges = [e for e in r["edges"] if e["relation"] == "calls"]
|
|
assert len(call_edges) >= 1
|
|
|
|
|
|
def test_fortran_case_insensitive_names():
|
|
r = extract_fortran(FIXTURES / "sample.f90")
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert all(l == l.lower() or "(" in l for l in labels if l.endswith(("()", "")) and not "." in l)
|
|
assert "geometry" in labels
|
|
assert "main" in labels
|
|
|
|
|
|
def test_fortran_no_dangling_edges():
|
|
r = extract_fortran(FIXTURES / "sample.f90")
|
|
node_ids = {n["id"] for n in r["nodes"]}
|
|
for e in r["edges"]:
|
|
assert e["source"] in node_ids, f"Dangling source: {e}"
|
|
|
|
|
|
def test_fortran_capital_F_parses_preprocessed():
|
|
r = extract_fortran(FIXTURES / "sample_preprocessed.F90")
|
|
assert "error" not in r
|
|
labels = [n["label"] for n in r["nodes"]]
|
|
assert "shapes" in labels
|
|
assert any("compute_volume" in l for l in labels)
|
|
|
|
|
|
# ── TypeScript dynamic imports ───────────────────────────────────────────────
|
|
|
|
def test_ts_dynamic_import_no_error():
|
|
r = extract_js(FIXTURES / "dynamic_import.ts")
|
|
assert "error" not in r
|
|
|
|
def test_ts_dynamic_import_extracts_edges():
|
|
"""Dynamic import() calls inside functions should produce imports_from edges."""
|
|
r = extract_js(FIXTURES / "dynamic_import.ts")
|
|
dyn_edges = [e for e in r["edges"] if e["relation"] == "imports_from"]
|
|
targets = {e["target"] for e in dyn_edges}
|
|
# Should find: static ./logger, dynamic ./mayaEngine.js, dynamic ./queue.js
|
|
assert any("logger" in t for t in targets), f"Missing static import of logger: {targets}"
|
|
assert any("mayaengine" in t.lower() for t in targets), f"Missing dynamic import of mayaEngine: {targets}"
|
|
assert any("queue" in t.lower() for t in targets), f"Missing dynamic import of queue: {targets}"
|
|
|
|
def test_ts_dynamic_import_confidence():
|
|
"""Dynamic imports should have EXTRACTED confidence (they are deterministic string literals)."""
|
|
r = extract_js(FIXTURES / "dynamic_import.ts")
|
|
dyn_edges = [e for e in r["edges"]
|
|
if e["relation"] == "imports_from"
|
|
and "mayaengine" in e["target"].lower()]
|
|
assert len(dyn_edges) >= 1
|
|
assert dyn_edges[0]["confidence"] == "EXTRACTED"
|
|
|
|
def test_ts_dynamic_import_source_is_function():
|
|
"""Dynamic import edge source should be the enclosing function, not the file."""
|
|
r = extract_js(FIXTURES / "dynamic_import.ts")
|
|
node_labels = {n["id"]: n["label"] for n in r["nodes"]}
|
|
dyn_edges = [e for e in r["edges"]
|
|
if e["relation"] == "imports_from"
|
|
and "mayaengine" in e["target"].lower()]
|
|
assert len(dyn_edges) >= 1
|
|
src_label = node_labels.get(dyn_edges[0]["source"], "")
|
|
assert "processInbound" in src_label, f"Expected processInbound as source, got {src_label}"
|
|
|
|
def test_ts_no_dynamic_import_in_sync_fn():
|
|
"""Functions without dynamic imports should not get spurious imports_from edges."""
|
|
r = extract_js(FIXTURES / "dynamic_import.ts")
|
|
node_ids = {n["label"]: n["id"] for n in r["nodes"]}
|
|
sync_nid = node_ids.get("syncOnly()")
|
|
if sync_nid:
|
|
sync_imports = [e for e in r["edges"]
|
|
if e["source"] == sync_nid and e["relation"] == "imports_from"]
|
|
assert len(sync_imports) == 0
|
|
|
|
def test_ts_dynamic_template_literal_skipped():
|
|
"""Dynamic template literals (with ${}) must not produce an imports_from edge."""
|
|
r = extract_js(FIXTURES / "dynamic_import.ts")
|
|
targets = {e["target"] for e in r["edges"] if e["relation"] == "imports_from"}
|
|
# loadHandler uses `./handlers/${handlerName}` — no static path, must be absent
|
|
assert not any("handler" in t.lower() and "$" in t for t in targets), \
|
|
f"Garbage edge from dynamic template literal found: {targets}"
|
|
# More robust: no target should contain a brace character
|
|
assert not any("{" in t or "}" in t for t in targets), \
|
|
f"Target contains unresolved template expression: {targets}"
|
|
|
|
def test_ts_static_template_literal_resolved():
|
|
"""Static template literals (no ${}) should resolve the same as a plain string."""
|
|
r = extract_js(FIXTURES / "dynamic_import.ts")
|
|
targets = {e["target"] for e in r["edges"] if e["relation"] == "imports_from"}
|
|
assert any("statichelper" in t.lower() for t in targets), \
|
|
f"Static template literal import not resolved: {targets}"
|
|
|
|
|
|
# ── Markdown ─────────────────────────────────────────────────────────────────
|
|
|
|
from graphify.extract import extract_markdown
|
|
|
|
def test_markdown_no_error():
|
|
r = extract_markdown(FIXTURES / "deploy_guide.md")
|
|
assert "error" not in r
|
|
|
|
def test_markdown_finds_headings():
|
|
r = extract_markdown(FIXTURES / "deploy_guide.md")
|
|
labels = _labels(r)
|
|
assert any("Deploy Guide" in l for l in labels)
|
|
assert any("Prerequisites" in l for l in labels)
|
|
assert any("Full Deploy" in l for l in labels)
|
|
assert any("Rollback" in l for l in labels)
|
|
|
|
def test_markdown_finds_nested_heading():
|
|
"""### Database Migration is nested under ## Full Deploy."""
|
|
r = extract_markdown(FIXTURES / "deploy_guide.md")
|
|
labels = _labels(r)
|
|
assert any("Database Migration" in l for l in labels)
|
|
|
|
def test_markdown_finds_code_blocks():
|
|
r = extract_markdown(FIXTURES / "deploy_guide.md")
|
|
labels = _labels(r)
|
|
assert any("code:bash" in l for l in labels)
|
|
assert any("code:sql" in l for l in labels)
|
|
assert any("code:python" in l for l in labels)
|
|
|
|
def test_markdown_contains_edges():
|
|
"""Headings and code blocks should be connected via 'contains' edges."""
|
|
r = extract_markdown(FIXTURES / "deploy_guide.md")
|
|
assert "contains" in _relations(r)
|
|
contains_edges = [e for e in r["edges"] if e["relation"] == "contains"]
|
|
assert len(contains_edges) >= 5 # file->h1, h1->h2s, h2->h3, h2->codeblocks
|
|
|
|
def test_markdown_no_dangling_edges():
|
|
r = extract_markdown(FIXTURES / "deploy_guide.md")
|
|
node_ids = {n["id"] for n in r["nodes"]}
|
|
for e in r["edges"]:
|
|
assert e["source"] in node_ids, f"Dangling source: {e}"
|
|
|
|
|
|
# ── Groovy ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
def test_groovy_no_error():
|
|
r = extract_groovy(FIXTURES / "sample.groovy")
|
|
assert "error" not in r
|
|
|
|
|
|
def test_groovy_finds_class():
|
|
r = extract_groovy(FIXTURES / "sample.groovy")
|
|
assert any("SampleService" in l for l in _labels(r))
|
|
|
|
|
|
def test_groovy_finds_methods():
|
|
r = extract_groovy(FIXTURES / "sample.groovy")
|
|
labels = _labels(r)
|
|
assert any("process" in l for l in labels)
|
|
assert any("reset" in l for l in labels)
|
|
|
|
|
|
def test_groovy_finds_imports():
|
|
r = extract_groovy(FIXTURES / "sample.groovy")
|
|
assert "imports" in _relations(r)
|
|
|
|
|
|
def test_groovy_import_edges_have_import_context():
|
|
r = extract_groovy(FIXTURES / "sample.groovy")
|
|
import_edges = _edges_with_relation(r, "imports", "imports_from")
|
|
assert import_edges
|
|
assert all(e.get("context") == "import" for e in import_edges)
|
|
|
|
|
|
def test_groovy_no_dangling_edges():
|
|
r = extract_groovy(FIXTURES / "sample.groovy")
|
|
node_ids = {n["id"] for n in r["nodes"]}
|
|
for e in r["edges"]:
|
|
assert e["source"] in node_ids
|
|
|
|
|
|
def test_groovy_spock_finds_class():
|
|
r = extract_groovy(FIXTURES / "sample_spock.groovy")
|
|
assert any("SampleSpec" in l for l in _labels(r))
|
|
|
|
|
|
def test_groovy_spock_finds_feature_methods():
|
|
r = extract_groovy(FIXTURES / "sample_spock.groovy")
|
|
feature_labels = [l for l in _labels(r) if l.startswith('"')]
|
|
assert len(feature_labels) >= 2
|
|
|
|
|
|
def test_groovy_spock_finds_method_with_apostrophe():
|
|
r = extract_groovy(FIXTURES / "sample_spock.groovy")
|
|
assert any("it's" in l for l in _labels(r))
|
|
|
|
|
|
def test_groovy_spock_preserves_import_edges():
|
|
r = extract_groovy(FIXTURES / "sample_spock.groovy")
|
|
assert "imports" in _relations(r)
|
|
|
|
|
|
def test_groovy_spock_no_dangling_edges():
|
|
r = extract_groovy(FIXTURES / "sample_spock.groovy")
|
|
node_ids = {n["id"] for n in r["nodes"]}
|
|
for e in r["edges"]:
|
|
assert e["source"] in node_ids
|
|
|
|
|
|
# -- .NET project files (.sln, .csproj, .razor) -------------------------------
|
|
|
|
def test_sln_no_error():
|
|
r = extract_sln(FIXTURES / "sample.sln")
|
|
assert "error" not in r
|
|
|
|
def test_sln_finds_projects():
|
|
r = extract_sln(FIXTURES / "sample.sln")
|
|
labels = _labels(r)
|
|
assert any("WebApi" in l for l in labels)
|
|
assert any("Domain" in l for l in labels)
|
|
|
|
def test_sln_contains_edges():
|
|
r = extract_sln(FIXTURES / "sample.sln")
|
|
assert "contains" in _relations(r)
|
|
|
|
def test_sln_project_dependency_edges():
|
|
r = extract_sln(FIXTURES / "sample.sln")
|
|
assert "imports" in _relations(r)
|
|
|
|
def test_csproj_no_error():
|
|
r = extract_csproj(FIXTURES / "sample.csproj")
|
|
assert "error" not in r
|
|
|
|
def test_csproj_finds_packages():
|
|
r = extract_csproj(FIXTURES / "sample.csproj")
|
|
labels = _labels(r)
|
|
assert any("MediatR" in l for l in labels)
|
|
assert any("FluentValidation" in l for l in labels)
|
|
|
|
def test_csproj_finds_project_references():
|
|
r = extract_csproj(FIXTURES / "sample.csproj")
|
|
labels = _labels(r)
|
|
assert any("Domain.csproj" in l for l in labels)
|
|
|
|
def test_csproj_finds_target_framework():
|
|
r = extract_csproj(FIXTURES / "sample.csproj")
|
|
assert any("net8.0" in l for l in _labels(r))
|
|
|
|
def test_csproj_finds_sdk():
|
|
r = extract_csproj(FIXTURES / "sample.csproj")
|
|
assert any("Microsoft.NET.Sdk.Web" in l for l in _labels(r))
|
|
|
|
def test_razor_no_error():
|
|
r = extract_razor(FIXTURES / "sample.razor")
|
|
assert "error" not in r
|
|
|
|
def test_razor_finds_using_directives():
|
|
r = extract_razor(FIXTURES / "sample.razor")
|
|
assert "imports" in _relations(r)
|
|
|
|
def test_razor_finds_component_references():
|
|
r = extract_razor(FIXTURES / "sample.razor")
|
|
assert "calls" in _relations(r)
|
|
|
|
def test_razor_finds_inherits():
|
|
r = extract_razor(FIXTURES / "sample.razor")
|
|
assert "inherits" in _relations(r)
|
|
|
|
def test_razor_finds_code_block_methods():
|
|
r = extract_razor(FIXTURES / "sample.razor")
|
|
labels = _labels(r)
|
|
assert any("IncrementCount" in l for l in labels)
|
|
assert any("LoadData" in l for l in labels)
|
|
|
|
def test_razor_no_dangling_edges():
|
|
r = extract_razor(FIXTURES / "sample.razor")
|
|
node_ids = {n["id"] for n in r["nodes"]}
|
|
for e in r["edges"]:
|
|
assert e["source"] in node_ids
|