gabriel / musehub public
test_storage_backends.py python
131 lines 5.1 KB
Raw
sha256:d035733f21ccff27735fddebfbbe0ed24565a32a22db8de5885402262671ecd2 chore: bump version to 0.2.0rc15 for musehub#113 fix release Sonnet 4.6 patch 15 days ago
1 """TDD contract for BlobBackend presign URL rewriting.
2
3 Bug (issue #62 — Phase 1):
4 presign_get does not call _rewrite_presign_url, unlike presign_put and
5 presign_batch. When BLOB_STORAGE_PUBLIC_ENDPOINT differs from
6 BLOB_STORAGE_ENDPOINT (e.g. http://minio:9000 vs http://localhost:9000 in
7 local dev), presign_get returns a URL with the Docker-internal hostname that
8 external clients cannot reach.
9
10 Root cause: backends.py:492 — return value is not wrapped in _rewrite_presign_url.
11
12 Fix: wrap return value the same way presign_put (line 471) and presign_batch
13 (line 517) already do.
14 """
15 from __future__ import annotations
16
17 import asyncio
18 from unittest.mock import MagicMock, patch
19
20 import pytest
21
22 from musehub.storage.backends import BlobBackend
23
24
25 _INTERNAL = "http://minio:9000"
26 _PUBLIC = "http://localhost:9000"
27 _OID = "sha256:" + "a" * 64
28
29
30 def _make_backend() -> BlobBackend:
31 """Backend wired with mismatched internal/public endpoints — mirrors local dev."""
32 return BlobBackend(
33 bucket="test-bucket",
34 endpoint_url=_INTERNAL,
35 public_endpoint_url=_PUBLIC,
36 access_key_id="minioadmin",
37 secret_access_key="minioadmin",
38 region="us-east-1",
39 )
40
41
42 def _fake_presigned(method: str) -> str:
43 """Simulated URL that boto3 would generate using the internal endpoint."""
44 return f"{_INTERNAL}/test-bucket/objects/{_OID}?X-Amz-SignedHeaders=host&Action={method}"
45
46
47 # ── T-A1 ──────────────────────────────────────────────────────────────────────
48
49 def test_presign_get_rewrites_internal_to_public_endpoint() -> None:
50 """presign_get must rewrite Docker-internal URL to the public endpoint.
51
52 RED before fix: presign_get returns http://minio:9000/... because it does
53 not call _rewrite_presign_url. External clients (muse CLI) cannot resolve
54 'minio' → [Errno 8] nodename nor servname provided, or not known.
55
56 GREEN after fix: backends.py:492 wraps the return with _rewrite_presign_url,
57 matching the pattern already used by presign_put and presign_batch.
58 """
59 backend = _make_backend()
60
61 mock_client = MagicMock()
62 mock_client.generate_presigned_url.return_value = _fake_presigned("GetObject")
63
64 with patch.object(backend, "_get_client", return_value=mock_client):
65 result = asyncio.run(backend.presign_get(_OID, ttl_seconds=3600))
66
67 assert result.startswith(_PUBLIC), (
68 f"presign_get must rewrite internal endpoint to public endpoint.\n"
69 f" internal ({_INTERNAL!r}) leaked into presigned URL: {result!r}\n"
70 f" expected URL to start with: {_PUBLIC!r}\n"
71 f"Fix: in backends.py presign_get, wrap the return value with\n"
72 f" self._rewrite_presign_url(...) — identical to presign_put."
73 )
74
75
76 # ── consistency ───────────────────────────────────────────────────────────────
77
78 def test_presign_put_already_rewrites() -> None:
79 """presign_put already rewrites correctly — serves as the passing baseline."""
80 backend = _make_backend()
81
82 mock_client = MagicMock()
83 mock_client.generate_presigned_url.return_value = _fake_presigned("PutObject")
84
85 with patch.object(backend, "_get_client", return_value=mock_client):
86 result = asyncio.run(backend.presign_put(_OID, ttl_seconds=3600))
87
88 assert result.startswith(_PUBLIC), (
89 f"presign_put baseline broken — expected {_PUBLIC!r}, got: {result!r}"
90 )
91
92
93 def test_presign_batch_get_already_rewrites() -> None:
94 """presign_batch (get direction) already rewrites — serves as baseline."""
95 backend = _make_backend()
96
97 mock_client = MagicMock()
98 mock_client.generate_presigned_url.return_value = _fake_presigned("GetObject")
99
100 with patch.object(backend, "_get_client", return_value=mock_client):
101 result = asyncio.run(backend.presign_batch([_OID], "get", 3600))
102
103 assert result[_OID].startswith(_PUBLIC), (
104 f"presign_batch baseline broken — expected {_PUBLIC!r}, got: {result[_OID]!r}"
105 )
106
107
108 def test_presign_get_no_rewrite_when_public_endpoint_unset() -> None:
109 """presign_get must not crash when public_endpoint_url is unset (production).
110
111 In prod, endpoint_url IS the public URL (R2 has one address), so no rewrite
112 is needed. _rewrite_presign_url is a no-op when _public_endpoint_url is None.
113 """
114 backend = BlobBackend(
115 bucket="test-bucket",
116 endpoint_url=_INTERNAL,
117 # public_endpoint_url intentionally omitted
118 access_key_id="minioadmin",
119 secret_access_key="minioadmin",
120 region="us-east-1",
121 )
122 assert backend._public_endpoint_url is None
123
124 mock_client = MagicMock()
125 mock_client.generate_presigned_url.return_value = _fake_presigned("GetObject")
126
127 with patch.object(backend, "_get_client", return_value=mock_client):
128 result = asyncio.run(backend.presign_get(_OID, ttl_seconds=3600))
129
130 # No rewrite — URL unchanged from what boto3 returned.
131 assert result == _fake_presigned("GetObject")
File History 11 commits
sha256:d035733f21ccff27735fddebfbbe0ed24565a32a22db8de5885402262671ecd2 chore: bump version to 0.2.0rc15 for musehub#113 fix release Sonnet 4.6 patch 15 days ago
sha256:0032d6cfa33bc3c8367436ad768e7dd0e339b4332153160247da8266cb5fa352 Merge branch 'task/version-tags-phase3-server' into dev Human 17 days ago
sha256:4669620efda9ff41c55bdefd1f7bfe1c239d468428744c84ead9957e5a003a53 merge: rescue snapshot-recovery hardening (c00aa21d) into d… Opus 4.8 minor 30 days ago
sha256:a59da49c4611b970fc4b6ae48678ce4943261c213a07ddbd73ce9201df869b4a fix: remove false-positive proposal_comments index drop fro… Sonnet 4.6 patch 34 days ago
sha256:0a240d6dbff234f07d98a28a4a9a68db702f3f9ff9260196f24219bdb1c0b6f3 feat: render markdown mists as HTML with heading anchor links Sonnet 4.6 patch 35 days ago
sha256:24a7d47486ebc4ebd1832830580e177ec6f877b48dced8c000e198cdec4ce9d6 Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 36 days ago
sha256:b9ff931d147e0114a1f17060f415b89ed551c170a91ff226c70437aa5c85f9ee Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 36 days ago
sha256:d1122d21e73471879b460037b22c0b50fded7c423444a176f248428f75dac39c Merge 'task/fix-issue-pagination-cursor' into 'dev' — propo… Human 36 days ago
sha256:01e18975e73d2b3cd5b6db7929c895bef9aa6e0d4391dc5b2adfc548b41318dd Merge 'feat/adding-debug-logs-to-staging' into 'dev' — prop… Human 36 days ago
sha256:6b1949fc2797ca4c1936a637a4cbfec828ef56cf52398a2e74ca3c4f494e728f fix: use wire_bytes not mpack_bytes_raw in compute_object_b… Sonnet 4.6 patch 48 days ago
sha256:b99f2455dc346966d040133f5203297e6e3ef5803a93728a2c30568d0a0f7583 rename: delta_add → delta_upsert across wire format, models… Sonnet 4.6 patch 50 days ago