|
6 | 6 | https://pytest.org/latest/plugins.html
|
7 | 7 | """
|
8 | 8 |
|
| 9 | +import hashlib |
| 10 | +import json |
| 11 | +import time |
9 | 12 | from collections.abc import Generator
|
| 13 | +from datetime import datetime, timezone |
10 | 14 | from pathlib import Path
|
11 | 15 | from tempfile import NamedTemporaryFile
|
| 16 | +from unittest.mock import AsyncMock, patch |
12 | 17 |
|
13 | 18 | import pytest
|
14 | 19 | from aleph.sdk.chains.common import generate_key
|
| 20 | +from aleph.sdk.chains.ethereum import ETHAccount, get_fallback_private_key |
| 21 | +from aleph.sdk.types import StoredContent |
| 22 | +from aleph_message.models import Chain, ItemHash, ItemType, StoreContent, StoreMessage |
| 23 | +from aleph_message.models.base import MessageType |
15 | 24 |
|
16 | 25 | from aleph_client.models import CRNInfo
|
17 | 26 |
|
|
25 | 34 | FAKE_CRN_GPU_ADDRESS,
|
26 | 35 | FAKE_CRN_GPU_HASH,
|
27 | 36 | FAKE_CRN_GPU_URL,
|
| 37 | + FAKE_STORE_HASH, |
28 | 38 | )
|
29 | 39 |
|
30 | 40 |
|
@@ -259,3 +269,200 @@ def mock_crn_list():
|
259 | 269 | def mock_crn_info(mock_crn_list):
|
260 | 270 | """Create a mock CRNInfo object."""
|
261 | 271 | return CRNInfo.from_unsanitized_input(mock_crn_list[0])
|
| 272 | + |
| 273 | + |
| 274 | +@pytest.fixture |
| 275 | +def mock_pricing_info_response(): |
| 276 | + pricing_file = Path(__file__).parent / "mock_data/pricing_data.json" |
| 277 | + with open(pricing_file) as f: |
| 278 | + pricing_data = json.load(f) |
| 279 | + |
| 280 | + # Create a mock response for the HTTP get call |
| 281 | + mock_response = AsyncMock() |
| 282 | + mock_response.__aenter__.return_value = mock_response |
| 283 | + mock_response.status = 200 |
| 284 | + mock_response.json = AsyncMock(return_value=pricing_data) |
| 285 | + |
| 286 | + return mock_response |
| 287 | + |
| 288 | + |
| 289 | +@pytest.fixture |
| 290 | +def mock_settings_info(): |
| 291 | + settings_file = Path(__file__).parent / "mock_data/settings_aggregate.json" |
| 292 | + with open(settings_file) as f: |
| 293 | + settings_data = json.load(f) |
| 294 | + |
| 295 | + # Create a mock response for the HTTP get call |
| 296 | + mock_response = AsyncMock() |
| 297 | + mock_response.__aenter__.return_value = mock_response |
| 298 | + mock_response.status = 200 |
| 299 | + mock_response.json = AsyncMock(return_value=settings_data) |
| 300 | + |
| 301 | + return mock_response |
| 302 | + |
| 303 | + |
| 304 | +@pytest.fixture |
| 305 | +def mock_store_message_upload_fixture(): |
| 306 | + return { |
| 307 | + "sender": "0xe0aaF578B287de16852dbc54Ae34a263FF2F4b9E", |
| 308 | + "chain": "ETH", |
| 309 | + "signature": ( |
| 310 | + "0xe2d0bd0476e73652b1dbac082f250387b0a7691ee19f39ad6ffce2e8a45028160f3e35ef346beb4a4b5f50" |
| 311 | + "aacdd0d9b454f63eeedc3f8058eb25f7b096eadd231c" |
| 312 | + ), |
| 313 | + "type": "STORE", |
| 314 | + "item_content": ( |
| 315 | + '{"item_type":"storage","item_hash":"QmTestHashForMockedUpload","ref":null,' |
| 316 | + '"address":"0xe0aaF578B287de16852dbc54Ae34a263FF2F4b9E","time":1738837907}' |
| 317 | + ), |
| 318 | + "item_type": "inline", |
| 319 | + "item_hash": "5b868dc8c2df0dd9bb810b7a31cc50c8ad1e6569905e45ab4fd2eee36fecc4d2", |
| 320 | + "time": 1738837907, |
| 321 | + "channel": "test-chan-1", |
| 322 | + "content": { |
| 323 | + "address": "0xe0aaF578B287de16852dbc54Ae34a263FF2F4b9E", |
| 324 | + "time": 1738837907, |
| 325 | + "item_type": "storage", |
| 326 | + "item_hash": "QmTestHashForMockedUpload", |
| 327 | + "size": 12, |
| 328 | + "content_type": "text/plain", |
| 329 | + "ref": None, |
| 330 | + "metadata": None, |
| 331 | + }, |
| 332 | + } |
| 333 | + |
| 334 | + |
| 335 | +@pytest.fixture |
| 336 | +def mock_upload_store(mocker, store_message_fixture): |
| 337 | + """Create a mock for the AuthenticatedAlephHttpClient.create_store method.""" |
| 338 | + from aleph_message.models import StoreMessage |
| 339 | + |
| 340 | + message = StoreMessage.model_validate(store_message_fixture) |
| 341 | + return mocker.patch("aleph.sdk.AuthenticatedAlephHttpClient.create_store", return_value=[message, "processed"]) |
| 342 | + |
| 343 | + |
| 344 | +@pytest.fixture |
| 345 | +def mock_api_response(mock_pricing_info_response, mock_settings_info): |
| 346 | + """ |
| 347 | + Side-effect function for ClientSession.get that returns the right mocked response |
| 348 | + depending on the URL. It is a SYNC callable returning an async context manager. |
| 349 | + """ |
| 350 | + |
| 351 | + def side_effect(url, *args, **kwargs): |
| 352 | + if "keys=pricing" in url: |
| 353 | + return mock_pricing_info_response |
| 354 | + if "keys=settings" in url: |
| 355 | + return mock_settings_info |
| 356 | + return mock_pricing_info_response |
| 357 | + |
| 358 | + return side_effect |
| 359 | + |
| 360 | + |
| 361 | +@pytest.fixture |
| 362 | +def mock_authenticated_aleph_http_client(): |
| 363 | + with patch("aleph_client.commands.files.AuthenticatedAlephHttpClient", autospec=True) as mock_client: |
| 364 | + instance = mock_client.return_value |
| 365 | + instance.__aenter__.return_value = instance |
| 366 | + instance.__aexit__.return_value = None |
| 367 | + |
| 368 | + # Build a real account so we can reuse its address |
| 369 | + pkey = get_fallback_private_key() |
| 370 | + account = ETHAccount(private_key=pkey) |
| 371 | + instance.account = account |
| 372 | + |
| 373 | + async def create_store(file_content, *args, **kwargs): |
| 374 | + file_hash = hashlib.sha256(file_content).hexdigest() |
| 375 | + |
| 376 | + sender = account.get_address() |
| 377 | + content = StoreContent( |
| 378 | + item_type=ItemType("storage"), |
| 379 | + item_hash=ItemHash(file_hash), |
| 380 | + address=sender, |
| 381 | + time=time.time(), |
| 382 | + ) |
| 383 | + |
| 384 | + msg = StoreMessage( |
| 385 | + type=MessageType.store, |
| 386 | + sender=sender, |
| 387 | + chain=Chain.ETH, |
| 388 | + channel="test", |
| 389 | + content=content, |
| 390 | + signature="ababababab", |
| 391 | + item_hash=ItemHash(file_hash), |
| 392 | + time=datetime.now(tz=timezone.utc), |
| 393 | + item_type=ItemType.storage, |
| 394 | + ) |
| 395 | + status = {"status": "success"} |
| 396 | + return msg, status |
| 397 | + |
| 398 | + instance.create_store = AsyncMock(side_effect=create_store) |
| 399 | + |
| 400 | + yield mock_client |
| 401 | + |
| 402 | + |
| 403 | +@pytest.fixture |
| 404 | +def mock_aleph_http_client(): |
| 405 | + """Create a mock for the AlephHttpClient class.""" |
| 406 | + with patch("aleph_client.commands.files.AlephHttpClient", autospec=True) as mock_client: |
| 407 | + instance = mock_client.return_value |
| 408 | + instance.__aenter__.return_value = instance |
| 409 | + instance.__aexit__.return_value = None |
| 410 | + |
| 411 | + # Mock download_file_to_buffer |
| 412 | + async def mock_download_file(*args, **kwargs): |
| 413 | + # Just create a dummy file content |
| 414 | + return b"Test file content" |
| 415 | + |
| 416 | + instance.download_file_to_buffer = AsyncMock(side_effect=mock_download_file) |
| 417 | + instance.download_file_ipfs_to_buffer = AsyncMock(side_effect=mock_download_file) |
| 418 | + |
| 419 | + # Mock get_stored_content |
| 420 | + async def mock_get_stored_content(item_hash, *args, **kwargs): |
| 421 | + return StoredContent( |
| 422 | + hash=item_hash, |
| 423 | + filename=f"{item_hash}.txt", |
| 424 | + url=f"https://api.aleph.im/storage/{item_hash}", |
| 425 | + ) |
| 426 | + |
| 427 | + instance.get_stored_content = AsyncMock(side_effect=mock_get_stored_content) |
| 428 | + |
| 429 | + yield mock_client |
| 430 | + |
| 431 | + |
| 432 | +@pytest.fixture |
| 433 | +def mock_aiohttp_client_session(): |
| 434 | + """Create a mock for the aiohttp.ClientSession.""" |
| 435 | + with patch("aiohttp.ClientSession") as mock_session: |
| 436 | + instance = mock_session.return_value |
| 437 | + instance.__aenter__.return_value = instance |
| 438 | + instance.__aexit__.return_value = None |
| 439 | + |
| 440 | + # Create a mock response |
| 441 | + mock_response = AsyncMock() |
| 442 | + mock_response.status = 200 |
| 443 | + |
| 444 | + # Create example file listing data |
| 445 | + files_data = { |
| 446 | + "files": [ |
| 447 | + { |
| 448 | + "file_hash": "QmeomffUNfmQy76CQGy9NdmqEnnHU9soCexBnGU3ezPHVH", |
| 449 | + "size": "1024", |
| 450 | + "type": "file", |
| 451 | + "created": "2025-08-01T12:00:00.000000+00:00", |
| 452 | + "item_hash": FAKE_STORE_HASH, |
| 453 | + } |
| 454 | + ], |
| 455 | + "pagination_page": 1, |
| 456 | + "pagination_total": 1, |
| 457 | + "pagination_per_page": 100, |
| 458 | + "address": "0xe0aaF578B287de16852dbc54Ae34a263FF2F4b9E", |
| 459 | + "total_size": "1024", |
| 460 | + } |
| 461 | + |
| 462 | + async def mock_json(): |
| 463 | + return files_data |
| 464 | + |
| 465 | + mock_response.json = AsyncMock(side_effect=mock_json) |
| 466 | + instance.get = AsyncMock(return_value=mock_response) |
| 467 | + |
| 468 | + yield mock_session |
0 commit comments