feat: complete published registry contracts and gated SRO ingestion
All checks were successful
Mostovik Backend CI/CD / Tests and lint (push) Successful in 3m55s
Mostovik Backend CI/CD / Build linux/amd64 release images (push) Successful in 3m43s
Mostovik Backend CI/CD / Deploy and verify internal main (push) Has been skipped
Mostovik Backend CI/CD / Deploy customer main (push) Has been skipped
Mostovik Backend CI/CD / Deploy dev (push) Successful in 1m45s
All checks were successful
Mostovik Backend CI/CD / Tests and lint (push) Successful in 3m55s
Mostovik Backend CI/CD / Build linux/amd64 release images (push) Successful in 3m43s
Mostovik Backend CI/CD / Deploy and verify internal main (push) Has been skipped
Mostovik Backend CI/CD / Deploy customer main (push) Has been skipped
Mostovik Backend CI/CD / Deploy dev (push) Successful in 1m45s
This commit is contained in:
127
tests/apps/parsers/test_private_sro_artifact_storage.py
Normal file
127
tests/apps/parsers/test_private_sro_artifact_storage.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""SRO raw files stay outside every public MEDIA_ROOT serving path."""
|
||||
|
||||
import stat
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from types import ModuleType
|
||||
|
||||
from apps.parsers.models import ParserSourceArtifact
|
||||
from apps.parsers.source_artifacts import cleanup_parser_source_artifacts
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db.migrations.writer import MigrationWriter
|
||||
from django.test import RequestFactory, TestCase, override_settings
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class PrivateSroArtifactStorageTest(TestCase):
|
||||
def setUp(self):
|
||||
self.temporary = TemporaryDirectory(prefix="private-sro-artifacts-")
|
||||
self.addCleanup(self.temporary.cleanup)
|
||||
self.media = Path(self.temporary.name) / "media"
|
||||
self.private = Path(self.temporary.name) / "private"
|
||||
self.media.mkdir()
|
||||
self.overrides = override_settings(
|
||||
DEBUG=True,
|
||||
MEDIA_ROOT=self.media,
|
||||
PARSER_PRIVATE_ARTIFACT_ROOT=self.private,
|
||||
)
|
||||
self.overrides.enable()
|
||||
self.addCleanup(self.overrides.disable)
|
||||
# Same Django DEBUG media-serving helper as core.urls; no authentication.
|
||||
routes = ModuleType("private_sro_media_routes")
|
||||
routes.urlpatterns = static(
|
||||
settings.MEDIA_URL, document_root=settings.MEDIA_ROOT
|
||||
)
|
||||
self.routes = override_settings(ROOT_URLCONF=routes)
|
||||
self.routes.enable()
|
||||
self.addCleanup(self.routes.disable)
|
||||
|
||||
def save(self, source="sro_membership_check"):
|
||||
artifact = ParserSourceArtifact.objects.create(
|
||||
source=source, load_batch=1, status=ParserSourceArtifact.Status.PUBLISHED
|
||||
)
|
||||
artifact.file.save("fixture.zip", ContentFile(b"synthetic-only"), save=True)
|
||||
return artifact
|
||||
|
||||
def test_anonymous_media_get_cannot_read_saved_sro_raw(self):
|
||||
artifact = self.save()
|
||||
response = self.client.get(settings.MEDIA_URL + artifact.file.name)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
self.assertFalse((self.media / artifact.file.name).exists())
|
||||
self.assertTrue(Path(artifact.file.path).is_relative_to(self.private.resolve()))
|
||||
|
||||
def test_private_file_read_permissions_and_url_rejection(self):
|
||||
artifact = self.save()
|
||||
artifact.refresh_from_db()
|
||||
with artifact.file.open("rb") as handle:
|
||||
self.assertEqual(handle.read(), b"synthetic-only")
|
||||
self.assertEqual(artifact.file.size, len(b"synthetic-only"))
|
||||
self.assertEqual(stat.S_IMODE(Path(artifact.file.path).stat().st_mode), 0o600)
|
||||
self.assertEqual(
|
||||
stat.S_IMODE(Path(artifact.file.path).parent.stat().st_mode), 0o700
|
||||
)
|
||||
with self.assertRaisesMessage(
|
||||
ValueError, "Private parser artifacts have no public URL"
|
||||
):
|
||||
_ = artifact.file.url
|
||||
|
||||
def test_other_sources_keep_default_storage_and_public_url(self):
|
||||
artifact = self.save("budget_ubpandnubp")
|
||||
self.assertTrue(Path(artifact.file.path).is_relative_to(self.media))
|
||||
response = self.client.get(artifact.file.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(b"".join(response.streaming_content), b"synthetic-only")
|
||||
|
||||
def test_private_root_inside_media_is_rejected_before_file_write(self):
|
||||
for root in (self.media, self.media / "private"):
|
||||
with self.subTest(root=root.name), override_settings(
|
||||
PARSER_PRIVATE_ARTIFACT_ROOT=root
|
||||
), self.assertRaisesMessage(ImproperlyConfigured, "outside MEDIA_ROOT"):
|
||||
self.save()
|
||||
self.assertEqual(list(self.media.iterdir()), [])
|
||||
|
||||
def test_symlinked_private_root_inside_media_is_rejected(self):
|
||||
alias = Path(self.temporary.name) / "alias"
|
||||
alias.symlink_to(self.media, target_is_directory=True)
|
||||
with override_settings(
|
||||
PARSER_PRIVATE_ARTIFACT_ROOT=alias
|
||||
), self.assertRaisesMessage(ImproperlyConfigured, "outside MEDIA_ROOT"):
|
||||
self.save()
|
||||
self.assertEqual(list(self.media.iterdir()), [])
|
||||
|
||||
def test_existing_retention_removes_private_file_and_model(self):
|
||||
artifact = self.save()
|
||||
path = Path(artifact.file.path)
|
||||
ParserSourceArtifact.objects.filter(pk=artifact.pk).update(
|
||||
created_at=timezone.now() - timedelta(days=100)
|
||||
)
|
||||
self.assertEqual(cleanup_parser_source_artifacts(minimum_versions=0), 1)
|
||||
self.assertFalse(path.exists())
|
||||
self.assertFalse(ParserSourceArtifact.objects.filter(pk=artifact.pk).exists())
|
||||
|
||||
def test_storage_deconstruction_does_not_capture_machine_paths(self):
|
||||
storage = ParserSourceArtifact._meta.get_field("file").storage
|
||||
value, imports = MigrationWriter.serialize(storage)
|
||||
self.assertEqual(value, "apps.parsers.artifact_storage.ParserArtifactStorage()")
|
||||
self.assertEqual(imports, {"import apps.parsers.artifact_storage"})
|
||||
with override_settings(PARSER_PRIVATE_ARTIFACT_ROOT=self.private / "changed"):
|
||||
self.assertEqual(MigrationWriter.serialize(storage), (value, imports))
|
||||
|
||||
def test_private_artifact_admin_uses_readonly_text_instead_of_public_link(self):
|
||||
from apps.parsers.admin import ParserSourceArtifactAdmin
|
||||
|
||||
artifact = self.save()
|
||||
model_admin = ParserSourceArtifactAdmin(ParserSourceArtifact, AdminSite())
|
||||
request = RequestFactory().get("/admin/")
|
||||
fields = model_admin.get_fields(request, artifact)
|
||||
self.assertNotIn("file", fields)
|
||||
self.assertIn("private_file_name", fields)
|
||||
self.assertIn(
|
||||
"private_file_name", model_admin.get_readonly_fields(request, artifact)
|
||||
)
|
||||
self.assertEqual(model_admin.private_file_name(artifact), artifact.file.name)
|
||||
Reference in New Issue
Block a user