Skip to content

tests/spa/orchestration/test_orchestration_audit_destinations.py

Source from this local checkout, regenerated when the reader rebuilds. Line links use #L<number>; a GitHub line range opens its first line.

1 # Copyright 2025 Softwell S.r.l.2 #3 # Licensed under the Apache License, Version 2.0 (the "License");4 # you may not use this file except in compliance with the License.5 # You may obtain a copy of the License at6 #7 #     https://www.apache.org/licenses/LICENSE-2.08 #9 # Unless required by applicable law or agreed to in writing, software10 # distributed under the License is distributed on an "AS IS" BASIS,11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12 # See the License for the specific language governing permissions and13 # limitations under the License.14 15 """Where the audit of a configuration attempt lands — three destinations (T22).16 17 Design section 9: a boot that fails before the vertex exists can only speak on18 the front's own module logger; anything that reaches the handler leaves an19 orchestration row, refusals included; a body the request layer never hydrated is20 the ONE thing that leaves no row at all.21 """22 23 from __future__ import annotations24 25 import logging26 27 import pytest28 29 from genro_asgi_multiworker_spa.spa_app import ORCHESTRATION_ROOT30 from genro_asgi_multiworker_spa.orchestration.group_policy import GroupPolicyError31 from genro_asgi_multiworker_spa.orchestration.spa_commander import ORDERS_LOGGER_NAME32 33 from ..test_spa_app_profiles import ask, boot, pool_server, written34 35 SPA_APP_LOGGER = "genro_asgi_multiworker_spa.spa_app"36 37 38 async def test_boot_failure_logs_on_spa_app_logger(tmp_path, caplog):39     # wf:contract: T22a — a failed boot logs on the SpaApplication module logger40     # wf:contract: (caplog) with the violations, and leaves NO orchestration log41     # wf:contract: line: the commander does not exist yet.42     folder = tmp_path / "profiles"43     written(folder, "wrong", {"memory_max_percent": 0.0})44     server = pool_server(tmp_path, profiles_path=folder, profile_name="wrong")45     front = server.applications["site0"]46 47     with caplog.at_level(logging.INFO):48         with pytest.raises(GroupPolicyError) as refused:49             await boot(server)50 51     said = [52         record.getMessage() for record in caplog.records if record.name == SPA_APP_LOGGER53     ]54     assert len(said) == 155     for violation in refused.value.violations:56         assert violation in said[0]57     # There is nothing to write an orchestration row with, and none was written.58     assert front._commander is None59     assert not [record for record in caplog.records if record.name == ORDERS_LOGGER_NAME]60 61 62 async def test_reload_handler_errors_audited_as_rejected(tmp_path, caplog):63     # wf:contract: T22b — a reload of a missing or corrupt profile that reaches64     # wf:contract: the handler leaves a commander log_order line with outcome65     # wf:contract: "rejected: ...".66     folder = tmp_path / "profiles"67     server = pool_server(tmp_path, profiles_path=folder, control_enabled=True)68     await boot(server)69     (folder / "corrupt.json").write_text("{not json")70 71     with caplog.at_level(logging.INFO, logger=ORDERS_LOGGER_NAME):72         missing, _ = await ask(73             server, f"/{ORCHESTRATION_ROOT}/reload", method="POST", body={"name": "nowhere"}74         )75         corrupt, _ = await ask(76             server, f"/{ORCHESTRATION_ROOT}/reload", method="POST", body={"name": "corrupt"}77         )78 79     assert (missing, corrupt) == (404, 400)80     rows = [81         record.getMessage()82         for record in caplog.records83         if record.name == ORDERS_LOGGER_NAME and "apply_group_settings" in record.args84     ]85     assert len(rows) == 286     for row in rows:87         assert "outcome=rejected: " in row88     # The vertex's own record says the same, and nothing moved.89     commander = server.applications["site0"].commander90     assert commander.last_apply["outcome"].startswith("rejected: ")91     assert commander.configuration_generation == 192 93 94 async def test_request_parser_400_is_not_orchestration_audit(tmp_path, caplog):95     # wf:contract: T22c — a body the handler cannot take is answered by the96     # wf:contract: dispatcher (422 here: the malformed JSON reaches the handler97     # wf:contract: as a string and pydantic rejects it) and leaves NO98     # wf:contract: orchestration log line: the single exclusion from the audit.99     server = pool_server(tmp_path, control_enabled=True)100     await boot(server)101 102     with caplog.at_level(logging.INFO, logger=ORDERS_LOGGER_NAME):103         status, _ = await ask(104             server, f"/{ORCHESTRATION_ROOT}/apply", method="POST", body=b"{not json"105         )106 107     assert status == 422108     assert not [record for record in caplog.records if record.name == ORDERS_LOGGER_NAME]109     commander = server.applications["site0"].commander110     # The last attempt on record is still the boot: this one never reached the vertex.111     assert commander.last_apply["source"] == "boot"