pytest coverage of Python source
[!NOTE] All paths are relative to
<repo-root>/src/main/webapp/plugins/rdfexport/
pvzhelnov commented on Oct 26, 2025
This directory only contains pytest tests for the main
legacy/draw_io_parser.py <-> pyodide_pipeline/drawio_pipeline.pyPython source that actually gets embedded indist/rdfexport.jsto be used as a Draw.io plugin under Pyodide runtime. The Bun scriptbun run testrelies on exactly these tests, running them throughscripts/test_legacy.shentrypoint.pytest tests for dev modules like
meta_builder/anddebug/are located undermeta_builder/tests/anddebug/tests/, respectively. Tests formeta_builder/are also included inbun run test(again, throughscripts/test_legacy.shentrypoint) because building oflegacy/draw_io_parser.pydepends on metabuilder logic (executed usingbun run build:py, also performed by the entrypoint). Tests fordebug/, however, are not included in that script – instead,bun run test:pytest:allwas designed to run all and any pytest tests found across the repo.Salient points for test developers
Layer 0 – Original Records in Contexts parser for draw.io
This includes both the original post-v0.2.0 commit 5d85cf0 (May 13, 2024) version by Richard Williamson (backed up here in this repo) and a modified version frozen for
meta_builder/overrides. They are archived and should not be tested.
debug/offers a within-Python round trip for regression testing that runs a given fixture through an olderdraw_io_parser.pyextracted from an arbitrary historical commit. The commit it defaults to is not the original version from Richard Williamson’s release, but the core there is still largely intact.Layer 1 – Low-level Python SDK
legacy/overrides/andmeta_builder/__main__.pyprovide patches and a bundling mechanism, respectively, for the modified, frozen version oflegacy/original/draw_io_parser.pyand are amply described here: meta_builder/readme.mdTests that access
legacy/draw_io_parser.pyafter it has been successfully built by metabuilder (e.g., throughbun run build:py) can be found and written underlegacy/tests/. It is a low-level SDK.For instance:
# legacy/tests/test_patched_parser.py LEGACY_DIR = Path(__file__).resolve().parents[1] if str(LEGACY_DIR) not in sys.path: sys.path.insert(0, str(LEGACY_DIR)) import draw_io_parser def test_curie_literal_style_rounding(tmp_path: Path): def parse(path: Path) -> Graph: return draw_io_parser.parse_drawio_to_graph( str(path), metacharacter_substitute=["url"] ) prefixes = draw_io_parser.get_prefixes() expected_individual = URIRef(f"{prefixes['rdfs']}Address") def literal_present(graph: Graph, value: str) -> bool: return any( isinstance(obj, Literal) and str(obj) == value for obj in graph.objects() ) base_graph = parse(FIXTURES_DIR / "Class_Diagram_tweaked.drawio") assert literal_present(base_graph, "Address") assert (expected_individual, RDF.type, OWL.NamedIndividual) not in base_graph curie_path = _write_class_diagram_variant(tmp_path, value="rdfs:Address") curie_graph = parse(curie_path) assert literal_present(curie_graph, "rdfs:Address") assert (expected_individual, RDF.type, OWL.NamedIndividual) in curie_graph rounded_path = _write_class_diagram_variant( tmp_path, value="rdfs:Address", rounded=1 ) rounded_graph = parse(rounded_path) assert literal_present(rounded_graph, "rdfs:Address") assert ( expected_individual, RDF.type, OWL.NamedIndividual, ) not in rounded_graph, ( "rounded=1 literal styling should suppress individual classification" )Layer 2 – High-level Python SDK
legacy/tests/features a neat workflow that allows roundtrip testing of the full Python cycle with just a few lines of code and without leaving Python – and could, thus, effectively be considered its higher-level SDK.Consider this example:
# legacy/tests/test_pyodide_pipeline.py def test_parse_drawio_respects_include_label_toggle() -> None: reset_graph_store() xml_payload = _load_fixture("AA37 Department of Health.drawio") _, graph_without_labels = parse_drawio_xml(xml_payload, {"include_label": False}) without_count = sum( 1 for _ in graph_without_labels.triples((None, RDFS.label, None)) ) assert without_count == 0 reset_graph_store() _, graph_with_labels = parse_drawio_xml(xml_payload, {"include_label": True}) with_count = sum(1 for _ in graph_with_labels.triples((None, RDFS.label, None))) assert with_count > 0As a bit of context, functions in this snippet (except
_load_fixture, which is a test-specific method that simply reads a file fromtests/fixtures/dir) come frompyodide_pipeline/drawio_pipeline.pymodule, which is a wrapper for the corelegacy/draw_io_parser.pythat ultimately gets invoked by TypeScript runtime. In particular,src/pyodideRuntime.tsmodule does the job:After setting up paths, it invokes
from pyodide_pipeline import reset_graph_store; reset_graph_store(), which unsets global graph vars, and then does this:from pyodide_pipeline.drawio_pipeline import parse_drawio_xml_to_json; import json; parse_drawio_xml_to_json(${JSON.stringify(serializedXml)}, json.loads(${JSON.stringify(configJson)})– and “the returned promise will resolve to the value of this expression” (quote from Pyodide docs).Layers 3 & 4 – Python/TypeScript CLI and REPL
In contrast, tooling from
debug/exposes a comprehensive roundtrip suite that goes beyond within-Python testing and actually runs inputs through the outer TypeScript layers, of which there are two:Layer 3: TypeScript/Pyodide wrapper exposing
mockBlackBoxModule.runDrawioPipelinefromsrc/mockBlackBox.ts.Layer 4: The outermost source layer – plugin export hooks defined in
src/rdfexport.ts, main plugin source file.
debug/triggers both layers using a customdebug/run_scenario.tsharness. For the plugin layer, it effectively simulates Draw.io logic by invokingexportRdfXmlorexportRmlplugin action directly.Of note,
bun run testincludes a Bun test suite (fromtests/rdfexport.test.ts) that runs its own implementation of Layer 0–3–4 roundtrips for all-fixture regression tests.debug/specializes in manual/injections but also has a runner (slow) that does all fixtures at once (debug/debug_cli_regression.py).Layer 5 – Production plugin
scripts/build.tstranspilessrc/rdfexport.tsand bundles it together withlegacy/draw_io_parser.pyandpyodide_pipeline/drawio_pipeline.pycode and base64-encoded Python dependency wheels (see the list here:scripts/download_pyodide_assets.sh) to producedist/rdfexport.js. When run viabun run build:ts, this also conveniently copies the distributable one level higher to ultimately become available to the Draw.io app for import.After this, a static server can be run to serve precompiled Draw.io application files (e.g., using
bun run servethat just starts a default Python HTTP server). The app is available underhttp://localhost:8000/src/main/webapp/, and the plugin can be accessed by adding?p=rdfto the URL or through the app settings.I have not implemented E2E testing (e.g., using Playwright) because Codex does not seem to support it well. It has also been quite enjoyable to just test comprehensively up until Layer 4 and then run sanity checks manually in the browser, especially since the application is highly graphical in nature. However, I recognize that browser automation is still a consideration for apt testers.