Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion MultiServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3077,7 +3077,7 @@ def parse_args() -> argparse.Namespace:
parser.add_argument('multidata', nargs="?", default=defaults["multidata"])
parser.add_argument('--host', default=defaults["host"])
parser.add_argument('--port', default=defaults["port"], type=int)
parser.add_argument('--admin-password', default=defaults["admin_password"])
parser.add_argument('--admin-password')
parser.add_argument('--server-password', dest="admin_password", default=argparse.SUPPRESS,
help=argparse.SUPPRESS) # pre-rename alias
parser.add_argument('--password', default=defaults["password"])
Expand Down Expand Up @@ -3146,6 +3146,10 @@ def parse_args() -> argparse.Namespace:
""")
parser.add_argument('--log_network', default=defaults["log_network"], action="store_true")
args = parser.parse_args()
# a command-line admin password outranks the one a save restores; host.yaml's does not
args.admin_password_given = args.admin_password is not None
if not args.admin_password_given:
args.admin_password = defaults["admin_password"]
return args


Expand Down Expand Up @@ -3237,6 +3241,8 @@ async def main(args: argparse.Namespace):
raise

ctx.init_save(not args.disable_save)
if args.admin_password_given:
ctx.admin_password = args.admin_password

# Set up signal handler for Ctrl+C (SIGINT) - cross-platform compatible
def shutdown_handler():
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ def _find_libmtdev() -> str | None:
"mwgg_gui",
"mwgg_tui",
"mwgg_splash",
"worlds"
"worlds",
# Submodules like cached_world are only imported by out-of-tree worlds.
"rule_builder",
],
"includes": [
"ModuleUpdate",
Expand Down
71 changes: 71 additions & 0 deletions test/programs/test_server_password_alias.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
"""server_password is the pre-rename spelling of admin_password; multidata, saves and
command lines written before the rename must still reach Context.admin_password."""

import asyncio
import logging
import os
import pickle
import signal
import sys
import tempfile
import types
import unittest
import zlib
from unittest import mock

import MultiServer
from MultiServer import Context
from NetUtils import NetworkSlot, SlotType


def _stub() -> types.SimpleNamespace:
Expand All @@ -34,5 +41,69 @@ def test_command_line_alias(self) -> None:
self.assertFalse(hasattr(args, "server_password"))


class _Stop(Exception):
pass


class TestCommandLinePasswordOutranksSave(unittest.TestCase):
"""The launcher's host dialog passes --admin-password; a save written with another
password must not replace it, or remote admin answers "disabled"."""

def setUp(self) -> None:
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
self.data = os.path.join(tmp.name, "AP_TEST.archipelago")
multidata = {
"slot_info": {1: NetworkSlot("Player1", "Game A", SlotType.player)},
"connect_names": {"Player1": (0, 1)},
"locations": {1: {}},
"slot_data": {1: {}},
"er_hint_data": {},
"precollected_items": {1: []},
"precollected_hints": {1: set()},
"version": (0, 6, 2),
"minimum_versions": {"server": (0, 1, 6), "clients": {}},
"seed_name": "TESTSEED",
"datapackage": {"Game A": {"item_name_to_id": {}, "location_name_to_id": {}, "item_name_groups": {}}},
}
with open(self.data, "wb") as f:
f.write(bytes([3]) + zlib.compress(pickle.dumps(multidata)))
no_autosave = mock.patch.object(Context, "_start_async_saving")
no_autosave.start()
self.addCleanup(no_autosave.stop)

ctx = Context("", 0, "saved", None, 1, 10, True)
ctx.load(self.data)
ctx.init_save()
self.assertTrue(ctx._save())

def _run_main(self, *flags: str) -> Context:
created = []
init_save = Context.init_save

def recording_init_save(ctx: Context, *args, **kwargs) -> None:
created.append(ctx)
init_save(ctx, *args, **kwargs)

with mock.patch.object(sys, "argv", ["MultiServer", self.data, *flags]):
args = MultiServer.parse_args()
sigint = signal.getsignal(signal.SIGINT)
try:
with mock.patch.object(Context, "init_save", recording_init_save), \
mock.patch.object(MultiServer.Utils, "init_logging"), \
mock.patch.object(MultiServer.websockets, "serve", side_effect=_Stop):
with self.assertRaises(_Stop):
asyncio.run(MultiServer.main(args))
finally:
signal.signal(signal.SIGINT, sigint)
return created[0]

def test_command_line_password_beats_save(self) -> None:
self.assertEqual(self._run_main("--admin-password", "typed").admin_password, "typed")

def test_save_beats_host_yaml_default(self) -> None:
self.assertEqual(self._run_main().admin_password, "saved")


if __name__ == "__main__":
unittest.main()
Loading