"""Tests for storage.mirror — the INV-STG1 dual-write seal. mirror() must: be a no-op under the filesystem backend (the disk write is canonical), persist to the S3 sub-backend under s3/dual, and never raise (an S3 failure is logged, the request proceeds on the disk copy). Offline. """ from __future__ import annotations import asyncio import pytest from legal_mcp.services import storage class _FakeS3: def __init__(self, fail: bool = False) -> None: self.fail = fail self.puts: list[tuple] = [] async def put_bytes(self, key, data, *, bucket, content_type=None, metadata=None): if self.fail: raise RuntimeError("s3 down") self.puts.append((key, bytes(data), bucket)) return f"s3://{bucket}/{key}" class _FakeFilesystem: name = "filesystem" class _FakeDual: name = "dual" def __init__(self, s3: _FakeS3) -> None: self.s3 = s3 def _run(coro): loop = asyncio.new_event_loop() try: return loop.run_until_complete(coro) finally: loop.close() def test_mirror_noop_under_filesystem(monkeypatch): monkeypatch.setattr(storage, "get_storage", lambda: _FakeFilesystem()) # must not raise and must not attempt any S3 work _run(storage.mirror("cases/x/y.pdf", b"data", bucket=storage.Bucket.DOCUMENTS)) def test_mirror_persists_under_dual(monkeypatch): s3 = _FakeS3() monkeypatch.setattr(storage, "get_storage", lambda: _FakeDual(s3)) _run(storage.mirror("cases/x/y.pdf", b"data", bucket=storage.Bucket.DOCUMENTS)) assert s3.puts == [("cases/x/y.pdf", b"data", storage.Bucket.DOCUMENTS)] def test_mirror_best_effort_never_raises(monkeypatch): s3 = _FakeS3(fail=True) monkeypatch.setattr(storage, "get_storage", lambda: _FakeDual(s3)) # S3 failure must be swallowed-with-log, never propagate (disk copy holds) _run(storage.mirror("cases/x/y.pdf", b"data", bucket=storage.Bucket.DOCUMENTS)) def test_mirror_uses_backend_itself_when_no_s3_attr(monkeypatch): # a pure s3 backend has no .s3 sub-attr → getattr falls back to the backend s3 = _FakeS3() s3.name = "s3" monkeypatch.setattr(storage, "get_storage", lambda: s3) _run(storage.mirror("k", b"d", bucket=storage.Bucket.DERIVED)) assert s3.puts == [("k", b"d", storage.Bucket.DERIVED)]