package org.stellar.sdk;

import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.Test;
import org.stellar.sdk.requests.ErrorResponse;
import org.stellar.sdk.responses.Page;
import org.stellar.sdk.responses.SubmitTransactionResponse;
import org.stellar.sdk.responses.SubmitTransactionTimeoutResponseException;
import org.stellar.sdk.responses.SubmitTransactionUnknownResponseException;
import org.stellar.sdk.responses.operations.OperationResponse;
import org.stellar.sdk.xdr.EnvelopeType;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.*;

public class ServerTest {
    private final String publicRootResponse = "{\n" +
            "  \"_links\": {\n" +
            "    \"account\": {\n" +
            "      \"href\": \"https://horizon.stellar.org/accounts/{account_id}\",\n" +
            "      \"templated\": true\n" +
            "    },\n" +
            "    \"account_transactions\": {\n" +
            "      \"href\": \"https://horizon.stellar.org/accounts/{account_id}/transactions{?cursor,limit,order}\",\n" +
            "      \"templated\": true\n" +
            "    },\n" +
            "    \"assets\": {\n" +
            "      \"href\": \"https://horizon.stellar.org/assets{?asset_code,asset_issuer,cursor,limit,order}\",\n" +
            "      \"templated\": true\n" +
            "    },\n" +
            "    \"metrics\": {\n" +
            "      \"href\": \"https://horizon.stellar.org/metrics\"\n" +
            "    },\n" +
            "    \"order_book\": {\n" +
            "      \"href\": \"https://horizon.stellar.org/order_book{?selling_asset_type,selling_asset_code,selling_asset_issuer,buying_asset_type,buying_asset_code,buying_asset_issuer,limit}\",\n" +
            "      \"templated\": true\n" +
            "    },\n" +
            "    \"self\": {\n" +
            "      \"href\": \"https://horizon.stellar.org/\"\n" +
            "    },\n" +
            "    \"transaction\": {\n" +
            "      \"href\": \"https://horizon.stellar.org/transactions/{hash}\",\n" +
            "      \"templated\": true\n" +
            "    },\n" +
            "    \"transactions\": {\n" +
            "      \"href\": \"https://horizon.stellar.org/transactions{?cursor,limit,order}\",\n" +
            "      \"templated\": true\n" +
            "    }\n" +
            "  },\n" +
            "  \"horizon_version\": \"0.18.0-92259749c681df66a8347f846e94681a24f2a920\",\n" +
            "  \"core_version\": \"stellar-core 11.1.0 (324c1bd61b0e9bada63e0d696d799421b00a7950)\",\n" +
            "  \"history_latest_ledger\": 24345129,\n" +
            "  \"history_elder_ledger\": 1,\n" +
            "  \"core_latest_ledger\": 24345130,\n" +
            "  \"network_passphrase\": \"Public Global Stellar Network ; September 2015\",\n" +
            "  \"current_protocol_version\": 11,\n" +
            "  \"core_supported_protocol_version\": 11\n" +
            "}";
    private final String successResponse =
            "{\n" +
            "  \"_links\": {\n" +
            "    \"transaction\": {\n" +
            "      \"href\": \"/transactions/2634d2cf5adcbd3487d1df042166eef53830115844fdde1588828667bf93ff42\"\n" +
            "    }\n" +
            "  },\n" +
            "  \"hash\": \"2634d2cf5adcbd3487d1df042166eef53830115844fdde1588828667bf93ff42\",\n" +
            "  \"ledger\": 826150,\n" +
            "  \"envelope_xdr\": \"AAAAAKu3N77S+cHLEDfVD2eW/CqRiN9yvAKH+qkeLjHQs1u+AAAAZAAMkoMAAAADAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAbYQq8ek1GitmNBUloGnetfWxSpxlsgK48Xi66dIL3MoAAAAAC+vCAAAAAAAAAAAB0LNbvgAAAEDadQ25SNHWTg0L+2wr/KNWd8/EwSNFkX/ncGmBGA3zkNGx7lAow78q8SQmnn2IsdkD9MwICirhsOYDNbaqShwO\",\n" +
            "  \"result_xdr\": \"AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA=\",\n" +
            "  \"result_meta_xdr\": \"AAAAAAAAAAEAAAACAAAAAAAMmyYAAAAAAAAAAG2EKvHpNRorZjQVJaBp3rX1sUqcZbICuPF4uunSC9zKAAAAAAvrwgAADJsmAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAMmyYAAAAAAAAAAKu3N77S+cHLEDfVD2eW/CqRiN9yvAKH+qkeLjHQs1u+AAAAFzCfYtQADJKDAAAAAwAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA\"\n" +
            "}";

