Skip to content

Commit 86fe5bc

Browse files
committed
chore(nft): use internal calls for simplehash instead of redirecting
1 parent 83f31f7 commit 86fe5bc

File tree

3 files changed

+186
-23
lines changed

3 files changed

+186
-23
lines changed

app/api/nft/routes.py

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import httpx
44
from fastapi import APIRouter, Path, Query
5-
from fastapi.responses import RedirectResponse
65

76
from app.api.common.models import ChainId
87
from app.api.nft.models import (
@@ -462,16 +461,15 @@ async def get_simplehash_nfts_by_owner(
462461
else list(SimpleHashChain)
463462
)
464463

465-
params = httpx.QueryParams(
466-
wallet_address=wallet_addresses[0],
467-
chain_ids=[_simplehash_chain_to_chain_id(chain) for chain in filtered_chains],
468-
)
469-
470-
if cursor:
471-
params = params.set("page_key", cursor)
464+
# Convert SimpleHash chains to internal chain IDs
465+
chain_ids = [_simplehash_chain_to_chain_id(chain) for chain in filtered_chains]
472466

473-
return RedirectResponse(
474-
url=router.url_path_for("get_nfts_by_owner") + f"?{params}", status_code=307
467+
# Call the internal function directly instead of redirecting
468+
return await get_nfts_by_owner(
469+
wallet_address=wallet_addresses[0],
470+
chain_ids=chain_ids,
471+
page_key=cursor,
472+
page_size=50, # Use default page size
475473
)
476474

477475

@@ -483,11 +481,8 @@ async def get_simplehash_compressed_nft_proof(
483481
..., description="The token address to fetch the proof for"
484482
),
485483
) -> SolanaAssetMerkleProof:
486-
return RedirectResponse(
487-
url=router.url_path_for("get_solana_asset_proof")
488-
+ f"?token_address={token_address}",
489-
status_code=307,
490-
)
484+
# Call the internal function directly instead of redirecting
485+
return await get_solana_asset_proof(token_address=token_address)
491486

492487

493488
@simplehash_router.get("/nfts/assets", response_model=SimpleHashNFTResponse)
@@ -519,8 +514,5 @@ async def get_simplehash_nfts_by_ids(
519514
# For EVM chains: chain.address.token_id -> chain_id.address.token_id
520515
internal_nft_ids.append(f"{chain_id}.{parts[1]}.{parts[2]}")
521516

522-
return RedirectResponse(
523-
url=router.url_path_for("get_nfts_by_ids")
524-
+ f"?ids={','.join(internal_nft_ids)}",
525-
status_code=307,
526-
)
517+
# Call the internal function directly instead of redirecting
518+
return await get_nfts_by_ids(ids=",".join(internal_nft_ids))

app/api/nft/test_routes.py

Lines changed: 173 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,23 @@ def mock_settings(monkeypatch):
8585
@pytest.fixture
8686
def mock_httpx_client(monkeypatch):
8787
mock_client = AsyncMock()
88-
mock_client.get = AsyncMock()
89-
mock_client.post = AsyncMock()
88+
89+
# Create mock response objects that return actual values, not coroutines
90+
mock_get_response = Mock()
91+
mock_post_response = Mock()
92+
93+
# Set up the mock responses to return actual values, not coroutines
94+
mock_get_response.status_code = 200
95+
mock_get_response.json.return_value = {}
96+
mock_get_response.raise_for_status.return_value = None
97+
98+
mock_post_response.status_code = 200
99+
mock_post_response.json.return_value = {}
100+
mock_post_response.raise_for_status.return_value = None
101+
102+
# Configure the client methods
103+
mock_client.get.return_value = mock_get_response
104+
mock_client.post.return_value = mock_post_response
90105

91106
# Create a mock context manager
92107
mock_context = AsyncMock()
@@ -203,3 +218,159 @@ def test_get_solana_asset_proof_error(mock_httpx_client):
203218
client.get("/api/nft/v1/getSolanaAssetProof?token_address=invalid_token")
204219

205220
assert str(e.value) == "Alchemy API error: Token not found"
221+
222+
223+
def test_get_simplehash_nfts_by_owner(mock_httpx_client, mock_settings):
224+
mock_response = {
225+
"ownedNfts": [MOCK_NFT_ALCHEMY_RESPONSE],
226+
"totalCount": 1,
227+
"pageKey": None,
228+
}
229+
230+
# Configure the mock response
231+
mock_httpx_client.get.return_value.json.return_value = mock_response
232+
233+
response = client.get(
234+
"/simplehash/api/v0/nfts/owners?wallet_addresses=0x123&chains=ethereum"
235+
)
236+
assert response.status_code == 200
237+
data = response.json()
238+
239+
sh_response = SimpleHashNFTResponse.model_validate(data)
240+
assert len(sh_response.nfts) == 1
241+
nft = sh_response.nfts[0]
242+
assert nft.chain == "ethereum"
243+
assert nft.contract_address == "0x123"
244+
assert nft.token_id == "1"
245+
assert nft.name == "Mock NFT #1"
246+
assert nft.description == "A mock NFT description"
247+
assert nft.image_url == "https://example.com/cached.jpg"
248+
assert nft.background_color is None
249+
assert nft.external_url is None
250+
assert nft.contract.type == "ERC721"
251+
assert nft.contract.name == "MockNFT"
252+
assert nft.contract.symbol == "MOCK"
253+
assert nft.collection.name == "MockNFT"
254+
assert nft.collection.spam_score == 0
255+
attributes = nft.extra_metadata.attributes
256+
assert len(attributes) == 2
257+
assert attributes[0].trait_type == "Color"
258+
assert attributes[0].value == "Red"
259+
assert attributes[1].trait_type == "Shape"
260+
assert attributes[1].value == "Round"
261+
262+
263+
def test_get_simplehash_nfts_by_owner_multiple_chains(mock_httpx_client, mock_settings):
264+
mock_response = {
265+
"ownedNfts": [MOCK_NFT_ALCHEMY_RESPONSE],
266+
"totalCount": 1,
267+
"pageKey": None,
268+
}
269+
270+
mock_httpx_client.get.return_value.json.return_value = mock_response
271+
272+
response = client.get(
273+
"/simplehash/api/v0/nfts/owners?wallet_addresses=0x123&chains=ethereum,polygon"
274+
)
275+
assert response.status_code == 200
276+
data = response.json()
277+
sh_response = SimpleHashNFTResponse.model_validate(data)
278+
# Should get 2 NFTs - one from Ethereum and one from Polygon
279+
assert len(sh_response.nfts) == 2
280+
281+
282+
def test_get_simplehash_nfts_by_owner_with_cursor(mock_httpx_client, mock_settings):
283+
mock_response = {
284+
"ownedNfts": [MOCK_NFT_ALCHEMY_RESPONSE],
285+
"totalCount": 1,
286+
"pageKey": "next_page_key",
287+
}
288+
289+
mock_httpx_client.get.return_value.json.return_value = mock_response
290+
291+
response = client.get(
292+
"/simplehash/api/v0/nfts/owners?wallet_addresses=0x123&chains=ethereum&cursor=page123"
293+
)
294+
assert response.status_code == 200
295+
data = response.json()
296+
sh_response = SimpleHashNFTResponse.model_validate(data)
297+
assert len(sh_response.nfts) == 1
298+
assert sh_response.next_cursor == "next_page_key"
299+
300+
301+
def test_get_simplehash_compressed_nft_proof(mock_httpx_client, mock_settings):
302+
mock_response = {
303+
"result": {
304+
"proof": ["hash1", "hash2", "hash3"],
305+
"root": "root_hash",
306+
"tree_id": "tree_123",
307+
"node_index": 42,
308+
"leaf": "leaf_hash",
309+
"status": "finalized",
310+
},
311+
"error": None,
312+
}
313+
314+
mock_httpx_client.post.return_value.json.return_value = mock_response
315+
316+
response = client.get("/simplehash/api/v0/nfts/proof/solana/mint123")
317+
assert response.status_code == 200
318+
data = response.json()
319+
sh_response = SolanaAssetMerkleProof.model_validate(data)
320+
assert sh_response.root == "root_hash"
321+
assert sh_response.tree_id == "tree_123"
322+
assert sh_response.node_index == 42
323+
assert sh_response.leaf == "leaf_hash"
324+
assert sh_response.proof == ["hash1", "hash2", "hash3"]
325+
326+
327+
def test_get_simplehash_nfts_by_ids_solana(mock_httpx_client, mock_settings):
328+
mock_solana_asset = {
329+
"id": "mint123",
330+
"interface": "ProgrammableNFT",
331+
"content": {
332+
"metadata": {
333+
"name": "Mock Solana NFT",
334+
"symbol": "MSN",
335+
"description": "A mock Solana NFT",
336+
"attributes": [],
337+
},
338+
"links": {
339+
"image": "https://example.com/solana-image.jpg",
340+
"external_url": "https://example.com",
341+
},
342+
"json_uri": "https://example.com/metadata/solana.json",
343+
},
344+
"grouping": [],
345+
"mutable": False,
346+
"burnt": False,
347+
}
348+
349+
mock_response = {
350+
"result": [mock_solana_asset],
351+
}
352+
353+
mock_httpx_client.post.return_value.json.return_value = mock_response
354+
355+
response = client.get("/simplehash/api/v0/nfts/assets?nft_ids=solana.mint123")
356+
assert response.status_code == 200
357+
data = response.json()
358+
sh_response = SimpleHashNFTResponse.model_validate(data)
359+
assert len(sh_response.nfts) == 1
360+
361+
362+
def test_get_simplehash_nfts_by_ids(mock_httpx_client, mock_settings):
363+
mock_response = {
364+
"nfts": [MOCK_NFT_ALCHEMY_RESPONSE],
365+
}
366+
367+
mock_httpx_client.post.return_value.json.return_value = mock_response
368+
369+
response = client.get(
370+
"/simplehash/api/v0/nfts/assets?nft_ids=ethereum.0x123.456,polygon.0x789.101112"
371+
)
372+
assert response.status_code == 200
373+
data = response.json()
374+
sh_response = SimpleHashNFTResponse.model_validate(data)
375+
# Should get 2 NFTs - one from Ethereum and one from Polygon
376+
assert len(sh_response.nfts) == 2

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "gate3"
3-
version = "0.3.1"
3+
version = "0.4.0"
44
description = "Gate API for web3 applications at Brave"
55
authors = [
66
]

0 commit comments

Comments
 (0)