Kaynağa Gözat

Read the upload ID from the field chattolib actually returns

Every upload failed with "CreateUpload returned no upload ID": we read
upload.id, but AssetUpload names it upload_id (there is no id). The value
came through str(getattr(cast(Any, upload), "id", "")), so the wrong name
silently produced "" instead of failing loudly or at type-check time.

The existing send_* tests stub _upload_asset out entirely, which is why
nothing caught it. Cover the real function against real chattolib result
types instead. Asset.id at the completion step was already correct, and
no other attribute read in the adapter misses its field.
Paul Klumpp 1 hafta önce
ebeveyn
işleme
7f7cb37ba4
2 değiştirilmiş dosya ile 41 ekleme ve 1 silme
  1. 3 1
      adapter.py
  2. 38 0
      test_adapter.py

+ 3 - 1
adapter.py

@@ -1492,7 +1492,9 @@ class ChattoAdapter(BasePlatformAdapter):
                 sha256=sha256_hash,
                 content_type=mime_type,
             )
-            upload_id = str(getattr(cast(Any, upload), "id", ""))
+            # AssetUpload names this upload_id, not id — reading it through an
+            # untyped getattr default is what let the mismatch reach production.
+            upload_id = upload.upload_id
             if not upload_id:
                 logger.error("Chatto: CreateUpload returned no upload ID")
                 return None

+ 38 - 0
test_adapter.py

@@ -43,6 +43,8 @@ from adapter import (
 )
 from chattolib.realtime_types import ReactionPayload
 from chattolib.types import (
+    Asset,
+    AssetUpload,
     AssetUrl,
     Message,
     MessageAttachment,
@@ -519,6 +521,42 @@ class TestHandoffThread:
 
 # -- Native file / video / audio delivery --
 
+class TestUploadAsset:
+    """Drives the real _upload_asset against real chattolib result types.
+
+    The send_* tests stub _upload_asset out, so a wrong field name on the
+    chattolib response was invisible to them until it hit a live server.
+    """
+
+    @pytest_asyncio.fixture
+    def adapter(self, tmp_path):
+        adapter = _make_adapter()
+        self.path = tmp_path / "horse.jpg"
+        self.path.write_bytes(b"\xff\xd8\xff" + b"x" * 100)
+        upload = AssetUpload(upload_id="up-1", room_id="room-1")
+        adapter._chatto_client.create_upload = AsyncMock(return_value=upload)
+        adapter._chatto_client.upload_chunk = AsyncMock(return_value=upload)
+        adapter._chatto_client.complete_upload = AsyncMock(return_value=(
+            upload,
+            Asset(id="asset-9", filename="horse.jpg", content_type="image/jpeg", size=103),
+        ))
+        return adapter
+
+    async def test_returns_the_asset_id(self, adapter):
+        assert await adapter._upload_asset("room-1", str(self.path)) == "asset-9"
+
+    async def test_chunks_go_to_the_upload_id_from_create_upload(self, adapter):
+        """AssetUpload calls it upload_id, not id — reading the wrong field made
+        every upload fail with 'CreateUpload returned no upload ID'."""
+        await adapter._upload_asset("room-1", str(self.path))
+        assert adapter._chatto_client.upload_chunk.await_args.kwargs["upload_id"] == "up-1"
+
+    async def test_missing_upload_id_is_reported(self, adapter):
+        adapter._chatto_client.create_upload = AsyncMock(
+            return_value=AssetUpload(upload_id="", room_id="room-1"))
+        assert await adapter._upload_asset("room-1", str(self.path)) is None
+
+
 class TestNativeSends:
     """send_document/_video/_voice upload instead of apologising in text."""