Skip to content

Commit 50fb1c5

Browse files
nesitor1yam
andauthored
Solve CLI failing tests (#392)
* Fix: Solve CLI test removing real calls to staging CCN, instead mock the SDK calls. * Fix: mock pricing aggregate instead of doing HTTP call * fix: ignore mypy error (caused by mock of the http request) * fix: pricing unit test * fix: mocked http request on test_instances * feat: unit test new fixture and some some mocked data (pricing / settings) * fix: mocked download and upload commands * fix: linting issue * fix: last issue * unit: coverage increase by adding failing case * fix: lint issue --------- Co-authored-by: 1yam <[email protected]>
1 parent 27fce58 commit 50fb1c5

File tree

8 files changed

+900
-37
lines changed

8 files changed

+900
-37
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ dependencies = [
9494
type = "virtual"
9595
dependencies = [
9696
"pytest==8.2.2",
97+
"pytest-mock==3.14.0",
9798
"pytest-asyncio==0.23.7",
9899
"pytest-cov==5.0.0",
99100
"mypy==1.10.0",

tests/unit/conftest.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@
66
https://pytest.org/latest/plugins.html
77
"""
88

9+
import hashlib
10+
import json
11+
import time
912
from collections.abc import Generator
13+
from datetime import datetime, timezone
1014
from pathlib import Path
1115
from tempfile import NamedTemporaryFile
16+
from unittest.mock import AsyncMock, patch
1217

1318
import pytest
1419
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
1524

1625
from aleph_client.models import CRNInfo
1726

@@ -25,6 +34,7 @@
2534
FAKE_CRN_GPU_ADDRESS,
2635
FAKE_CRN_GPU_HASH,
2736
FAKE_CRN_GPU_URL,
37+
FAKE_STORE_HASH,
2838
)
2939

3040

@@ -259,3 +269,200 @@ def mock_crn_list():
259269
def mock_crn_info(mock_crn_list):
260270
"""Create a mock CRNInfo object."""
261271
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

Comments
 (0)