    private final String failureResponse =
            "{\n" +
            "  \"type\": \"https://stellar.org/horizon-errors/transaction_failed\",\n" +
            "  \"title\": \"Transaction Failed\",\n" +
            "  \"status\": 400,\n" +
            "  \"detail\": \"TODO\",\n" +
            "  \"instance\": \"horizon-testnet-001.prd.stellar001.internal.stellar-ops.com/IxhaI70Tqo-112305\",\n" +
            "  \"extras\": {\n" +
            "    \"envelope_xdr\": \"AAAAAK4Pg4OEkjGmSN0AN37K/dcKyKPT2DC90xvjjawKp136AAAAZAAKsZQAAAABAAAAAAAAAAEAAAAJSmF2YSBGVFchAAAAAAAAAQAAAAAAAAABAAAAAG9wfBI7rRYoBlX3qRa0KOnI75W5BaPU6NbyKmm2t71MAAAAAAAAAAABMS0AAAAAAAAAAAEKp136AAAAQOWEjL+Sm+WP2puE9dLIxWlOibIEOz8PsXyG77jOCVdHZfQvkgB49Mu5wqKCMWWIsDSLFekwUsLaunvmXrpyBwQ=\",\n" +
            "    \"result_codes\": {\n" +
            "      \"transaction\": \"tx_failed\",\n" +
            "      \"operations\": [\n" +
            "        \"op_no_destination\"\n" +
            "      ]\n" +
            "    },\n" +
            "    \"result_xdr\": \"AAAAAAAAAGT/////AAAAAQAAAAAAAAAB////+wAAAAA=\"\n" +
            "  }\n" +
            "}";

    private final String timeoutResponse =
            "{\n" +
            "  \"type\": \"https://stellar.org/horizon-errors/transaction_failed\",\n" +
            "  \"title\": \"Timeout\",\n" +
            "  \"status\": 403,\n" +
            "  \"detail\": \"TODO\",\n" +
            "  \"instance\": \"horizon-testnet-001.prd.stellar001.internal.stellar-ops.com/IxhaI70Tqo-112305\"\n" +
            "}";

    private final String internalServerErrorResponse =
            "{\n" +
            "  \"type\":     \"https://www.stellar.org/docs/horizon/problems/server_error\",\n" +
            "  \"title\":    \"Internal Server Error\",\n" +
            "  \"status\":   500,\n" +
            "  \"instance\": \"d3465740-ec3a-4a0b-9d4a-c9ea734ce58a\"\n" +
            "}";

    private final String operationsPageResponse = "{\n" +
            "  \"_links\": {\n" +
            "    \"self\": {\n" +
            "      \"href\": \"http://horizon-testnet.stellar.org/operations?order=desc\\u0026limit=10\\u0026cursor=\"\n" +
            "    },\n" +
            "    \"next\": {\n" +
            "      \"href\": \"http://horizon-testnet.stellar.org/operations?order=desc\\u0026limit=10\\u0026cursor=3695540185337857\"\n" +
            "    },\n" +
            "    \"prev\": {\n" +
            "      \"href\": \"http://horizon-testnet.stellar.org/operations?order=asc\\u0026limit=10\\u0026cursor=3717508943056897\"\n" +
            "    }\n" +
            "  },\n" +
            "  \"_embedded\": {\n" +
            "    \"records\": [\n" +
            "      {\n" +
            "        \"_links\": {\n" +
            "          \"self\": {\n" +
            "            \"href\": \"http://horizon-testnet.stellar.org/operations/3717508943056897\"\n" +
            "          },\n" +
            "          \"transaction\": {\n" +
            "            \"href\": \"http://horizon-testnet.stellar.org/transactions/ce81d957352501a46d9b938462cbef76283dcba8108d2649e0d79279a8f36488\"\n" +
            "          },\n" +
            "          \"effects\": {\n" +
            "            \"href\": \"http://horizon-testnet.stellar.org/operations/3717508943056897/effects\"\n" +
            "          },\n" +
            "          \"succeeds\": {\n" +
            "            \"href\": \"http://horizon-testnet.stellar.org/effects?order=desc\\u0026cursor=3717508943056897\"\n" +
            "          },\n" +
            "          \"precedes\": {\n" +
            "            \"href\": \"http://horizon-testnet.stellar.org/effects?order=asc\\u0026cursor=3717508943056897\"\n" +
            "          }\n" +
            "        },\n" +
            "        \"id\": \"3717508943056897\",\n" +
            "        \"paging_token\": \"3717508943056897\",\n" +
            "        \"source_account\": \"GBS43BF24ENNS3KPACUZVKK2VYPOZVBQO2CISGZ777RYGOPYC2FT6S3K\",\n" +
            "        \"type\": \"create_account\",\n" +
            "        \"type_i\": 0,\n" +
            "        \"created_at\": \"2018-01-22T21:30:53Z\",\n" +
            "        \"transaction_hash\": \"dd9d10c80a344f4464df3ecaa63705a5ef4a0533ff2f2099d5ef371ab5e1c046\","+
            "        \"starting_balance\": \"10000.0\",\n" +
            "        \"funder\": \"GBS43BF24ENNS3KPACUZVKK2VYPOZVBQO2CISGZ777RYGOPYC2FT6S3K\",\n" +
            "        \"account\": \"GDFH4NIYMIIAKRVEJJZOIGWKXGQUF3XHJG6ZM6CEA64AMTVDN44LHOQE\"\n" +
            "      }\n"+
            "    ]\n" +
            "  }\n" +
            "}";

