from datetime import datetime, timezone import pytest from botocore.exceptions import ClientError from app.garage_client import GarageClient class _FakePaginator: def __init__(self, sdk): self._sdk = sdk def paginate(self, Bucket, Prefix="", Delimiter="/"): contents = [] for key, (data, content_type, last_modified) in self._sdk.objects.items(): if key.startswith(Prefix): contents.append({"Key": key, "Size": len(data), "LastModified": last_modified}) yield {"Contents": contents, "CommonPrefixes": []} class FakeS3SDK: def __init__(self): self.objects = {} self.put_calls = [] self.removed_keys = [] def get_paginator(self, name): return _FakePaginator(self) def get_object(self, Bucket, Key): if Key not in self.objects: raise ClientError({"Error": {"Code": "NoSuchKey"}}, "GetObject") data, _, _ = self.objects[Key] class _Body: def read(self_inner): return data return {"Body": _Body()} def put_object(self, Bucket, Key, Body, ContentLength, ContentType): data = Body.read() self.objects[Key] = (data, ContentType, datetime.now(timezone.utc)) self.put_calls.append((Bucket, Key, ContentType)) def delete_object(self, Bucket, Key): del self.objects[Key] self.removed_keys.append(Key) def test_list_objects_returns_keys_matching_prefix(): sdk = FakeS3SDK() sdk.objects["产品文档/架构设计.md"] = (b"# hi", "text/markdown", datetime(2026, 8, 1, tzinfo=timezone.utc)) sdk.objects["团队规范/编码规范.md"] = (b"# rules", "text/markdown", datetime(2026, 8, 1, tzinfo=timezone.utc)) client = GarageClient(sdk, bucket="dochub") result = client.list_objects("产品文档/") assert len(result) == 1 assert result[0]["key"] == "产品文档/架构设计.md" assert result[0]["size"] == 4 def test_get_object_returns_bytes(): sdk = FakeS3SDK() sdk.objects["产品文档/架构设计.md"] = (b"# architecture", "text/markdown", datetime.now(timezone.utc)) client = GarageClient(sdk, bucket="dochub") data = client.get_object("产品文档/架构设计.md") assert data == b"# architecture" def test_get_object_raises_file_not_found_for_missing_key(): sdk = FakeS3SDK() client = GarageClient(sdk, bucket="dochub") with pytest.raises(FileNotFoundError): client.get_object("不存在/文档.md") def test_put_object_stores_data_with_content_type(): sdk = FakeS3SDK() client = GarageClient(sdk, bucket="dochub") client.put_object("产品文档/新文档.md", b"# new doc", "text/markdown") assert sdk.objects["产品文档/新文档.md"][0] == b"# new doc" assert sdk.put_calls == [("dochub", "产品文档/新文档.md", "text/markdown")] def test_delete_object_removes_key(): sdk = FakeS3SDK() sdk.objects["产品文档/旧文档.md"] = (b"old", "text/markdown", datetime.now(timezone.utc)) client = GarageClient(sdk, bucket="dochub") client.delete_object("产品文档/旧文档.md") assert "产品文档/旧文档.md" not in sdk.objects assert sdk.removed_keys == ["产品文档/旧文档.md"]