Files
aigc-home/calliope-backend/tests/test_frontend_export_comparison.py
T

140 lines
5.3 KiB
Python

"""Synthetic tooling tests; none of these objects is a real frontend golden export."""
import copy
import hashlib
import json
import pytest
from scripts.compare_frontend_exports import compare_case, differences
@pytest.fixture
def pair(tmp_path):
source = {
"nodes": [
{"id": "1:2", "type": "Source", "widgets_values": {"text": "中文\n原文"}},
{"id": 3, "type": "Sink", "inputs": [{"name": "value", "link": 9}]},
],
"links": [[9, "1:2", 1, 3, 0, "STRING"]],
}
expected = {
"1:2": {
"class_type": "Source",
"inputs": {"text": "中文\n原文"},
"_meta": {"title": "Source"},
},
"3": {"class_type": "Sink", "inputs": {"value": ["1:2", 1]}, "_meta": {"title": "Sink"}},
}
info = {
"Source": {"input": {"required": {"text": ["STRING", {}]}}, "output": ["IMAGE", "STRING"]},
"Sink": {"input": {"required": {"value": ["STRING", {}]}}, "output": []},
}
raw = json.dumps(source).encode()
(tmp_path / "source.json").write_bytes(raw)
case = {
"id": "fixture",
"source_path": "source.json",
"source_sha": hashlib.sha1(raw).hexdigest(),
"export_file": "01.api.json",
"binding_file": "01.binding.json",
"comparison": "全部字段与连线",
}
def write_export(value):
payload = json.dumps(value).encode()
(tmp_path / case["export_file"]).write_bytes(payload)
binding = {
"source_path": case["source_path"],
"source_sha": case["source_sha"],
"export_sha256": hashlib.sha256(payload).hexdigest(),
"original_frontend_export": True,
"exported_at": "fixture-time",
"frontend_version": "fixture",
"node_versions": "fixture",
"origin": "synthetic tool test; NOT original export",
}
(tmp_path / case["binding_file"]).write_text(json.dumps(binding))
write_export(expected)
return tmp_path, case, expected, {"object_info": info}, write_export
def test_equal_pair_does_not_enable_or_claim_execution(pair):
root, case, expected, snapshot, _ = pair
before = (root / "source.json").read_bytes()
result = compare_case(root, root, case, snapshot)
assert result["status"] == "equal"
assert result["differences"] == [] and result["metadata_differences"] == []
assert {"path": "/3/inputs/value/1", "value": 1} in result["preserved_fields"]
assert not result["executable"] and not result["execution_verified"]
assert (root / "source.json").read_bytes() == before
assert not list(root.glob("*.db"))
def test_missing_exports_and_definitions_fail_closed(pair):
root, case, _, _, _ = pair
(root / case["export_file"]).unlink()
result = compare_case(root, root, case)
assert result["status"] == "missing_materials"
assert result["missing"] == [case["export_file"]]
assert result["node_definitions_available"] is False
def test_export_present_but_conversion_incomplete_is_blocked(pair):
root, case, _, _, _ = pair
result = compare_case(root, root, case)
assert result["status"] == "conversion_blocked"
assert "differences" not in result
assert {i["code"] for i in result["issues"]} == {"node_definition_unavailable", "empty_prompt"}
@pytest.mark.parametrize(
"mutation, expected_path",
[
(lambda g: g["3"]["inputs"].update(value=["1:2", 0]), "/3/inputs/value/1"),
(lambda g: g["1:2"]["inputs"].update(text="改字"), "/1:2/inputs/text"),
(lambda g: g["3"].update(class_type="Other"), "/3/class_type"),
],
)
def test_field_and_nonzero_link_slot_differences_are_exact(pair, mutation, expected_path):
root, case, golden, snapshot, write = pair
mutation(golden)
write(golden)
result = compare_case(root, root, case, snapshot)
assert result["status"] == "different"
assert expected_path in {d["path"] for d in result["differences"]}
def test_meta_is_separate_and_not_silently_ignored(pair):
root, case, golden, snapshot, write = pair
golden["3"]["_meta"]["title"] = "不同标题"
write(golden)
result = compare_case(root, root, case, snapshot)
assert result["status"] == "different"
assert result["differences"] == []
assert result["metadata_differences"][0]["path"] == "/3/title"
def test_changed_source_and_changed_export_cannot_borrow_old_binding(pair):
root, case, _, snapshot, _ = pair
wrong = copy.deepcopy(case)
wrong["source_sha"] = "bad"
assert compare_case(root, root, wrong, snapshot)["status"] == "source_sha_mismatch"
(root / case["export_file"]).write_text("{}")
assert compare_case(root, root, case, snapshot)["status"] == "invalid_binding"
@pytest.mark.parametrize(
"payload", [[], {"nodes": []}, {}, {"1": {"class_type": "Sink", "inputs": {"x": ["2", 0]}}}]
)
def test_wrong_or_broken_export_is_not_compared(pair, payload):
root, case, _, snapshot, write = pair
write(payload)
assert compare_case(root, root, case, snapshot)["status"] == "invalid_export"
def test_comparison_keeps_missing_null_type_list_order_and_json_pointer_escaping():
result = differences({"a/b~c": [1, None], "empty": None}, {"a/b~c": [True]})
assert {r["path"] for r in result} == {"/a~1b~0c/0", "/a~1b~0c/1", "/empty"}
assert result[0]["kind"] == "type"