"""Where uploaded documents live (OPS-011). Contract paths were absolute and built from `os.getcwd()`, so the storage root followed whatever directory the process was started from. That is a defect on its own — a server restarted from elsewhere writes new contracts into a new tree and cannot read the old ones, while the database goes on saying they are there — and it is what made a release-directory deployment impossible: every stored path would point inside a release about to be replaced. The compatibility case is the one that matters most here. Rows written before this change hold absolute paths, and they have to keep working without a data migration, because the migration tooling does not exist yet (DB-002, blocked). """ import os import pytest from app.storage import CONTRACTS_DIR, document_path, documents_root class TestDocumentsRoot: def test_the_environment_wins(self, tmp_path, monkeypatch): monkeypatch.setenv('DOCUMENTS_ROOT', str(tmp_path / 'elsewhere')) assert documents_root() == str(tmp_path / 'elsewhere') def test_a_relative_override_is_made_absolute(self, monkeypatch): monkeypatch.setenv('DOCUMENTS_ROOT', 'docs-here') assert os.path.isabs(documents_root()) def test_the_default_does_not_follow_the_working_directory(self, tmp_path, monkeypatch): """The whole point. `os.getcwd()` made this move; the package location does not.""" monkeypatch.delenv('DOCUMENTS_ROOT', raising=False) before = documents_root() monkeypatch.chdir(tmp_path) after = documents_root() assert before == after def test_the_default_sits_beside_the_package(self, monkeypatch): monkeypatch.delenv('DOCUMENTS_ROOT', raising=False) # From the module file, not from `app.__file__`: `app` has no # __init__.py, so it is a namespace package and __file__ is None. from app import storage package_dir = os.path.dirname(os.path.abspath(storage.__file__)) expected = os.path.join(os.path.dirname(package_dir), 'documents') assert documents_root() == expected class TestDocumentPath: def test_a_relative_path_is_resolved_against_the_root(self, tmp_path, monkeypatch): monkeypatch.setenv('DOCUMENTS_ROOT', str(tmp_path)) resolved = document_path(os.path.join(CONTRACTS_DIR, 'abc.pdf')) assert resolved == str(tmp_path / CONTRACTS_DIR / 'abc.pdf') def test_an_absolute_path_is_left_alone(self, tmp_path, monkeypatch): """Rows written before this module existed. They must keep resolving to where the file actually is, or every contract uploaded so far becomes a 500 on download the day this ships.""" monkeypatch.setenv('DOCUMENTS_ROOT', str(tmp_path / 'new-root')) legacy = os.path.abspath(os.path.join('C:' + os.sep, 'old', 'place', 'abc.pdf')) assert document_path(legacy) == legacy def test_moving_the_root_moves_new_documents_and_not_old_ones(self, tmp_path, monkeypatch): relative = os.path.join(CONTRACTS_DIR, 'abc.pdf') legacy = os.path.abspath(os.path.join(str(tmp_path), 'legacy', 'abc.pdf')) monkeypatch.setenv('DOCUMENTS_ROOT', str(tmp_path / 'one')) first_new, first_old = document_path(relative), document_path(legacy) monkeypatch.setenv('DOCUMENTS_ROOT', str(tmp_path / 'two')) second_new, second_old = document_path(relative), document_path(legacy) assert first_new != second_new, 'a release switch has to move new documents' assert first_old == second_old, 'and must not move the ones already filed' class TestThroughTheUploadRoute: @pytest.fixture def uploaded(self, app, client, as_role, make_user): """A contract filed by an admin for a player.""" as_role('admin') player_id = make_user('player') response = client.post( '/users/contracts/upload', data={ 'player_id': str(player_id), 'notes': 'Season contract', 'contract_file': (_pdf(), 'contract.pdf'), }, content_type='multipart/form-data', follow_redirects=True, ) assert response.status_code == 200 return player_id def test_the_stored_path_is_relative(self, app, uploaded): from app.models import Contract with app.app_context(): contract = Contract.query.one() assert not os.path.isabs(contract.file_path), ( 'an absolute path pins the file to the directory the process ' 'was started from — the one thing a release switch changes' ) assert contract.file_path.startswith(CONTRACTS_DIR) def test_the_file_lands_under_the_configured_root(self, app, uploaded): from app.models import Contract with app.app_context(): contract = Contract.query.one() resolved = document_path(contract.file_path) assert os.path.exists(resolved) assert resolved.startswith(documents_root()) def test_it_can_be_downloaded_back(self, app, client, uploaded): from app.models import Contract with app.app_context(): contract_id = Contract.query.one().id response = client.get(f'/users/contracts/{contract_id}/download') assert response.status_code == 200 assert response.data.startswith(b'%PDF-') def _pdf(): """The smallest thing pdf_upload_error accepts.""" import io return io.BytesIO(b'%PDF-1.4\n%%EOF\n')