    Transaction buildTransaction() throws IOException {
        return buildTransaction(Network.PUBLIC);
    }

    Transaction buildTransaction(Network network) throws IOException {
        // GBPMKIRA2OQW2XZZQUCQILI5TMVZ6JNRKM423BSAISDM7ZFWQ6KWEBC4
        KeyPair source = KeyPair.fromSecretSeed("SCH27VUZZ6UAKB67BDNF6FA42YMBMQCBKXWGMFD5TZ6S5ZZCZFLRXKHS");
        KeyPair destination = KeyPair.fromAccountId("GDW6AUTBXTOC7FIKUO5BOO3OGLK4SF7ZPOBLMQHMZDI45J2Z6VXRB5NR");

        Account account = new Account(source.getAccountId(), 2908908335136768L);
        Transaction.Builder builder = new Transaction.Builder(account, network)
                .addOperation(new CreateAccountOperation.Builder(destination.getAccountId(), "2000").build())
                .addMemo(Memo.text("Hello world!"))
                .setBaseFee(Transaction.MIN_BASE_FEE)
                .setTimeout(Transaction.Builder.TIMEOUT_INFINITE);

        assertEquals(1, builder.getOperationsCount());
        Transaction transaction = builder.build();
        assertEquals(2908908335136769L, transaction.getSequenceNumber());
        assertEquals(new Long(2908908335136769L), account.getSequenceNumber());
        transaction.sign(source);
        return transaction;
    }

