From 51db1e432976326df0658fe64513d2bc56fbef25 Mon Sep 17 00:00:00 2001 From: rekushi <1+rekushi@noreply.flipnote.world> Date: Thu, 29 Oct 2026 04:43:04 +0000 Subject: [PATCH] test(deploy): cover configuration, setup and backups Check rejected settings, administrator replacement and restorable database/media backups. --- tests/test_deployment.py | 108 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 tests/test_deployment.py diff --git a/tests/test_deployment.py b/tests/test_deployment.py new file mode 100644 index 0000000..8d5da3f --- /dev/null +++ b/tests/test_deployment.py @@ -0,0 +1,108 @@ +from contextlib import ExitStack, closing +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import patch +import json +import os +import sqlite3 +import subprocess +import sys +import unittest +import zipfile + +import manage +import server_config + + +class ConfigurationTests(unittest.TestCase): + def setUp(self): + self.stack = ExitStack() + self.directory = Path(self.stack.enter_context(TemporaryDirectory())) + self.stack.enter_context(patch.dict(os.environ, {"REXIMEMO_CONFIG": str(self.directory / "settings.json")})) + + def tearDown(self): + self.stack.close() + + def test_defaults_and_partial_save_preserve_unrelated_settings(self): + self.assertEqual(server_config.load_config()["port"], 8080) + saved = server_config.save_config({"site_name": "My Flipnotes", "port": 8088, "unknown_secret": "not saved"}) + self.assertEqual(saved["site_name"], "My Flipnotes") + self.assertNotIn("unknown_secret", saved) + saved = server_config.save_config({"welcome_message": "Hello!", "guest_posting": "0"}) + self.assertEqual(saved["site_name"], "My Flipnotes") + self.assertEqual(saved["port"], 8088) + self.assertFalse(saved["guest_posting"]) + self.assertEqual(list(self.directory.glob("*.tmp")), []) + + def test_bad_settings_leave_existing_configuration_intact(self): + original = server_config.save_config({"site_name": "Keep this"}) + for field in ({"accent_colour": "red;background:url(evil)"}, {"port": 80}, + {"host": "someone else's host"}, {"max_upload_bytes": 1}, + {"logo_url": "https://other.example/picture.png"}, + {"logo_url": "/static/../private.png"}, + {"logo_url": "/static/%2e%2e/private.png"}): + with self.subTest(field=field), self.assertRaises(ValueError): + server_config.save_config(field) + self.assertEqual(server_config.load_config(), original) + self.assertEqual(server_config.save_config({"logo_url": "/branding/logo.png"})["logo_url"], "/branding/logo.png") + + def test_corrupt_settings_do_not_silently_reset_the_server(self): + server_config.config_path().write_text("not json", encoding="utf-8") + with self.assertRaisesRegex(ValueError, "valid JSON"): + server_config.load_config() + + def test_backup_contains_snapshot_media_and_branding_without_logs(self): + database_path = self.directory / "database" / "reximemo.sqlite3" + database_path.parent.mkdir() + with closing(sqlite3.connect(database_path)) as database: + with database: + database.execute("CREATE TABLE example(value TEXT)") + database.execute("INSERT INTO example VALUES('preserved')") + for name, data in (("database/Creators/SAMPLE/note.ppm", b"PARA"), + ("database/Comments/note.ppm", b"mini"), + ("web/static/logo.png", b"logo"), + ("database/site-logo.png", b"custom logo"), + ("logs/private.log", b"do not export")): + path = self.directory / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(data) + server_config.save_config({"site_name": "Backup server"}) + destination = self.directory / "copy.zip" + with patch.object(manage, "ROOT", self.directory), patch.object(manage, "database_path", return_value=database_path): + self.assertEqual(manage.backup_server(destination), destination) + with self.assertRaises(ValueError): + manage.backup_server(destination) + with zipfile.ZipFile(destination) as archive: + self.assertEqual(archive.read("database/Creators/SAMPLE/note.ppm"), b"PARA") + self.assertEqual(archive.read("database/Comments/note.ppm"), b"mini") + self.assertEqual(archive.read("database/site-logo.png"), b"custom logo") + self.assertEqual(json.loads(archive.read("server-settings.json"))["site_name"], "Backup server") + self.assertFalse(any(name.startswith("logs/") for name in archive.namelist())) + restored = self.directory / "restored.sqlite3" + restored.write_bytes(archive.read("database/reximemo.sqlite3")) + with closing(sqlite3.connect(restored)) as database: + self.assertEqual(database.execute("SELECT value FROM example").fetchone()[0], "preserved") + + def test_setup_chooses_an_admin_and_disables_the_default_login(self): + script = ''' +import json +import manage +answers = iter(["A friendly server", "8099", "127.0.0.1", "friendlyadmin"]) +manage.setup_server(input_fn=lambda prompt: next(answers), password_fn=lambda prompt: "a strong setup password") +from database import Database +assert Database.verify_account("friendlyadmin", "a strong setup password")["is_admin"] == 1 +assert Database.verify_account("reximemo", "alpine") is None +from server_config import load_config +assert load_config()["setup_complete"] is True +assert load_config()["port"] == 8099 +Database.close() +''' + environment = {**os.environ, "REXIMEMO_DB": str(self.directory / "setup.sqlite3")} + result = subprocess.run([sys.executable, "-c", script], cwd=Path(manage.__file__).parent, + env=environment, capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertNotIn("a strong setup password", server_config.config_path().read_text()) + + +if __name__ == "__main__": + unittest.main()