Skip to content

Commit daad8db

Browse files
authored
Update test_controllers.py
1 parent 2923980 commit daad8db

1 file changed

Lines changed: 94 additions & 0 deletions

File tree

‎tests/test_controllers.py‎

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,97 @@ def test_detail_is_the_message(self) -> None:
103103
err = NotFound("conversation not found")
104104
assert err.detail == "conversation not found"
105105
assert str(err) == "conversation not found"
106+
107+
108+
class TestUploadDurability:
109+
"""store_upload write-through: a durability failure must not leave a
110+
half-persisted file in the sandbox workspace."""
111+
112+
def _session(self):
113+
from app.infra.config import get_settings
114+
from app.infra.db import get_session_factory, reset_engine_for_tests
115+
116+
reset_engine_for_tests(get_settings().db_url)
117+
return get_session_factory()()
118+
119+
def _user_conv(self, db):
120+
from app.models import Conversation, User, new_id
121+
122+
user = User(id=new_id("usr"), email="up@example.com", password_hash="x")
123+
db.add(user)
124+
db.flush()
125+
conv = Conversation(id=new_id("cnv"), user_id=user.id, title="t")
126+
db.add(conv)
127+
db.flush()
128+
return user, conv
129+
130+
def test_durability_failure_unlinks_local_file(self) -> None:
131+
import asyncio
132+
133+
from app.controllers import files
134+
from app.infra.config import conversation_workspace
135+
from app.infra.storage import StorageError
136+
137+
class _BrokenStorage:
138+
def put_stream(self, key, stream):
139+
raise StorageError("durable tier down")
140+
141+
async def _run(db, user, conv):
142+
chunks = [b"partial-bytes", b""]
143+
144+
async def read_chunk(_n: int) -> bytes:
145+
return chunks.pop(0)
146+
147+
await files.store_upload(
148+
db,
149+
user,
150+
conv,
151+
filename="x.csv",
152+
read_chunk=read_chunk,
153+
storage=_BrokenStorage(),
154+
)
155+
156+
with self._session() as db:
157+
user, conv = self._user_conv(db)
158+
with pytest.raises(StorageError):
159+
asyncio.run(_run(db, user, conv))
160+
# no file left behind in the workspace, and no DB row committed
161+
workspace = conversation_workspace(conv.id)
162+
assert list(workspace.iterdir()) == []
163+
from app.models import ConversationFile
164+
165+
assert db.query(ConversationFile).count() == 0
166+
167+
def test_success_persists_to_both_tiers(self) -> None:
168+
import asyncio
169+
from pathlib import Path
170+
171+
from app.controllers import files
172+
173+
class _MemStorage:
174+
def __init__(self):
175+
self.puts = {}
176+
177+
def put_stream(self, key, stream):
178+
data = stream.read()
179+
self.puts[key] = data
180+
return len(data)
181+
182+
store = _MemStorage()
183+
184+
async def _run(db, user, conv):
185+
chunks = [b"hello,world\n", b""]
186+
187+
async def read_chunk(_n: int) -> bytes:
188+
return chunks.pop(0)
189+
190+
return await files.store_upload(
191+
db, user, conv, filename="x.csv", read_chunk=read_chunk, storage=store
192+
)
193+
194+
with self._session() as db:
195+
user, conv = self._user_conv(db)
196+
row = asyncio.run(_run(db, user, conv))
197+
key = files.durable_key(conv.id, row.stored_name)
198+
assert store.puts[key] == b"hello,world\n" # durable tier
199+
assert Path(row.path).read_bytes() == b"hello,world\n" # sandbox tier

0 commit comments

Comments
 (0)