112 lines
4.3 KiB
Python
112 lines
4.3 KiB
Python
"""Unit tests for canonical flow files (export.py). No DB or LFX needed."""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from app.flows import export
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("name", "expected"),
|
|
[
|
|
("simple_rule", "simple_rule"),
|
|
("test_flow", "test_flow"),
|
|
("Gehaltserhöhung", "Gehaltserhöhung"),
|
|
("Dachsanierung 200k", "Dachsanierung 200k"),
|
|
(" A/B\\C ", "A-B-C"),
|
|
('a:b*c?d"e<f>g|h', "a-b-c-d-e-f-g-h"),
|
|
("trailing...", "trailing"),
|
|
("", "flow"),
|
|
],
|
|
)
|
|
def test_filename_stem_keeps_original_names(name, expected):
|
|
assert export.filename_stem(name) == expected
|
|
|
|
|
|
def test_canonical_bytes_stable_under_key_reorder():
|
|
doc = {"name": "x", "data": {"nodes": [], "edges": []}, "tags": ["b", "a"]}
|
|
shuffled = {"tags": ["b", "a"], "data": {"edges": [], "nodes": []}, "name": "x"}
|
|
assert export.canonical_bytes(shuffled) == export.canonical_bytes(doc)
|
|
assert export.content_hash(doc) == export.content_hash(shuffled)
|
|
|
|
|
|
def test_write_and_read_roundtrip_is_byte_stable(tmp_path):
|
|
doc = {"name": "My Flow", "data": {"nodes": [], "edges": []}}
|
|
path = export.write_flow_file(tmp_path, doc)
|
|
assert path == tmp_path / "My Flow.json"
|
|
assert json.loads(path.read_text()) == doc
|
|
# Rewriting identical content is byte-identical (no phantom git diffs).
|
|
before = path.read_bytes()
|
|
export.write_flow_file(tmp_path, doc)
|
|
assert path.read_bytes() == before
|
|
|
|
|
|
def test_iter_flow_files_only_top_level_json(tmp_path):
|
|
(tmp_path / "a.json").write_text("{}")
|
|
(tmp_path / "b.txt").write_text("{}")
|
|
sub = tmp_path / "sub"
|
|
sub.mkdir()
|
|
(sub / "c.json").write_text("{}")
|
|
assert export.iter_flow_files(tmp_path) == [tmp_path / "a.json"]
|
|
|
|
|
|
def test_flowsignore_parsing_and_filtering(tmp_path):
|
|
assert export.ignored_stems(tmp_path) == set()
|
|
(tmp_path / export.IGNORE_FILENAME).write_text(
|
|
"# experiments stay Langflow-only\n\ni_love_this_app\n", encoding="utf-8"
|
|
)
|
|
assert export.ignored_stems(tmp_path) == {"i_love_this_app"}
|
|
(tmp_path / "a.json").write_text("{}")
|
|
(tmp_path / "i_love_this_app.json").write_text("{}")
|
|
assert export.iter_flow_files(tmp_path) == [tmp_path / "a.json"]
|
|
# Explicit override still works.
|
|
assert export.iter_flow_files(tmp_path, ignored=set()) == [
|
|
tmp_path / "a.json",
|
|
tmp_path / "i_love_this_app.json",
|
|
]
|
|
|
|
|
|
def test_find_reuse_path_ignores_ignored_files(tmp_path):
|
|
(tmp_path / export.IGNORE_FILENAME).write_text("alt\n", encoding="utf-8")
|
|
(tmp_path / "alt.json").write_text(json.dumps({"name": "Neu"}))
|
|
assert export.find_reuse_path(tmp_path, {"name": "Neu"}) == (None, False)
|
|
|
|
|
|
def test_find_reuse_path_matches_identical_content_first(tmp_path):
|
|
doc = {"name": "Simple Rule", "data": {"nodes": [], "edges": []}}
|
|
other = tmp_path / "other-name.json"
|
|
other.write_text(export.canonical_bytes(doc).decode())
|
|
path, identical = export.find_reuse_path(tmp_path, dict(doc))
|
|
assert (path, identical) == (other, True)
|
|
|
|
|
|
def test_find_reuse_path_matches_same_name(tmp_path):
|
|
old = {"name": "Simple Rule", "data": {"nodes": [], "edges": []}, "extra": 1}
|
|
(tmp_path / "simple_rule.json").write_text(json.dumps(old))
|
|
new = {"name": "Simple Rule", "data": {"nodes": [], "edges": []}}
|
|
path, identical = export.find_reuse_path(tmp_path, new)
|
|
assert (path, identical) == (tmp_path / "simple_rule.json", False)
|
|
|
|
|
|
def test_find_reuse_path_no_match_and_missing_dir(tmp_path):
|
|
(tmp_path / "a.json").write_text(json.dumps({"name": "Other"}))
|
|
assert export.find_reuse_path(tmp_path, {"name": "Neu"}) == (None, False)
|
|
assert export.find_reuse_path(tmp_path / "gibts-nicht", {"name": "Neu"}) == (None, False)
|
|
|
|
|
|
def test_find_reuse_path_skips_unreadable_files(tmp_path):
|
|
(tmp_path / "kaputt.json").write_text("{kein json")
|
|
assert export.find_reuse_path(tmp_path, {"name": "Neu"}) == (None, False)
|
|
|
|
|
|
def test_write_flow_file_is_world_readable(tmp_path):
|
|
import os
|
|
import stat
|
|
|
|
doc = {"name": "Lesbar", "data": {"nodes": [], "edges": []}}
|
|
path = export.write_flow_file(tmp_path, doc)
|
|
mode = stat.S_IMODE(os.stat(path).st_mode)
|
|
# Read bits for user/group/other (mkstemp defaults to 0o600; on Windows
|
|
# chmod maps to 0o666, so only assert the read bits, not the exact mode).
|
|
assert mode & 0o444 == 0o444, f"expected world-readable, got {oct(mode)}"
|