Update Dockerfile and settings for user permissions, add test URL patterns, and adjust volume paths
This commit is contained in:
parent
ea4cd498d0
commit
b08143980c
5 changed files with 219 additions and 4 deletions
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
|
@ -50,12 +51,21 @@ def reload_settings_module() -> Generator[Callable[..., ModuleType]]:
|
|||
env[key] = value
|
||||
|
||||
with temporary_env(env):
|
||||
return importlib.reload(settings)
|
||||
# If another test removed the module from sys.modules (we do that in a
|
||||
# few tests to simulate startup failure), importlib.reload() will
|
||||
# raise. Import afresh in that case so the fixture is resilient.
|
||||
if "config.settings" in sys.modules:
|
||||
return importlib.reload(sys.modules["config.settings"])
|
||||
return importlib.import_module("config.settings")
|
||||
|
||||
yield _reload
|
||||
|
||||
with temporary_env(original_env):
|
||||
importlib.reload(settings)
|
||||
# Ensure `config.settings` is restored even if some tests removed the
|
||||
# module from sys.modules (several tests intentionally pop it). Use
|
||||
# importlib.import_module to (re)import and then reload to execute
|
||||
# module-level initialization under the original environment.
|
||||
importlib.reload(importlib.import_module("config.settings"))
|
||||
|
||||
|
||||
def test_env_bool_truthy_values(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
|
@ -134,3 +144,149 @@ def test_sessions_app_installed() -> None:
|
|||
def test_session_table_exists() -> None:
|
||||
"""The sessions table should be available in the database."""
|
||||
Session.objects.count()
|
||||
|
||||
|
||||
def test_env_bool_falsey_values(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""env_bool should treat common falsey strings as False."""
|
||||
falsy_values: list[str] = ["0", "false", "no", "n", "off", "", " "]
|
||||
for value in falsy_values:
|
||||
monkeypatch.setenv("FEATURE_FLAG", value)
|
||||
assert settings.env_bool("FEATURE_FLAG") is False
|
||||
|
||||
|
||||
def test_env_int_invalid_raises_value_error(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""env_int should raise ValueError for non-integer strings."""
|
||||
monkeypatch.setenv("MAX_COUNT", "not-an-int")
|
||||
with pytest.raises(ValueError, match="invalid literal for int"):
|
||||
settings.env_int("MAX_COUNT", 1)
|
||||
|
||||
|
||||
def test_testing_true_when_sys_argv_contains_test(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
reload_settings_module: Callable[..., ModuleType],
|
||||
) -> None:
|
||||
"""TESTING should be true when 'test' is present in sys.argv."""
|
||||
monkeypatch.setattr("sys.argv", ["manage.py", "test"])
|
||||
reloaded: ModuleType = reload_settings_module()
|
||||
|
||||
assert reloaded.TESTING is True
|
||||
|
||||
|
||||
def test_testing_true_when_pytest_version_set(reload_settings_module: Callable[..., ModuleType]) -> None:
|
||||
"""TESTING should be true when PYTEST_VERSION is set in the env."""
|
||||
reloaded: ModuleType = reload_settings_module(PYTEST_VERSION="7.0.0")
|
||||
|
||||
assert reloaded.TESTING is True
|
||||
|
||||
|
||||
def test_media_and_static_directories_created_on_import(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
reload_settings_module: Callable[..., ModuleType],
|
||||
) -> None:
|
||||
"""Importing settings should create MEDIA_ROOT and STATIC_ROOT under DATA_DIR."""
|
||||
fake_dir: Path = tmp_path / "app_data"
|
||||
|
||||
def fake_user_data_dir(**_: str) -> str:
|
||||
fake_dir.mkdir(parents=True, exist_ok=True)
|
||||
return str(fake_dir)
|
||||
|
||||
monkeypatch.setattr("platformdirs.user_data_dir", fake_user_data_dir)
|
||||
reloaded: ModuleType = reload_settings_module()
|
||||
|
||||
assert fake_dir == reloaded.DATA_DIR
|
||||
assert reloaded.MEDIA_ROOT.exists() is True
|
||||
assert reloaded.STATIC_ROOT.exists() is True
|
||||
|
||||
|
||||
def test_missing_secret_key_causes_system_exit(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Missing DJANGO_SECRET_KEY should abort startup with SystemExit."""
|
||||
monkeypatch.delenv("DJANGO_SECRET_KEY", raising=False)
|
||||
# Prevent load_dotenv from repopulating values from the repository .env file
|
||||
monkeypatch.setattr("dotenv.load_dotenv", lambda *a, **k: None)
|
||||
|
||||
# Remove the cached module so the import executes module-level code freshly
|
||||
sys.modules.pop("config.settings", None)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
__import__("config.settings")
|
||||
|
||||
|
||||
def test_email_settings_from_env(reload_settings_module: Callable[..., ModuleType]) -> None:
|
||||
"""EMAIL_* values should be read from the environment and cast correctly."""
|
||||
reloaded: ModuleType = reload_settings_module(
|
||||
EMAIL_HOST="smtp.example.com",
|
||||
EMAIL_PORT="1025",
|
||||
EMAIL_USE_TLS="0",
|
||||
EMAIL_USE_SSL="1",
|
||||
EMAIL_HOST_USER="me@example.com",
|
||||
EMAIL_HOST_PASSWORD="s3cret",
|
||||
EMAIL_TIMEOUT="3",
|
||||
)
|
||||
|
||||
assert reloaded.EMAIL_HOST == "smtp.example.com"
|
||||
assert reloaded.EMAIL_PORT == 1025
|
||||
assert reloaded.EMAIL_USE_TLS is False
|
||||
assert reloaded.EMAIL_USE_SSL is True
|
||||
assert reloaded.EMAIL_HOST_USER == "me@example.com"
|
||||
assert reloaded.EMAIL_HOST_PASSWORD == "s3cret"
|
||||
assert reloaded.EMAIL_TIMEOUT == 3
|
||||
# DEFAULT_FROM_EMAIL and SERVER_EMAIL mirror EMAIL_HOST_USER in settings.py
|
||||
assert reloaded.DEFAULT_FROM_EMAIL == "me@example.com"
|
||||
assert reloaded.SERVER_EMAIL == "me@example.com"
|
||||
|
||||
|
||||
def test_database_settings_when_not_testing(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
reload_settings_module: Callable[..., ModuleType],
|
||||
) -> None:
|
||||
"""When not running tests, DATABASES should use the Postgres configuration."""
|
||||
# Ensure the module believes it's not running tests
|
||||
monkeypatch.setattr("sys.argv", ["manage.py", "runserver"])
|
||||
monkeypatch.delenv("PYTEST_VERSION", raising=False)
|
||||
|
||||
reloaded: ModuleType = reload_settings_module(
|
||||
POSTGRES_DB="prod_db",
|
||||
POSTGRES_USER="prod_user",
|
||||
POSTGRES_PASSWORD="secret",
|
||||
POSTGRES_HOST="db.host",
|
||||
POSTGRES_PORT="5433",
|
||||
CONN_MAX_AGE="120",
|
||||
CONN_HEALTH_CHECKS="0",
|
||||
)
|
||||
|
||||
assert reloaded.TESTING is False
|
||||
db_cfg = reloaded.DATABASES["default"]
|
||||
assert db_cfg["ENGINE"] == "django.db.backends.postgresql"
|
||||
assert db_cfg["NAME"] == "prod_db"
|
||||
assert db_cfg["USER"] == "prod_user"
|
||||
assert db_cfg["PASSWORD"] == "secret"
|
||||
assert db_cfg["HOST"] == "db.host"
|
||||
assert db_cfg["PORT"] == 5433
|
||||
assert db_cfg["CONN_MAX_AGE"] == 120
|
||||
assert db_cfg["CONN_HEALTH_CHECKS"] is False
|
||||
|
||||
|
||||
def test_debug_tools_installed_only_when_not_testing(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
reload_settings_module: Callable[..., ModuleType],
|
||||
) -> None:
|
||||
"""`debug_toolbar` and `silk` are only added when not TESTING."""
|
||||
# Not testing -> tools should be present
|
||||
monkeypatch.setattr("sys.argv", ["manage.py", "runserver"])
|
||||
monkeypatch.delenv("PYTEST_VERSION", raising=False)
|
||||
not_testing: ModuleType = reload_settings_module()
|
||||
assert "debug_toolbar" in not_testing.INSTALLED_APPS
|
||||
assert "silk" in not_testing.INSTALLED_APPS
|
||||
|
||||
# Testing -> tools should not be present
|
||||
testing: ModuleType = reload_settings_module(PYTEST_VERSION="7.0.0")
|
||||
assert "debug_toolbar" not in testing.INSTALLED_APPS
|
||||
assert "silk" not in testing.INSTALLED_APPS
|
||||
|
||||
|
||||
def test_logging_configuration_structure() -> None:
|
||||
"""Basic logging structure and levels should be present in LOGGING."""
|
||||
assert settings.LOGGING["handlers"]["console"]["class"] == "logging.StreamHandler"
|
||||
assert settings.LOGGING["loggers"]["ttvdrops"]["level"] == "DEBUG"
|
||||
assert settings.LOGGING["loggers"][""]["level"] == "INFO"
|
||||
|
|
|
|||
50
config/tests/test_urls.py
Normal file
50
config/tests/test_urls.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable
|
||||
from types import ModuleType
|
||||
|
||||
|
||||
def _pattern_strings(module: ModuleType) -> Iterable[str]:
|
||||
"""Return string representations of the URL patterns' route/regex."""
|
||||
return (str(p.pattern) for p in module.urlpatterns)
|
||||
|
||||
|
||||
def _reload_urls_with(**overrides) -> ModuleType:
|
||||
"""Reload `config.urls` while temporarily overriding Django settings.
|
||||
|
||||
Returns:
|
||||
ModuleType: The `config.urls` module as imported under the
|
||||
overridden settings. The real `config.urls` module is reloaded
|
||||
back to the test-default settings before this function returns.
|
||||
"""
|
||||
# Import under overridden settings
|
||||
with override_settings(**overrides):
|
||||
mod = importlib.reload(importlib.import_module("config.urls"))
|
||||
|
||||
# Restore to the normal test settings to avoid leaking changes to other tests
|
||||
importlib.reload(importlib.import_module("config.urls"))
|
||||
return mod
|
||||
|
||||
|
||||
def test_top_level_named_routes_available() -> None:
|
||||
"""Top-level routes defined in `config.urls` are reversible."""
|
||||
assert reverse("sitemap") == "/sitemap.xml"
|
||||
assert reverse("robots") == "/robots.txt"
|
||||
# ensure the included `twitch` namespace is present
|
||||
assert reverse("twitch:dashboard") == "/"
|
||||
|
||||
|
||||
def test_debug_tools_not_present_while_testing() -> None:
|
||||
"""`silk` and Django Debug Toolbar URL patterns are not present while running tests."""
|
||||
# Default test environment should *not* expose debug routes.
|
||||
mod = _reload_urls_with(TESTING=True)
|
||||
patterns = list(_pattern_strings(mod))
|
||||
assert not any("silk" in p for p in patterns)
|
||||
assert not any("__debug__" in p or "debug" in p for p in patterns)
|
||||
Loading…
Add table
Add a link
Reference in a new issue