require "test_helper" class CoinstatsItemsControllerTest < ActionDispatch::IntegrationTest setup do sign_in users(:family_admin) @family = families(:dylan_family) @coinstats_item = CoinstatsItem.create!( family: @family, name: "Test CoinStats Connection", api_key: "test_api_key_123" ) tailwind_build = Rails.root.join("app/assets/builds/tailwind.css") FileUtils.mkdir_p(tailwind_build.dirname) File.write(tailwind_build, "/* test */") unless tailwind_build.exist? end # Helper to wrap data in Provider::Response def success_response(data) Provider::Response.new(success?: true, data: data, error: nil) end def error_response(message) Provider::Response.new(success?: false, data: nil, error: Provider::Error.new(message)) end test "should get new" do get new_coinstats_item_url assert_response :success end test "should create coinstats item with valid api key" do # Mock the API key validation Provider::Coinstats.any_instance.expects(:get_blockchains).returns(success_response([])).once assert_difference("CoinstatsItem.count", 1) do post coinstats_items_url, params: { coinstats_item: { name: "New CoinStats Connection", api_key: "valid_api_key" } } end end test "should not create coinstats item with invalid api key" do # Mock the API key validation to fail Provider::Coinstats.any_instance.expects(:get_blockchains) .returns(error_response("Invalid API key")) assert_no_difference("CoinstatsItem.count") do post coinstats_items_url, params: { coinstats_item: { name: "New CoinStats Connection", api_key: "invalid_api_key" } } end end test "should destroy coinstats item" do # Schedules for deletion, doesn't actually delete immediately assert_no_difference("CoinstatsItem.count") do delete coinstats_item_url(@coinstats_item) end assert_redirected_to settings_providers_path @coinstats_item.reload assert @coinstats_item.scheduled_for_deletion? end test "should sync coinstats item" do post sync_coinstats_item_url(@coinstats_item) assert_redirected_to accounts_path end test "sync responds to json format" do post sync_coinstats_item_url(@coinstats_item, format: :json) assert_response :ok end test "should update coinstats item with valid api key" do Provider::Coinstats.any_instance.expects(:get_blockchains).returns(success_response([])).once patch coinstats_item_url(@coinstats_item), params: { coinstats_item: { name: "Updated Name", api_key: "new_valid_api_key" } } @coinstats_item.reload assert_equal "Updated Name", @coinstats_item.name end test "should not update coinstats item with invalid api key" do Provider::Coinstats.any_instance.expects(:get_blockchains) .returns(error_response("Invalid API key")) original_name = @coinstats_item.name patch coinstats_item_url(@coinstats_item), params: { coinstats_item: { name: "Updated Name", api_key: "invalid_api_key" } } @coinstats_item.reload assert_equal original_name, @coinstats_item.name end test "link_wallet requires all parameters" do post link_wallet_coinstats_items_url, params: { coinstats_item_id: @coinstats_item.id, address: "0x123" # missing blockchain } assert_response :unprocessable_entity end test "link_wallet with valid params creates accounts" do balance_data = [ { coinId: "ethereum", name: "Ethereum", symbol: "ETH", amount: 1.5, price: 2000 } ] bulk_response = [ { blockchain: "ethereum", address: "0x123abc", connectionId: "ethereum", balances: balance_data } ] Provider::Coinstats.any_instance.expects(:get_wallet_balances) .with("ethereum:0x123abc") .returns(success_response(bulk_response)) Provider::Coinstats.any_instance.expects(:extract_wallet_balance) .with(bulk_response, "0x123abc", "ethereum") .returns(balance_data) assert_difference("Account.count", 1) do assert_difference("CoinstatsAccount.count", 1) do post link_wallet_coinstats_items_url, params: { coinstats_item_id: @coinstats_item.id, address: "0x123abc", blockchain: "ethereum" } end end assert_redirected_to accounts_path end test "link_wallet handles provider errors" do Provider::Coinstats.any_instance.expects(:get_wallet_balances) .raises(Provider::Coinstats::Error.new("Invalid API key")) post link_wallet_coinstats_items_url, params: { coinstats_item_id: @coinstats_item.id, address: "0x123abc", blockchain: "ethereum" } assert_response :unprocessable_entity end test "link_wallet handles no tokens found" do Provider::Coinstats.any_instance.expects(:get_wallet_balances) .returns(success_response([])) Provider::Coinstats.any_instance.expects(:extract_wallet_balance) .returns([]) post link_wallet_coinstats_items_url, params: { coinstats_item_id: @coinstats_item.id, address: "0x123abc", blockchain: "ethereum" } assert_response :unprocessable_entity assert_match(/No tokens found/, response.body) end test "link_exchange filters unexpected connection fields" do Provider::Coinstats.any_instance.expects(:get_exchanges).returns(success_response([ { connectionId: "bitvavo", name: "Bitvavo", connectionFields: [ { key: "apiKey", name: "API Key" }, { key: "apiSecret", name: "API Secret" } ] } ])).once linker_result = CoinstatsItem::ExchangeLinker::Result.new(success?: true, created_count: 0, errors: []) CoinstatsItem::ExchangeLinker.expects(:new).with( @coinstats_item, connection_id: "bitvavo", connection_fields: { "apiKey" => "key", "apiSecret" => "secret" }, name: "Bitvavo" ).returns(stub(link: linker_result)) post link_exchange_coinstats_items_url, params: { coinstats_item_id: @coinstats_item.id, exchange_connection_id: "bitvavo", exchange_connection_name: "Bitvavo", connection_fields: { apiKey: " key ", apiSecret: " secret ", unexpected: "should_not_be_forwarded" } } assert_redirected_to accounts_path end end