diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex index 15a757065f..a0e96df02a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex @@ -1,8 +1,10 @@ defmodule BlockScoutWeb.AddressTokenTransferController do use BlockScoutWeb, :controller - alias Explorer.{Chain, Market} + alias BlockScoutWeb.TransactionView alias Explorer.ExchangeRates.Token + alias Explorer.{Chain, Market} + alias Phoenix.View import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1] @@ -11,12 +13,16 @@ defmodule BlockScoutWeb.AddressTokenTransferController do def index( conn, - %{"address_id" => address_hash_string, "address_token_id" => token_hash_string} = params + %{ + "address_id" => address_hash_string, + "address_token_id" => token_hash_string, + "type" => "JSON" + } = params ) do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), {:ok, token_hash} <- Chain.string_to_address_hash(token_hash_string), {:ok, address} <- Chain.hash_to_address(address_hash), - {:ok, token} <- Chain.token_from_address_hash(token_hash) do + {:ok, _} <- Chain.token_from_address_hash(token_hash) do transactions = Chain.address_to_transactions_with_token_transfers( address_hash, @@ -26,15 +32,58 @@ defmodule BlockScoutWeb.AddressTokenTransferController do {transactions_paginated, next_page} = split_list_by_page(transactions) + next_page_path = + case next_page_params(next_page, transactions_paginated, params) do + nil -> + nil + + next_page_params -> + address_token_transfers_path( + conn, + :index, + address_hash_string, + token_hash_string, + Map.delete(next_page_params, "type") + ) + end + + transfers_json = + Enum.map(transactions_paginated, fn transaction -> + View.render_to_string( + TransactionView, + "_tile.html", + conn: conn, + transaction: transaction, + current_address: address + ) + end) + + json(conn, %{items: transfers_json, next_page_path: next_page_path}) + else + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def index( + conn, + %{"address_id" => address_hash_string, "address_token_id" => token_hash_string} + ) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, token_hash} <- Chain.string_to_address_hash(token_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash), + {:ok, token} <- Chain.token_from_address_hash(token_hash) do render( conn, "index.html", address: address, exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), - next_page_params: next_page_params(next_page, transactions_paginated, params), + current_path: current_path(conn), token: token, transaction_count: transaction_count(address), - transactions: transactions_paginated, validation_count: validation_count(address) ) else diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs index aeb32b06ac..0e9379625f 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs @@ -1,7 +1,8 @@ defmodule BlockScoutWeb.AddressTokenTransferControllerTest do use BlockScoutWeb.ConnCase - import BlockScoutWeb.Router.Helpers, only: [address_token_transfers_path: 4] + import BlockScoutWeb.Router.Helpers, + only: [address_token_transfers_path: 4, address_token_transfers_path: 5] alias Explorer.Chain.{Address, Token} @@ -15,6 +16,7 @@ defmodule BlockScoutWeb.AddressTokenTransferControllerTest do test "with invalid token hash", %{conn: conn} do address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + conn = get(conn, address_token_transfers_path(conn, :index, address_hash, "invalid_address")) assert html_response(conn, 422) @@ -35,46 +37,26 @@ defmodule BlockScoutWeb.AddressTokenTransferControllerTest do assert html_response(conn, 404) end + end + describe "GET index/2 JSON" do test "without token transfers for a token", %{conn: conn} do %Address{hash: address_hash} = insert(:address) %Token{contract_address_hash: token_hash} = insert(:token) - conn = get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash)) - - assert html_response(conn, 200) - assert conn.assigns.transactions == [] - end - - test "returns the transactions that have token transfers for the given address and token", %{conn: conn} do - address = insert(:address) - token = insert(:token) - - transaction = - :transaction - |> insert() - |> with_block() - - insert( - :token_transfer, - to_address: address, - transaction: transaction, - token_contract_address: token.contract_address - ) - - conn = get(conn, address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash)) - - transaction_hashes = Enum.map(conn.assigns.transactions, & &1.hash) + conn = + get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash), %{ + type: "JSON" + }) - assert html_response(conn, 200) - assert transaction_hashes == [transaction.hash] + assert json_response(conn, 200) == %{"items" => [], "next_page_path" => nil} end - test "returns next page of results based on last seen transactions", %{conn: conn} do + test "returns correct next_page_path", %{conn: conn} do address = insert(:address) token = insert(:token) - second_page_transactions = + page_last_transfer = 1..50 |> Enum.map(fn index -> block = insert(:block, number: 1000 - index) @@ -93,22 +75,84 @@ defmodule BlockScoutWeb.AddressTokenTransferControllerTest do transaction end) - |> Enum.map(& &1.hash) + |> List.last() + + Enum.each(51..60, fn index -> + block = insert(:block, number: 1000 - index) + + transaction = + :transaction + |> insert() + |> with_block(block) + + insert( + :token_transfer, + to_address: address, + transaction: transaction, + token_contract_address: token.contract_address + ) + end) + + conn = + get( + conn, + address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash), + %{type: "JSON"} + ) + + expected_path = + address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash, %{ + block_number: page_last_transfer.block_number, + index: page_last_transfer.index + }) + + assert Map.get(json_response(conn, 200), "next_page_path") == expected_path + end + + test "with invalid address hash", %{conn: conn} do + token_hash = "0xc8982771dd50285389c352c175ada74d074427c7" + + conn = + get(conn, address_token_transfers_path(conn, :index, "invalid_address", token_hash), %{ + type: "JSON" + }) + + assert html_response(conn, 422) + end + + test "with invalid token hash", %{conn: conn} do + address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + + conn = + get(conn, address_token_transfers_path(conn, :index, address_hash, "invalid_address"), %{ + type: "JSON" + }) + + assert html_response(conn, 422) + end - transaction = - :transaction - |> insert() - |> with_block(insert(:block, number: 1002)) + test "with an address that doesn't exist in our database", %{conn: conn} do + address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + %Token{contract_address_hash: token_hash} = insert(:token) conn = - get(conn, address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash), %{ - "block_number" => Integer.to_string(transaction.block_number), - "index" => Integer.to_string(transaction.index) + get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash), %{ + type: "JSON" }) - actual_transactions = Enum.map(conn.assigns.transactions, & &1.hash) + assert html_response(conn, 404) + end - assert second_page_transactions == actual_transactions + test "with a token that doesn't exist in our database", %{conn: conn} do + %Address{hash: address_hash} = insert(:address) + token_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + + conn = + get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash), %{ + type: "JSON" + }) + + assert html_response(conn, 404) end end end