This commit is contained in:
Jonas Bengtsson
2026-10-04 09:28:02 +02:00
parent 87c1c8db7b
commit 4adbb4171e
16 changed files with 788 additions and 29 deletions
+136 -1
View File
@@ -313,4 +313,139 @@ class TestModuleConfig:
assert r.status_code == 200
# write an output via the internal dispatch and read the mirror
app.datastore.write_words(0x0000, [0x1234])
assert app.datastore.read_words(0x0200, 1) == [0x1234]
assert app.datastore.read_words(0x0200, 1) == [0x1234]
class TestChannelNames:
async def test_names_in_layout_and_values(self, client, app):
r = await client.put("/api/config/channel-names", json={
"channel_names": {"DI0.0": "start_button", "AI4.0": "tank_level"},
"persist": False,
})
assert r.status_code == 200
# layout carries the name on the right channel
r = await client.get("/api/node")
di0 = next(c for m in r.json()["modules"] if m["index"] == 0
for c in m["channels"] if c["address"] == "DI0.0")
assert di0["channel_name"] == "start_button"
# values snapshot carries it too
r = await client.get("/api/values")
entry = next(v for v in r.json()["values"]
if (v["kind"], v["module"], v["channel"]) == ("ai", 4, 0))
assert entry["name"] == "tank_level"
async def test_names_persist_to_yaml(self, client, app, tmp_path):
cfg_file = tmp_path / "node.yaml"
app.node_config_path = str(cfg_file)
r = await client.put("/api/config/channel-names", json={
"channel_names": {"DO2.0": "pump_relay"}, "persist": True,
})
assert r.status_code == 200
text = cfg_file.read_text(encoding="utf-8")
assert "channel_names:" in text
assert "DO2.0: pump_relay" in text
# and it loads back
from wago_sim.scenario.dsl import load_yaml, load_channel_names
names = load_channel_names(load_yaml(cfg_file))
assert names == {"DO2.0": "pump_relay"}
async def test_invalid_code_rejected(self, client):
r = await client.put("/api/config/channel-names", json={
"channel_names": {"XX0.0": "nope"}, "persist": False,
})
assert r.status_code == 400
assert "invalid I/O code" in r.json()["detail"]
async def test_names_survive_module_save(self, client, app, tmp_path):
"""Module SAVE CONFIG must not wipe channel names (they persist)."""
app.node_config_path = str(tmp_path / "node.yaml")
await client.put("/api/config/channel-names", json={
"channel_names": {"DI0.0": "start_button"}, "persist": True,
})
r = await client.put("/api/config/modules", json={
"modules": ["750-430", "750-530"], "persist": True,
})
assert r.status_code == 200
assert app.node.channel_names.get("DI0.0") == "start_button"
# YAML contains both sections
text = (tmp_path / "node.yaml").read_text(encoding="utf-8")
assert "DI0.0: start_button" in text
async def test_readonly_blocks_rename(self, app):
ro = build_webui(app, user="admin", password="simu", readonly=True)
transport = httpx.ASGITransport(app=ro)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as c:
await c.post("/login", json={"user": "admin", "password": "simu"})
r = await c.put("/api/config/channel-names", json={"channel_names": {}})
assert r.status_code == 403
class TestConfigExportImport:
async def test_export_shape(self, client):
r = await client.get("/api/config/export")
assert r.status_code == 200
data = r.json()
assert data["format"] == "wago-sim-config"
assert data["version"] == 1
assert len(data["modules"]) == 6
assert "coupler_settings" in data
assert "channel_names" in data
async def test_export_import_roundtrip(self, client, app, tmp_path):
# configure something recognizable
await client.put("/api/config/channel-names", json={
"channel_names": {"DI0.0": "start_button"}, "persist": False,
})
await client.put("/api/config/coupler", json={"response_delay_ms": 33})
exported = (await client.get("/api/config/export")).json()
app.node_config_path = str(tmp_path / "node.yaml")
r = await client.post("/api/config/import", json=exported)
assert r.status_code == 200
body = r.json()
assert body["ok"] is True
assert "750-430" in body["modules"]
# names and coupler settings applied
assert app.node.channel_names.get("DI0.0") == "start_button"
assert app.registers.read_config(0x1037, 1) == [33]
# persisted to YAML
text = (tmp_path / "node.yaml").read_text(encoding="utf-8")
assert "DI0.0: start_button" in text
async def test_import_rejects_bad_payload(self, client, app):
# wrong format marker
r = await client.post("/api/config/import", json={"format": "nope"})
assert r.status_code == 400
# unknown article: nothing applied
before = [m.article for m in app.node.modules]
r = await client.post("/api/config/import", json={
"format": "wago-sim-config", "modules": ["750-9999"],
"channel_names": {}, "coupler_settings": {},
})
assert r.status_code == 400
assert [m.article for m in app.node.modules] == before # untouched
async def test_import_drops_stale_name_codes(self, client, app):
# names for channels that don't exist on the imported layout
r = await client.post("/api/config/import", json={
"format": "wago-sim-config",
"modules": ["750-530"],
"channel_names": {"DO0.0": "pump", "DI0.0": "stale_name"},
"coupler_settings": {},
"persist": False,
})
assert r.status_code == 200
body = r.json()
assert body["dropped_codes"] == ["DI0.0"]
assert app.node.channel_names == {"DO0.0": "pump"}
async def test_readonly_blocks_import_export(self, app):
ro = build_webui(app, user="admin", password="simu", readonly=True)
transport = httpx.ASGITransport(app=ro)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as c:
await c.post("/login", json={"user": "admin", "password": "simu"})
r = await c.get("/api/config/export")
assert r.status_code == 200 # export is a read
r = await c.post("/api/config/import", json={"format": "wago-sim-config",
"modules": ["750-430"]})
assert r.status_code == 403