    @Test
    public void testSubmitTransactionNetworkMisMatch() throws IOException, AccountRequiresMemoException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(publicRootResponse));
        mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(successResponse));
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        try {
            server.submitTransaction(this.buildTransaction(Network.TESTNET), true);
            fail("expected NetworkMismatchException exception");
        } catch (NetworkMismatchException e) {
            // expect exception
        }
    }

    @Test
    public void testSubmitTransactionSuccess() throws IOException, AccountRequiresMemoException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(publicRootResponse));
        mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(successResponse));
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        SubmitTransactionResponse response = server.submitTransaction(this.buildTransaction(), true);
        assertTrue(response.isSuccess());
        assertEquals(response.getLedger(), new Long(826150L));
        assertEquals(response.getHash(), "2634d2cf5adcbd3487d1df042166eef53830115844fdde1588828667bf93ff42");
        assertEquals(response.getEnvelopeXdr().get(), "AAAAAKu3N77S+cHLEDfVD2eW/CqRiN9yvAKH+qkeLjHQs1u+AAAAZAAMkoMAAAADAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAbYQq8ek1GitmNBUloGnetfWxSpxlsgK48Xi66dIL3MoAAAAAC+vCAAAAAAAAAAAB0LNbvgAAAEDadQ25SNHWTg0L+2wr/KNWd8/EwSNFkX/ncGmBGA3zkNGx7lAow78q8SQmnn2IsdkD9MwICirhsOYDNbaqShwO");
        assertEquals(response.getResultXdr().get(), "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA=");
        assertNull(response.getExtras());
    }

    @Test
    public void testSubmitTransactionFail() throws IOException, AccountRequiresMemoException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(publicRootResponse));
        mockWebServer.enqueue(new MockResponse().setResponseCode(400).setBody(failureResponse));
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        SubmitTransactionResponse response = server.submitTransaction(this.buildTransaction(), true);
        assertFalse(response.isSuccess());
        assertNull(response.getLedger());
        assertNull(response.getHash());
        assertEquals(response.getExtras().getEnvelopeXdr(), "AAAAAK4Pg4OEkjGmSN0AN37K/dcKyKPT2DC90xvjjawKp136AAAAZAAKsZQAAAABAAAAAAAAAAEAAAAJSmF2YSBGVFchAAAAAAAAAQAAAAAAAAABAAAAAG9wfBI7rRYoBlX3qRa0KOnI75W5BaPU6NbyKmm2t71MAAAAAAAAAAABMS0AAAAAAAAAAAEKp136AAAAQOWEjL+Sm+WP2puE9dLIxWlOibIEOz8PsXyG77jOCVdHZfQvkgB49Mu5wqKCMWWIsDSLFekwUsLaunvmXrpyBwQ=");
        assertEquals(response.getEnvelopeXdr().get(), "AAAAAK4Pg4OEkjGmSN0AN37K/dcKyKPT2DC90xvjjawKp136AAAAZAAKsZQAAAABAAAAAAAAAAEAAAAJSmF2YSBGVFchAAAAAAAAAQAAAAAAAAABAAAAAG9wfBI7rRYoBlX3qRa0KOnI75W5BaPU6NbyKmm2t71MAAAAAAAAAAABMS0AAAAAAAAAAAEKp136AAAAQOWEjL+Sm+WP2puE9dLIxWlOibIEOz8PsXyG77jOCVdHZfQvkgB49Mu5wqKCMWWIsDSLFekwUsLaunvmXrpyBwQ=");
        assertEquals(response.getExtras().getResultXdr(), "AAAAAAAAAGT/////AAAAAQAAAAAAAAAB////+wAAAAA=");
        assertEquals(response.getResultXdr().get(), "AAAAAAAAAGT/////AAAAAQAAAAAAAAAB////+wAAAAA=");
        assertNotNull(response.getExtras());
        assertEquals("tx_failed", response.getExtras().getResultCodes().getTransactionResultCode());
        assertEquals("op_no_destination", response.getExtras().getResultCodes().getOperationsResultCodes().get(0));
    }

    @Test(expected = SubmitTransactionTimeoutResponseException.class)
    public void testSubmitTransactionTimeout() throws IOException, AccountRequiresMemoException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(publicRootResponse));
        mockWebServer.enqueue(new MockResponse().setResponseCode(504).setBody(timeoutResponse).setBodyDelay(5, TimeUnit.SECONDS));
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        // We're creating a new OkHttpClient to make this test faster
        OkHttpClient testSubmitHttpClient = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .retryOnConnectionFailure(false)
                .build();
        server.setSubmitHttpClient(testSubmitHttpClient);

        server.submitTransaction(this.buildTransaction(), true);
    }

    @Test(expected = SubmitTransactionTimeoutResponseException.class)
    public void testSubmitTransactionTimeoutWithoutResponse() throws IOException, AccountRequiresMemoException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(publicRootResponse));
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        // We're creating a new OkHttpClient to make this test faster
        OkHttpClient testSubmitHttpClient = new OkHttpClient.Builder()
                .connectTimeout(1, TimeUnit.SECONDS)
                .readTimeout(1, TimeUnit.SECONDS)
                .retryOnConnectionFailure(false)
                .build();
        server.setSubmitHttpClient(testSubmitHttpClient);

        server.submitTransaction(this.buildTransaction(), true);
    }

    @Test
    public void testSubmitTransactionInternalError() throws IOException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(publicRootResponse));
        mockWebServer.enqueue(new MockResponse().setResponseCode(500).setBody(internalServerErrorResponse));
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());
        // We're creating a new OkHttpClient to make this test faster
        OkHttpClient testSubmitHttpClient = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .retryOnConnectionFailure(false)
                .build();
        server.setSubmitHttpClient(testSubmitHttpClient);

        try {
            server.submitTransaction(this.buildTransaction(), true);
            fail("submitTransaction didn't throw exception");
        } catch (SubmitTransactionUnknownResponseException e) {
            assertEquals(500, e.getCode());
        } catch (Exception e) {
            fail("submitTransaction thrown invalid exception");
        }
    }

    @Test
    public void testNextPage() throws IOException, URISyntaxException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(operationsPageResponse));
        mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(operationsPageResponse));
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        Page<OperationResponse> page = server.operations().execute();
        assertEquals(1, page.getRecords().size());
        assertEquals("dd9d10c80a344f4464df3ecaa63705a5ef4a0533ff2f2099d5ef371ab5e1c046", page.getRecords().get(0).getTransactionHash());
        Page<OperationResponse> nextPage = page.getNextPage(server.getHttpClient());
        assertEquals(1, page.getRecords().size());
    }


    /**
     * The following tests are related to SEP-0029.
     */
    public static final String DESTINATION_ACCOUNT_MEMO_REQUIRED_A = "GCMDQXJJGQE6TJ5XUHJMJUUIWECC5S6VANRAOWIQMMV4ALW43JOY2SEB";
    public static final String DESTINATION_ACCOUNT_MEMO_REQUIRED_B = "GDUR2DMT5AQ7DJUGBIBB45NKRNQXGRJTWTQ7DPRP37EKBELSMK57RMZK";
    public static final String DESTINATION_ACCOUNT_MEMO_REQUIRED_C = "GCS36NBLT6OKYN5EUQOQ7ZZIM6WXXNX5ME4JGTCG3HVZOYXRRMNUHNMM";
    public static final String DESTINATION_ACCOUNT_MEMO_REQUIRED_D = "GAKQNN6GNGNPLYBVEDCD5QAIEHAZVNCQET3HAUR4YWQAP5RPBLU2W7UG";
    public static final String DESTINATION_ACCOUNT_NO_MEMO_REQUIRED = "GDYC2D4P2SRC5DCEDDK2OUFESSPCTZYLDOEF6NYHR2T7X5GUTEABCQC2";
    public static final String DESTINATION_ACCOUNT_NO_FOUND = "GD2OVSQPGD5FBJPMW4YN3FGDJ7JDFKNOMJT35T4H52FLHXJK5MFSR5RA";
    public static final String DESTINATION_ACCOUNT_FETCH_ERROR = "GB7WNQUTDLD6YJ4MR3KQN3Y6ZIDIGTA7GRKNH47HOGMP2ETFGRSLD6OG";
    public static final String DESTINATION_ACCOUNT_MEMO_ID = "MCAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITKNOG";

    private FeeBumpTransaction feeBump(Transaction inner) {
        inner.setEnvelopeType(EnvelopeType.ENVELOPE_TYPE_TX);
        KeyPair signer = KeyPair.fromSecretSeed("SA5ZEFDVFZ52GRU7YUGR6EDPBNRU2WLA6IQFQ7S2IH2DG3VFV3DOMV2Q");
        FeeBumpTransaction tx = new FeeBumpTransaction.Builder(inner)
            .setFeeAccount(signer.getAccountId())
            .setBaseFee(FeeBumpTransaction.MIN_BASE_FEE*10)
            .build();
        tx.sign(signer);
        return tx;
    }

    @Test
    public void testCheckMemoRequiredWithMemo() throws IOException, AccountRequiresMemoException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.setDispatcher(buildTestCheckMemoRequiredMockDispatcher());
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY");
        Account account = new Account(source.getAccountId(), 1L);
        Transaction transaction = new Transaction.Builder(account, Network.PUBLIC)
                .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeNative(), "10").build())
                .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_B, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_D).build())
                .setTimeout(Transaction.Builder.TIMEOUT_INFINITE)
                .addMemo(new MemoText("Hello, Stellar."))
                .setBaseFee(100)
                .build();
        transaction.sign(source);
        server.submitTransaction(transaction);
        server.submitTransaction(feeBump(transaction));
    }

    @Test
    public void testCheckMemoRequiredWithMemoIdAddress() throws IOException, AccountRequiresMemoException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.setDispatcher(buildTestCheckMemoRequiredMockDispatcher());
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY");
        Account account = new Account(source.getAccountId(), 1L);
        Transaction transaction = new Transaction.Builder(account, Network.PUBLIC)
            .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_ID, new AssetTypeNative(), "10").build())
            .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_ID, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
            .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_ID, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
            .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_ID).build())
            .setTimeout(Transaction.Builder.TIMEOUT_INFINITE)
            .setBaseFee(100)
            .build();

        try {
            transaction.sign(source);
            fail();
        } catch (IllegalArgumentException e) {
            assertEquals("invalid address length: MCAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITKNOG", e.getMessage());
        }

        try {
            server.submitTransaction(transaction);
            fail();
        } catch (IllegalArgumentException e) {
            assertEquals("invalid address length: MCAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITKNOG", e.getMessage());
        }

        try {
            server.submitTransaction(feeBump(transaction));
            fail();
        } catch (IllegalArgumentException e) {
            assertEquals("invalid address length: MCAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITKNOG", e.getMessage());
        }
    }

    @Test
    public void testCheckMemoRequiredWithSkipCheck() throws IOException, AccountRequiresMemoException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.setDispatcher(buildTestCheckMemoRequiredMockDispatcher());
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY");
        Account account = new Account(source.getAccountId(), 1L);
        Transaction transaction = new Transaction.Builder(account, Network.PUBLIC)
                .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeNative(), "10").build())
                .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build())
                .setTimeout(Transaction.Builder.TIMEOUT_INFINITE)
                .setBaseFee(100)
                .build();
        transaction.sign(source);
        server.submitTransaction(transaction, true);
        server.submitTransaction(feeBump(transaction), true);
    }

    @Test
    public void testCheckMemoRequiredWithPaymentOperationNoMemo() throws IOException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.setDispatcher(buildTestCheckMemoRequiredMockDispatcher());
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY");
        Account account = new Account(source.getAccountId(), 1L);
        Transaction transaction = new Transaction.Builder(account, Network.PUBLIC)
                .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeNative(), "10").build())
                .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build())
                .setTimeout(Transaction.Builder.TIMEOUT_INFINITE)
                .setBaseFee(100)
                .build();
        transaction.sign(source);
        try {
            server.submitTransaction(transaction);
            fail();
        } catch (AccountRequiresMemoException e) {
            assertEquals("Destination account requires a memo in the transaction.", e.getMessage());
            assertEquals(0, e.getOperationIndex());
            assertEquals(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, e.getAccountId());
        }

        try {
            server.submitTransaction(feeBump(transaction));
            fail();
        } catch (AccountRequiresMemoException e) {
            assertEquals("Destination account requires a memo in the transaction.", e.getMessage());
            assertEquals(0, e.getOperationIndex());
            assertEquals(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, e.getAccountId());
        }
    }

    @Test
    public void testCheckMemoRequiredWithPathPaymentStrictReceiveOperationNoMemo() throws IOException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.setDispatcher(buildTestCheckMemoRequiredMockDispatcher());
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY");
        Account account = new Account(source.getAccountId(), 1L);
        Transaction transaction = new Transaction.Builder(account, Network.PUBLIC)
                .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeNative(), "10").build())
                .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_B, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build())
                .setTimeout(Transaction.Builder.TIMEOUT_INFINITE)
                .setBaseFee(100)
                .build();
        transaction.sign(source);
        try {
            server.submitTransaction(transaction);
            fail();
        } catch (AccountRequiresMemoException e) {
            assertEquals("Destination account requires a memo in the transaction.", e.getMessage());
            assertEquals(1, e.getOperationIndex());
            assertEquals(DESTINATION_ACCOUNT_MEMO_REQUIRED_B, e.getAccountId());
        }

        try {
            server.submitTransaction(feeBump(transaction));
            fail();
        } catch (AccountRequiresMemoException e) {
            assertEquals("Destination account requires a memo in the transaction.", e.getMessage());
            assertEquals(1, e.getOperationIndex());
            assertEquals(DESTINATION_ACCOUNT_MEMO_REQUIRED_B, e.getAccountId());
        }
    }

    @Test
    public void testCheckMemoRequiredWithPathPaymentStrictSendOperationNoMemo() throws IOException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.setDispatcher(buildTestCheckMemoRequiredMockDispatcher());
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY");
        Account account = new Account(source.getAccountId(), 1L);
        Transaction transaction = new Transaction.Builder(account, Network.PUBLIC)
                .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeNative(), "10").build())
                .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build())
                .setTimeout(Transaction.Builder.TIMEOUT_INFINITE)
                .setBaseFee(100)
                .build();
        transaction.sign(source);
        try {
            server.submitTransaction(transaction);
            fail();
        } catch (AccountRequiresMemoException e) {
            assertEquals("Destination account requires a memo in the transaction.", e.getMessage());
            assertEquals(2, e.getOperationIndex());
            assertEquals(DESTINATION_ACCOUNT_MEMO_REQUIRED_C, e.getAccountId());
        }

        try {
            server.submitTransaction(feeBump(transaction));
            fail();
        } catch (AccountRequiresMemoException e) {
            assertEquals("Destination account requires a memo in the transaction.", e.getMessage());
            assertEquals(2, e.getOperationIndex());
            assertEquals(DESTINATION_ACCOUNT_MEMO_REQUIRED_C, e.getAccountId());
        }
    }

    @Test
    public void testCheckMemoRequiredWithAccountMergeOperationNoMemo() throws IOException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.setDispatcher(buildTestCheckMemoRequiredMockDispatcher());
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY");
        Account account = new Account(source.getAccountId(), 1L);
        Transaction transaction = new Transaction.Builder(account, Network.PUBLIC)
                .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeNative(), "10").build())
                .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_D).build())
                .setTimeout(Transaction.Builder.TIMEOUT_INFINITE)
                .setBaseFee(100)
                .build();
        transaction.sign(source);
        try {
            server.submitTransaction(transaction);
            fail();
        } catch (AccountRequiresMemoException e) {
            assertEquals("Destination account requires a memo in the transaction.", e.getMessage());
            assertEquals(3, e.getOperationIndex());
            assertEquals(DESTINATION_ACCOUNT_MEMO_REQUIRED_D, e.getAccountId());
        }

        try {
            server.submitTransaction(feeBump(transaction));
            fail();
        } catch (AccountRequiresMemoException e) {
            assertEquals("Destination account requires a memo in the transaction.", e.getMessage());
            assertEquals(3, e.getOperationIndex());
            assertEquals(DESTINATION_ACCOUNT_MEMO_REQUIRED_D, e.getAccountId());
        }
    }

    @Test
    public void testCheckMemoRequiredTwoOperationsWithSameDestination() throws IOException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.setDispatcher(buildTestCheckMemoRequiredMockDispatcher());
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY");
        Account account = new Account(source.getAccountId(), 1L);
        Transaction transaction = new Transaction.Builder(account, Network.PUBLIC)
                .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeNative(), "10").build())
                .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_D).build())
                .setTimeout(Transaction.Builder.TIMEOUT_INFINITE)
                .setBaseFee(100)
                .build();
        transaction.sign(source);
        try {
            server.submitTransaction(transaction);
            fail();
        } catch (AccountRequiresMemoException e) {
            assertEquals("Destination account requires a memo in the transaction.", e.getMessage());
            assertEquals(2, e.getOperationIndex());
            assertEquals(DESTINATION_ACCOUNT_MEMO_REQUIRED_C, e.getAccountId());
        }

        try {
            server.submitTransaction(feeBump(transaction));
            fail();
        } catch (AccountRequiresMemoException e) {
            assertEquals("Destination account requires a memo in the transaction.", e.getMessage());
            assertEquals(2, e.getOperationIndex());
            assertEquals(DESTINATION_ACCOUNT_MEMO_REQUIRED_C, e.getAccountId());
        }
    }

    @Test
    public void testCheckMemoRequiredNoDestinationOperation() throws IOException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.setDispatcher(buildTestCheckMemoRequiredMockDispatcher());
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY");
        Account account = new Account(source.getAccountId(), 1L);
        Transaction transaction = new Transaction.Builder(account, Network.PUBLIC)
                .addOperation(new ManageDataOperation.Builder("Hello", "Stellar".getBytes()).build())
                .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeNative(), "10").build())
                .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).build())
                .setTimeout(Transaction.Builder.TIMEOUT_INFINITE)
                .setBaseFee(100)
                .build();
        transaction.sign(source);
        try {
            server.submitTransaction(transaction);
            fail();
        } catch (AccountRequiresMemoException e) {
            assertEquals("Destination account requires a memo in the transaction.", e.getMessage());
            assertEquals(1, e.getOperationIndex());
            assertEquals(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, e.getAccountId());
        }

        try {
            server.submitTransaction(feeBump(transaction));
            fail();
        } catch (AccountRequiresMemoException e) {
            assertEquals("Destination account requires a memo in the transaction.", e.getMessage());
            assertEquals(1, e.getOperationIndex());
            assertEquals(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, e.getAccountId());
        }
    }

    @Test
    public void testCheckMemoRequiredAccountNotFound() throws IOException, AccountRequiresMemoException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.setDispatcher(buildTestCheckMemoRequiredMockDispatcher());
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY");
        Account account = new Account(source.getAccountId(), 1L);
        Transaction transaction = new Transaction.Builder(account, Network.PUBLIC)
                .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_NO_FOUND, new AssetTypeNative(), "10").build())
                .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_FOUND, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_NO_FOUND, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_NO_FOUND).build())
                .setTimeout(Transaction.Builder.TIMEOUT_INFINITE)
                .setBaseFee(100)
                .build();
        transaction.sign(source);
        server.submitTransaction(transaction);
        server.submitTransaction(feeBump(transaction));
    }

    @Test
    public void testCheckMemoRequiredFetchAccountError() throws IOException, AccountRequiresMemoException {
        MockWebServer mockWebServer = new MockWebServer();
        mockWebServer.setDispatcher(buildTestCheckMemoRequiredMockDispatcher());
        mockWebServer.start();
        HttpUrl baseUrl = mockWebServer.url("");
        Server server = new Server(baseUrl.toString());

        KeyPair source = KeyPair.fromSecretSeed("SDQXFKA32UVQHUTLYJ42N56ZUEM5PNVVI4XE7EA5QFMLA2DHDCQX3GPY");
        Account account = new Account(source.getAccountId(), 1L);
        Transaction transaction = new Transaction.Builder(account, Network.PUBLIC)
                .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_FETCH_ERROR, new AssetTypeNative(), "10").build())
                .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_A, new AssetTypeNative(), "10").build())
                .addOperation(new PaymentOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_B, new AssetTypeNative(), "10").build())
                .addOperation(new PathPaymentStrictReceiveOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_C, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new PathPaymentStrictSendOperation.Builder(new AssetTypeNative(), "10", DESTINATION_ACCOUNT_MEMO_REQUIRED_D, new AssetTypeCreditAlphaNum4("BTC", "GA7GYB3QGLTZNHNGXN3BMANS6TC7KJT3TCGTR763J4JOU4QHKL37RVV2"), "5").build())
                .addOperation(new AccountMergeOperation.Builder(DESTINATION_ACCOUNT_MEMO_REQUIRED_D).build())
                .setTimeout(Transaction.Builder.TIMEOUT_INFINITE)
                .setBaseFee(100)
                .build();
        transaction.sign(source);
        try {
            server.submitTransaction(transaction);
            fail();
        } catch (ErrorResponse e) {
            assertEquals(400, e.getCode());
        }

        try {
            server.submitTransaction(feeBump(transaction));
            fail();
        } catch (ErrorResponse e) {
            assertEquals(400, e.getCode());
        }
    }

    private Dispatcher buildTestCheckMemoRequiredMockDispatcher() {
        final String memoRequiredResponse = "{\n" +
                "    \"data\": {\n" +
                "        \"config.memo_required\": \"MQ==\"\n" +
                "    }\n" +
                "}";
        final String noMemoRequiredResponse = "{\n" +
                "    \"data\": {\n" +
                "    }\n" +
                "}";
        final String successTransactionResponse =
                "{\n" +
                        "  \"_links\": {\n" +
                        "    \"transaction\": {\n" +
                        "      \"href\": \"/transactions/2634d2cf5adcbd3487d1df042166eef53830115844fdde1588828667bf93ff42\"\n" +
                        "    }\n" +
                        "  },\n" +
                        "  \"hash\": \"2634d2cf5adcbd3487d1df042166eef53830115844fdde1588828667bf93ff42\",\n" +
                        "  \"ledger\": 826150,\n" +
                        "  \"envelope_xdr\": \"AAAAAKu3N77S+cHLEDfVD2eW/CqRiN9yvAKH+qkeLjHQs1u+AAAAZAAMkoMAAAADAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAbYQq8ek1GitmNBUloGnetfWxSpxlsgK48Xi66dIL3MoAAAAAC+vCAAAAAAAAAAAB0LNbvgAAAEDadQ25SNHWTg0L+2wr/KNWd8/EwSNFkX/ncGmBGA3zkNGx7lAow78q8SQmnn2IsdkD9MwICirhsOYDNbaqShwO\",\n" +
                        "  \"result_xdr\": \"AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA=\",\n" +
                        "  \"result_meta_xdr\": \"AAAAAAAAAAEAAAACAAAAAAAMmyYAAAAAAAAAAG2EKvHpNRorZjQVJaBp3rX1sUqcZbICuPF4uunSC9zKAAAAAAvrwgAADJsmAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAMmyYAAAAAAAAAAKu3N77S+cHLEDfVD2eW/CqRiN9yvAKH+qkeLjHQs1u+AAAAFzCfYtQADJKDAAAAAwAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA\"\n" +
                        "}";
        final String rootResponse = "{\n" +
                "  \"_links\": {\n" +
                "    \"account\": {\n" +
                "      \"href\": \"https://horizon.stellar.org/accounts/{account_id}\",\n" +
                "      \"templated\": true\n" +
                "    },\n" +
                "    \"account_transactions\": {\n" +
                "      \"href\": \"https://horizon.stellar.org/accounts/{account_id}/transactions{?cursor,limit,order}\",\n" +
                "      \"templated\": true\n" +
                "    },\n" +
                "    \"assets\": {\n" +
                "      \"href\": \"https://horizon.stellar.org/assets{?asset_code,asset_issuer,cursor,limit,order}\",\n" +
                "      \"templated\": true\n" +
                "    },\n" +
                "    \"metrics\": {\n" +
                "      \"href\": \"https://horizon.stellar.org/metrics\"\n" +
                "    },\n" +
                "    \"order_book\": {\n" +
                "      \"href\": \"https://horizon.stellar.org/order_book{?selling_asset_type,selling_asset_code,selling_asset_issuer,buying_asset_type,buying_asset_code,buying_asset_issuer,limit}\",\n" +
                "      \"templated\": true\n" +
                "    },\n" +
                "    \"self\": {\n" +
                "      \"href\": \"https://horizon.stellar.org/\"\n" +
                "    },\n" +
                "    \"transaction\": {\n" +
                "      \"href\": \"https://horizon.stellar.org/transactions/{hash}\",\n" +
                "      \"templated\": true\n" +
                "    },\n" +
                "    \"transactions\": {\n" +
                "      \"href\": \"https://horizon.stellar.org/transactions{?cursor,limit,order}\",\n" +
                "      \"templated\": true\n" +
                "    }\n" +
                "  },\n" +
                "  \"horizon_version\": \"0.18.0-92259749c681df66a8347f846e94681a24f2a920\",\n" +
                "  \"core_version\": \"stellar-core 11.1.0 (324c1bd61b0e9bada63e0d696d799421b00a7950)\",\n" +
                "  \"history_latest_ledger\": 24345129,\n" +
                "  \"history_elder_ledger\": 1,\n" +
                "  \"core_latest_ledger\": 24345130,\n" +
                "  \"network_passphrase\": \"Public Global Stellar Network ; September 2015\",\n" +
                "  \"current_protocol_version\": 11,\n" +
                "  \"core_supported_protocol_version\": 11\n" +
                "}";

        Dispatcher dispatcher = new Dispatcher() {
            @Override
            public MockResponse dispatch(RecordedRequest request) {
                String path = request.getPath();
                if ("/".equals(path)) {
                    return new MockResponse().setResponseCode(200).setBody(rootResponse);
                }
                if (String.format("/accounts/%s", DESTINATION_ACCOUNT_MEMO_REQUIRED_A).equals(path) ||
                        String.format("/accounts/%s", DESTINATION_ACCOUNT_MEMO_REQUIRED_B).equals(path) ||
                        String.format("/accounts/%s", DESTINATION_ACCOUNT_MEMO_REQUIRED_C).equals(path) ||
                        String.format("/accounts/%s", DESTINATION_ACCOUNT_MEMO_REQUIRED_D).equals(path)) {
                    return new MockResponse().setResponseCode(200).setBody(memoRequiredResponse);
                } else if (String.format("/accounts/%s", DESTINATION_ACCOUNT_NO_MEMO_REQUIRED).equals(path)) {
                    return new MockResponse().setResponseCode(200).setBody(noMemoRequiredResponse);
                } else if (String.format("/accounts/%s", DESTINATION_ACCOUNT_NO_FOUND).equals(path)) {
                    return new MockResponse().setResponseCode(404);
                } else if (String.format("/accounts/%s", DESTINATION_ACCOUNT_FETCH_ERROR).equals(path)) {
                    return new MockResponse().setResponseCode(400);
                } else if ("/transactions".equals(path)) {
                    return new MockResponse().setResponseCode(200).setBody(successTransactionResponse);
                } else {
                    return new MockResponse().setResponseCode(404);
                }
            }
        };
        return dispatcher;
    }
}