203 lines
6.4 KiB
Python
203 lines
6.4 KiB
Python
"""DFD Гейна–Сарсона: локальный Pillow, без Graphviz."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from md2gost.dfd_gs import (
|
|
SAMPLE,
|
|
DfdGsParseError,
|
|
_arrange,
|
|
parse_dfd_gs,
|
|
render_dfd_gs,
|
|
)
|
|
from md2gost.diagram_renderer import (
|
|
DIAGRAM_LANGS,
|
|
configure_diagrams,
|
|
is_diagram_lang,
|
|
prepare_source,
|
|
render_diagram,
|
|
)
|
|
|
|
|
|
def test_fence_aliases_do_not_steal_yourdon():
|
|
assert is_diagram_lang("dfd-gs")
|
|
assert is_diagram_lang("uml-dfd-gs")
|
|
assert is_diagram_lang("gane-sarson")
|
|
assert is_diagram_lang("uml-gane-sarson")
|
|
assert "dfd-gs" in DIAGRAM_LANGS
|
|
assert prepare_source("dfd-gs", SAMPLE)[1] == "dfd-gs"
|
|
assert prepare_source("gane-sarson", SAMPLE)[1] == "dfd-gs"
|
|
assert prepare_source("uml-dfd-gs", SAMPLE)[1] == "dfd-gs"
|
|
assert prepare_source("dfd", "entity A Клиент\n")[1] == "dfd"
|
|
assert prepare_source("uml-dfd", "entity A Клиент\n")[1] == "dfd"
|
|
assert prepare_source("yourdon", "entity A Клиент\n")[1] == "dfd"
|
|
|
|
|
|
def test_badges_and_colon_label():
|
|
diagram = parse_dfd_gs(
|
|
"""
|
|
entity A Художник
|
|
entity B Заказчик
|
|
process Accept Принять заявку
|
|
process 3.1 Проверить срок
|
|
store Orders Заказы
|
|
store D4 Акты
|
|
A --> Accept : файлы работы
|
|
"""
|
|
)
|
|
by_id = {node.id: node for node in diagram.nodes}
|
|
assert by_id["A"].badge == "1"
|
|
assert by_id["B"].badge == "2"
|
|
assert by_id["Accept"].badge == "1"
|
|
assert by_id["3.1"].badge == "3.1"
|
|
assert by_id["Orders"].badge == "D1"
|
|
assert by_id["D4"].badge == "D4"
|
|
assert diagram.flows[0].label == "файлы работы"
|
|
assert diagram.form == "kit"
|
|
assert diagram.title == ""
|
|
|
|
|
|
def test_store_code_skips_taken_numbers():
|
|
diagram = parse_dfd_gs(
|
|
"""
|
|
store D1 Заказы
|
|
store Orders Журнал
|
|
"""
|
|
)
|
|
badges = {node.id: node.badge for node in diagram.nodes}
|
|
assert badges == {"D1": "D1", "Orders": "D2"}
|
|
|
|
|
|
def test_parse_errors():
|
|
with pytest.raises(DfdGsParseError, match="пустой"):
|
|
parse_dfd_gs(" \n\n")
|
|
with pytest.raises(DfdGsParseError, match="без узлов"):
|
|
parse_dfd_gs("# только комментарий\n")
|
|
with pytest.raises(DfdGsParseError, match="без узлов"):
|
|
parse_dfd_gs("title Только шапка\n")
|
|
with pytest.raises(DfdGsParseError, match="повтор"):
|
|
parse_dfd_gs("entity A Один\nprocess A Два\n")
|
|
with pytest.raises(DfdGsParseError, match="неизвестный узел"):
|
|
parse_dfd_gs("entity A Один\nA --> B поток\n")
|
|
with pytest.raises(DfdGsParseError, match="в себя"):
|
|
parse_dfd_gs("entity A Один\nA --> A петля\n")
|
|
with pytest.raises(DfdGsParseError, match="form"):
|
|
parse_dfd_gs("form fancy\nentity A Один\n")
|
|
with pytest.raises(DfdGsParseError, match="нет подписи"):
|
|
parse_dfd_gs("process 1\n")
|
|
|
|
|
|
def test_columns_and_straight_arrow():
|
|
diagram = parse_dfd_gs(
|
|
"""
|
|
entity A Художник
|
|
process P Принять
|
|
store S Файлы
|
|
A --> P заявка
|
|
P --> S файл
|
|
"""
|
|
)
|
|
layout = _arrange(diagram, 1)
|
|
assert layout.placed["A"].x < layout.placed["P"].x < layout.placed["S"].x
|
|
straight = _arrange(
|
|
parse_dfd_gs("entity A Художник\nprocess P Принять\nA --> P заявка\n"),
|
|
1,
|
|
)
|
|
pts = straight.routes[0].pts
|
|
assert len(pts) == 2
|
|
assert abs(pts[0][1] - pts[1][1]) < 1
|
|
|
|
|
|
def test_opposite_flows_do_not_share_a_port():
|
|
diagram = parse_dfd_gs(
|
|
"""
|
|
process P Принять
|
|
store S Заказы
|
|
P --> S статус
|
|
S --> P обновление
|
|
"""
|
|
)
|
|
layout = _arrange(diagram, 1)
|
|
y1 = layout.routes[0].pts[0][1]
|
|
y2 = layout.routes[1].pts[0][1]
|
|
assert abs(y1 - y2) > 10
|
|
|
|
|
|
def test_stacked_processes_link_vertically():
|
|
diagram = parse_dfd_gs(
|
|
"""
|
|
process A Один
|
|
process B Два
|
|
A --> B дальше
|
|
"""
|
|
)
|
|
pts = _arrange(diagram, 1).routes[0].pts
|
|
assert len(pts) == 2
|
|
assert abs(pts[0][0] - pts[1][0]) < 1
|
|
assert pts[0][1] < pts[1][1]
|
|
|
|
|
|
def test_skipped_process_is_an_elbow():
|
|
diagram = parse_dfd_gs(
|
|
"""
|
|
process A Один
|
|
process B Два
|
|
process C Три
|
|
A --> C дальше
|
|
"""
|
|
)
|
|
pts = _arrange(diagram, 1).routes[0].pts
|
|
assert len(pts) >= 3
|
|
|
|
|
|
def test_render_png_and_svg_without_graphviz():
|
|
png, svg = render_dfd_gs(parse_dfd_gs(SAMPLE), scale=1)
|
|
assert png.startswith(b"\x89PNG")
|
|
assert svg is not None
|
|
assert "USED AT" in svg
|
|
assert "Тарасов" in svg
|
|
assert "A44" in svg
|
|
assert "rx=" in svg
|
|
assert "Художник" in svg
|
|
assert "rotate(-90" in svg
|
|
assert "D2" in svg
|
|
|
|
plain = parse_dfd_gs("form plain\nentity A Художник\nprocess P Принять\nA --> P заявка\n")
|
|
_png, svg_plain = render_dfd_gs(plain, scale=1)
|
|
assert "USED AT" not in (svg_plain or "")
|
|
assert "rx=" in (svg_plain or "")
|
|
|
|
|
|
def test_render_diagram_skips_jar_and_kroki(tmp_path):
|
|
calls = {"jar": 0, "kroki": 0}
|
|
|
|
def fake_jar(*_args, **_kwargs):
|
|
calls["jar"] += 1
|
|
return False
|
|
|
|
def fake_kroki(*_args, **_kwargs):
|
|
calls["kroki"] += 1
|
|
return False
|
|
|
|
configure_diagrams(fallback="off", cache_dir=str(tmp_path), diagram_scale=1, diagram_format="svg")
|
|
with patch("md2gost.diagram_renderer._render_plantuml_jar", side_effect=fake_jar), \
|
|
patch("md2gost.diagram_renderer._render_kroki", side_effect=fake_kroki):
|
|
result = render_diagram(
|
|
"dfd-gs", SAMPLE, cache_dir=str(tmp_path), diagram_scale=1, diagram_format="svg",
|
|
)
|
|
assert calls["jar"] == 0
|
|
assert calls["kroki"] == 0
|
|
assert Path(result.png_path).read_bytes().startswith(b"\x89PNG")
|
|
assert result.svg_path and Path(result.svg_path).read_text(encoding="utf-8").startswith("<svg")
|
|
assert result.pixel_scale == 1
|
|
|
|
|
|
def test_render_diagram_bad_source(tmp_path):
|
|
configure_diagrams(fallback="off", cache_dir=str(tmp_path))
|
|
with pytest.raises(RuntimeError, match="пустой"):
|
|
render_diagram("gane-sarson", " ", cache_dir=str(tmp_path))
|