143 lines
5.5 KiB
Python
143 lines
5.5 KiB
Python
"""Where the application puts its files, and who agrees about it.
|
|
|
|
OBS-006. Three roots were built as `os.path.join(os.getcwd(), …)`: the
|
|
document store, the log directory and the backup directory. Each therefore
|
|
moved with whatever directory the process was started from — and the
|
|
processes that start this application are Waitress under Pterodactyl, a task
|
|
scheduler, pytest, and a person in a shell. Only one of the four is reliably
|
|
in the project directory.
|
|
|
|
The document store was fixed in wave G. The other two were not, and the gap
|
|
that opened between them is the reason this file exists: `backup.py` kept
|
|
archiving `./documents` while the application wrote to `DOCUMENTS_ROOT`.
|
|
The script now resolves the shared root and fails the run when it is absent.
|
|
"""
|
|
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from app.storage import backups_root, documents_root, logs_root, project_root
|
|
|
|
|
|
class TestTheProjectAnchor:
|
|
def test_it_is_the_directory_holding_the_app_package(self):
|
|
assert os.path.isdir(os.path.join(project_root(), 'app'))
|
|
|
|
def test_it_does_not_follow_the_working_directory(self, tmp_path, monkeypatch):
|
|
"""The whole point. Every root below inherits this."""
|
|
before = project_root()
|
|
monkeypatch.chdir(tmp_path)
|
|
|
|
assert project_root() == before
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
('root', 'env_name', 'default_name'),
|
|
[
|
|
(documents_root, 'DOCUMENTS_ROOT', 'documents'),
|
|
(logs_root, 'LOG_DIR', 'logs'),
|
|
(backups_root, 'BACKUP_DIR', 'backups'),
|
|
],
|
|
)
|
|
class TestEachRoot:
|
|
def test_it_defaults_beside_the_project(self, root, env_name, default_name, monkeypatch):
|
|
monkeypatch.delenv(env_name, raising=False)
|
|
|
|
assert root() == os.path.join(project_root(), default_name)
|
|
|
|
def test_it_ignores_the_working_directory(
|
|
self, root, env_name, default_name, tmp_path, monkeypatch
|
|
):
|
|
monkeypatch.delenv(env_name, raising=False)
|
|
monkeypatch.chdir(tmp_path)
|
|
|
|
assert root() == os.path.join(project_root(), default_name)
|
|
assert not root().startswith(str(tmp_path))
|
|
|
|
def test_the_environment_wins(self, root, env_name, default_name, tmp_path, monkeypatch):
|
|
monkeypatch.setenv(env_name, str(tmp_path / 'elsewhere'))
|
|
|
|
assert root() == os.path.abspath(str(tmp_path / 'elsewhere'))
|
|
|
|
|
|
class TestTheBackupScriptAgreesWithTheApplication:
|
|
"""The regression this file was written for.
|
|
|
|
`backup.py` held its own `os.getcwd()`-based constant, so the two
|
|
answers to "where are the contracts" could differ — and did, for anyone
|
|
who set DOCUMENTS_ROOT because docs/deployment.md said to.
|
|
"""
|
|
|
|
def test_it_archives_the_directory_the_application_writes_to(self, tmp_path, monkeypatch):
|
|
from app.supporting_scripts import backup
|
|
|
|
store = tmp_path / 'somewhere-else' / 'documents'
|
|
(store / 'contrats signés').mkdir(parents=True)
|
|
(store / 'contrats signés' / 'a.pdf').write_bytes(b'%PDF-1.4 test')
|
|
monkeypatch.setenv('DOCUMENTS_ROOT', str(store))
|
|
monkeypatch.setattr(backup, 'BACKUP_DIR', str(tmp_path / 'backups'))
|
|
(tmp_path / 'backups').mkdir()
|
|
|
|
archive = backup.backup_documents()
|
|
|
|
assert archive is not None, (
|
|
'the script skipped, which is what it did on production while reporting success'
|
|
)
|
|
assert os.path.getsize(archive) > 0
|
|
|
|
def test_the_archive_actually_contains_the_contract(self, tmp_path, monkeypatch):
|
|
"""An empty zip is still a file of non-zero size. This is the
|
|
assertion that would have caught the original defect."""
|
|
import zipfile
|
|
|
|
from app.supporting_scripts import backup
|
|
|
|
store = tmp_path / 'documents'
|
|
(store / 'contrats signés').mkdir(parents=True)
|
|
(store / 'contrats signés' / 'contrat.pdf').write_bytes(b'%PDF-1.4 test')
|
|
monkeypatch.setenv('DOCUMENTS_ROOT', str(store))
|
|
monkeypatch.setattr(backup, 'BACKUP_DIR', str(tmp_path / 'backups'))
|
|
(tmp_path / 'backups').mkdir()
|
|
|
|
archive = backup.backup_documents()
|
|
|
|
with zipfile.ZipFile(archive) as zf:
|
|
assert any(name.endswith('contrat.pdf') for name in zf.namelist())
|
|
|
|
def test_a_missing_store_names_the_path_it_looked_in(self, tmp_path, monkeypatch):
|
|
"""A missing configured store is an actionable failure, not a skip."""
|
|
from app.supporting_scripts import backup
|
|
|
|
missing = tmp_path / 'not-here'
|
|
monkeypatch.setenv('DOCUMENTS_ROOT', str(missing))
|
|
|
|
with pytest.raises(backup.BackupError, match=str(missing).replace('\\', '\\\\')):
|
|
backup.backup_documents()
|
|
|
|
|
|
class TestLogsFollowTheSameRule:
|
|
def test_configure_logging_writes_where_logs_root_says(self, tmp_path, monkeypatch):
|
|
from app.app import create_app
|
|
|
|
target = tmp_path / 'var' / 'log'
|
|
monkeypatch.setenv('LOG_DIR', str(target))
|
|
|
|
create_app(
|
|
{
|
|
'SECRET_KEY': 'test-secret-not-used-anywhere-real',
|
|
'SQLALCHEMY_DATABASE_URI': f'sqlite:///{tmp_path / "t.sqlite"}',
|
|
'TESTING': True,
|
|
'WTF_CSRF_ENABLED': False,
|
|
'FORCE_HTTPS': False,
|
|
'SESSION_COOKIE_SECURE': False,
|
|
'ENABLE_DISCORD_BOT': False,
|
|
'AUTO_CREATE_TABLES': True,
|
|
'CORS_ALLOWED_ORIGINS': '',
|
|
'RATELIMIT_ENABLED': False,
|
|
}
|
|
)
|
|
|
|
assert target.is_dir(), 'the log directory is created where LOG_DIR points'
|
|
assert (target / 'app.log').exists()
|