diff --git a/l4d2web/services/l4d2_facade.py b/l4d2web/services/l4d2_facade.py index eaf8c21..80e9e8d 100644 --- a/l4d2web/services/l4d2_facade.py +++ b/l4d2web/services/l4d2_facade.py @@ -44,11 +44,16 @@ def build_server_spec_payload( for overlay_id, _, expose in reversed(overlay_rows) if expose ] + config_lines: list[str] = exec_lines + json.loads(blueprint.config) + # rcon_password is appended LAST so neither overlays nor user blueprint + # config can override it (Source's cvar semantics are last-wins). + if server.rcon_password: + config_lines.append(f'rcon_password "{server.rcon_password}"') return { "port": server.port, "overlays": overlays, "arguments": json.loads(blueprint.arguments), - "config": exec_lines + json.loads(blueprint.config), + "config": config_lines, } diff --git a/l4d2web/tests/test_l4d2_facade.py b/l4d2web/tests/test_l4d2_facade.py index 60ad3a6..c5e20c8 100644 --- a/l4d2web/tests/test_l4d2_facade.py +++ b/l4d2web/tests/test_l4d2_facade.py @@ -343,3 +343,45 @@ def test_initialize_fails_fast_on_uncached_workshop_items( assert all("initialize" not in cmd for cmd in invocations), invocations +# --------------------------------------------------------------------------- +# build_server_spec_payload — rcon_password injection +# --------------------------------------------------------------------------- + +def _make_server_blueprint(rcon: str = "") -> tuple[Server, Blueprint]: + bp = Blueprint( + id=1, user_id=1, name="bp", + arguments='["-tickrate","60"]', + config='["sv_consistency 1","mp_gamemode coop"]', + ) + srv = Server( + id=1, user_id=1, blueprint_id=1, name="s", port=27500, + rcon_password=rcon, + ) + return srv, bp + + +def test_build_server_spec_payload_appends_rcon_password_last() -> None: + from l4d2web.services.l4d2_facade import build_server_spec_payload + + srv, bp = _make_server_blueprint(rcon="topsecret123") + overlays = [(7, "/overlays/7", True), (8, "/overlays/8", False)] + spec = build_server_spec_payload(srv, bp, overlays) + + cfg = spec["config"] + # rcon_password line is the LAST entry — overlay exec lines + blueprint + # config + rcon_password. + assert cfg[-1] == 'rcon_password "topsecret123"' + # Lines before our injection still contain the blueprint config. + assert "sv_consistency 1" in cfg + assert "mp_gamemode coop" in cfg + + +def test_build_server_spec_payload_omits_rcon_password_when_empty() -> None: + from l4d2web.services.l4d2_facade import build_server_spec_payload + + srv, bp = _make_server_blueprint(rcon="") + spec = build_server_spec_payload(srv, bp, []) + for line in spec["config"]: + assert not line.startswith("rcon_password ") + +