Initial public release
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import asyncio
|
||||
from hashlib import sha256
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from app.core.errors import AppError
|
||||
from app.services.dataset_service import DatasetService
|
||||
from app.services.storage_service import StorageService
|
||||
|
||||
|
||||
def test_persist_dataset_file_records_metadata(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setattr(
|
||||
"app.services.storage_service.get_settings",
|
||||
lambda: SimpleNamespace(storage_root=str(tmp_path)),
|
||||
)
|
||||
|
||||
metadata = StorageService.persist_dataset_file(
|
||||
project_id="project-123",
|
||||
dataset_id="dataset-456",
|
||||
dataset_type="vector",
|
||||
original_filename="../weird name!@#.geojson",
|
||||
content=b"example-bytes",
|
||||
content_type="application/geo+json",
|
||||
)
|
||||
|
||||
assert metadata["original_filename"] == "weird name___.geojson"
|
||||
assert metadata["stored_filename"] == "dataset-456_weird name___.geojson"
|
||||
assert metadata["content_type"] == "application/geo+json"
|
||||
assert metadata["size_bytes"] == 13
|
||||
assert len(metadata["checksum_sha256"]) == 64
|
||||
assert Path(metadata["storage_path"]).exists()
|
||||
assert str(Path(tmp_path, "uploads", "project-123", "vector", "dataset-456")) in metadata["storage_path"]
|
||||
|
||||
|
||||
class _ChunkedUpload:
|
||||
def __init__(self, content: bytes) -> None:
|
||||
self.content = content
|
||||
self.requested_sizes: list[int] = []
|
||||
|
||||
async def read(self, size: int) -> bytes:
|
||||
self.requested_sizes.append(size)
|
||||
chunk, self.content = self.content[:size], self.content[size:]
|
||||
return chunk
|
||||
|
||||
|
||||
def test_persist_upload_file_streams_bounded_chunks(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setattr(StorageService, "_base_dir", staticmethod(lambda: tmp_path))
|
||||
upload = _ChunkedUpload(b"abcdefghijk")
|
||||
|
||||
metadata = asyncio.run(
|
||||
StorageService.persist_upload_file(
|
||||
project_id="project",
|
||||
dataset_id="dataset",
|
||||
dataset_type="raster",
|
||||
original_filename="source.tif",
|
||||
upload=upload,
|
||||
content_type="image/tiff",
|
||||
max_bytes=32,
|
||||
chunk_size=4,
|
||||
)
|
||||
)
|
||||
|
||||
stored = Path(metadata["storage_path"])
|
||||
assert upload.requested_sizes == [4, 4, 4, 4]
|
||||
assert stored.read_bytes() == b"abcdefghijk"
|
||||
assert metadata["size_bytes"] == 11
|
||||
assert metadata["checksum_sha256"] == sha256(b"abcdefghijk").hexdigest()
|
||||
|
||||
|
||||
def test_persist_upload_file_rejects_oversize_and_removes_partial_file(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setattr(StorageService, "_base_dir", staticmethod(lambda: tmp_path))
|
||||
upload = _ChunkedUpload(b"0123456789")
|
||||
expected_path = Path(
|
||||
StorageService.dataset_file_path(
|
||||
"project",
|
||||
"dataset",
|
||||
"vector",
|
||||
"source.geojson",
|
||||
)
|
||||
)
|
||||
|
||||
with pytest.raises(AppError) as exc_info:
|
||||
asyncio.run(
|
||||
StorageService.persist_upload_file(
|
||||
project_id="project",
|
||||
dataset_id="dataset",
|
||||
dataset_type="vector",
|
||||
original_filename="source.geojson",
|
||||
upload=upload,
|
||||
content_type="application/geo+json",
|
||||
max_bytes=8,
|
||||
chunk_size=3,
|
||||
)
|
||||
)
|
||||
|
||||
assert exc_info.value.code == "UPLOAD_TOO_LARGE"
|
||||
assert exc_info.value.status_code == 413
|
||||
assert not expected_path.exists()
|
||||
|
||||
|
||||
def test_vector_staging_uses_lower_in_memory_limit(monkeypatch) -> None:
|
||||
captured = {}
|
||||
|
||||
async def persist_upload_file(**kwargs):
|
||||
captured.update(kwargs)
|
||||
return {"storage_path": "unused"}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.services.dataset_service.get_settings",
|
||||
lambda: SimpleNamespace(max_upload_mb=500, max_in_memory_vector_mb=32),
|
||||
)
|
||||
monkeypatch.setattr(StorageService, "persist_upload_file", persist_upload_file)
|
||||
|
||||
asyncio.run(
|
||||
DatasetService._stage_upload(
|
||||
project_id="project",
|
||||
dataset_id="dataset",
|
||||
dataset_type="vector",
|
||||
filename="source.geojson",
|
||||
file=SimpleNamespace(content_type="application/geo+json"),
|
||||
)
|
||||
)
|
||||
|
||||
assert captured["max_bytes"] == 32 * 1024 * 1024
|
||||
|
||||
|
||||
def test_staged_vector_read_is_bounded_before_loading_file(monkeypatch, tmp_path) -> None:
|
||||
source = tmp_path / "large.geojson"
|
||||
source.write_bytes(b"x" * (1024 * 1024 + 1))
|
||||
monkeypatch.setattr(
|
||||
"app.services.dataset_service.get_settings",
|
||||
lambda: SimpleNamespace(max_upload_mb=500, max_in_memory_vector_mb=1),
|
||||
)
|
||||
|
||||
with pytest.raises(AppError) as exc_info:
|
||||
DatasetService._read_staged_vector_bytes({"storage_path": str(source)})
|
||||
|
||||
assert exc_info.value.code == "UPLOAD_TOO_LARGE"
|
||||
assert exc_info.value.status_code == 413
|
||||
assert not source.exists()
|
||||
Reference in New Issue
Block a user