import io import pytest from django.core.files.uploadedfile import SimpleUploadedFile from infrasynth.files.models import PipelineExecution, ProcessingPipeline from infrasynth.files.processing import PipelineExecutor, run_pipeline_execution from infrasynth.files.services import FileService pytestmark = pytest.mark.django_db def _make_image_png(width=200, height=100): from PIL import Image buffer = io.BytesIO() Image.new("RGB", (width, height), color="red").save(buffer, format="PNG") return buffer.getvalue() @pytest.fixture def image_file(): return SimpleUploadedFile("image.png", _make_image_png(), content_type="image/png") @pytest.fixture def stored_image(user, media_root, image_file): return FileService().upload(image_file, filename="image.png", user=user) @pytest.fixture def sync_processing(settings): settings.INFRASYNTH_FILES = { **settings.INFRASYNTH_FILES, "PROCESSING_BACKEND": "sync", } class TestPipelineExecutor: def test_resize_step(self, stored_image, sync_processing): pipeline = ProcessingPipeline.objects.create( name="Resize", slug="resize", steps=[{"type": "resize", "params": {"width": 50}}], ) execution = PipelineExecution.objects.create(file=stored_image, pipeline=pipeline) PipelineExecutor().execute(execution) execution.refresh_from_db() assert execution.status == PipelineExecution.Status.COMPLETED assert execution.output_file_id is not None from PIL import Image out = execution.output_file backend = FileService()._resolve_file(out.id) from infrasynth.files.storage import get_storage_backend fh = get_storage_backend("local").open(backend.storage_key, "rb") img = Image.open(fh) assert img.width <= 50 assert img.height <= 50 def test_optimize_and_scan_steps(self, stored_image, sync_processing): pipeline = ProcessingPipeline.objects.create( name="Optimize", slug="optimize", steps=[{"type": "optimize", "params": {}}, {"type": "scan"}], ) execution = PipelineExecution.objects.create(file=stored_image, pipeline=pipeline) PipelineExecutor().execute(execution) execution.refresh_from_db() assert execution.status == PipelineExecution.Status.COMPLETED assert execution.output_file_id is not None def test_watermark_step(self, stored_image, sync_processing): pipeline = ProcessingPipeline.objects.create( name="Watermark", slug="watermark", steps=[{"type": "watermark", "params": {"text": "CONF"}}], ) execution = PipelineExecution.objects.create(file=stored_image, pipeline=pipeline) PipelineExecutor().execute(execution) execution.refresh_from_db() assert execution.status == PipelineExecution.Status.COMPLETED assert execution.output_file.mime_type == "image/png" def test_unknown_step_marks_failed(self, stored_image, sync_processing): pipeline = ProcessingPipeline.objects.create( name="Bad", slug="bad", steps=[{"type": "explode"}], ) execution = PipelineExecution.objects.create(file=stored_image, pipeline=pipeline) PipelineExecutor().execute(execution) execution.refresh_from_db() assert execution.status == PipelineExecution.Status.FAILED assert "Unknown pipeline step" in execution.error def test_emits_file_processed_signal(self, stored_image, sync_processing): from infrasynth.files.signals import file_processed sent = [] receiver = lambda **kw: sent.append(kw) # noqa: E731 file_processed.connect(receiver, weak=False) pipeline = ProcessingPipeline.objects.create( name="Resize", slug="resize2", steps=[{"type": "resize", "params": {}}] ) execution = PipelineExecution.objects.create(file=stored_image, pipeline=pipeline) PipelineExecutor().execute(execution) assert sent assert sent[0]["file_id"] == stored_image.id assert sent[0]["pipeline_name"] == "resize2" assert sent[0]["status"] == "completed" file_processed.disconnect(receiver) class TestRunPipelineExecutionTask: def test_task_completes_execution(self, stored_image): pipeline = ProcessingPipeline.objects.create( name="Resize", slug="resize_task", steps=[{"type": "resize", "params": {}}] ) execution = PipelineExecution.objects.create(file=stored_image, pipeline=pipeline) result = run_pipeline_execution.delay(execution.id) execution.refresh_from_db() assert execution.status == PipelineExecution.Status.COMPLETED assert result.id def test_task_missing_execution_returns_none(self): assert run_pipeline_execution.run(999999) is None