Release v0.1.2
This commit is contained in:
@@ -215,19 +215,18 @@ class ApiSmokeTests(unittest.TestCase):
|
||||
"smtp": {
|
||||
"host": "mock.smtp",
|
||||
"port": 2525,
|
||||
"username": "sender@example.org",
|
||||
"password": "smtp-secret",
|
||||
"security": "starttls",
|
||||
},
|
||||
"imap": {
|
||||
"enabled": True,
|
||||
"host": "mock.imap",
|
||||
"port": 993,
|
||||
"username": "sender@example.org",
|
||||
"password": "imap-secret",
|
||||
"security": "tls",
|
||||
"sent_folder": "Sent",
|
||||
},
|
||||
"credentials": {
|
||||
"smtp": {"username": "sender@example.org", "password": "smtp-secret"},
|
||||
"imap": {"username": "sender@example.org", "password": "imap-secret"},
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(created_profile.status_code, 201, created_profile.text)
|
||||
@@ -264,25 +263,28 @@ class ApiSmokeTests(unittest.TestCase):
|
||||
"smtp": {
|
||||
"host": "mock.smtp",
|
||||
"port": 2525,
|
||||
"username": "sender@example.org",
|
||||
"password": "smtp-secret",
|
||||
"security": "starttls",
|
||||
},
|
||||
"imap": {
|
||||
"enabled": True,
|
||||
"host": "mock.imap",
|
||||
"port": 993,
|
||||
"username": "sender@example.org",
|
||||
"password": "imap-secret",
|
||||
"security": "tls",
|
||||
"sent_folder": "Sent",
|
||||
},
|
||||
"credentials": {
|
||||
"smtp": {"username": "sender@example.org", "password": "smtp-secret"},
|
||||
"imap": {"username": "sender@example.org", "password": "imap-secret"},
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(created_profile.status_code, 201, created_profile.text)
|
||||
profile_payload = created_profile.json()
|
||||
self.assertNotIn("password", profile_payload["smtp"])
|
||||
self.assertNotIn("username", profile_payload["smtp"])
|
||||
self.assertNotIn("password", profile_payload["imap"])
|
||||
self.assertNotIn("username", profile_payload["imap"])
|
||||
self.assertEqual(profile_payload["credentials"]["smtp"]["username"], "sender@example.org")
|
||||
self.assertEqual(profile_payload["credentials"]["imap"]["username"], "sender@example.org")
|
||||
profile_id = profile_payload["id"]
|
||||
|
||||
campaign_json = {
|
||||
@@ -325,8 +327,12 @@ class ApiSmokeTests(unittest.TestCase):
|
||||
assert profile is not None
|
||||
self.assertNotEqual(profile.smtp_password_encrypted, "smtp-secret")
|
||||
self.assertNotEqual(profile.imap_password_encrypted, "imap-secret")
|
||||
self.assertEqual(profile.smtp_username, "sender@example.org")
|
||||
self.assertEqual(profile.imap_username, "sender@example.org")
|
||||
self.assertNotIn("password", profile.smtp_config)
|
||||
self.assertNotIn("username", profile.smtp_config)
|
||||
self.assertNotIn("password", profile.imap_config or {})
|
||||
self.assertNotIn("username", profile.imap_config or {})
|
||||
|
||||
version = session.get(CampaignVersion, version_id)
|
||||
self.assertIsNotNone(version)
|
||||
@@ -346,19 +352,18 @@ class ApiSmokeTests(unittest.TestCase):
|
||||
"smtp": {
|
||||
"host": "mock.smtp",
|
||||
"port": 2525,
|
||||
"username": "profile-smtp@example.org",
|
||||
"password": "profile-smtp-secret",
|
||||
"security": "starttls",
|
||||
},
|
||||
"imap": {
|
||||
"enabled": True,
|
||||
"host": "mock.imap",
|
||||
"port": 993,
|
||||
"username": "profile-imap@example.org",
|
||||
"password": "profile-imap-secret",
|
||||
"security": "tls",
|
||||
"sent_folder": "Sent",
|
||||
},
|
||||
"credentials": {
|
||||
"smtp": {"username": "profile-smtp@example.org", "password": "profile-smtp-secret"},
|
||||
"imap": {"username": "profile-imap@example.org", "password": "profile-imap-secret"},
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(created_profile.status_code, 201, created_profile.text)
|
||||
@@ -373,8 +378,10 @@ class ApiSmokeTests(unittest.TestCase):
|
||||
"mail_profile_id": profile_id,
|
||||
"inherit_smtp_credentials": False,
|
||||
"inherit_imap_credentials": False,
|
||||
"smtp": {"username": "campaign-smtp@example.org", "password": "campaign-smtp-secret"},
|
||||
"imap": {"username": "campaign-imap@example.org", "password": "campaign-imap-secret"},
|
||||
"credentials": {
|
||||
"smtp": {"username": "campaign-smtp@example.org", "password": "campaign-smtp-secret"},
|
||||
"imap": {"username": "campaign-imap@example.org", "password": "campaign-imap-secret"},
|
||||
},
|
||||
},
|
||||
"recipients": {
|
||||
"from": {"email": "sender@example.org", "name": "Sender", "type": "to"},
|
||||
@@ -2713,10 +2720,11 @@ class ApiSmokeTests(unittest.TestCase):
|
||||
"smtp": {
|
||||
"host": "mock.smtp",
|
||||
"port": 2525,
|
||||
"username": "profile-smtp@example.org",
|
||||
"password": "profile-smtp-secret",
|
||||
"security": "starttls",
|
||||
},
|
||||
"credentials": {
|
||||
"smtp": {"username": "profile-smtp@example.org", "password": "profile-smtp-secret"},
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(profile.status_code, 201, profile.text)
|
||||
@@ -2725,10 +2733,14 @@ class ApiSmokeTests(unittest.TestCase):
|
||||
policy = self.client.put(
|
||||
"/api/v1/mail/policies/tenant",
|
||||
headers=headers,
|
||||
json={"policy": {"smtp_credentials": {"inherit": True, "allow_override": False}}},
|
||||
json={"policy": {
|
||||
"smtp_credentials": {"inherit": True},
|
||||
"allow_lower_level_limits": {"smtp_credentials.inherit": False},
|
||||
}},
|
||||
)
|
||||
self.assertEqual(policy.status_code, 200, policy.text)
|
||||
self.assertTrue(policy.json()["effective_policy"] is None)
|
||||
self.assertEqual(policy.json()["effective_policy"]["smtp_credentials"]["inherit"], True)
|
||||
self.assertEqual(policy.json()["effective_policy"]["allow_lower_level_limits"]["smtp_credentials.inherit"], False)
|
||||
|
||||
campaign_json = {
|
||||
"version": "1.0",
|
||||
@@ -2738,7 +2750,9 @@ class ApiSmokeTests(unittest.TestCase):
|
||||
"server": {
|
||||
"mail_profile_id": profile_id,
|
||||
"inherit_smtp_credentials": False,
|
||||
"smtp": {"username": "campaign-smtp@example.org", "password": "campaign-smtp-secret"},
|
||||
"credentials": {
|
||||
"smtp": {"username": "campaign-smtp@example.org", "password": "campaign-smtp-secret"},
|
||||
},
|
||||
},
|
||||
"recipients": {
|
||||
"from": {"email": "sender@example.org", "name": "Sender", "type": "to"},
|
||||
|
||||
@@ -12,6 +12,7 @@ from govoplan_core.db.base import Base
|
||||
from govoplan_core.db.migrations import (
|
||||
REVISION_AUTH_RBAC,
|
||||
REVISION_FILE_FOLDERS,
|
||||
REVISION_HIERARCHICAL_SETTINGS,
|
||||
alembic_config,
|
||||
migrate_database,
|
||||
)
|
||||
@@ -151,6 +152,85 @@ class DatabaseMigrationTests(unittest.TestCase):
|
||||
self.assertEqual(system_owner_count, 1 if user_count else 0)
|
||||
finally:
|
||||
engine.dispose()
|
||||
|
||||
def test_repairs_current_schema_stamped_at_file_folders(self) -> None:
|
||||
with tempfile.TemporaryDirectory(prefix="msm-current-schema-marker-test-") as directory:
|
||||
database = Path(directory) / "current-schema-marker.db"
|
||||
url = f"sqlite:///{database}"
|
||||
command.upgrade(alembic_config(database_url=url), REVISION_HIERARCHICAL_SETTINGS)
|
||||
|
||||
engine = create_engine(url)
|
||||
try:
|
||||
with engine.begin() as connection:
|
||||
connection.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO mail_server_profiles
|
||||
(id, tenant_id, scope_type, scope_id, name, slug, description, is_active,
|
||||
smtp_config, smtp_password_encrypted, imap_config, imap_password_encrypted,
|
||||
created_by_user_id, updated_by_user_id, created_at, updated_at)
|
||||
VALUES
|
||||
('profile-1', NULL, 'system', 'system', 'System Mail', 'system-mail', NULL, 1,
|
||||
:smtp_config, NULL, :imap_config, NULL,
|
||||
NULL, NULL, '2026-06-25 15:00:00', '2026-06-25 15:00:00')
|
||||
"""
|
||||
),
|
||||
{
|
||||
"smtp_config": '{"host":"smtp.example.test","port":587,"security":"starttls","username":"smtp-user","enabled":true}',
|
||||
"imap_config": '{"host":"imap.example.test","port":993,"security":"ssl","username":"imap-user","enabled":true}',
|
||||
},
|
||||
)
|
||||
connection.execute(
|
||||
text("UPDATE alembic_version SET version_num = :revision"),
|
||||
{"revision": REVISION_FILE_FOLDERS},
|
||||
)
|
||||
with engine.connect() as connection:
|
||||
self.assertEqual(
|
||||
MigrationContext.configure(connection).get_current_revision(),
|
||||
REVISION_FILE_FOLDERS,
|
||||
)
|
||||
profile_columns = {
|
||||
column["name"]
|
||||
for column in inspect(connection).get_columns("mail_server_profiles")
|
||||
}
|
||||
self.assertNotIn("smtp_username", profile_columns)
|
||||
self.assertNotIn("imap_username", profile_columns)
|
||||
finally:
|
||||
engine.dispose()
|
||||
|
||||
result = migrate_database(database_url=url)
|
||||
self.assertEqual(result.previous_revision, REVISION_FILE_FOLDERS)
|
||||
self.assertEqual(result.reconciled_revision, REVISION_HIERARCHICAL_SETTINGS)
|
||||
|
||||
engine = create_engine(url)
|
||||
try:
|
||||
with engine.connect() as connection:
|
||||
self.assertEqual(
|
||||
MigrationContext.configure(connection).get_current_revision(),
|
||||
result.current_revision,
|
||||
)
|
||||
profile_columns = {
|
||||
column["name"]
|
||||
for column in inspect(connection).get_columns("mail_server_profiles")
|
||||
}
|
||||
profile = connection.execute(text(
|
||||
"""
|
||||
SELECT smtp_username, smtp_config, imap_username, imap_config
|
||||
FROM mail_server_profiles
|
||||
WHERE id = 'profile-1'
|
||||
"""
|
||||
)).mappings().one()
|
||||
self.assertIn("smtp_username", profile_columns)
|
||||
self.assertIn("imap_username", profile_columns)
|
||||
self.assertEqual(profile["smtp_username"], "smtp-user")
|
||||
self.assertEqual(profile["imap_username"], "imap-user")
|
||||
self.assertNotIn("username", profile["smtp_config"])
|
||||
self.assertNotIn("enabled", profile["smtp_config"])
|
||||
self.assertNotIn("username", profile["imap_config"])
|
||||
self.assertNotIn("enabled", profile["imap_config"])
|
||||
finally:
|
||||
engine.dispose()
|
||||
|
||||
def test_migrates_legacy_login_identity_and_bootstraps_system_owner(self) -> None:
|
||||
with tempfile.TemporaryDirectory(prefix="msm-account-migration-test-") as directory:
|
||||
database = Path(directory) / "accounts.db"
|
||||
|
||||
51
tests/test_devserver.py
Normal file
51
tests/test_devserver.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class DevserverSmokeTests(unittest.TestCase):
|
||||
def test_smoke_mode_bootstraps_missing_local_sqlite_database(self) -> None:
|
||||
repo_root = Path(__file__).resolve().parents[1]
|
||||
src_root = repo_root / "src"
|
||||
with tempfile.TemporaryDirectory(prefix="govoplan-devserver-smoke-") as directory:
|
||||
runtime_root = Path(directory)
|
||||
env = os.environ.copy()
|
||||
for key in (
|
||||
"DATABASE_URL",
|
||||
"DEV_BOOTSTRAP_ENABLED",
|
||||
"GOVOPLAN_SERVER_CONFIG",
|
||||
"FILE_STORAGE_LOCAL_ROOT",
|
||||
"MOCK_MAILBOX_DIR",
|
||||
):
|
||||
env.pop(key, None)
|
||||
env["APP_ENV"] = "dev"
|
||||
env["CELERY_ENABLED"] = "false"
|
||||
env["ENABLED_MODULES"] = "access"
|
||||
env["PYTHONPATH"] = str(src_root) + (os.pathsep + env["PYTHONPATH"] if env.get("PYTHONPATH") else "")
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "govoplan_core.devserver", "--smoke", "--no-reload"],
|
||||
cwd=runtime_root,
|
||||
env=env,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result.returncode, 0, result.stderr or result.stdout)
|
||||
db_path = runtime_root / "runtime" / "multimailer-dev.db"
|
||||
self.assertTrue(db_path.exists(), result.stdout)
|
||||
self.assertGreater(db_path.stat().st_size, 0, result.stdout)
|
||||
self.assertIn(f"Runtime root: {runtime_root}", result.stdout)
|
||||
self.assertIn(f"Database: sqlite:///{db_path}", result.stdout)
|
||||
self.assertIn("Dev bootstrap for missing SQLite DB: enabled", result.stdout)
|
||||
self.assertIn("Smoke check: OK", result.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
243
tests/test_module_system.py
Normal file
243
tests/test_module_system.py
Normal file
@@ -0,0 +1,243 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
# Keep the default app import side effect from bootstrapping a development DB.
|
||||
_TEST_ROOT = Path(tempfile.mkdtemp(prefix="govoplan-module-tests-"))
|
||||
os.environ.setdefault("APP_ENV", "test")
|
||||
os.environ.setdefault("DATABASE_URL", f"sqlite:///{_TEST_ROOT / 'module-test.db'}")
|
||||
os.environ.setdefault("DEV_BOOTSTRAP_ENABLED", "false")
|
||||
os.environ.setdefault("CELERY_ENABLED", "false")
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from govoplan_core.core.migrations import migration_metadata_plan
|
||||
from govoplan_core.server.app import create_app
|
||||
from govoplan_core.server.config import GovoplanServerConfig
|
||||
from govoplan_core.server.registry import available_module_manifests, build_platform_registry
|
||||
from govoplan_files.backend.storage.backends import LocalFilesystemStorageBackend, StorageBackendError
|
||||
|
||||
|
||||
def _settings(root: Path) -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
app_env="test",
|
||||
database_url=f"sqlite:///{root / 'test.db'}",
|
||||
dev_mailbox_api_enabled=False,
|
||||
file_storage_backend="local",
|
||||
file_storage_local_root=str(root / "files"),
|
||||
file_storage_local_fallback_roots="",
|
||||
file_storage_s3_endpoint_url=None,
|
||||
file_storage_s3_region=None,
|
||||
file_storage_s3_bucket=None,
|
||||
file_storage_s3_access_key_id=None,
|
||||
file_storage_s3_secret_access_key=None,
|
||||
s3_endpoint_url=None,
|
||||
s3_region=None,
|
||||
s3_bucket=None,
|
||||
s3_access_key_id=None,
|
||||
s3_secret_access_key=None,
|
||||
file_upload_max_bytes=1024 * 1024,
|
||||
file_upload_zip_max_bytes=10 * 1024 * 1024,
|
||||
celery_enabled=False,
|
||||
redis_url="redis://127.0.0.1:6379/15",
|
||||
mock_mailbox_dir=str(root / "mock-mailbox"),
|
||||
)
|
||||
|
||||
|
||||
class ModuleSystemTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def tearDownClass(cls) -> None:
|
||||
shutil.rmtree(_TEST_ROOT, ignore_errors=True)
|
||||
|
||||
def _app_for_modules(self, modules: tuple[str, ...]):
|
||||
root = Path(tempfile.mkdtemp(prefix="govoplan-module-app-", dir=_TEST_ROOT))
|
||||
settings = _settings(root)
|
||||
config = GovoplanServerConfig(
|
||||
title="GovOPlaN Module Test",
|
||||
version="test",
|
||||
settings=settings,
|
||||
enabled_modules=modules,
|
||||
base_routers=(),
|
||||
post_module_routers=(),
|
||||
extra_routers=(),
|
||||
lifespan=None,
|
||||
app_configurators=(),
|
||||
)
|
||||
return create_app(config), settings
|
||||
|
||||
def test_discovers_installed_core_and_product_module_manifests(self) -> None:
|
||||
manifests = available_module_manifests()
|
||||
self.assertTrue({"access", "files", "mail", "campaigns"}.issubset(manifests))
|
||||
self.assertEqual(manifests["campaigns"].dependencies, ("access",))
|
||||
self.assertEqual(manifests["campaigns"].optional_dependencies, ("files", "mail"))
|
||||
|
||||
def test_enabled_module_permutations_register_expected_routes(self) -> None:
|
||||
cases = (
|
||||
("core_only", (), {"access"}, set()),
|
||||
("files_only", ("files",), {"access", "files"}, {"/api/v1/files"}),
|
||||
("mail_only", ("mail",), {"access", "mail"}, {"/api/v1/mail"}),
|
||||
("campaign_without_files_or_mail", ("campaigns",), {"access", "campaigns"}, {"/api/v1/campaigns"}),
|
||||
("campaign_without_files", ("campaigns", "mail"), {"access", "campaigns", "mail"}, {"/api/v1/campaigns", "/api/v1/mail"}),
|
||||
("campaign_with_files_but_no_mail", ("campaigns", "files"), {"access", "campaigns", "files"}, {"/api/v1/campaigns", "/api/v1/files"}),
|
||||
("full_product", ("campaigns", "files", "mail"), {"access", "campaigns", "files", "mail"}, {"/api/v1/campaigns", "/api/v1/files", "/api/v1/mail"}),
|
||||
)
|
||||
for name, enabled_modules, expected_modules, expected_prefixes in cases:
|
||||
with self.subTest(name=name):
|
||||
app, _settings_obj = self._app_for_modules(enabled_modules)
|
||||
route_paths = {getattr(route, "path", "") for route in app.routes}
|
||||
self.assertIn("/api/v1/platform/modules", route_paths)
|
||||
for prefix in ("/api/v1/campaigns", "/api/v1/files", "/api/v1/mail"):
|
||||
has_prefix = any(path == prefix or path.startswith(f"{prefix}/") for path in route_paths)
|
||||
self.assertEqual(prefix in expected_prefixes, has_prefix, f"{name}: {prefix}")
|
||||
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/api/v1/platform/modules")
|
||||
self.assertEqual(response.status_code, 200, response.text)
|
||||
payload_modules = {item["id"] for item in response.json()["modules"]}
|
||||
self.assertEqual(expected_modules, payload_modules)
|
||||
|
||||
|
||||
def _run_physical_absence_probe(self, *, enabled_modules: tuple[str, ...], blocked_modules: tuple[str, ...]) -> None:
|
||||
root = Path(tempfile.mkdtemp(prefix="govoplan-absence-probe-", dir=_TEST_ROOT))
|
||||
enabled = ",".join(("access", *enabled_modules)) if enabled_modules else "access"
|
||||
script = f"""
|
||||
import importlib.abc
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
os.environ["APP_ENV"] = "test"
|
||||
os.environ["DATABASE_URL"] = {f"sqlite:///{root / 'probe.db'}"!r}
|
||||
os.environ["DEV_BOOTSTRAP_ENABLED"] = "false"
|
||||
os.environ["CELERY_ENABLED"] = "false"
|
||||
os.environ["ENABLED_MODULES"] = {enabled!r}
|
||||
|
||||
BLOCKED = {blocked_modules!r}
|
||||
|
||||
class Blocker(importlib.abc.MetaPathFinder):
|
||||
def find_spec(self, fullname, path=None, target=None):
|
||||
for prefix in BLOCKED:
|
||||
if fullname == prefix or fullname.startswith(prefix + "."):
|
||||
raise ModuleNotFoundError(f"No module named {{prefix!r}}", name=prefix)
|
||||
return None
|
||||
|
||||
sys.meta_path.insert(0, Blocker())
|
||||
|
||||
from govoplan_core.server.app import create_app
|
||||
from govoplan_core.server.config import GovoplanServerConfig
|
||||
from govoplan_core.server.registry import build_platform_registry
|
||||
|
||||
registry = build_platform_registry({enabled_modules!r})
|
||||
config = GovoplanServerConfig(
|
||||
title="absence probe",
|
||||
version="test",
|
||||
enabled_modules={enabled_modules!r},
|
||||
base_routers=(),
|
||||
post_module_routers=(),
|
||||
extra_routers=(),
|
||||
lifespan=None,
|
||||
app_configurators=(),
|
||||
)
|
||||
app = create_app(config)
|
||||
route_paths = [getattr(route, "path", "") for route in app.routes]
|
||||
print(json.dumps({{"modules": [item.id for item in registry.manifests()], "routes": route_paths}}, sort_keys=True))
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-c", script],
|
||||
cwd=str(Path(__file__).resolve().parents[1]),
|
||||
env=os.environ.copy(),
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
self.fail(result.stderr or result.stdout)
|
||||
|
||||
def test_campaign_optional_integrations_follow_enabled_modules(self) -> None:
|
||||
cases = (
|
||||
(("campaigns",), False, False),
|
||||
(("campaigns", "mail"), False, True),
|
||||
(("campaigns", "files"), True, False),
|
||||
(("campaigns", "files", "mail"), True, True),
|
||||
)
|
||||
for enabled_modules, files_enabled, mail_enabled in cases:
|
||||
with self.subTest(enabled_modules=enabled_modules):
|
||||
registry = build_platform_registry(enabled_modules)
|
||||
self.assertTrue(registry.has_module("campaigns"))
|
||||
self.assertEqual(files_enabled, registry.integration_enabled("campaigns", "files"))
|
||||
self.assertEqual(mail_enabled, registry.integration_enabled("campaigns", "mail"))
|
||||
|
||||
|
||||
def test_module_permutations_start_when_absent_modules_are_physically_unavailable(self) -> None:
|
||||
cases = (
|
||||
((), ("govoplan_files", "govoplan_mail", "govoplan_campaign")),
|
||||
(("files",), ("govoplan_mail", "govoplan_campaign")),
|
||||
(("mail",), ("govoplan_files", "govoplan_campaign")),
|
||||
(("campaigns",), ("govoplan_files", "govoplan_mail")),
|
||||
(("campaigns", "files"), ("govoplan_mail",)),
|
||||
(("campaigns", "mail"), ("govoplan_files",)),
|
||||
(("campaigns", "files", "mail"), ()),
|
||||
)
|
||||
for enabled_modules, blocked_modules in cases:
|
||||
with self.subTest(enabled_modules=enabled_modules, blocked_modules=blocked_modules):
|
||||
self._run_physical_absence_probe(enabled_modules=enabled_modules, blocked_modules=blocked_modules)
|
||||
|
||||
def test_module_route_factories_receive_runtime_settings(self) -> None:
|
||||
app, settings = self._app_for_modules(("files", "mail"))
|
||||
self.assertIsNotNone(app)
|
||||
|
||||
from govoplan_files.backend.runtime import get_settings as get_files_settings
|
||||
from govoplan_mail.backend.runtime import get_settings as get_mail_settings
|
||||
|
||||
self.assertIs(settings, get_files_settings())
|
||||
self.assertIs(settings, get_mail_settings())
|
||||
|
||||
def test_module_migrations_are_registered_only_for_enabled_modules(self) -> None:
|
||||
core_plan = migration_metadata_plan(build_platform_registry(()))
|
||||
self.assertEqual((), core_plan.script_locations)
|
||||
self.assertEqual(1, len(core_plan.metadata))
|
||||
|
||||
files_plan = migration_metadata_plan(build_platform_registry(("files",)))
|
||||
self.assertEqual(1, len(files_plan.script_locations))
|
||||
self.assertTrue(files_plan.script_locations[0].endswith("govoplan_files/backend/migrations/versions"))
|
||||
|
||||
full_plan = migration_metadata_plan(build_platform_registry(("campaigns", "files", "mail")))
|
||||
locations = "\n".join(full_plan.script_locations)
|
||||
self.assertIn("govoplan_campaign/backend/migrations/versions", locations)
|
||||
self.assertIn("govoplan_files/backend/migrations/versions", locations)
|
||||
self.assertIn("govoplan_mail/backend/migrations/versions", locations)
|
||||
|
||||
def test_local_storage_backend_reads_from_fallback_roots_without_copying(self) -> None:
|
||||
with tempfile.TemporaryDirectory(prefix="govoplan-storage-fallback-") as directory:
|
||||
root = Path(directory) / "primary"
|
||||
fallback = Path(directory) / "fallback"
|
||||
key = "tenant-a/files/demo.txt"
|
||||
(fallback / key).parent.mkdir(parents=True)
|
||||
(fallback / key).write_bytes(b"fallback-data")
|
||||
|
||||
backend = LocalFilesystemStorageBackend(root=root, fallback_roots=(fallback,))
|
||||
|
||||
self.assertTrue(backend.exists(key))
|
||||
self.assertEqual(b"fallback-data", backend.get_bytes(key))
|
||||
self.assertEqual([b"fallback", b"-data"], list(backend.iter_bytes(key, chunk_size=8)))
|
||||
self.assertFalse((root / key).exists())
|
||||
|
||||
backend.delete(key)
|
||||
self.assertTrue((fallback / key).exists())
|
||||
self.assertEqual(b"fallback-data", backend.get_bytes(key))
|
||||
|
||||
with self.assertRaises(StorageBackendError):
|
||||
backend.get_bytes("../escape.txt")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user