mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-04 20:54:13 -04:00
Bisq
This commit is contained in:
commit
8a38081c04
2800 changed files with 344130 additions and 0 deletions
184
apitest/src/test/java/bisq/apitest/ApiTestCase.java
Normal file
184
apitest/src/test/java/bisq/apitest/ApiTestCase.java
Normal file
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static bisq.proto.grpc.DisputeAgentsGrpc.getRegisterDisputeAgentMethod;
|
||||
import static bisq.proto.grpc.GetVersionGrpc.getGetVersionMethod;
|
||||
import static java.net.InetAddress.getLoopbackAddress;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.config.ApiTestConfig;
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.cli.GrpcClient;
|
||||
import bisq.daemon.grpc.GrpcVersionService;
|
||||
import bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig;
|
||||
|
||||
/**
|
||||
* Base class for all test types: 'method', 'scenario' and 'e2e'.
|
||||
* <p>
|
||||
* During scaffold setup, various combinations of bitcoind and bisq instances
|
||||
* can be started in the background before test cases are run. Currently, this test
|
||||
* harness supports only the "Bisq DAO development environment running against a
|
||||
* local Bitcoin regtest network" as described in
|
||||
* <a href="https://github.com/bisq-network/bisq/blob/master/docs/dev-setup.md">dev-setup.md</a>
|
||||
* and <a href="https://github.com/bisq-network/bisq/blob/master/docs/dao-setup.md">dao-setup.md</a>.
|
||||
* <p>
|
||||
* Those documents contain information about the configurations used by this test harness:
|
||||
* bitcoin-core's bitcoin.conf and blocknotify values, bisq instance options, the DAO genesis
|
||||
* transaction id, initial BSQ and BTC balances for Bob & Alice accounts, and Bob and
|
||||
* Alice's default payment accounts.
|
||||
* <p>
|
||||
* During a build, the
|
||||
* <a href="https://github.com/bisq-network/bisq/blob/master/docs/dao-setup.zip">dao-setup.zip</a>
|
||||
* file is downloaded and extracted if necessary. In each test case's @BeforeClass
|
||||
* method, the DAO setup files are re-installed into the run time's data directories
|
||||
* (each test case runs on a refreshed DAO/regtest environment setup).
|
||||
* <p>
|
||||
* Initial Alice balances & accounts: 10.0 BTC, 1000000.00 BSQ, USD PerfectMoney dummy
|
||||
* <p>
|
||||
* Initial Bob balances & accounts: 10.0 BTC, 1500000.00 BSQ, USD PerfectMoney dummy
|
||||
*/
|
||||
@Slf4j
|
||||
public class ApiTestCase {
|
||||
|
||||
protected static Scaffold scaffold;
|
||||
protected static ApiTestConfig config;
|
||||
protected static BitcoinCliHelper bitcoinCli;
|
||||
|
||||
@Nullable
|
||||
protected static GrpcClient arbClient;
|
||||
@Nullable
|
||||
protected static GrpcClient aliceClient;
|
||||
@Nullable
|
||||
protected static GrpcClient bobClient;
|
||||
|
||||
public static void setUpScaffold(Enum<?>... supportingApps)
|
||||
throws InterruptedException, ExecutionException, IOException {
|
||||
String[] params = new String[]{
|
||||
"--supportingApps", stream(supportingApps).map(Enum::name).collect(Collectors.joining(",")),
|
||||
"--callRateMeteringConfigPath", defaultRateMeterInterceptorConfig().getAbsolutePath(),
|
||||
"--enableBisqDebugging", "false"
|
||||
};
|
||||
setUpScaffold(params);
|
||||
}
|
||||
|
||||
public static void setUpScaffold(String[] params)
|
||||
throws InterruptedException, ExecutionException, IOException {
|
||||
// Test cases needing to pass more than just an ApiTestConfig
|
||||
// --supportingApps option will use this setup method, but the
|
||||
// --supportingApps option will need to be passed too, with its comma
|
||||
// delimited app list value, e.g., "bitcoind,seednode,arbdaemon".
|
||||
scaffold = new Scaffold(params).setUp();
|
||||
config = scaffold.config;
|
||||
bitcoinCli = new BitcoinCliHelper((config));
|
||||
createGrpcClients();
|
||||
}
|
||||
|
||||
public static void tearDownScaffold() {
|
||||
scaffold.tearDown();
|
||||
}
|
||||
|
||||
protected static void createGrpcClients() {
|
||||
if (config.supportingApps.contains(alicedaemon.name())) {
|
||||
aliceClient = new GrpcClient(getLoopbackAddress().getHostAddress(),
|
||||
alicedaemon.apiPort,
|
||||
config.apiPassword);
|
||||
}
|
||||
if (config.supportingApps.contains(bobdaemon.name())) {
|
||||
bobClient = new GrpcClient(getLoopbackAddress().getHostAddress(),
|
||||
bobdaemon.apiPort,
|
||||
config.apiPassword);
|
||||
}
|
||||
if (config.supportingApps.contains(arbdaemon.name())) {
|
||||
arbClient = new GrpcClient(getLoopbackAddress().getHostAddress(),
|
||||
arbdaemon.apiPort,
|
||||
config.apiPassword);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void genBtcBlocksThenWait(int numBlocks, long wait) {
|
||||
bitcoinCli.generateBlocks(numBlocks);
|
||||
sleep(wait);
|
||||
}
|
||||
|
||||
protected static void sleep(long ms) {
|
||||
try {
|
||||
MILLISECONDS.sleep(ms);
|
||||
} catch (InterruptedException ignored) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
|
||||
protected final String testName(TestInfo testInfo) {
|
||||
return testInfo.getTestMethod().isPresent()
|
||||
? testInfo.getTestMethod().get().getName()
|
||||
: "unknown test name";
|
||||
}
|
||||
|
||||
protected static File defaultRateMeterInterceptorConfig() {
|
||||
GrpcServiceRateMeteringConfig.Builder builder = new GrpcServiceRateMeteringConfig.Builder();
|
||||
builder.addCallRateMeter(GrpcVersionService.class.getSimpleName(),
|
||||
getGetVersionMethod().getFullMethodName(),
|
||||
1,
|
||||
SECONDS);
|
||||
// Only GrpcVersionService is @VisibleForTesting, so we need to
|
||||
// hardcode other grpcServiceClassName parameter values used in
|
||||
// builder.addCallRateMeter(...).
|
||||
builder.addCallRateMeter("GrpcDisputeAgentsService",
|
||||
getRegisterDisputeAgentMethod().getFullMethodName(),
|
||||
10, // Same as default.
|
||||
SECONDS);
|
||||
// Define rate meters for non-existent method 'disabled', to override other grpc
|
||||
// services' default rate meters -- defined in their rateMeteringInterceptor()
|
||||
// methods.
|
||||
String[] serviceClassNames = new String[]{
|
||||
"GrpcGetTradeStatisticsService",
|
||||
"GrpcHelpService",
|
||||
"GrpcOffersService",
|
||||
"GrpcPaymentAccountsService",
|
||||
"GrpcPriceService",
|
||||
"GrpcTradesService",
|
||||
"GrpcWalletsService"
|
||||
};
|
||||
for (String service : serviceClassNames) {
|
||||
builder.addCallRateMeter(service, "disabled", 1, MILLISECONDS);
|
||||
}
|
||||
File file = builder.build();
|
||||
file.deleteOnExit();
|
||||
return file;
|
||||
}
|
||||
}
|
58
apitest/src/test/java/bisq/apitest/JUnitHelper.java
Normal file
58
apitest/src/test/java/bisq/apitest/JUnitHelper.java
Normal file
|
@ -0,0 +1,58 @@
|
|||
package bisq.apitest;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runner.JUnitCore;
|
||||
import org.junit.runner.Result;
|
||||
import org.junit.runner.notification.Failure;
|
||||
import org.junit.runner.notification.RunListener;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
@Slf4j
|
||||
public class JUnitHelper {
|
||||
|
||||
private static boolean allPass;
|
||||
|
||||
public static void runTests(Class<?>... testClasses) {
|
||||
JUnitCore jUnitCore = new JUnitCore();
|
||||
jUnitCore.addListener(new RunListener() {
|
||||
public void testStarted(Description description) {
|
||||
log.info("{}", description);
|
||||
}
|
||||
|
||||
public void testIgnored(Description description) {
|
||||
log.info("Ignored {}", description);
|
||||
}
|
||||
|
||||
public void testFailure(Failure failure) {
|
||||
log.error("Failed {}", failure.getTrace());
|
||||
}
|
||||
});
|
||||
Result result = jUnitCore.run(testClasses);
|
||||
printTestResults(result);
|
||||
}
|
||||
|
||||
public static boolean allTestsPassed() {
|
||||
return allPass;
|
||||
}
|
||||
|
||||
private static void printTestResults(Result result) {
|
||||
log.info("Total tests: {}, Failed: {}, Ignored: {}",
|
||||
result.getRunCount(),
|
||||
result.getFailureCount(),
|
||||
result.getIgnoreCount());
|
||||
|
||||
if (result.wasSuccessful()) {
|
||||
log.info("All {} tests passed", result.getRunCount());
|
||||
allPass = true;
|
||||
} else if (result.getFailureCount() > 0) {
|
||||
log.error("{} test(s) failed", result.getFailureCount());
|
||||
result.getFailures().iterator().forEachRemaining(f -> log.error(format("%s.%s()%n\t%s",
|
||||
f.getDescription().getTestClass().getName(),
|
||||
f.getDescription().getMethodName(),
|
||||
f.getTrace())));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.config.ApiTestConfig;
|
||||
import bisq.apitest.linux.BitcoinCli;
|
||||
|
||||
public final class BitcoinCliHelper {
|
||||
|
||||
private final ApiTestConfig config;
|
||||
|
||||
public BitcoinCliHelper(ApiTestConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
// Convenience methods for making bitcoin-cli calls.
|
||||
|
||||
public String getNewBtcAddress() {
|
||||
try {
|
||||
BitcoinCli newAddress = new BitcoinCli(config, "getnewaddress").run();
|
||||
|
||||
if (newAddress.isError())
|
||||
fail(format("Could not generate new bitcoin address:%n%s", newAddress.getErrorMessage()));
|
||||
|
||||
return newAddress.getOutput();
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
fail(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String[] generateToAddress(int blocks, String address) {
|
||||
try {
|
||||
String generateToAddressCmd = format("generatetoaddress %d \"%s\"", blocks, address);
|
||||
BitcoinCli generateToAddress = new BitcoinCli(config, generateToAddressCmd).run();
|
||||
|
||||
if (generateToAddress.isError())
|
||||
fail(format("Could not generate bitcoin block(s):%n%s", generateToAddress.getErrorMessage()));
|
||||
|
||||
return generateToAddress.getOutputValueAsStringArray();
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
fail(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void generateBlocks(int blocks) {
|
||||
generateToAddress(blocks, getNewBtcAddress());
|
||||
}
|
||||
|
||||
public String sendToAddress(String address, String amount) {
|
||||
// sendtoaddress "address" amount \
|
||||
// ( "comment" "comment_to" subtractfeefromamount \
|
||||
// replaceable conf_target "estimate_mode" )
|
||||
// returns a transaction id
|
||||
try {
|
||||
String sendToAddressCmd = format("sendtoaddress \"%s\" %s \"\" \"\" false",
|
||||
address, amount);
|
||||
BitcoinCli sendToAddress = new BitcoinCli(config, sendToAddressCmd).run();
|
||||
|
||||
if (sendToAddress.isError())
|
||||
fail(format("Could not send BTC to address:%n%s", sendToAddress.getErrorMessage()));
|
||||
|
||||
return sendToAddress.getOutput();
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
fail(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class CallRateMeteringInterceptorTest extends MethodTest {
|
||||
|
||||
private static final GetVersionTest getVersionTest = new GetVersionTest();
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
startSupportingApps(false,
|
||||
false,
|
||||
bitcoind, alicedaemon);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void sleep200Milliseconds() {
|
||||
sleep(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testGetVersionCall1IsAllowed() {
|
||||
getVersionTest.testGetVersion();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testGetVersionCall2ShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, getVersionTest::testGetVersion);
|
||||
assertEquals("PERMISSION_DENIED: the maximum allowed number of getversion calls (1/second) has been exceeded",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testGetVersionCall3ShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, getVersionTest::testGetVersion);
|
||||
assertEquals("PERMISSION_DENIED: the maximum allowed number of getversion calls (1/second) has been exceeded",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testGetVersionCall4IsAllowed() {
|
||||
sleep(1100); // Let the server's rate meter reset the call count.
|
||||
getVersionTest.testGetVersion();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.cli.Method.createoffer;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
public class GetMethodHelpTest extends MethodTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
try {
|
||||
setUpScaffold(alicedaemon);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testGetCreateOfferHelp() {
|
||||
var help = aliceClient.getMethodHelp(createoffer);
|
||||
assertNotNull(help);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.common.app.Version.VERSION;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
public class GetVersionTest extends MethodTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
try {
|
||||
setUpScaffold(alicedaemon);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testGetVersion() {
|
||||
var version = aliceClient.getVersion();
|
||||
assertEquals(VERSION, version);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
149
apitest/src/test/java/bisq/apitest/method/MethodTest.java
Normal file
149
apitest/src/test/java/bisq/apitest/method/MethodTest.java
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method;
|
||||
|
||||
import bisq.core.api.model.PaymentAccountForm;
|
||||
import bisq.core.payment.F2FAccount;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Arrays.stream;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.ApiTestCase;
|
||||
import bisq.cli.GrpcClient;
|
||||
|
||||
public class MethodTest extends ApiTestCase {
|
||||
|
||||
protected static final CoreProtoResolver CORE_PROTO_RESOLVER = new CoreProtoResolver();
|
||||
|
||||
private static final Function<Enum<?>[], String> toNameList = (enums) ->
|
||||
stream(enums).map(Enum::name).collect(Collectors.joining(","));
|
||||
|
||||
public static void startSupportingApps(File callRateMeteringConfigFile,
|
||||
boolean generateBtcBlock,
|
||||
Enum<?>... supportingApps) {
|
||||
startSupportingApps(callRateMeteringConfigFile,
|
||||
generateBtcBlock,
|
||||
false,
|
||||
supportingApps);
|
||||
}
|
||||
|
||||
public static void startSupportingApps(File callRateMeteringConfigFile,
|
||||
boolean generateBtcBlock,
|
||||
boolean startSupportingAppsInDebugMode,
|
||||
Enum<?>... supportingApps) {
|
||||
try {
|
||||
setUpScaffold(new String[]{
|
||||
"--supportingApps", toNameList.apply(supportingApps),
|
||||
"--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(),
|
||||
"--enableBisqDebugging", startSupportingAppsInDebugMode ? "true" : "false"
|
||||
});
|
||||
doPostStartup(generateBtcBlock);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void startSupportingApps(boolean generateBtcBlock,
|
||||
Enum<?>... supportingApps) {
|
||||
startSupportingApps(generateBtcBlock,
|
||||
false,
|
||||
supportingApps);
|
||||
}
|
||||
|
||||
public static void startSupportingApps(boolean generateBtcBlock,
|
||||
boolean startSupportingAppsInDebugMode,
|
||||
Enum<?>... supportingApps) {
|
||||
try {
|
||||
// Disable call rate metering where there is no callRateMeteringConfigFile.
|
||||
File callRateMeteringConfigFile = defaultRateMeterInterceptorConfig();
|
||||
setUpScaffold(new String[]{
|
||||
"--supportingApps", toNameList.apply(supportingApps),
|
||||
"--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(),
|
||||
"--enableBisqDebugging", startSupportingAppsInDebugMode ? "true" : "false"
|
||||
});
|
||||
doPostStartup(generateBtcBlock);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void doPostStartup(boolean generateBtcBlock) {
|
||||
// Generate 1 regtest block for alice's and/or bob's wallet to
|
||||
// show 10 BTC balance, and allow time for daemons parse the new block.
|
||||
if (generateBtcBlock)
|
||||
genBtcBlocksThenWait(1, 1500);
|
||||
}
|
||||
|
||||
protected final File getPaymentAccountForm(GrpcClient grpcClient, String paymentMethodId) {
|
||||
// We take seemingly unnecessary steps to get a File object, but the point is to
|
||||
// test the API, and we do not directly ask bisq.core.api.model.PaymentAccountForm
|
||||
// for an empty json form (file).
|
||||
String jsonString = grpcClient.getPaymentAcctFormAsJson(paymentMethodId);
|
||||
// Write the json string to a file here in the test case.
|
||||
File jsonFile = PaymentAccountForm.getTmpJsonFile(paymentMethodId);
|
||||
try (PrintWriter out = new PrintWriter(jsonFile, UTF_8)) {
|
||||
out.println(jsonString);
|
||||
} catch (IOException ex) {
|
||||
fail("Could not create tmp payment account form.", ex);
|
||||
}
|
||||
return jsonFile;
|
||||
}
|
||||
|
||||
|
||||
protected bisq.core.payment.PaymentAccount createDummyF2FAccount(GrpcClient grpcClient,
|
||||
String countryCode) {
|
||||
String f2fAccountJsonString = "{\n" +
|
||||
" \"_COMMENTS_\": \"This is a dummy account.\",\n" +
|
||||
" \"paymentMethodId\": \"F2F\",\n" +
|
||||
" \"accountName\": \"Dummy " + countryCode.toUpperCase() + " F2F Account\",\n" +
|
||||
" \"city\": \"Anytown\",\n" +
|
||||
" \"contact\": \"Morse Code\",\n" +
|
||||
" \"country\": \"" + countryCode.toUpperCase() + "\",\n" +
|
||||
" \"extraInfo\": \"Salt Lick #213\"\n" +
|
||||
"}\n";
|
||||
F2FAccount f2FAccount = (F2FAccount) createPaymentAccount(grpcClient, f2fAccountJsonString);
|
||||
return f2FAccount;
|
||||
}
|
||||
|
||||
protected final bisq.core.payment.PaymentAccount createPaymentAccount(GrpcClient grpcClient, String jsonString) {
|
||||
// Normally, we do asserts on the protos from the gRPC service, but in this
|
||||
// case we need a bisq.core.payment.PaymentAccount so it can be cast to its
|
||||
// sub type.
|
||||
var paymentAccount = grpcClient.createPaymentAccount(jsonString);
|
||||
return bisq.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER);
|
||||
}
|
||||
|
||||
// Static conveniences for test methods and test case fixture setups.
|
||||
|
||||
protected static String encodeToHex(String s) {
|
||||
return Utilities.bytesAsHexString(s.getBytes(UTF_8));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.ApiTestConfig.ARBITRATOR;
|
||||
import static bisq.apitest.config.ApiTestConfig.MEDIATOR;
|
||||
import static bisq.apitest.config.ApiTestConfig.REFUND_AGENT;
|
||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
public class RegisterDisputeAgentsTest extends MethodTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
try {
|
||||
setUpScaffold(bitcoind, seednode, arbdaemon);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testRegisterArbitratorShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
arbClient.registerDisputeAgent(ARBITRATOR, DEV_PRIVILEGE_PRIV_KEY));
|
||||
assertEquals("INVALID_ARGUMENT: arbitrators must be registered in a Bisq UI",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testInvalidDisputeAgentTypeArgShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
arbClient.registerDisputeAgent("badagent", DEV_PRIVILEGE_PRIV_KEY));
|
||||
assertEquals("INVALID_ARGUMENT: unknown dispute agent type 'badagent'",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testInvalidRegistrationKeyArgShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
arbClient.registerDisputeAgent(REFUND_AGENT, "invalid" + DEV_PRIVILEGE_PRIV_KEY));
|
||||
assertEquals("INVALID_ARGUMENT: invalid registration key",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testRegisterMediator() {
|
||||
arbClient.registerDisputeAgent(MEDIATOR, DEV_PRIVILEGE_PRIV_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
public void testRegisterRefundAgent() {
|
||||
arbClient.registerDisputeAgent(REFUND_AGENT, DEV_PRIVILEGE_PRIV_KEY);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.offer;
|
||||
|
||||
import bisq.core.monetary.Altcoin;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static bisq.common.util.MathUtils.roundDouble;
|
||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
||||
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
|
||||
import static java.math.RoundingMode.HALF_UP;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractOfferTest extends MethodTest {
|
||||
|
||||
@Setter
|
||||
protected static boolean isLongRunningTest;
|
||||
|
||||
protected static PaymentAccount alicesBsqAcct;
|
||||
protected static PaymentAccount bobsBsqAcct;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
startSupportingApps(true,
|
||||
false,
|
||||
bitcoind,
|
||||
seednode,
|
||||
arbdaemon,
|
||||
alicedaemon,
|
||||
bobdaemon);
|
||||
}
|
||||
|
||||
|
||||
public static void createBsqPaymentAccounts() {
|
||||
alicesBsqAcct = aliceClient.createCryptoCurrencyPaymentAccount("Alice's BSQ Account",
|
||||
BSQ,
|
||||
aliceClient.getUnusedBsqAddress(),
|
||||
false);
|
||||
bobsBsqAcct = bobClient.createCryptoCurrencyPaymentAccount("Bob's BSQ Account",
|
||||
BSQ,
|
||||
bobClient.getUnusedBsqAddress(),
|
||||
false);
|
||||
}
|
||||
|
||||
protected double getScaledOfferPrice(double offerPrice, String currencyCode) {
|
||||
int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
return scaleDownByPowerOf10(offerPrice, precision);
|
||||
}
|
||||
|
||||
protected final double getPercentageDifference(double price1, double price2) {
|
||||
return BigDecimal.valueOf(roundDouble((1 - (price1 / price2)), 5))
|
||||
.setScale(4, HALF_UP)
|
||||
.doubleValue();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.offer;
|
||||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class CancelOfferTest extends AbstractOfferTest {
|
||||
|
||||
private static final String DIRECTION = BUY.name();
|
||||
private static final String CURRENCY_CODE = "cad";
|
||||
private static final int MAX_OFFERS = 3;
|
||||
|
||||
private final Consumer<String> createOfferToCancel = (paymentAccountId) -> {
|
||||
aliceClient.createMarketBasedPricedOffer(DIRECTION,
|
||||
CURRENCY_CODE,
|
||||
10000000L,
|
||||
10000000L,
|
||||
0.00,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
paymentAccountId,
|
||||
BSQ);
|
||||
};
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testCancelOffer() {
|
||||
PaymentAccount cadAccount = createDummyF2FAccount(aliceClient, "CA");
|
||||
|
||||
// Create some offers.
|
||||
for (int i = 1; i <= MAX_OFFERS; i++) {
|
||||
createOfferToCancel.accept(cadAccount.getId());
|
||||
// Wait for Alice's AddToOfferBook task.
|
||||
// Wait times vary; my logs show >= 2 second delay.
|
||||
sleep(2500);
|
||||
}
|
||||
|
||||
List<OfferInfo> offers = aliceClient.getMyOffersSortedByDate(DIRECTION, CURRENCY_CODE);
|
||||
assertEquals(MAX_OFFERS, offers.size());
|
||||
|
||||
// Cancel the offers, checking the open offer count after each offer removal.
|
||||
for (int i = 1; i <= MAX_OFFERS; i++) {
|
||||
aliceClient.cancelOffer(offers.remove(0).getId());
|
||||
offers = aliceClient.getMyOffersSortedByDate(DIRECTION, CURRENCY_CODE);
|
||||
assertEquals(MAX_OFFERS - i, offers.size());
|
||||
}
|
||||
|
||||
sleep(1000); // wait for offer removal
|
||||
|
||||
offers = aliceClient.getMyOffersSortedByDate(DIRECTION, CURRENCY_CODE);
|
||||
assertEquals(0, offers.size());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.offer;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||
|
||||
private static final String MAKER_FEE_CURRENCY_CODE = BSQ;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
AbstractOfferTest.setUp();
|
||||
createBsqPaymentAccounts();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testCreateBuy1BTCFor20KBSQOffer() {
|
||||
// Remember alt coin trades are BTC trades. When placing an offer, you are
|
||||
// offering to buy or sell BTC, not BSQ, XMR, etc. In this test case,
|
||||
// Alice places an offer to BUY BTC with BSQ.
|
||||
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||
BSQ,
|
||||
100_000_000L,
|
||||
100_000_000L,
|
||||
"0.00005", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesBsqAcct.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("Sell BSQ (Buy BTC) OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(5_000, newOffer.getPrice());
|
||||
assertEquals(100_000_000L, newOffer.getAmount());
|
||||
assertEquals(100_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
genBtcBlockAndWaitForOfferPreparation();
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(5_000, newOffer.getPrice());
|
||||
assertEquals(100_000_000L, newOffer.getAmount());
|
||||
assertEquals(100_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testCreateSell1BTCFor20KBSQOffer() {
|
||||
// Alice places an offer to SELL BTC for BSQ.
|
||||
var newOffer = aliceClient.createFixedPricedOffer(SELL.name(),
|
||||
BSQ,
|
||||
100_000_000L,
|
||||
100_000_000L,
|
||||
"0.00005", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesBsqAcct.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("SELL 20K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(5_000, newOffer.getPrice());
|
||||
assertEquals(100_000_000L, newOffer.getAmount());
|
||||
assertEquals(100_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
genBtcBlockAndWaitForOfferPreparation();
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(5_000, newOffer.getPrice());
|
||||
assertEquals(100_000_000L, newOffer.getAmount());
|
||||
assertEquals(100_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testCreateBuyBTCWith1To2KBSQOffer() {
|
||||
// Alice places an offer to BUY 0.05 - 0.10 BTC with BSQ.
|
||||
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||
BSQ,
|
||||
10_000_000L,
|
||||
5_000_000L,
|
||||
"0.00005", // FIXED PRICE IN BTC sats FOR 1 BSQ
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesBsqAcct.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("BUY 1-2K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(5_000, newOffer.getPrice());
|
||||
assertEquals(10_000_000L, newOffer.getAmount());
|
||||
assertEquals(5_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
genBtcBlockAndWaitForOfferPreparation();
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(5_000, newOffer.getPrice());
|
||||
assertEquals(10_000_000L, newOffer.getAmount());
|
||||
assertEquals(5_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testCreateSellBTCFor5To10KBSQOffer() {
|
||||
// Alice places an offer to SELL 0.25 - 0.50 BTC for BSQ.
|
||||
var newOffer = aliceClient.createFixedPricedOffer(SELL.name(),
|
||||
BSQ,
|
||||
50_000_000L,
|
||||
25_000_000L,
|
||||
"0.00005", // FIXED PRICE IN BTC sats FOR 1 BSQ
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesBsqAcct.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("SELL 5-10K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(5_000, newOffer.getPrice());
|
||||
assertEquals(50_000_000L, newOffer.getAmount());
|
||||
assertEquals(25_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(7_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
genBtcBlockAndWaitForOfferPreparation();
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(5_000, newOffer.getPrice());
|
||||
assertEquals(50_000_000L, newOffer.getAmount());
|
||||
assertEquals(25_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(7_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesBsqAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BSQ, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
public void testGetAllMyBsqOffers() {
|
||||
List<OfferInfo> offers = aliceClient.getMyBsqOffersSortedByDate();
|
||||
log.info("ALL ALICE'S BSQ OFFERS:\n{}", formatOfferTable(offers, BSQ));
|
||||
assertEquals(4, offers.size());
|
||||
log.info("ALICE'S BALANCES\n{}", formatBalancesTbls(aliceClient.getBalances()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(6)
|
||||
public void testGetAvailableBsqOffers() {
|
||||
List<OfferInfo> offers = bobClient.getBsqOffersSortedByDate();
|
||||
log.info("ALL BOB'S AVAILABLE BSQ OFFERS:\n{}", formatOfferTable(offers, BSQ));
|
||||
assertEquals(4, offers.size());
|
||||
log.info("BOB'S BALANCES\n{}", formatBalancesTbls(bobClient.getBalances()));
|
||||
}
|
||||
|
||||
private void genBtcBlockAndWaitForOfferPreparation() {
|
||||
// Extra time is needed for the OfferUtils#isBsqForMakerFeeAvailable, which
|
||||
// can sometimes return an incorrect false value if the BsqWallet's
|
||||
// available confirmed balance is temporarily = zero during BSQ offer prep.
|
||||
genBtcBlocksThenWait(1, 5000);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.offer;
|
||||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
|
||||
private static final String MAKER_FEE_CURRENCY_CODE = BSQ;
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() {
|
||||
PaymentAccount audAccount = createDummyF2FAccount(aliceClient, "AU");
|
||||
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||
"aud",
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
"36000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
audAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "AUD"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(360_000_000, newOffer.getPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("AUD", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(360_000_000, newOffer.getPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("AUD", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() {
|
||||
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||
"usd",
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
"30000.1234",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
usdAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "USD"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(300_001_234, newOffer.getPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(300_001_234, newOffer.getPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testCreateEURBTCSellOfferUsingFixedPrice95001234() {
|
||||
PaymentAccount eurAccount = createDummyF2FAccount(aliceClient, "FR");
|
||||
var newOffer = aliceClient.createFixedPricedOffer(SELL.name(),
|
||||
"eur",
|
||||
10_000_000L,
|
||||
5_000_000L,
|
||||
"29500.1234",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
eurAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "EUR"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(295_001_234, newOffer.getPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("EUR", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(295_001_234, newOffer.getPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("EUR", newOffer.getCounterCurrencyCode());
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.offer;
|
||||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
||||
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static java.lang.Math.abs;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
|
||||
private static final DecimalFormat PCT_FORMAT = new DecimalFormat("##0.00");
|
||||
private static final double MKT_PRICE_MARGIN_ERROR_TOLERANCE = 0.0050; // 0.50%
|
||||
private static final double MKT_PRICE_MARGIN_WARNING_TOLERANCE = 0.0001; // 0.01%
|
||||
|
||||
private static final String MAKER_FEE_CURRENCY_CODE = BTC;
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testCreateUSDBTCBuyOffer5PctPriceMargin() {
|
||||
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
double priceMarginPctInput = 5.00;
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||
"usd",
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
usdAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() {
|
||||
PaymentAccount nzdAccount = createDummyF2FAccount(aliceClient, "NZ");
|
||||
double priceMarginPctInput = -2.00;
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||
"nzd",
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
nzdAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "nzd"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() {
|
||||
PaymentAccount gbpAccount = createDummyF2FAccount(aliceClient, "GB");
|
||||
double priceMarginPctInput = -1.5;
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
||||
"gbp",
|
||||
10_000_000L,
|
||||
5_000_000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
gbpAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "gbp"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() {
|
||||
PaymentAccount brlAccount = createDummyF2FAccount(aliceClient, "BR");
|
||||
double priceMarginPctInput = 6.55;
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
||||
"brl",
|
||||
10_000_000L,
|
||||
5_000_000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
brlAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("OFFER #4:\n{}", formatOfferTable(singletonList(newOffer), "brl"));
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||
}
|
||||
|
||||
private void assertCalculatedPriceIsCorrect(OfferInfo offer, double priceMarginPctInput) {
|
||||
assertTrue(() -> {
|
||||
String counterCurrencyCode = offer.getCounterCurrencyCode();
|
||||
double mktPrice = aliceClient.getBtcPrice(counterCurrencyCode);
|
||||
double scaledOfferPrice = getScaledOfferPrice(offer.getPrice(), counterCurrencyCode);
|
||||
double expectedDiffPct = scaleDownByPowerOf10(priceMarginPctInput, 2);
|
||||
double actualDiffPct = offer.getDirection().equals(BUY.name())
|
||||
? getPercentageDifference(scaledOfferPrice, mktPrice)
|
||||
: getPercentageDifference(mktPrice, scaledOfferPrice);
|
||||
double pctDiffDelta = abs(expectedDiffPct) - abs(actualDiffPct);
|
||||
return isCalculatedPriceWithinErrorTolerance(pctDiffDelta,
|
||||
expectedDiffPct,
|
||||
actualDiffPct,
|
||||
mktPrice,
|
||||
scaledOfferPrice,
|
||||
offer);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isCalculatedPriceWithinErrorTolerance(double delta,
|
||||
double expectedDiffPct,
|
||||
double actualDiffPct,
|
||||
double mktPrice,
|
||||
double scaledOfferPrice,
|
||||
OfferInfo offer) {
|
||||
if (abs(delta) > MKT_PRICE_MARGIN_ERROR_TOLERANCE) {
|
||||
logCalculatedPricePoppedErrorTolerance(expectedDiffPct,
|
||||
actualDiffPct,
|
||||
mktPrice,
|
||||
scaledOfferPrice);
|
||||
log.error(offer.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (abs(delta) >= MKT_PRICE_MARGIN_WARNING_TOLERANCE) {
|
||||
logCalculatedPricePoppedWarningTolerance(expectedDiffPct,
|
||||
actualDiffPct,
|
||||
mktPrice,
|
||||
scaledOfferPrice);
|
||||
log.warn(offer.toString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void logCalculatedPricePoppedWarningTolerance(double expectedDiffPct,
|
||||
double actualDiffPct,
|
||||
double mktPrice,
|
||||
double scaledOfferPrice) {
|
||||
log.warn(format("Calculated price %.4f & mkt price %.4f differ by ~ %s%s,"
|
||||
+ " not by %s%s, outside the %s%s warning tolerance,"
|
||||
+ " but within the %s%s error tolerance.",
|
||||
scaledOfferPrice, mktPrice,
|
||||
PCT_FORMAT.format(scaleUpByPowerOf10(actualDiffPct, 2)), "%",
|
||||
PCT_FORMAT.format(scaleUpByPowerOf10(expectedDiffPct, 2)), "%",
|
||||
PCT_FORMAT.format(scaleUpByPowerOf10(MKT_PRICE_MARGIN_WARNING_TOLERANCE, 2)), "%",
|
||||
PCT_FORMAT.format(scaleUpByPowerOf10(MKT_PRICE_MARGIN_ERROR_TOLERANCE, 2)), "%"));
|
||||
}
|
||||
|
||||
private void logCalculatedPricePoppedErrorTolerance(double expectedDiffPct,
|
||||
double actualDiffPct,
|
||||
double mktPrice,
|
||||
double scaledOfferPrice) {
|
||||
log.error(format("Calculated price %.4f & mkt price %.4f differ by ~ %s%s,"
|
||||
+ " not by %s%s, outside the %s%s error tolerance.",
|
||||
scaledOfferPrice, mktPrice,
|
||||
PCT_FORMAT.format(scaleUpByPowerOf10(actualDiffPct, 2)), "%",
|
||||
PCT_FORMAT.format(scaleUpByPowerOf10(expectedDiffPct, 2)), "%",
|
||||
PCT_FORMAT.format(scaleUpByPowerOf10(MKT_PRICE_MARGIN_ERROR_TOLERANCE, 2)), "%"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.offer;
|
||||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testAmtTooLargeShouldThrowException() {
|
||||
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
aliceClient.createFixedPricedOffer(BUY.name(),
|
||||
"usd",
|
||||
100000000000L, // exceeds amount limit
|
||||
100000000000L,
|
||||
"10000.0000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
usdAccount.getId(),
|
||||
BSQ));
|
||||
assertEquals("UNKNOWN: An error occurred at task: ValidateOffer", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testNoMatchingEURPaymentAccountShouldThrowException() {
|
||||
PaymentAccount chfAccount = createDummyF2FAccount(aliceClient, "ch");
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
aliceClient.createFixedPricedOffer(BUY.name(),
|
||||
"eur",
|
||||
10000000L,
|
||||
10000000L,
|
||||
"40000.0000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
chfAccount.getId(),
|
||||
BTC));
|
||||
String expectedError = format("UNKNOWN: cannot create EUR offer with payment account %s", chfAccount.getId());
|
||||
assertEquals(expectedError, exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testNoMatchingCADPaymentAccountShouldThrowException() {
|
||||
PaymentAccount audAccount = createDummyF2FAccount(aliceClient, "au");
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
aliceClient.createFixedPricedOffer(BUY.name(),
|
||||
"cad",
|
||||
10000000L,
|
||||
10000000L,
|
||||
"63000.0000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
audAccount.getId(),
|
||||
BTC));
|
||||
String expectedError = format("UNKNOWN: cannot create CAD offer with payment account %s", audAccount.getId());
|
||||
assertEquals(expectedError, exception.getMessage());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
package bisq.apitest.method.payment;
|
||||
|
||||
import bisq.core.api.model.PaymentAccountForm;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.getProperty;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
import bisq.cli.GrpcClient;
|
||||
|
||||
@Slf4j
|
||||
public class AbstractPaymentAccountTest extends MethodTest {
|
||||
|
||||
static final String PROPERTY_NAME_JSON_COMMENTS = "_COMMENTS_";
|
||||
static final List<String> PROPERTY_VALUE_JSON_COMMENTS = new ArrayList<>() {{
|
||||
add("Do not manually edit the paymentMethodId field.");
|
||||
add("Edit the salt field only if you are recreating a payment"
|
||||
+ " account on a new installation and wish to preserve the account age.");
|
||||
}};
|
||||
|
||||
static final String PROPERTY_NAME_PAYMENT_METHOD_ID = "paymentMethodId";
|
||||
|
||||
static final String PROPERTY_NAME_ACCOUNT_ID = "accountId";
|
||||
static final String PROPERTY_NAME_ACCOUNT_NAME = "accountName";
|
||||
static final String PROPERTY_NAME_ACCOUNT_NR = "accountNr";
|
||||
static final String PROPERTY_NAME_ACCOUNT_TYPE = "accountType";
|
||||
static final String PROPERTY_NAME_ANSWER = "answer";
|
||||
static final String PROPERTY_NAME_BANK_ACCOUNT_NAME = "bankAccountName";
|
||||
static final String PROPERTY_NAME_BANK_ACCOUNT_NUMBER = "bankAccountNumber";
|
||||
static final String PROPERTY_NAME_BANK_ACCOUNT_TYPE = "bankAccountType";
|
||||
static final String PROPERTY_NAME_BANK_BRANCH_CODE = "bankBranchCode";
|
||||
static final String PROPERTY_NAME_BANK_BRANCH_NAME = "bankBranchName";
|
||||
static final String PROPERTY_NAME_BANK_CODE = "bankCode";
|
||||
@SuppressWarnings("unused")
|
||||
static final String PROPERTY_NAME_BANK_ID = "bankId";
|
||||
static final String PROPERTY_NAME_BANK_NAME = "bankName";
|
||||
static final String PROPERTY_NAME_BRANCH_ID = "branchId";
|
||||
static final String PROPERTY_NAME_BIC = "bic";
|
||||
static final String PROPERTY_NAME_COUNTRY = "country";
|
||||
static final String PROPERTY_NAME_CITY = "city";
|
||||
static final String PROPERTY_NAME_CONTACT = "contact";
|
||||
static final String PROPERTY_NAME_EMAIL = "email";
|
||||
static final String PROPERTY_NAME_EMAIL_OR_MOBILE_NR = "emailOrMobileNr";
|
||||
static final String PROPERTY_NAME_EXTRA_INFO = "extraInfo";
|
||||
static final String PROPERTY_NAME_HOLDER_EMAIL = "holderEmail";
|
||||
static final String PROPERTY_NAME_HOLDER_NAME = "holderName";
|
||||
static final String PROPERTY_NAME_HOLDER_TAX_ID = "holderTaxId";
|
||||
static final String PROPERTY_NAME_IBAN = "iban";
|
||||
static final String PROPERTY_NAME_MOBILE_NR = "mobileNr";
|
||||
static final String PROPERTY_NAME_NATIONAL_ACCOUNT_ID = "nationalAccountId";
|
||||
static final String PROPERTY_NAME_PAY_ID = "payid";
|
||||
static final String PROPERTY_NAME_POSTAL_ADDRESS = "postalAddress";
|
||||
static final String PROPERTY_NAME_PROMPT_PAY_ID = "promptPayId";
|
||||
static final String PROPERTY_NAME_QUESTION = "question";
|
||||
static final String PROPERTY_NAME_REQUIREMENTS = "requirements";
|
||||
static final String PROPERTY_NAME_SALT = "salt";
|
||||
static final String PROPERTY_NAME_SORT_CODE = "sortCode";
|
||||
static final String PROPERTY_NAME_STATE = "state";
|
||||
static final String PROPERTY_NAME_TRADE_CURRENCIES = "tradeCurrencies";
|
||||
static final String PROPERTY_NAME_USERNAME = "userName";
|
||||
|
||||
static final Gson GSON = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.serializeNulls()
|
||||
.create();
|
||||
|
||||
static final Map<String, Object> COMPLETED_FORM_MAP = new HashMap<>();
|
||||
|
||||
// A payment account serializer / deserializer.
|
||||
static final PaymentAccountForm PAYMENT_ACCOUNT_FORM = new PaymentAccountForm();
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
Res.setup();
|
||||
}
|
||||
|
||||
protected final File getEmptyForm(TestInfo testInfo, String paymentMethodId) {
|
||||
// This would normally be done in @BeforeEach, but these test cases might be
|
||||
// called from a single 'scenario' test case, and the @BeforeEach -> clear()
|
||||
// would be skipped.
|
||||
COMPLETED_FORM_MAP.clear();
|
||||
|
||||
File emptyForm = getPaymentAccountForm(aliceClient, paymentMethodId);
|
||||
// A short cut over the API:
|
||||
// File emptyForm = PAYMENT_ACCOUNT_FORM.getPaymentAccountForm(paymentMethodId);
|
||||
log.debug("{} Empty form saved to {}",
|
||||
testName(testInfo),
|
||||
PAYMENT_ACCOUNT_FORM.getClickableURI(emptyForm));
|
||||
emptyForm.deleteOnExit();
|
||||
return emptyForm;
|
||||
}
|
||||
|
||||
protected final void verifyEmptyForm(File jsonForm, String paymentMethodId, String... fields) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> emptyForm = (Map<String, Object>) GSON.fromJson(
|
||||
PAYMENT_ACCOUNT_FORM.toJsonString(jsonForm),
|
||||
Object.class);
|
||||
assertNotNull(emptyForm);
|
||||
assertEquals(PROPERTY_VALUE_JSON_COMMENTS, emptyForm.get(PROPERTY_NAME_JSON_COMMENTS));
|
||||
assertEquals(paymentMethodId, emptyForm.get(PROPERTY_NAME_PAYMENT_METHOD_ID));
|
||||
assertEquals("your accountname", emptyForm.get(PROPERTY_NAME_ACCOUNT_NAME));
|
||||
for (String field : fields) {
|
||||
if (field.equals("country"))
|
||||
assertEquals("your two letter country code", emptyForm.get(field));
|
||||
else
|
||||
assertEquals("your " + field.toLowerCase(), emptyForm.get(field));
|
||||
}
|
||||
}
|
||||
|
||||
protected final void verifyCommonFormEntries(PaymentAccount paymentAccount) {
|
||||
// All PaymentAccount subclasses have paymentMethodId and an accountName fields.
|
||||
assertNotNull(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_PAYMENT_METHOD_ID), paymentAccount.getPaymentMethod().getId());
|
||||
assertTrue(paymentAccount.getCreationDate().getTime() > 0);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NAME), paymentAccount.getAccountName());
|
||||
}
|
||||
|
||||
protected final void verifyAccountSingleTradeCurrency(String expectedCurrencyCode, PaymentAccount paymentAccount) {
|
||||
assertNotNull(paymentAccount.getSingleTradeCurrency());
|
||||
assertEquals(expectedCurrencyCode, paymentAccount.getSingleTradeCurrency().getCode());
|
||||
}
|
||||
|
||||
protected final void verifyAccountTradeCurrencies(List<TradeCurrency> expectedTradeCurrencies,
|
||||
PaymentAccount paymentAccount) {
|
||||
assertNotNull(paymentAccount.getTradeCurrencies());
|
||||
assertArrayEquals(expectedTradeCurrencies.toArray(), paymentAccount.getTradeCurrencies().toArray());
|
||||
}
|
||||
|
||||
protected final void verifyUserPayloadHasPaymentAccountWithId(GrpcClient grpcClient,
|
||||
String paymentAccountId) {
|
||||
Optional<protobuf.PaymentAccount> paymentAccount = grpcClient.getPaymentAccounts()
|
||||
.stream()
|
||||
.filter(a -> a.getId().equals(paymentAccountId))
|
||||
.findFirst();
|
||||
assertTrue(paymentAccount.isPresent());
|
||||
}
|
||||
|
||||
protected final String getCompletedFormAsJsonString() {
|
||||
File completedForm = fillPaymentAccountForm();
|
||||
String jsonString = PAYMENT_ACCOUNT_FORM.toJsonString(completedForm);
|
||||
log.debug("Completed form: {}", jsonString);
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
private File fillPaymentAccountForm() {
|
||||
File tmpJsonForm = null;
|
||||
try {
|
||||
tmpJsonForm = File.createTempFile("temp_acct_form_",
|
||||
".json",
|
||||
Paths.get(getProperty("java.io.tmpdir")).toFile());
|
||||
JsonWriter writer = new JsonWriter(new OutputStreamWriter(new FileOutputStream(tmpJsonForm), UTF_8));
|
||||
writer.beginObject();
|
||||
|
||||
writer.name(PROPERTY_NAME_JSON_COMMENTS);
|
||||
writer.beginArray();
|
||||
for (String s : PROPERTY_VALUE_JSON_COMMENTS) {
|
||||
writer.value(s);
|
||||
}
|
||||
writer.endArray();
|
||||
|
||||
for (Map.Entry<String, Object> entry : COMPLETED_FORM_MAP.entrySet()) {
|
||||
String k = entry.getKey();
|
||||
Object v = entry.getValue();
|
||||
writer.name(k);
|
||||
writer.value(v.toString());
|
||||
}
|
||||
writer.endObject();
|
||||
writer.close();
|
||||
} catch (IOException ex) {
|
||||
log.error("", ex);
|
||||
fail(format("Could not write json file from form entries %s", COMPLETED_FORM_MAP));
|
||||
}
|
||||
tmpJsonForm.deleteOnExit();
|
||||
return tmpJsonForm;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,948 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.payment;
|
||||
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.payment.AdvancedCashAccount;
|
||||
import bisq.core.payment.AliPayAccount;
|
||||
import bisq.core.payment.AustraliaPayid;
|
||||
import bisq.core.payment.CashDepositAccount;
|
||||
import bisq.core.payment.ChaseQuickPayAccount;
|
||||
import bisq.core.payment.ClearXchangeAccount;
|
||||
import bisq.core.payment.F2FAccount;
|
||||
import bisq.core.payment.FasterPaymentsAccount;
|
||||
import bisq.core.payment.HalCashAccount;
|
||||
import bisq.core.payment.InteracETransferAccount;
|
||||
import bisq.core.payment.JapanBankAccount;
|
||||
import bisq.core.payment.MoneyBeamAccount;
|
||||
import bisq.core.payment.MoneyGramAccount;
|
||||
import bisq.core.payment.NationalBankAccount;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.PerfectMoneyAccount;
|
||||
import bisq.core.payment.PopmoneyAccount;
|
||||
import bisq.core.payment.PromptPayAccount;
|
||||
import bisq.core.payment.RevolutAccount;
|
||||
import bisq.core.payment.SameBankAccount;
|
||||
import bisq.core.payment.SepaAccount;
|
||||
import bisq.core.payment.SepaInstantAccount;
|
||||
import bisq.core.payment.SpecificBanksAccount;
|
||||
import bisq.core.payment.SwishAccount;
|
||||
import bisq.core.payment.TransferwiseAccount;
|
||||
import bisq.core.payment.USPostalMoneyOrderAccount;
|
||||
import bisq.core.payment.UpholdAccount;
|
||||
import bisq.core.payment.WeChatPayAccount;
|
||||
import bisq.core.payment.WesternUnionAccount;
|
||||
import bisq.core.payment.payload.BankAccountPayload;
|
||||
import bisq.core.payment.payload.CashDepositAccountPayload;
|
||||
import bisq.core.payment.payload.SameBankAccountPayload;
|
||||
import bisq.core.payment.payload.SpecificBanksAccountPayload;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.cli.TableFormat.formatPaymentAcctTbl;
|
||||
import static bisq.core.locale.CurrencyUtil.*;
|
||||
import static bisq.core.payment.payload.PaymentMethod.*;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
|
||||
@SuppressWarnings({"OptionalGetWithoutIsPresent", "ConstantConditions"})
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
try {
|
||||
setUpScaffold(bitcoind, alicedaemon);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateAdvancedCashAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, ADVANCED_CASH_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
ADVANCED_CASH_ID,
|
||||
PROPERTY_NAME_ACCOUNT_NR);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, ADVANCED_CASH_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Advanced Cash Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "0000 1111 2222");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Advanced Cash Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
AdvancedCashAccount paymentAccount = (AdvancedCashAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(getAllAdvancedCashCurrencies(), paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateAliPayAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, ALI_PAY_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
ALI_PAY_ID,
|
||||
PROPERTY_NAME_ACCOUNT_NR);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, ALI_PAY_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Ali Pay Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "2222 3333 4444");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
AliPayAccount paymentAccount = (AliPayAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("CNY", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateAustraliaPayidAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, AUSTRALIA_PAYID_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
AUSTRALIA_PAYID_ID,
|
||||
PROPERTY_NAME_BANK_ACCOUNT_NAME);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, AUSTRALIA_PAYID_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Australia Pay ID Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAY_ID, "123 456 789");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ACCOUNT_NAME, "Credit Union Australia");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Australia Pay ID Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
AustraliaPayid paymentAccount = (AustraliaPayid) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("AUD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_PAY_ID), paymentAccount.getPayid());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ACCOUNT_NAME), paymentAccount.getBankAccountName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCashDepositAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, CASH_DEPOSIT_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
CASH_DEPOSIT_ID,
|
||||
PROPERTY_NAME_ACCOUNT_NR,
|
||||
PROPERTY_NAME_ACCOUNT_TYPE,
|
||||
PROPERTY_NAME_BANK_ID,
|
||||
PROPERTY_NAME_BANK_NAME,
|
||||
PROPERTY_NAME_BRANCH_ID,
|
||||
PROPERTY_NAME_COUNTRY,
|
||||
PROPERTY_NAME_HOLDER_EMAIL,
|
||||
PROPERTY_NAME_HOLDER_NAME,
|
||||
PROPERTY_NAME_HOLDER_TAX_ID,
|
||||
PROPERTY_NAME_NATIONAL_ACCOUNT_ID,
|
||||
PROPERTY_NAME_REQUIREMENTS);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, CASH_DEPOSIT_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Cash Deposit Account");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "4444 5555 6666");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_TYPE, "Checking");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ID, "0001");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "BoF");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BRANCH_ID, "99-8888-7654");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "FR");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_EMAIL, "jean@johnson.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jean Johnson");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_TAX_ID, "123456789");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_NATIONAL_ACCOUNT_ID, "123456789");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_REQUIREMENTS, "Requirements...");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
CashDepositAccount paymentAccount = (CashDepositAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
|
||||
CashDepositAccountPayload payload = (CashDepositAccountPayload) paymentAccount.getPaymentAccountPayload();
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), payload.getAccountNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_TYPE), payload.getAccountType());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ID), payload.getBankId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BRANCH_ID), payload.getBranchId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_EMAIL), payload.getHolderEmail());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), payload.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_TAX_ID), payload.getHolderTaxId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_NATIONAL_ACCOUNT_ID), payload.getNationalAccountId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_REQUIREMENTS), payload.getRequirements());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateBrazilNationalBankAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, NATIONAL_BANK_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
NATIONAL_BANK_ID,
|
||||
PROPERTY_NAME_ACCOUNT_NR,
|
||||
PROPERTY_NAME_ACCOUNT_TYPE,
|
||||
PROPERTY_NAME_BANK_NAME,
|
||||
PROPERTY_NAME_BRANCH_ID,
|
||||
PROPERTY_NAME_COUNTRY,
|
||||
PROPERTY_NAME_HOLDER_NAME,
|
||||
PROPERTY_NAME_HOLDER_TAX_ID,
|
||||
PROPERTY_NAME_NATIONAL_ACCOUNT_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, NATIONAL_BANK_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Banco do Brasil");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "456789-87");
|
||||
// No BankId is required for BR.
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "Banco do Brasil");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BRANCH_ID, "456789-10");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "BR");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Joao da Silva");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_TAX_ID, "123456789");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_NATIONAL_ACCOUNT_ID, "123456789");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Banco do Brasil Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
NationalBankAccount paymentAccount = (NationalBankAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("BRL", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
|
||||
BankAccountPayload payload = (BankAccountPayload) paymentAccount.getPaymentAccountPayload();
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), payload.getAccountNr());
|
||||
// When no BankId is required, getBankId() returns bankName.
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BRANCH_ID), payload.getBranchId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), payload.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_TAX_ID), payload.getHolderTaxId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_NATIONAL_ACCOUNT_ID), payload.getNationalAccountId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateChaseQuickPayAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, CHASE_QUICK_PAY_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
CHASE_QUICK_PAY_ID,
|
||||
PROPERTY_NAME_EMAIL,
|
||||
PROPERTY_NAME_HOLDER_NAME);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, CHASE_QUICK_PAY_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Quick Pay Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "johndoe@quickpay.com");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "John Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
ChaseQuickPayAccount paymentAccount = (ChaseQuickPayAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateClearXChangeAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, CLEAR_X_CHANGE_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
CLEAR_X_CHANGE_ID,
|
||||
PROPERTY_NAME_EMAIL_OR_MOBILE_NR,
|
||||
PROPERTY_NAME_HOLDER_NAME);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, CLEAR_X_CHANGE_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "USD Zelle Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL_OR_MOBILE_NR, "jane@doe.com");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Zelle Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
ClearXchangeAccount paymentAccount = (ClearXchangeAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL_OR_MOBILE_NR), paymentAccount.getEmailOrMobileNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateF2FAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, F2F_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
F2F_ID,
|
||||
PROPERTY_NAME_COUNTRY,
|
||||
PROPERTY_NAME_CITY,
|
||||
PROPERTY_NAME_CONTACT,
|
||||
PROPERTY_NAME_EXTRA_INFO);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, F2F_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Conta Cara a Cara");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "BR");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_CITY, "Rio de Janeiro");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_CONTACT, "Freddy Beira Mar");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EXTRA_INFO, "So fim de semana");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
F2FAccount paymentAccount = (F2FAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("BRL", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_CITY), paymentAccount.getCity());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_CONTACT), paymentAccount.getContact());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EXTRA_INFO), paymentAccount.getExtraInfo());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateFasterPaymentsAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, FASTER_PAYMENTS_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
FASTER_PAYMENTS_ID,
|
||||
PROPERTY_NAME_ACCOUNT_NR,
|
||||
PROPERTY_NAME_SORT_CODE);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, FASTER_PAYMENTS_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Faster Payments Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "9999 8888 7777");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SORT_CODE, "3127");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Faster Payments Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
FasterPaymentsAccount paymentAccount = (FasterPaymentsAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("GBP", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SORT_CODE), paymentAccount.getSortCode());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateHalCashAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, HAL_CASH_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
HAL_CASH_ID,
|
||||
PROPERTY_NAME_MOBILE_NR);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, HAL_CASH_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Hal Cash Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_MOBILE_NR, "798 123 456");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
HalCashAccount paymentAccount = (HalCashAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_MOBILE_NR), paymentAccount.getMobileNr());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateInteracETransferAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, INTERAC_E_TRANSFER_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
INTERAC_E_TRANSFER_ID,
|
||||
PROPERTY_NAME_HOLDER_NAME,
|
||||
PROPERTY_NAME_EMAIL,
|
||||
PROPERTY_NAME_QUESTION,
|
||||
PROPERTY_NAME_ANSWER);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, INTERAC_E_TRANSFER_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Interac Transfer Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "John Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "john@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_QUESTION, "What is my dog's name?");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ANSWER, "Fido");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Interac Transfer Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
InteracETransferAccount paymentAccount = (InteracETransferAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("CAD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_QUESTION), paymentAccount.getQuestion());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ANSWER), paymentAccount.getAnswer());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateJapanBankAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, JAPAN_BANK_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
JAPAN_BANK_ID,
|
||||
PROPERTY_NAME_BANK_NAME,
|
||||
PROPERTY_NAME_BANK_CODE,
|
||||
PROPERTY_NAME_BANK_BRANCH_CODE,
|
||||
PROPERTY_NAME_BANK_BRANCH_NAME,
|
||||
PROPERTY_NAME_BANK_ACCOUNT_NAME,
|
||||
PROPERTY_NAME_BANK_ACCOUNT_TYPE,
|
||||
PROPERTY_NAME_BANK_ACCOUNT_NUMBER);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, JAPAN_BANK_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Fukuoka Account");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "Bank of Kyoto");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_CODE, "FKBKJPJT");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_BRANCH_CODE, "8100-8727");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_BRANCH_NAME, "Fukuoka Branch");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ACCOUNT_NAME, "Fukuoka Account");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ACCOUNT_TYPE, "Yen Account");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ACCOUNT_NUMBER, "8100-8727-0000");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
JapanBankAccount paymentAccount = (JapanBankAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("JPY", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_CODE), paymentAccount.getBankCode());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), paymentAccount.getBankName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_BRANCH_CODE), paymentAccount.getBankBranchCode());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_BRANCH_NAME), paymentAccount.getBankBranchName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ACCOUNT_NAME), paymentAccount.getBankAccountName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ACCOUNT_TYPE), paymentAccount.getBankAccountType());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ACCOUNT_NUMBER), paymentAccount.getBankAccountNumber());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateMoneyBeamAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, MONEY_BEAM_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
MONEY_BEAM_ID,
|
||||
PROPERTY_NAME_ACCOUNT_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, MONEY_BEAM_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Money Beam Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_ID, "MB 0000 1111");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Money Beam Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
MoneyBeamAccount paymentAccount = (MoneyBeamAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateMoneyGramAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, MONEY_GRAM_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
MONEY_GRAM_ID,
|
||||
PROPERTY_NAME_HOLDER_NAME,
|
||||
PROPERTY_NAME_EMAIL,
|
||||
PROPERTY_NAME_COUNTRY,
|
||||
PROPERTY_NAME_STATE);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, MONEY_GRAM_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Money Gram Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "John Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "john@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "US");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_STATE, "NY");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
MoneyGramAccount paymentAccount = (MoneyGramAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(getAllMoneyGramCurrencies(), paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_STATE), paymentAccount.getState());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePerfectMoneyAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, PERFECT_MONEY_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
PERFECT_MONEY_ID,
|
||||
PROPERTY_NAME_ACCOUNT_NR);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, PERFECT_MONEY_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Perfect Money Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "PM 0000 1111");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Perfect Money Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
PerfectMoneyAccount paymentAccount = (PerfectMoneyAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePopmoneyAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, POPMONEY_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
POPMONEY_ID,
|
||||
PROPERTY_NAME_ACCOUNT_ID,
|
||||
PROPERTY_NAME_HOLDER_NAME);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, POPMONEY_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Pop Money Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_ID, "POPMONEY 0000 1111");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
PopmoneyAccount paymentAccount = (PopmoneyAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePromptPayAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, PROMPT_PAY_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
PROMPT_PAY_ID,
|
||||
PROPERTY_NAME_PROMPT_PAY_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, PROMPT_PAY_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Prompt Pay Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PROMPT_PAY_ID, "PP 0000 1111");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Prompt Pay Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
PromptPayAccount paymentAccount = (PromptPayAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("THB", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_PROMPT_PAY_ID), paymentAccount.getPromptPayId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateRevolutAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, REVOLUT_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
REVOLUT_ID,
|
||||
PROPERTY_NAME_USERNAME);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, REVOLUT_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Revolut Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_USERNAME, "revolut123");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
RevolutAccount paymentAccount = (RevolutAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(getAllRevolutCurrencies(), paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_USERNAME), paymentAccount.getUserName());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSameBankAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, SAME_BANK_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
SAME_BANK_ID,
|
||||
PROPERTY_NAME_ACCOUNT_NR,
|
||||
PROPERTY_NAME_ACCOUNT_TYPE,
|
||||
PROPERTY_NAME_BANK_NAME,
|
||||
PROPERTY_NAME_BRANCH_ID,
|
||||
PROPERTY_NAME_COUNTRY,
|
||||
PROPERTY_NAME_HOLDER_NAME,
|
||||
PROPERTY_NAME_HOLDER_TAX_ID,
|
||||
PROPERTY_NAME_NATIONAL_ACCOUNT_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SAME_BANK_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Same Bank Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "000 1 4567");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_TYPE, "Checking");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "HSBC");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BRANCH_ID, "111");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "GB");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_TAX_ID, "123456789");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_NATIONAL_ACCOUNT_ID, "123456789");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Same Bank Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
SameBankAccount paymentAccount = (SameBankAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("GBP", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
SameBankAccountPayload payload = (SameBankAccountPayload) paymentAccount.getPaymentAccountPayload();
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), payload.getAccountNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_TYPE), payload.getAccountType());
|
||||
// The bankId == bankName because bank id is not required in the UK.
|
||||
assertEquals(payload.getBankId(), payload.getBankName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BRANCH_ID), payload.getBranchId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), payload.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_TAX_ID), payload.getHolderTaxId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_NATIONAL_ACCOUNT_ID), payload.getNationalAccountId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSepaInstantAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, SEPA_INSTANT_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
SEPA_INSTANT_ID,
|
||||
PROPERTY_NAME_COUNTRY,
|
||||
PROPERTY_NAME_HOLDER_NAME,
|
||||
PROPERTY_NAME_IBAN,
|
||||
PROPERTY_NAME_BIC);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SEPA_INSTANT_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Conta Sepa Instant");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "PT");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jose da Silva");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_IBAN, "909-909");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BIC, "909");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
SepaInstantAccount paymentAccount = (SepaInstantAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_IBAN), paymentAccount.getIban());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BIC), paymentAccount.getBic());
|
||||
// bankId == bic
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BIC), paymentAccount.getBankId());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSepaAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, SEPA_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
SEPA_ID,
|
||||
PROPERTY_NAME_COUNTRY,
|
||||
PROPERTY_NAME_HOLDER_NAME,
|
||||
PROPERTY_NAME_IBAN,
|
||||
PROPERTY_NAME_BIC);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SEPA_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Conta Sepa");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "PT");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jose da Silva");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_IBAN, "909-909");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BIC, "909");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Conta Sepa Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
SepaAccount paymentAccount = (SepaAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_IBAN), paymentAccount.getIban());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BIC), paymentAccount.getBic());
|
||||
// bankId == bic
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BIC), paymentAccount.getBankId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSpecificBanksAccount(TestInfo testInfo) {
|
||||
// TODO Supporting set of accepted banks may require some refactoring
|
||||
// of the SpecificBanksAccount and SpecificBanksAccountPayload classes, i.e.,
|
||||
// public void setAcceptedBanks(String... bankNames) { ... }
|
||||
File emptyForm = getEmptyForm(testInfo, SPECIFIC_BANKS_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
SPECIFIC_BANKS_ID,
|
||||
PROPERTY_NAME_ACCOUNT_NR,
|
||||
PROPERTY_NAME_ACCOUNT_TYPE,
|
||||
PROPERTY_NAME_BANK_NAME,
|
||||
PROPERTY_NAME_BRANCH_ID,
|
||||
PROPERTY_NAME_COUNTRY,
|
||||
PROPERTY_NAME_HOLDER_NAME,
|
||||
PROPERTY_NAME_HOLDER_TAX_ID,
|
||||
PROPERTY_NAME_NATIONAL_ACCOUNT_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SPECIFIC_BANKS_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Specific Banks Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "000 1 4567");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_TYPE, "Checking");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "HSBC");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BRANCH_ID, "111");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "GB");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_TAX_ID, "123456789");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_NATIONAL_ACCOUNT_ID, "123456789");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
SpecificBanksAccount paymentAccount = (SpecificBanksAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("GBP", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
SpecificBanksAccountPayload payload = (SpecificBanksAccountPayload) paymentAccount.getPaymentAccountPayload();
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), payload.getAccountNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_TYPE), payload.getAccountType());
|
||||
// The bankId == bankName because bank id is not required in the UK.
|
||||
assertEquals(payload.getBankId(), payload.getBankName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BRANCH_ID), payload.getBranchId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), payload.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_TAX_ID), payload.getHolderTaxId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_NATIONAL_ACCOUNT_ID), payload.getNationalAccountId());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSwishAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, SWISH_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
SWISH_ID,
|
||||
PROPERTY_NAME_MOBILE_NR,
|
||||
PROPERTY_NAME_HOLDER_NAME);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SWISH_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Swish Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_MOBILE_NR, "+46 7 6060 0101");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Swish Acct Holder");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Swish Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
SwishAccount paymentAccount = (SwishAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("SEK", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_MOBILE_NR), paymentAccount.getMobileNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTransferwiseAccountWith1TradeCurrency(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, TRANSFERWISE_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
TRANSFERWISE_ID,
|
||||
PROPERTY_NAME_EMAIL);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Transferwise Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "eur");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
assertEquals(1, paymentAccount.getTradeCurrencies().size());
|
||||
TradeCurrency expectedCurrency = getTradeCurrency("EUR").get();
|
||||
assertEquals(expectedCurrency, paymentAccount.getSelectedTradeCurrency());
|
||||
List<TradeCurrency> expectedTradeCurrencies = singletonList(expectedCurrency);
|
||||
verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTransferwiseAccountWith10TradeCurrencies(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, TRANSFERWISE_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
TRANSFERWISE_ID,
|
||||
PROPERTY_NAME_EMAIL);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Transferwise Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "ars, cad, hrk, czk, eur, hkd, idr, jpy, chf, nzd");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
assertEquals(10, paymentAccount.getTradeCurrencies().size());
|
||||
List<TradeCurrency> expectedTradeCurrencies = new ArrayList<>() {{
|
||||
add(getTradeCurrency("ARS").get()); // 1st in list = selected ccy
|
||||
add(getTradeCurrency("CAD").get());
|
||||
add(getTradeCurrency("HRK").get());
|
||||
add(getTradeCurrency("CZK").get());
|
||||
add(getTradeCurrency("EUR").get());
|
||||
add(getTradeCurrency("HKD").get());
|
||||
add(getTradeCurrency("IDR").get());
|
||||
add(getTradeCurrency("JPY").get());
|
||||
add(getTradeCurrency("CHF").get());
|
||||
add(getTradeCurrency("NZD").get());
|
||||
}};
|
||||
verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount);
|
||||
TradeCurrency expectedSelectedCurrency = expectedTradeCurrencies.get(0);
|
||||
assertEquals(expectedSelectedCurrency, paymentAccount.getSelectedTradeCurrency());
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTransferwiseAccountWithInvalidBrlTradeCurrencyShouldThrowException(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, TRANSFERWISE_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
TRANSFERWISE_ID,
|
||||
PROPERTY_NAME_EMAIL);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Transferwise Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "eur, hkd, idr, jpy, chf, nzd, brl, gbp");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
createPaymentAccount(aliceClient, jsonString));
|
||||
assertEquals("INVALID_ARGUMENT: BRL is not a member of valid currencies list",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTransferwiseAccountWithoutTradeCurrenciesShouldThrowException(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, TRANSFERWISE_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
TRANSFERWISE_ID,
|
||||
PROPERTY_NAME_EMAIL);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Transferwise Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
createPaymentAccount(aliceClient, jsonString));
|
||||
assertEquals("INVALID_ARGUMENT: no trade currencies defined for transferwise payment account",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateUpholdAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, UPHOLD_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
UPHOLD_ID,
|
||||
PROPERTY_NAME_ACCOUNT_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, UPHOLD_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Uphold Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_ID, "UA 9876");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Uphold Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
UpholdAccount paymentAccount = (UpholdAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(getAllUpholdCurrencies(), paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateUSPostalMoneyOrderAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, US_POSTAL_MONEY_ORDER_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
US_POSTAL_MONEY_ORDER_ID,
|
||||
PROPERTY_NAME_HOLDER_NAME,
|
||||
PROPERTY_NAME_POSTAL_ADDRESS);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, US_POSTAL_MONEY_ORDER_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Bubba's Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Bubba");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_POSTAL_ADDRESS, "000 Westwood Terrace Austin, TX 78700");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
USPostalMoneyOrderAccount paymentAccount = (USPostalMoneyOrderAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_POSTAL_ADDRESS), paymentAccount.getPostalAddress());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWeChatPayAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, WECHAT_PAY_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
WECHAT_PAY_ID,
|
||||
PROPERTY_NAME_ACCOUNT_NR);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, WECHAT_PAY_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "WeChat Pay Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "WC 1234");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored WeChat Pay Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
WeChatPayAccount paymentAccount = (WeChatPayAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("CNY", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWesternUnionAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, WESTERN_UNION_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
WESTERN_UNION_ID,
|
||||
PROPERTY_NAME_HOLDER_NAME,
|
||||
PROPERTY_NAME_CITY,
|
||||
PROPERTY_NAME_STATE,
|
||||
PROPERTY_NAME_COUNTRY,
|
||||
PROPERTY_NAME_EMAIL);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, WESTERN_UNION_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Western Union Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "Jane Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_CITY, "Fargo");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_STATE, "North Dakota");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "US");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
WesternUnionAccount paymentAccount = (WesternUnionAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_CITY), paymentAccount.getCity());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_STATE), paymentAccount.getState());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
|
||||
private void print(PaymentAccount paymentAccount) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
|
||||
log.debug("\n{}", formatPaymentAcctTbl(singletonList(paymentAccount.toProtoMessage())));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package bisq.apitest.method.payment;
|
||||
|
||||
import protobuf.PaymentMethod;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class GetPaymentMethodsTest extends MethodTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
try {
|
||||
setUpScaffold(bitcoind, alicedaemon);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testGetPaymentMethods() {
|
||||
List<String> paymentMethodIds = aliceClient.getPaymentMethods()
|
||||
.stream()
|
||||
.map(PaymentMethod::getId)
|
||||
.collect(Collectors.toList());
|
||||
assertTrue(paymentMethodIds.size() >= 20);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package bisq.apitest.method.trade;
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
import static bisq.cli.CurrencyFormat.formatBsqAmount;
|
||||
import static bisq.cli.TradeFormat.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
import bisq.cli.GrpcClient;
|
||||
|
||||
public class AbstractTradeTest extends AbstractOfferTest {
|
||||
|
||||
public static final ExpectedProtocolStatus EXPECTED_PROTOCOL_STATUS = new ExpectedProtocolStatus();
|
||||
|
||||
// A Trade ID cache for use in @Test sequences.
|
||||
protected static String tradeId;
|
||||
|
||||
protected final Supplier<Integer> maxTradeStateAndPhaseChecks = () -> isLongRunningTest ? 10 : 2;
|
||||
|
||||
@BeforeAll
|
||||
public static void initStaticFixtures() {
|
||||
EXPECTED_PROTOCOL_STATUS.init();
|
||||
}
|
||||
|
||||
protected final TradeInfo takeAlicesOffer(String offerId,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode) {
|
||||
return bobClient.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected final TradeInfo takeBobsOffer(String offerId,
|
||||
String paymentAccountId,
|
||||
String takerFeeCurrencyCode) {
|
||||
return aliceClient.takeOffer(offerId, paymentAccountId, takerFeeCurrencyCode);
|
||||
}
|
||||
|
||||
protected final void verifyExpectedProtocolStatus(TradeInfo trade) {
|
||||
assertNotNull(trade);
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.state.name(), trade.getState());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.phase.name(), trade.getPhase());
|
||||
|
||||
if (!isLongRunningTest)
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositPublished, trade.getIsDepositPublished());
|
||||
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositConfirmed, trade.getIsDepositConfirmed());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isFiatSent, trade.getIsFiatSent());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isFiatReceived, trade.getIsFiatReceived());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isPayoutPublished, trade.getIsPayoutPublished());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isWithdrawn, trade.getIsWithdrawn());
|
||||
}
|
||||
|
||||
protected final void sendBsqPayment(Logger log,
|
||||
GrpcClient grpcClient,
|
||||
TradeInfo trade) {
|
||||
var contract = trade.getContract();
|
||||
String receiverAddress = contract.getIsBuyerMakerAndSellerTaker()
|
||||
? contract.getTakerPaymentAccountPayload().getAddress()
|
||||
: contract.getMakerPaymentAccountPayload().getAddress();
|
||||
String sendBsqAmount = formatBsqAmount(trade.getOffer().getVolume());
|
||||
log.info("Sending {} BSQ to address {}", sendBsqAmount, receiverAddress);
|
||||
grpcClient.sendBsq(receiverAddress, sendBsqAmount, "");
|
||||
}
|
||||
|
||||
protected final void verifyBsqPaymentHasBeenReceived(Logger log,
|
||||
GrpcClient grpcClient,
|
||||
TradeInfo trade) {
|
||||
var contract = trade.getContract();
|
||||
var bsqSats = trade.getOffer().getVolume();
|
||||
var receiveAmountAsString = formatBsqAmount(bsqSats);
|
||||
var address = contract.getIsBuyerMakerAndSellerTaker()
|
||||
? contract.getTakerPaymentAccountPayload().getAddress()
|
||||
: contract.getMakerPaymentAccountPayload().getAddress();
|
||||
boolean receivedBsqSatoshis = grpcClient.verifyBsqSentToAddress(address, receiveAmountAsString);
|
||||
if (receivedBsqSatoshis)
|
||||
log.info("Payment of {} BSQ was received to address {} for trade with id {}.",
|
||||
receiveAmountAsString,
|
||||
address,
|
||||
trade.getTradeId());
|
||||
else
|
||||
fail(String.format("Payment of %s BSQ was was not sent to address %s for trade with id %s.",
|
||||
receiveAmountAsString,
|
||||
address,
|
||||
trade.getTradeId()));
|
||||
}
|
||||
|
||||
protected final void logTrade(Logger log,
|
||||
TestInfo testInfo,
|
||||
String description,
|
||||
TradeInfo trade) {
|
||||
logTrade(log, testInfo, description, trade, false);
|
||||
}
|
||||
|
||||
protected final void logTrade(Logger log,
|
||||
TestInfo testInfo,
|
||||
String description,
|
||||
TradeInfo trade,
|
||||
boolean force) {
|
||||
if (force)
|
||||
log.info(String.format("%s %s%n%s",
|
||||
testName(testInfo),
|
||||
description.toUpperCase(),
|
||||
format(trade)));
|
||||
else if (log.isDebugEnabled()) {
|
||||
log.debug(String.format("%s %s%n%s",
|
||||
testName(testInfo),
|
||||
description.toUpperCase(),
|
||||
format(trade)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package bisq.apitest.method.trade;
|
||||
|
||||
import bisq.core.trade.Trade;
|
||||
|
||||
/**
|
||||
* A test fixture encapsulating expected trade protocol status.
|
||||
* Status flags should be cleared via init() before starting a new trade protocol.
|
||||
*/
|
||||
public class ExpectedProtocolStatus {
|
||||
Trade.State state;
|
||||
Trade.Phase phase;
|
||||
boolean isDepositPublished;
|
||||
boolean isDepositConfirmed;
|
||||
boolean isFiatSent;
|
||||
boolean isFiatReceived;
|
||||
boolean isPayoutPublished;
|
||||
boolean isWithdrawn;
|
||||
|
||||
public ExpectedProtocolStatus setState(Trade.State state) {
|
||||
this.state = state;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedProtocolStatus setPhase(Trade.Phase phase) {
|
||||
this.phase = phase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedProtocolStatus setDepositPublished(boolean depositPublished) {
|
||||
isDepositPublished = depositPublished;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedProtocolStatus setDepositConfirmed(boolean depositConfirmed) {
|
||||
isDepositConfirmed = depositConfirmed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedProtocolStatus setFiatSent(boolean fiatSent) {
|
||||
isFiatSent = fiatSent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedProtocolStatus setFiatReceived(boolean fiatReceived) {
|
||||
isFiatReceived = fiatReceived;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedProtocolStatus setPayoutPublished(boolean payoutPublished) {
|
||||
isPayoutPublished = payoutPublished;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedProtocolStatus setWithdrawn(boolean withdrawn) {
|
||||
isWithdrawn = withdrawn;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
state = null;
|
||||
phase = null;
|
||||
isDepositPublished = false;
|
||||
isDepositConfirmed = false;
|
||||
isFiatSent = false;
|
||||
isFiatReceived = false;
|
||||
isPayoutPublished = false;
|
||||
isWithdrawn = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.trade;
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
|
||||
import static bisq.core.trade.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
|
||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
|
||||
// https://github.com/ghubstan/bisq/blob/master/cli/src/main/java/bisq/cli/TradeFormat.java
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class TakeBuyBSQOfferTest extends AbstractTradeTest {
|
||||
|
||||
// Alice is maker / bsq buyer (btc seller), Bob is taker / bsq seller (btc buyer).
|
||||
|
||||
// Maker and Taker fees are in BSQ.
|
||||
private static final String TRADE_FEE_CURRENCY_CODE = BSQ;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
AbstractOfferTest.setUp();
|
||||
createBsqPaymentAccounts();
|
||||
EXPECTED_PROTOCOL_STATUS.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testTakeAlicesSellBTCForBSQOffer(final TestInfo testInfo) {
|
||||
try {
|
||||
// Alice is going to BUY BSQ, but the Offer direction = SELL because it is a
|
||||
// BTC trade; Alice will SELL BTC for BSQ. Bob will send Alice BSQ.
|
||||
// Confused me, but just need to remember there are only BTC offers.
|
||||
var btcTradeDirection = SELL.name();
|
||||
var alicesOffer = aliceClient.createFixedPricedOffer(btcTradeDirection,
|
||||
BSQ,
|
||||
15_000_000L,
|
||||
7_500_000L,
|
||||
"0.000035", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesBsqAcct.getId(),
|
||||
TRADE_FEE_CURRENCY_CODE);
|
||||
log.info("ALICE'S BUY BSQ (SELL BTC) OFFER:\n{}", formatOfferTable(singletonList(alicesOffer), BSQ));
|
||||
genBtcBlocksThenWait(1, 5000);
|
||||
var offerId = alicesOffer.getId();
|
||||
assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
var alicesBsqOffers = aliceClient.getMyCryptoCurrencyOffers(btcTradeDirection, BSQ);
|
||||
assertEquals(1, alicesBsqOffers.size());
|
||||
|
||||
var trade = takeAlicesOffer(offerId, bobsBsqAcct.getId(), TRADE_FEE_CURRENCY_CODE);
|
||||
assertNotNull(trade);
|
||||
assertEquals(offerId, trade.getTradeId());
|
||||
assertFalse(trade.getIsCurrencyForTakerFeeBtc());
|
||||
// Cache the trade id for the other tests.
|
||||
tradeId = trade.getTradeId();
|
||||
|
||||
genBtcBlocksThenWait(1, 6000);
|
||||
alicesBsqOffers = aliceClient.getMyBsqOffersSortedByDate();
|
||||
assertEquals(0, alicesBsqOffers.size());
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
trade = bobClient.getTrade(trade.getTradeId());
|
||||
|
||||
if (!trade.getIsDepositConfirmed()) {
|
||||
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
|
||||
trade.getShortId(),
|
||||
trade.getDepositTxId(),
|
||||
i);
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
continue;
|
||||
} else {
|
||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
|
||||
.setPhase(DEPOSIT_CONFIRMED)
|
||||
.setDepositPublished(true)
|
||||
.setDepositConfirmed(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after taking offer and deposit confirmed", trade);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
genBtcBlocksThenWait(1, 2500);
|
||||
|
||||
if (!trade.getIsDepositConfirmed()) {
|
||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId), true);
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId), true);
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name())
|
||||
&& t.getPhase().equals(DEPOSIT_CONFIRMED.name());
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot send payment started msg yet.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
sleep(10_000);
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, could not send payment started msg.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
sendBsqPayment(log, bobClient, trade);
|
||||
genBtcBlocksThenWait(1, 2500);
|
||||
bobClient.confirmPaymentStarted(trade.getTradeId());
|
||||
sleep(6000);
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
|
||||
if (!trade.getIsFiatSent()) {
|
||||
log.warn("Alice still waiting for trade {} SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}",
|
||||
trade.getShortId(),
|
||||
i);
|
||||
sleep(5000);
|
||||
continue;
|
||||
} else {
|
||||
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_PAID.
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG)
|
||||
.setPhase(FIAT_SENT)
|
||||
.setFiatSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Sent)", aliceClient.getTrade(tradeId), true);
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Sent)", bobClient.getTrade(tradeId), true);
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name())
|
||||
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_SENT.name()));
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
sleep(1000 * 10);
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, cannot confirm payment received.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
sleep(2000);
|
||||
verifyBsqPaymentHasBeenReceived(log, aliceClient, trade);
|
||||
|
||||
aliceClient.confirmPaymentReceived(trade.getTradeId());
|
||||
sleep(3000);
|
||||
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED)
|
||||
.setPayoutPublished(true)
|
||||
.setFiatReceived(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Received)", aliceClient.getTrade(tradeId), true);
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Received)", bobClient.getTrade(tradeId), true);
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testBobsKeepFunds(final TestInfo testInfo) {
|
||||
try {
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
logTrade(log, testInfo, "Alice's view before keeping funds", trade);
|
||||
|
||||
bobClient.keepFunds(tradeId);
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after keeping funds", trade);
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId), true);
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId), true);
|
||||
|
||||
var alicesBalances = aliceClient.getBalances();
|
||||
log.info("{} Alice's Current Balance:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(alicesBalances));
|
||||
var bobsBalances = bobClient.getBalances();
|
||||
log.info("{} Bob's Current Balance:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(bobsBalances));
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.trade;
|
||||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.State.*;
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||
|
||||
// Alice is maker/buyer, Bob is taker/seller.
|
||||
|
||||
// Maker and Taker fees are in BSQ.
|
||||
private static final String TRADE_FEE_CURRENCY_CODE = BSQ;
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testTakeAlicesBuyOffer(final TestInfo testInfo) {
|
||||
try {
|
||||
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
var alicesOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||
"usd",
|
||||
12_500_000L,
|
||||
12_500_000L, // min-amount = amount
|
||||
0.00,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesUsdAccount.getId(),
|
||||
TRADE_FEE_CURRENCY_CODE);
|
||||
var offerId = alicesOffer.getId();
|
||||
assertFalse(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
// Wait for Alice's AddToOfferBook task.
|
||||
// Wait times vary; my logs show >= 2 second delay.
|
||||
sleep(3000); // TODO loop instead of hard code wait time
|
||||
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), "usd");
|
||||
assertEquals(1, alicesUsdOffers.size());
|
||||
|
||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE);
|
||||
assertNotNull(trade);
|
||||
assertEquals(offerId, trade.getTradeId());
|
||||
assertFalse(trade.getIsCurrencyForTakerFeeBtc());
|
||||
// Cache the trade id for the other tests.
|
||||
tradeId = trade.getTradeId();
|
||||
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), "usd");
|
||||
assertEquals(0, alicesUsdOffers.size());
|
||||
|
||||
genBtcBlocksThenWait(1, 2500);
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
trade = bobClient.getTrade(trade.getTradeId());
|
||||
|
||||
if (!trade.getIsDepositConfirmed()) {
|
||||
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
|
||||
trade.getShortId(),
|
||||
trade.getDepositTxId(),
|
||||
i);
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
continue;
|
||||
} else {
|
||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
|
||||
.setPhase(DEPOSIT_CONFIRMED)
|
||||
.setDepositPublished(true)
|
||||
.setDepositConfirmed(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!trade.getIsDepositConfirmed()) {
|
||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name())
|
||||
&& t.getPhase().equals(DEPOSIT_CONFIRMED.name());
|
||||
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment started yet.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
// fail("Bad trade state and phase.");
|
||||
sleep(1000 * 10);
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, could not confirm payment started.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
aliceClient.confirmPaymentStarted(trade.getTradeId());
|
||||
sleep(6000);
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
|
||||
if (!trade.getIsFiatSent()) {
|
||||
log.warn("Alice still waiting for trade {} BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}",
|
||||
trade.getShortId(),
|
||||
i);
|
||||
sleep(5000);
|
||||
continue;
|
||||
} else {
|
||||
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
|
||||
.setPhase(FIAT_SENT)
|
||||
.setFiatSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment sent", trade);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name())
|
||||
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_SENT.name()));
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
// fail("Bad trade state and phase.");
|
||||
sleep(1000 * 10);
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, cannot confirm payment received.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
bobClient.confirmPaymentReceived(trade.getTradeId());
|
||||
sleep(3000);
|
||||
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
// Note: offer.state == available
|
||||
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED)
|
||||
.setPayoutPublished(true)
|
||||
.setFiatReceived(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after confirming fiat payment received", trade);
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testAlicesKeepFunds(final TestInfo testInfo) {
|
||||
try {
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
logTrade(log, testInfo, "Alice's view before keeping funds", trade);
|
||||
|
||||
aliceClient.keepFunds(tradeId);
|
||||
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after keeping funds", trade);
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId), true);
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId), true);
|
||||
|
||||
var alicesBalances = aliceClient.getBalances();
|
||||
log.info("{} Alice's Current Balance:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(alicesBalances));
|
||||
var bobsBalances = bobClient.getBalances();
|
||||
log.info("{} Bob's Current Balance:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(bobsBalances));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.trade;
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BSQ;
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
||||
import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN;
|
||||
import static bisq.core.trade.Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
|
||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static bisq.core.trade.Trade.State.WITHDRAW_COMPLETED;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class TakeSellBSQOfferTest extends AbstractTradeTest {
|
||||
|
||||
// Alice is maker / bsq seller (btc buyer), Bob is taker / bsq buyer (btc seller).
|
||||
|
||||
// Maker and Taker fees are in BTC.
|
||||
private static final String TRADE_FEE_CURRENCY_CODE = BTC;
|
||||
|
||||
private static final String WITHDRAWAL_TX_MEMO = "Bob's trade withdrawal";
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
AbstractOfferTest.setUp();
|
||||
createBsqPaymentAccounts();
|
||||
EXPECTED_PROTOCOL_STATUS.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testTakeAlicesBuyBTCForBSQOffer(final TestInfo testInfo) {
|
||||
try {
|
||||
// Alice is going to SELL BSQ, but the Offer direction = BUY because it is a
|
||||
// BTC trade; Alice will BUY BTC for BSQ. Alice will send Bob BSQ.
|
||||
// Confused me, but just need to remember there are only BTC offers.
|
||||
var btcTradeDirection = BUY.name();
|
||||
var alicesOffer = aliceClient.createFixedPricedOffer(btcTradeDirection,
|
||||
BSQ,
|
||||
15_000_000L,
|
||||
7_500_000L,
|
||||
"0.000035", // FIXED PRICE IN BTC (satoshis) FOR 1 BSQ
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesBsqAcct.getId(),
|
||||
TRADE_FEE_CURRENCY_CODE);
|
||||
log.info("ALICE'S SELL BSQ (BUY BTC) OFFER:\n{}", formatOfferTable(singletonList(alicesOffer), BSQ));
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
var offerId = alicesOffer.getId();
|
||||
assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
var alicesBsqOffers = aliceClient.getMyCryptoCurrencyOffers(btcTradeDirection, BSQ);
|
||||
assertEquals(1, alicesBsqOffers.size());
|
||||
|
||||
var trade = takeAlicesOffer(offerId, bobsBsqAcct.getId(), TRADE_FEE_CURRENCY_CODE);
|
||||
assertNotNull(trade);
|
||||
assertEquals(offerId, trade.getTradeId());
|
||||
assertTrue(trade.getIsCurrencyForTakerFeeBtc());
|
||||
// Cache the trade id for the other tests.
|
||||
tradeId = trade.getTradeId();
|
||||
|
||||
genBtcBlocksThenWait(1, 6000);
|
||||
alicesBsqOffers = aliceClient.getMyBsqOffersSortedByDate();
|
||||
assertEquals(0, alicesBsqOffers.size());
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
trade = bobClient.getTrade(trade.getTradeId());
|
||||
|
||||
if (!trade.getIsDepositConfirmed()) {
|
||||
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
|
||||
trade.getShortId(),
|
||||
trade.getDepositTxId(),
|
||||
i);
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
continue;
|
||||
} else {
|
||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
|
||||
.setPhase(DEPOSIT_CONFIRMED)
|
||||
.setDepositPublished(true)
|
||||
.setDepositConfirmed(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after taking offer and deposit confirmed", trade);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
genBtcBlocksThenWait(1, 2500);
|
||||
|
||||
if (!trade.getIsDepositConfirmed()) {
|
||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Seller View", aliceClient.getTrade(tradeId), true);
|
||||
logTrade(log, testInfo, "Bob's Taker/Buyer View", bobClient.getTrade(tradeId), true);
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name())
|
||||
&& t.getPhase().equals(DEPOSIT_CONFIRMED.name());
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot send payment started msg yet.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
sleep(10_000);
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, could not send payment started msg.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
sendBsqPayment(log, aliceClient, trade);
|
||||
genBtcBlocksThenWait(1, 2500);
|
||||
aliceClient.confirmPaymentStarted(trade.getTradeId());
|
||||
sleep(6000);
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
|
||||
if (!trade.getIsFiatSent()) {
|
||||
log.warn("Bob still waiting for trade {} SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}",
|
||||
trade.getShortId(),
|
||||
i);
|
||||
sleep(5000);
|
||||
continue;
|
||||
} else {
|
||||
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_PAID.
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG)
|
||||
.setPhase(FIAT_SENT)
|
||||
.setFiatSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Seller View (Payment Sent)", aliceClient.getTrade(tradeId), true);
|
||||
logTrade(log, testInfo, "Bob's Taker/Buyer View (Payment Sent)", bobClient.getTrade(tradeId), true);
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name())
|
||||
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_SENT.name()));
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
sleep(1000 * 10);
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, cannot confirm payment received.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
sleep(2000);
|
||||
verifyBsqPaymentHasBeenReceived(log, bobClient, trade);
|
||||
|
||||
bobClient.confirmPaymentReceived(trade.getTradeId());
|
||||
sleep(3000);
|
||||
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_PAID.
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED)
|
||||
.setPayoutPublished(true)
|
||||
.setFiatReceived(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Seller View (Payment Received)", aliceClient.getTrade(tradeId), true);
|
||||
logTrade(log, testInfo, "Bob's Taker/Buyer View (Payment Received)", bobClient.getTrade(tradeId), true);
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testAlicesBtcWithdrawalToExternalAddress(final TestInfo testInfo) {
|
||||
try {
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
logTrade(log, testInfo, "Alice's view before withdrawing BTC funds to external wallet", trade);
|
||||
|
||||
String toAddress = bitcoinCli.getNewBtcAddress();
|
||||
aliceClient.withdrawFunds(tradeId, toAddress, WITHDRAWAL_TX_MEMO);
|
||||
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
EXPECTED_PROTOCOL_STATUS.setState(WITHDRAW_COMPLETED)
|
||||
.setPhase(WITHDRAWN)
|
||||
.setWithdrawn(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after withdrawing funds to external wallet", trade);
|
||||
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Seller View (Done)", aliceClient.getTrade(tradeId), true);
|
||||
logTrade(log, testInfo, "Bob's Taker/Buyer View (Done)", bobClient.getTrade(tradeId), true);
|
||||
|
||||
var alicesBalances = aliceClient.getBalances();
|
||||
log.info("{} Alice's Current Balance:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(alicesBalances));
|
||||
var bobsBalances = bobClient.getBalances();
|
||||
log.info("{} Bob's Current Balance:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(bobsBalances));
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.trade;
|
||||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED;
|
||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
||||
import static bisq.core.trade.Trade.State.*;
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.Offer.State.OFFER_FEE_PAID;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||
|
||||
// Alice is maker/seller, Bob is taker/buyer.
|
||||
|
||||
// Maker and Taker fees are in BTC.
|
||||
private static final String TRADE_FEE_CURRENCY_CODE = BTC;
|
||||
|
||||
private static final String WITHDRAWAL_TX_MEMO = "Bob's trade withdrawal";
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testTakeAlicesSellOffer(final TestInfo testInfo) {
|
||||
try {
|
||||
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
var alicesOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
||||
"usd",
|
||||
12_500_000L,
|
||||
12_500_000L, // min-amount = amount
|
||||
0.00,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesUsdAccount.getId(),
|
||||
TRADE_FEE_CURRENCY_CODE);
|
||||
var offerId = alicesOffer.getId();
|
||||
assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
// Wait for Alice's AddToOfferBook task.
|
||||
// Wait times vary; my logs show >= 2 second delay, but taking sell offers
|
||||
// seems to require more time to prepare.
|
||||
sleep(3000); // TODO loop instead of hard code wait time
|
||||
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(SELL.name(), "usd");
|
||||
assertEquals(1, alicesUsdOffers.size());
|
||||
|
||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId(), TRADE_FEE_CURRENCY_CODE);
|
||||
assertNotNull(trade);
|
||||
assertEquals(offerId, trade.getTradeId());
|
||||
assertTrue(trade.getIsCurrencyForTakerFeeBtc());
|
||||
// Cache the trade id for the other tests.
|
||||
tradeId = trade.getTradeId();
|
||||
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
var takeableUsdOffers = bobClient.getOffersSortedByDate(SELL.name(), "usd");
|
||||
assertEquals(0, takeableUsdOffers.size());
|
||||
|
||||
genBtcBlocksThenWait(1, 2500);
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
trade = bobClient.getTrade(trade.getTradeId());
|
||||
|
||||
if (!trade.getIsDepositConfirmed()) {
|
||||
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
|
||||
trade.getShortId(),
|
||||
trade.getDepositTxId(),
|
||||
i);
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
continue;
|
||||
} else {
|
||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
|
||||
.setPhase(DEPOSIT_CONFIRMED)
|
||||
.setDepositPublished(true)
|
||||
.setDepositConfirmed(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!trade.getIsDepositConfirmed()) {
|
||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name()) && t.getPhase().equals(DEPOSIT_CONFIRMED.name());
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot confirm payment started yet.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
// fail("Bad trade state and phase.");
|
||||
sleep(1000 * 10);
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, could not confirm payment started.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
bobClient.confirmPaymentStarted(tradeId);
|
||||
sleep(6000);
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
|
||||
if (!trade.getIsFiatSent()) {
|
||||
log.warn("Bob still waiting for trade {} BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}",
|
||||
trade.getShortId(),
|
||||
i);
|
||||
sleep(5000);
|
||||
continue;
|
||||
} else {
|
||||
// Note: offer.state == available
|
||||
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
|
||||
.setPhase(FIAT_SENT)
|
||||
.setFiatSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after confirming fiat payment sent", trade);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name())
|
||||
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_SENT.name()));
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
// fail("Bad trade state and phase.");
|
||||
sleep(1000 * 10);
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, could not confirm payment received.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
aliceClient.confirmPaymentReceived(trade.getTradeId());
|
||||
sleep(3000);
|
||||
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED)
|
||||
.setPayoutPublished(true)
|
||||
.setFiatReceived(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testBobsBtcWithdrawalToExternalAddress(final TestInfo testInfo) {
|
||||
try {
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
logTrade(log, testInfo, "Bob's view before withdrawing funds to external wallet", trade);
|
||||
|
||||
String toAddress = bitcoinCli.getNewBtcAddress();
|
||||
bobClient.withdrawFunds(tradeId, toAddress, WITHDRAWAL_TX_MEMO);
|
||||
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
EXPECTED_PROTOCOL_STATUS.setState(WITHDRAW_COMPLETED)
|
||||
.setPhase(WITHDRAWN)
|
||||
.setWithdrawn(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after withdrawing BTC funds to external wallet", trade);
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId), true);
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId), true);
|
||||
|
||||
var alicesBalances = aliceClient.getBalances();
|
||||
log.info("{} Alice's Current Balance:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(alicesBalances));
|
||||
var bobsBalances = bobClient.getBalances();
|
||||
log.info("{} Bob's Current Balance:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(bobsBalances));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
package bisq.apitest.method.wallet;
|
||||
|
||||
import bisq.proto.grpc.BsqBalanceInfo;
|
||||
|
||||
import org.bitcoinj.core.LegacyAddress;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static bisq.apitest.method.wallet.WalletTestUtil.ALICES_INITIAL_BSQ_BALANCES;
|
||||
import static bisq.apitest.method.wallet.WalletTestUtil.BOBS_INITIAL_BSQ_BALANCES;
|
||||
import static bisq.apitest.method.wallet.WalletTestUtil.bsqBalanceModel;
|
||||
import static bisq.apitest.method.wallet.WalletTestUtil.verifyBsqBalances;
|
||||
import static bisq.cli.TableFormat.formatBsqBalanceInfoTbl;
|
||||
import static org.bitcoinj.core.NetworkParameters.PAYMENT_PROTOCOL_ID_MAINNET;
|
||||
import static org.bitcoinj.core.NetworkParameters.PAYMENT_PROTOCOL_ID_REGTEST;
|
||||
import static org.bitcoinj.core.NetworkParameters.PAYMENT_PROTOCOL_ID_TESTNET;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.config.BisqAppConfig;
|
||||
import bisq.apitest.method.MethodTest;
|
||||
import bisq.cli.GrpcClient;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
public class BsqWalletTest extends MethodTest {
|
||||
|
||||
private static final String SEND_BSQ_AMOUNT = "25000.50";
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
startSupportingApps(false,
|
||||
true,
|
||||
bitcoind,
|
||||
seednode,
|
||||
arbdaemon,
|
||||
alicedaemon,
|
||||
bobdaemon);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testGetUnusedBsqAddress() {
|
||||
var address = aliceClient.getUnusedBsqAddress();
|
||||
assertFalse(address.isEmpty());
|
||||
assertTrue(address.startsWith("B"));
|
||||
|
||||
NetworkParameters networkParameters = LegacyAddress.getParametersFromAddress(address.substring(1));
|
||||
String addressNetwork = networkParameters.getPaymentProtocolId();
|
||||
assertNotEquals(PAYMENT_PROTOCOL_ID_MAINNET, addressNetwork);
|
||||
// TODO Fix bug causing the regtest bsq address network to be evaluated as 'testnet' here.
|
||||
assertTrue(addressNetwork.equals(PAYMENT_PROTOCOL_ID_TESTNET)
|
||||
|| addressNetwork.equals(PAYMENT_PROTOCOL_ID_REGTEST));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testInitialBsqBalances(final TestInfo testInfo) {
|
||||
BsqBalanceInfo alicesBsqBalances = aliceClient.getBsqBalances();
|
||||
log.debug("{} -> Alice's BSQ Initial Balances -> \n{}",
|
||||
testName(testInfo),
|
||||
formatBsqBalanceInfoTbl(alicesBsqBalances));
|
||||
verifyBsqBalances(ALICES_INITIAL_BSQ_BALANCES, alicesBsqBalances);
|
||||
|
||||
BsqBalanceInfo bobsBsqBalances = bobClient.getBsqBalances();
|
||||
log.debug("{} -> Bob's BSQ Initial Balances -> \n{}",
|
||||
testName(testInfo),
|
||||
formatBsqBalanceInfoTbl(bobsBsqBalances));
|
||||
verifyBsqBalances(BOBS_INITIAL_BSQ_BALANCES, bobsBsqBalances);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testSendBsqAndCheckBalancesBeforeGeneratingBtcBlock(final TestInfo testInfo) {
|
||||
String bobsBsqAddress = bobClient.getUnusedBsqAddress();
|
||||
aliceClient.sendBsq(bobsBsqAddress, SEND_BSQ_AMOUNT, "100");
|
||||
sleep(2000);
|
||||
|
||||
BsqBalanceInfo alicesBsqBalances = aliceClient.getBsqBalances();
|
||||
BsqBalanceInfo bobsBsqBalances = waitForNonZeroBsqUnverifiedBalance(bobClient);
|
||||
|
||||
log.debug("BSQ Balances Before BTC Block Gen...");
|
||||
printBobAndAliceBsqBalances(testInfo,
|
||||
bobsBsqBalances,
|
||||
alicesBsqBalances,
|
||||
alicedaemon);
|
||||
|
||||
verifyBsqBalances(bsqBalanceModel(150000000,
|
||||
2500050,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0),
|
||||
bobsBsqBalances);
|
||||
|
||||
verifyBsqBalances(bsqBalanceModel(97499950,
|
||||
97499950,
|
||||
97499950,
|
||||
0,
|
||||
0,
|
||||
0),
|
||||
alicesBsqBalances);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testBalancesAfterSendingBsqAndGeneratingBtcBlock(final TestInfo testInfo) {
|
||||
// There is a wallet persist delay; we have to
|
||||
// wait for both wallets to be saved to disk.
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
|
||||
BsqBalanceInfo alicesBsqBalances = aliceClient.getBalances().getBsq();
|
||||
BsqBalanceInfo bobsBsqBalances = waitForBsqNewAvailableConfirmedBalance(bobClient, 150000000);
|
||||
|
||||
log.debug("See Available Confirmed BSQ Balances...");
|
||||
printBobAndAliceBsqBalances(testInfo,
|
||||
bobsBsqBalances,
|
||||
alicesBsqBalances,
|
||||
alicedaemon);
|
||||
|
||||
verifyBsqBalances(bsqBalanceModel(152500050,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0),
|
||||
bobsBsqBalances);
|
||||
|
||||
verifyBsqBalances(bsqBalanceModel(97499950,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0),
|
||||
alicesBsqBalances);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
|
||||
private BsqBalanceInfo waitForNonZeroBsqUnverifiedBalance(GrpcClient grpcClient) {
|
||||
// A BSQ recipient needs to wait for her daemon to detect a new tx.
|
||||
// Loop here until her unverifiedBalance != 0, or give up after 15 seconds.
|
||||
// A slow test is preferred over a flaky test.
|
||||
BsqBalanceInfo bsqBalance = grpcClient.getBsqBalances();
|
||||
for (int numRequests = 1; numRequests <= 15 && bsqBalance.getUnverifiedBalance() == 0; numRequests++) {
|
||||
sleep(1000);
|
||||
bsqBalance = grpcClient.getBsqBalances();
|
||||
}
|
||||
return bsqBalance;
|
||||
}
|
||||
|
||||
private BsqBalanceInfo waitForBsqNewAvailableConfirmedBalance(GrpcClient grpcClient,
|
||||
long staleBalance) {
|
||||
BsqBalanceInfo bsqBalance = grpcClient.getBsqBalances();
|
||||
for (int numRequests = 1;
|
||||
numRequests <= 15 && bsqBalance.getAvailableConfirmedBalance() == staleBalance;
|
||||
numRequests++) {
|
||||
sleep(1000);
|
||||
bsqBalance = grpcClient.getBsqBalances();
|
||||
}
|
||||
return bsqBalance;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private void printBobAndAliceBsqBalances(final TestInfo testInfo,
|
||||
BsqBalanceInfo bobsBsqBalances,
|
||||
BsqBalanceInfo alicesBsqBalances,
|
||||
BisqAppConfig senderApp) {
|
||||
log.debug("{} -> Bob's BSQ Balances After {} {} BSQ-> \n{}",
|
||||
testName(testInfo),
|
||||
senderApp.equals(bobdaemon) ? "Sending" : "Receiving",
|
||||
SEND_BSQ_AMOUNT,
|
||||
formatBsqBalanceInfoTbl(bobsBsqBalances));
|
||||
|
||||
log.debug("{} -> Alice's Balances After {} {} BSQ-> \n{}",
|
||||
testName(testInfo),
|
||||
senderApp.equals(alicedaemon) ? "Sending" : "Receiving",
|
||||
SEND_BSQ_AMOUNT,
|
||||
formatBsqBalanceInfoTbl(alicesBsqBalances));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package bisq.apitest.method.wallet;
|
||||
|
||||
import bisq.core.api.model.TxFeeRateInfo;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
public class BtcTxFeeRateTest extends MethodTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
startSupportingApps(false,
|
||||
true,
|
||||
bitcoind,
|
||||
seednode,
|
||||
alicedaemon);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testGetTxFeeRate(final TestInfo testInfo) {
|
||||
var txFeeRateInfo = TxFeeRateInfo.fromProto(aliceClient.getTxFeeRate());
|
||||
log.debug("{} -> Fee rate with no preference: {}", testName(testInfo), txFeeRateInfo);
|
||||
|
||||
assertFalse(txFeeRateInfo.isUseCustomTxFeeRate());
|
||||
assertTrue(txFeeRateInfo.getFeeServiceRate() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testSetInvalidTxFeeRateShouldThrowException(final TestInfo testInfo) {
|
||||
var currentTxFeeRateInfo = TxFeeRateInfo.fromProto(aliceClient.getTxFeeRate());
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
aliceClient.setTxFeeRate(10));
|
||||
String expectedExceptionMessage =
|
||||
format("UNKNOWN: tx fee rate preference must be >= %d sats/byte",
|
||||
currentTxFeeRateInfo.getMinFeeServiceRate());
|
||||
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testSetValidTxFeeRate(final TestInfo testInfo) {
|
||||
var currentTxFeeRateInfo = TxFeeRateInfo.fromProto(aliceClient.getTxFeeRate());
|
||||
var customFeeRate = currentTxFeeRateInfo.getMinFeeServiceRate() + 5;
|
||||
var txFeeRateInfo = TxFeeRateInfo.fromProto(aliceClient.setTxFeeRate(customFeeRate));
|
||||
log.debug("{} -> Fee rates with custom preference: {}", testName(testInfo), txFeeRateInfo);
|
||||
|
||||
assertTrue(txFeeRateInfo.isUseCustomTxFeeRate());
|
||||
assertEquals(customFeeRate, txFeeRateInfo.getCustomTxFeeRate());
|
||||
assertTrue(txFeeRateInfo.getFeeServiceRate() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testUnsetTxFeeRate(final TestInfo testInfo) {
|
||||
var txFeeRateInfo = TxFeeRateInfo.fromProto(aliceClient.unsetTxFeeRate());
|
||||
log.debug("{} -> Fee rate with no preference: {}", testName(testInfo), txFeeRateInfo);
|
||||
|
||||
assertFalse(txFeeRateInfo.isUseCustomTxFeeRate());
|
||||
assertTrue(txFeeRateInfo.getFeeServiceRate() > 0);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
package bisq.apitest.method.wallet;
|
||||
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
import bisq.proto.grpc.TxInfo;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static bisq.apitest.method.wallet.WalletTestUtil.INITIAL_BTC_BALANCES;
|
||||
import static bisq.apitest.method.wallet.WalletTestUtil.verifyBtcBalances;
|
||||
import static bisq.cli.TableFormat.formatAddressBalanceTbl;
|
||||
import static bisq.cli.TableFormat.formatBtcBalanceInfoTbl;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
public class BtcWalletTest extends MethodTest {
|
||||
|
||||
private static final String TX_MEMO = "tx memo";
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
startSupportingApps(false,
|
||||
true,
|
||||
bitcoind,
|
||||
seednode,
|
||||
alicedaemon,
|
||||
bobdaemon);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testInitialBtcBalances(final TestInfo testInfo) {
|
||||
// Bob & Alice's regtest Bisq wallets were initialized with 10 BTC.
|
||||
|
||||
BtcBalanceInfo alicesBalances = aliceClient.getBtcBalances();
|
||||
log.debug("{} Alice's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(alicesBalances));
|
||||
|
||||
BtcBalanceInfo bobsBalances = bobClient.getBtcBalances();
|
||||
log.debug("{} Bob's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(bobsBalances));
|
||||
|
||||
assertEquals(INITIAL_BTC_BALANCES.getAvailableBalance(), alicesBalances.getAvailableBalance());
|
||||
assertEquals(INITIAL_BTC_BALANCES.getAvailableBalance(), bobsBalances.getAvailableBalance());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testFundAlicesBtcWallet(final TestInfo testInfo) {
|
||||
String newAddress = aliceClient.getUnusedBtcAddress();
|
||||
bitcoinCli.sendToAddress(newAddress, "2.5");
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
BtcBalanceInfo btcBalanceInfo = aliceClient.getBtcBalances();
|
||||
// New balance is 12.5 BTC
|
||||
assertEquals(1250000000, btcBalanceInfo.getAvailableBalance());
|
||||
|
||||
log.debug("{} -> Alice's Funded Address Balance -> \n{}",
|
||||
testName(testInfo),
|
||||
formatAddressBalanceTbl(singletonList(aliceClient.getAddressBalance(newAddress))));
|
||||
|
||||
// New balance is 12.5 BTC
|
||||
btcBalanceInfo = aliceClient.getBtcBalances();
|
||||
bisq.core.api.model.BtcBalanceInfo alicesExpectedBalances =
|
||||
bisq.core.api.model.BtcBalanceInfo.valueOf(1250000000,
|
||||
0,
|
||||
1250000000,
|
||||
0);
|
||||
verifyBtcBalances(alicesExpectedBalances, btcBalanceInfo);
|
||||
log.debug("{} -> Alice's BTC Balances After Sending 2.5 BTC -> \n{}",
|
||||
testName(testInfo),
|
||||
formatBtcBalanceInfoTbl(btcBalanceInfo));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testAliceSendBTCToBob(TestInfo testInfo) {
|
||||
String bobsBtcAddress = bobClient.getUnusedBtcAddress();
|
||||
log.debug("Sending 5.5 BTC From Alice to Bob @ {}", bobsBtcAddress);
|
||||
|
||||
TxInfo txInfo = aliceClient.sendBtc(bobsBtcAddress,
|
||||
"5.50",
|
||||
"100",
|
||||
TX_MEMO);
|
||||
assertTrue(txInfo.getIsPending());
|
||||
|
||||
// Note that the memo is not set on the tx yet.
|
||||
assertTrue(txInfo.getMemo().isEmpty());
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
// Fetch the tx and check for confirmation and memo.
|
||||
txInfo = aliceClient.getTransaction(txInfo.getTxId());
|
||||
assertFalse(txInfo.getIsPending());
|
||||
assertEquals(TX_MEMO, txInfo.getMemo());
|
||||
|
||||
BtcBalanceInfo alicesBalances = aliceClient.getBtcBalances();
|
||||
log.debug("{} Alice's BTC Balances:\n{}",
|
||||
testName(testInfo),
|
||||
formatBtcBalanceInfoTbl(alicesBalances));
|
||||
bisq.core.api.model.BtcBalanceInfo alicesExpectedBalances =
|
||||
bisq.core.api.model.BtcBalanceInfo.valueOf(700000000,
|
||||
0,
|
||||
700000000,
|
||||
0);
|
||||
verifyBtcBalances(alicesExpectedBalances, alicesBalances);
|
||||
|
||||
BtcBalanceInfo bobsBalances = bobClient.getBtcBalances();
|
||||
log.debug("{} Bob's BTC Balances:\n{}",
|
||||
testName(testInfo),
|
||||
formatBtcBalanceInfoTbl(bobsBalances));
|
||||
// The sendbtc tx weight and size randomly varies between two distinct values
|
||||
// (876 wu, 219 bytes, OR 880 wu, 220 bytes) from test run to test run, hence
|
||||
// the assertion of an available balance range [1549978000, 1549978100].
|
||||
assertTrue(bobsBalances.getAvailableBalance() >= 1549978000);
|
||||
assertTrue(bobsBalances.getAvailableBalance() <= 1549978100);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package bisq.apitest.method.wallet;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
public class WalletProtectionTest extends MethodTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
try {
|
||||
setUpScaffold(alicedaemon);
|
||||
MILLISECONDS.sleep(2000);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testSetWalletPassword() {
|
||||
aliceClient.setWalletPassword("first-password");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testGetBalanceOnEncryptedWalletShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testUnlockWalletFor4Seconds() {
|
||||
aliceClient.unlockWallet("first-password", 4);
|
||||
aliceClient.getBtcBalances(); // should not throw 'wallet locked' exception
|
||||
sleep(4500); // let unlock timeout expire
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testGetBalanceAfterUnlockTimeExpiryShouldThrowException() {
|
||||
aliceClient.unlockWallet("first-password", 3);
|
||||
sleep(4000); // let unlock timeout expire
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
public void testLockWalletBeforeUnlockTimeoutExpiry() {
|
||||
aliceClient.unlockWallet("first-password", 60);
|
||||
aliceClient.lockWallet();
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(6)
|
||||
public void testLockWalletWhenWalletAlreadyLockedShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.lockWallet());
|
||||
assertEquals("UNKNOWN: wallet is already locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(7)
|
||||
public void testUnlockWalletTimeoutOverride() {
|
||||
aliceClient.unlockWallet("first-password", 2);
|
||||
sleep(500); // override unlock timeout after 0.5s
|
||||
aliceClient.unlockWallet("first-password", 6);
|
||||
sleep(5000);
|
||||
aliceClient.getBtcBalances(); // getbalance 5s after overriding timeout to 6s
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(8)
|
||||
public void testSetNewWalletPassword() {
|
||||
aliceClient.setWalletPassword("first-password", "second-password");
|
||||
sleep(2500); // allow time for wallet save
|
||||
aliceClient.unlockWallet("second-password", 2);
|
||||
aliceClient.getBtcBalances();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(9)
|
||||
public void testSetNewWalletPasswordWithIncorrectNewPasswordShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
aliceClient.setWalletPassword("bad old password", "irrelevant"));
|
||||
assertEquals("UNKNOWN: incorrect old password", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(10)
|
||||
public void testRemoveNewWalletPassword() {
|
||||
aliceClient.removeWalletPassword("second-password");
|
||||
aliceClient.getBtcBalances(); // should not throw 'wallet locked' exception
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package bisq.apitest.method.wallet;
|
||||
|
||||
import bisq.proto.grpc.BsqBalanceInfo;
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@Slf4j
|
||||
public class WalletTestUtil {
|
||||
|
||||
// All api tests depend on the DAO / regtest environment, and Bob & Alice's wallets
|
||||
// are initialized with 10 BTC during the scaffolding setup.
|
||||
public static final bisq.core.api.model.BtcBalanceInfo INITIAL_BTC_BALANCES =
|
||||
bisq.core.api.model.BtcBalanceInfo.valueOf(1000000000,
|
||||
0,
|
||||
1000000000,
|
||||
0);
|
||||
|
||||
|
||||
// Alice's regtest BSQ wallet is initialized with 1,000,000 BSQ.
|
||||
public static final bisq.core.api.model.BsqBalanceInfo ALICES_INITIAL_BSQ_BALANCES =
|
||||
bsqBalanceModel(100000000,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
|
||||
// Bob's regtest BSQ wallet is initialized with 1,500,000 BSQ.
|
||||
public static final bisq.core.api.model.BsqBalanceInfo BOBS_INITIAL_BSQ_BALANCES =
|
||||
bsqBalanceModel(150000000,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
public static bisq.core.api.model.BsqBalanceInfo bsqBalanceModel(long availableConfirmedBalance,
|
||||
long unverifiedBalance,
|
||||
long unconfirmedChangeBalance,
|
||||
long lockedForVotingBalance,
|
||||
long lockupBondsBalance,
|
||||
long unlockingBondsBalance) {
|
||||
return bisq.core.api.model.BsqBalanceInfo.valueOf(availableConfirmedBalance,
|
||||
unverifiedBalance,
|
||||
unconfirmedChangeBalance,
|
||||
lockedForVotingBalance,
|
||||
lockupBondsBalance,
|
||||
unlockingBondsBalance);
|
||||
}
|
||||
|
||||
public static void verifyBsqBalances(bisq.core.api.model.BsqBalanceInfo expected,
|
||||
BsqBalanceInfo actual) {
|
||||
assertEquals(expected.getAvailableConfirmedBalance(), actual.getAvailableConfirmedBalance());
|
||||
assertEquals(expected.getUnverifiedBalance(), actual.getUnverifiedBalance());
|
||||
assertEquals(expected.getUnconfirmedChangeBalance(), actual.getUnconfirmedChangeBalance());
|
||||
assertEquals(expected.getLockedForVotingBalance(), actual.getLockedForVotingBalance());
|
||||
assertEquals(expected.getLockupBondsBalance(), actual.getLockupBondsBalance());
|
||||
assertEquals(expected.getUnlockingBondsBalance(), actual.getUnlockingBondsBalance());
|
||||
}
|
||||
|
||||
public static void verifyBtcBalances(bisq.core.api.model.BtcBalanceInfo expected,
|
||||
BtcBalanceInfo actual) {
|
||||
assertEquals(expected.getAvailableBalance(), actual.getAvailableBalance());
|
||||
assertEquals(expected.getReservedBalance(), actual.getReservedBalance());
|
||||
assertEquals(expected.getTotalAvailableBalance(), actual.getTotalAvailableBalance());
|
||||
assertEquals(expected.getLockedBalance(), actual.getLockedBalance());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.junit.jupiter.api.condition.EnabledIf;
|
||||
|
||||
import static java.lang.System.getenv;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.trade.AbstractTradeTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
||||
import bisq.apitest.method.trade.TakeSellBTCOfferTest;
|
||||
|
||||
@EnabledIf("envLongRunningTestEnabled")
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class LongRunningTradesTest extends AbstractTradeTest {
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void TradeLoop(final TestInfo testInfo) {
|
||||
int numTrades = 0;
|
||||
while (numTrades < 50) {
|
||||
|
||||
log.info("*******************************************************************");
|
||||
log.info("Trade # {}", ++numTrades);
|
||||
log.info("*******************************************************************");
|
||||
|
||||
EXPECTED_PROTOCOL_STATUS.init();
|
||||
testTakeBuyBTCOffer(testInfo);
|
||||
|
||||
genBtcBlocksThenWait(1, 1000 * 15);
|
||||
|
||||
log.info("*******************************************************************");
|
||||
log.info("Trade # {}", ++numTrades);
|
||||
log.info("*******************************************************************");
|
||||
|
||||
EXPECTED_PROTOCOL_STATUS.init();
|
||||
testTakeSellBTCOffer(testInfo);
|
||||
|
||||
genBtcBlocksThenWait(1, 1000 * 15);
|
||||
}
|
||||
}
|
||||
|
||||
public void testTakeBuyBTCOffer(final TestInfo testInfo) {
|
||||
TakeBuyBTCOfferTest test = new TakeBuyBTCOfferTest();
|
||||
setLongRunningTest(true);
|
||||
test.testTakeAlicesBuyOffer(testInfo);
|
||||
test.testAlicesConfirmPaymentStarted(testInfo);
|
||||
test.testBobsConfirmPaymentReceived(testInfo);
|
||||
test.testAlicesKeepFunds(testInfo);
|
||||
}
|
||||
|
||||
public void testTakeSellBTCOffer(final TestInfo testInfo) {
|
||||
TakeSellBTCOfferTest test = new TakeSellBTCOfferTest();
|
||||
setLongRunningTest(true);
|
||||
test.testTakeAlicesSellOffer(testInfo);
|
||||
test.testBobsConfirmPaymentStarted(testInfo);
|
||||
test.testAlicesConfirmPaymentReceived(testInfo);
|
||||
test.testBobsBtcWithdrawalToExternalAddress(testInfo);
|
||||
}
|
||||
|
||||
protected static boolean envLongRunningTestEnabled() {
|
||||
String envName = "LONG_RUNNING_TRADES_TEST_ENABLED";
|
||||
String envX = getenv(envName);
|
||||
if (envX != null) {
|
||||
log.info("Enabled, found {}.", envName);
|
||||
return true;
|
||||
} else {
|
||||
log.info("Skipped, no environment variable {} defined.", envName);
|
||||
log.info("To enable on Mac OS or Linux:"
|
||||
+ "\tIf running in terminal, export LONG_RUNNING_TRADES_TEST_ENABLED=true in bash shell."
|
||||
+ "\tIf running in Intellij, set LONG_RUNNING_TRADES_TEST_ENABLED=true in launcher's Environment variables field.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
88
apitest/src/test/java/bisq/apitest/scenario/OfferTest.java
Normal file
88
apitest/src/test/java/bisq/apitest/scenario/OfferTest.java
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
import bisq.apitest.method.offer.CancelOfferTest;
|
||||
import bisq.apitest.method.offer.CreateBSQOffersTest;
|
||||
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
|
||||
import bisq.apitest.method.offer.CreateOfferUsingMarketPriceMarginTest;
|
||||
import bisq.apitest.method.offer.ValidateCreateOfferTest;
|
||||
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class OfferTest extends AbstractOfferTest {
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testAmtTooLargeShouldThrowException() {
|
||||
ValidateCreateOfferTest test = new ValidateCreateOfferTest();
|
||||
test.testAmtTooLargeShouldThrowException();
|
||||
test.testNoMatchingEURPaymentAccountShouldThrowException();
|
||||
test.testNoMatchingCADPaymentAccountShouldThrowException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testCancelOffer() {
|
||||
CancelOfferTest test = new CancelOfferTest();
|
||||
test.testCancelOffer();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testCreateOfferUsingFixedPrice() {
|
||||
CreateOfferUsingFixedPriceTest test = new CreateOfferUsingFixedPriceTest();
|
||||
test.testCreateAUDBTCBuyOfferUsingFixedPrice16000();
|
||||
test.testCreateUSDBTCBuyOfferUsingFixedPrice100001234();
|
||||
test.testCreateEURBTCSellOfferUsingFixedPrice95001234();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testCreateOfferUsingMarketPriceMargin() {
|
||||
CreateOfferUsingMarketPriceMarginTest test = new CreateOfferUsingMarketPriceMarginTest();
|
||||
test.testCreateUSDBTCBuyOffer5PctPriceMargin();
|
||||
test.testCreateNZDBTCBuyOfferMinus2PctPriceMargin();
|
||||
test.testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin();
|
||||
test.testCreateBRLBTCSellOffer6Point55PctPriceMargin();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
public void testCreateBSQOffersTest() {
|
||||
CreateBSQOffersTest test = new CreateBSQOffersTest();
|
||||
CreateBSQOffersTest.createBsqPaymentAccounts();
|
||||
test.testCreateBuy1BTCFor20KBSQOffer();
|
||||
test.testCreateSell1BTCFor20KBSQOffer();
|
||||
test.testCreateBuyBTCWith1To2KBSQOffer();
|
||||
test.testCreateSellBTCFor5To10KBSQOffer();
|
||||
test.testGetAllMyBsqOffers();
|
||||
test.testGetAvailableBsqOffers();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package bisq.apitest.scenario;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.payment.AbstractPaymentAccountTest;
|
||||
import bisq.apitest.method.payment.CreatePaymentAccountTest;
|
||||
import bisq.apitest.method.payment.GetPaymentMethodsTest;
|
||||
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class PaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
try {
|
||||
setUpScaffold(bitcoind, seednode, alicedaemon);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testGetPaymentMethods() {
|
||||
GetPaymentMethodsTest test = new GetPaymentMethodsTest();
|
||||
test.testGetPaymentMethods();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testCreatePaymentAccount(TestInfo testInfo) {
|
||||
CreatePaymentAccountTest test = new CreatePaymentAccountTest();
|
||||
|
||||
test.testCreateAdvancedCashAccount(testInfo);
|
||||
test.testCreateAliPayAccount(testInfo);
|
||||
test.testCreateAustraliaPayidAccount(testInfo);
|
||||
test.testCreateCashDepositAccount(testInfo);
|
||||
test.testCreateBrazilNationalBankAccount(testInfo);
|
||||
test.testCreateChaseQuickPayAccount(testInfo);
|
||||
test.testCreateClearXChangeAccount(testInfo);
|
||||
test.testCreateF2FAccount(testInfo);
|
||||
test.testCreateFasterPaymentsAccount(testInfo);
|
||||
test.testCreateHalCashAccount(testInfo);
|
||||
test.testCreateInteracETransferAccount(testInfo);
|
||||
test.testCreateJapanBankAccount(testInfo);
|
||||
test.testCreateMoneyBeamAccount(testInfo);
|
||||
test.testCreateMoneyGramAccount(testInfo);
|
||||
test.testCreatePerfectMoneyAccount(testInfo);
|
||||
test.testCreatePopmoneyAccount(testInfo);
|
||||
test.testCreatePromptPayAccount(testInfo);
|
||||
test.testCreateRevolutAccount(testInfo);
|
||||
test.testCreateSameBankAccount(testInfo);
|
||||
test.testCreateSepaInstantAccount(testInfo);
|
||||
test.testCreateSepaAccount(testInfo);
|
||||
test.testCreateSpecificBanksAccount(testInfo);
|
||||
test.testCreateSwishAccount(testInfo);
|
||||
|
||||
// TransferwiseAccount is only PaymentAccount with a
|
||||
// tradeCurrencies field in the json form.
|
||||
test.testCreateTransferwiseAccountWith1TradeCurrency(testInfo);
|
||||
test.testCreateTransferwiseAccountWith10TradeCurrencies(testInfo);
|
||||
test.testCreateTransferwiseAccountWithInvalidBrlTradeCurrencyShouldThrowException(testInfo);
|
||||
test.testCreateTransferwiseAccountWithoutTradeCurrenciesShouldThrowException(testInfo);
|
||||
|
||||
test.testCreateUpholdAccount(testInfo);
|
||||
test.testCreateUSPostalMoneyOrderAccount(testInfo);
|
||||
test.testCreateWeChatPayAccount(testInfo);
|
||||
test.testCreateWesternUnionAccount(testInfo);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
121
apitest/src/test/java/bisq/apitest/scenario/ScriptedBotTest.java
Normal file
121
apitest/src/test/java/bisq/apitest/scenario/ScriptedBotTest.java
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.junit.jupiter.api.condition.EnabledIf;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.startShutdownTimer;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.config.ApiTestConfig;
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.AbstractBotTest;
|
||||
import bisq.apitest.scenario.bot.BotClient;
|
||||
import bisq.apitest.scenario.bot.RobotBob;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
|
||||
// The test case is enabled if AbstractBotTest#botScriptExists() returns true.
|
||||
@EnabledIf("botScriptExists")
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class ScriptedBotTest extends AbstractBotTest {
|
||||
|
||||
private RobotBob robotBob;
|
||||
|
||||
@BeforeAll
|
||||
public static void startTestHarness() {
|
||||
botScript = deserializeBotScript();
|
||||
|
||||
if (botScript.isUseTestHarness()) {
|
||||
startSupportingApps(true,
|
||||
true,
|
||||
bitcoind,
|
||||
seednode,
|
||||
arbdaemon,
|
||||
alicedaemon,
|
||||
bobdaemon);
|
||||
} else {
|
||||
// We need just enough configurations to make sure Bob and testers use
|
||||
// the right apiPassword, to create a bitcoin-cli helper, and RobotBob's
|
||||
// gRPC stubs. But the user will have to register dispute agents before
|
||||
// an offer can be taken.
|
||||
config = new ApiTestConfig("--apiPassword", "xyz");
|
||||
bitcoinCli = new BitcoinCliHelper(config);
|
||||
log.warn("Don't forget to register dispute agents before trying to trade with me.");
|
||||
}
|
||||
|
||||
botClient = new BotClient(bobClient);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void initRobotBob() {
|
||||
try {
|
||||
BashScriptGenerator bashScriptGenerator = getBashScriptGenerator();
|
||||
robotBob = new RobotBob(botClient, botScript, bitcoinCli, bashScriptGenerator);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void runRobotBob() {
|
||||
try {
|
||||
|
||||
startShutdownTimer();
|
||||
robotBob.run();
|
||||
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
// This exception is thrown if a /tmp/bottest-shutdown file was found.
|
||||
// You can also kill -15 <pid>
|
||||
// of worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Test Executor #'
|
||||
//
|
||||
// This will cleanly shut everything down as well, but you will see a
|
||||
// Process 'Gradle Test Executor #' finished with non-zero exit value 143 error,
|
||||
// which you may think is a test failure.
|
||||
log.warn("{} Shutting down test case before test completion;"
|
||||
+ " this is not a test failure.",
|
||||
ex.getMessage());
|
||||
} catch (Throwable throwable) {
|
||||
fail(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
if (botScript.isUseTestHarness())
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
115
apitest/src/test/java/bisq/apitest/scenario/StartupTest.java
Normal file
115
apitest/src/test/java/bisq/apitest/scenario/StartupTest.java
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
import static bisq.common.file.FileUtil.deleteFileIfExists;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.CallRateMeteringInterceptorTest;
|
||||
import bisq.apitest.method.GetMethodHelpTest;
|
||||
import bisq.apitest.method.GetVersionTest;
|
||||
import bisq.apitest.method.MethodTest;
|
||||
import bisq.apitest.method.RegisterDisputeAgentsTest;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class StartupTest extends MethodTest {
|
||||
|
||||
private static File callRateMeteringConfigFile;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
try {
|
||||
callRateMeteringConfigFile = defaultRateMeterInterceptorConfig();
|
||||
startSupportingApps(callRateMeteringConfigFile,
|
||||
false,
|
||||
false,
|
||||
bitcoind, seednode, arbdaemon, alicedaemon);
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testCallRateMeteringInterceptor() {
|
||||
CallRateMeteringInterceptorTest test = new CallRateMeteringInterceptorTest();
|
||||
test.testGetVersionCall1IsAllowed();
|
||||
test.sleep200Milliseconds();
|
||||
test.testGetVersionCall2ShouldThrowException();
|
||||
test.sleep200Milliseconds();
|
||||
test.testGetVersionCall3ShouldThrowException();
|
||||
test.sleep200Milliseconds();
|
||||
test.testGetVersionCall4IsAllowed();
|
||||
sleep(1000); // Wait 1 second before calling getversion in next test.
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testGetVersion() {
|
||||
GetVersionTest test = new GetVersionTest();
|
||||
test.testGetVersion();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testRegisterDisputeAgents() {
|
||||
RegisterDisputeAgentsTest test = new RegisterDisputeAgentsTest();
|
||||
test.testRegisterArbitratorShouldThrowException();
|
||||
test.testInvalidDisputeAgentTypeArgShouldThrowException();
|
||||
test.testInvalidRegistrationKeyArgShouldThrowException();
|
||||
test.testRegisterMediator();
|
||||
test.testRegisterRefundAgent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testGetCreateOfferHelp() {
|
||||
GetMethodHelpTest test = new GetMethodHelpTest();
|
||||
test.testGetCreateOfferHelp();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
try {
|
||||
deleteFileIfExists(callRateMeteringConfigFile);
|
||||
} catch (IOException ex) {
|
||||
log.error(ex.getMessage());
|
||||
}
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
88
apitest/src/test/java/bisq/apitest/scenario/TradeTest.java
Normal file
88
apitest/src/test/java/bisq/apitest/scenario/TradeTest.java
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.trade.AbstractTradeTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBSQOfferTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
||||
import bisq.apitest.method.trade.TakeSellBSQOfferTest;
|
||||
import bisq.apitest.method.trade.TakeSellBTCOfferTest;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class TradeTest extends AbstractTradeTest {
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
EXPECTED_PROTOCOL_STATUS.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testTakeBuyBTCOffer(final TestInfo testInfo) {
|
||||
TakeBuyBTCOfferTest test = new TakeBuyBTCOfferTest();
|
||||
test.testTakeAlicesBuyOffer(testInfo);
|
||||
test.testAlicesConfirmPaymentStarted(testInfo);
|
||||
test.testBobsConfirmPaymentReceived(testInfo);
|
||||
test.testAlicesKeepFunds(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testTakeSellBTCOffer(final TestInfo testInfo) {
|
||||
TakeSellBTCOfferTest test = new TakeSellBTCOfferTest();
|
||||
test.testTakeAlicesSellOffer(testInfo);
|
||||
test.testBobsConfirmPaymentStarted(testInfo);
|
||||
test.testAlicesConfirmPaymentReceived(testInfo);
|
||||
test.testBobsBtcWithdrawalToExternalAddress(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testTakeBuyBSQOffer(final TestInfo testInfo) {
|
||||
TakeBuyBSQOfferTest test = new TakeBuyBSQOfferTest();
|
||||
TakeBuyBSQOfferTest.createBsqPaymentAccounts();
|
||||
test.testTakeAlicesSellBTCForBSQOffer(testInfo);
|
||||
test.testBobsConfirmPaymentStarted(testInfo);
|
||||
test.testAlicesConfirmPaymentReceived(testInfo);
|
||||
test.testBobsKeepFunds(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testTakeSellBSQOffer(final TestInfo testInfo) {
|
||||
TakeSellBSQOfferTest test = new TakeSellBSQOfferTest();
|
||||
TakeSellBSQOfferTest.createBsqPaymentAccounts();
|
||||
test.testTakeAlicesBuyBTCForBSQOffer(testInfo);
|
||||
test.testAlicesConfirmPaymentStarted(testInfo);
|
||||
test.testBobsConfirmPaymentReceived(testInfo);
|
||||
test.testAlicesBtcWithdrawalToExternalAddress(testInfo);
|
||||
}
|
||||
}
|
116
apitest/src/test/java/bisq/apitest/scenario/WalletTest.java
Normal file
116
apitest/src/test/java/bisq/apitest/scenario/WalletTest.java
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.BisqAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.bobdaemon;
|
||||
import static bisq.apitest.config.BisqAppConfig.seednode;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
import bisq.apitest.method.wallet.BsqWalletTest;
|
||||
import bisq.apitest.method.wallet.BtcTxFeeRateTest;
|
||||
import bisq.apitest.method.wallet.BtcWalletTest;
|
||||
import bisq.apitest.method.wallet.WalletProtectionTest;
|
||||
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class WalletTest extends MethodTest {
|
||||
|
||||
// Batching all wallet tests in this test case reduces scaffold setup
|
||||
// time. Here, we create a method WalletProtectionTest instance and run each
|
||||
// test in declared order.
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
startSupportingApps(true,
|
||||
false,
|
||||
bitcoind,
|
||||
seednode,
|
||||
arbdaemon,
|
||||
alicedaemon,
|
||||
bobdaemon);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testBtcWalletFunding(final TestInfo testInfo) {
|
||||
BtcWalletTest btcWalletTest = new BtcWalletTest();
|
||||
|
||||
btcWalletTest.testInitialBtcBalances(testInfo);
|
||||
btcWalletTest.testFundAlicesBtcWallet(testInfo);
|
||||
btcWalletTest.testAliceSendBTCToBob(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testBsqWalletFunding(final TestInfo testInfo) {
|
||||
BsqWalletTest bsqWalletTest = new BsqWalletTest();
|
||||
|
||||
bsqWalletTest.testGetUnusedBsqAddress();
|
||||
bsqWalletTest.testInitialBsqBalances(testInfo);
|
||||
bsqWalletTest.testSendBsqAndCheckBalancesBeforeGeneratingBtcBlock(testInfo);
|
||||
bsqWalletTest.testBalancesAfterSendingBsqAndGeneratingBtcBlock(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testWalletProtection() {
|
||||
WalletProtectionTest walletProtectionTest = new WalletProtectionTest();
|
||||
|
||||
walletProtectionTest.testSetWalletPassword();
|
||||
walletProtectionTest.testGetBalanceOnEncryptedWalletShouldThrowException();
|
||||
walletProtectionTest.testUnlockWalletFor4Seconds();
|
||||
walletProtectionTest.testGetBalanceAfterUnlockTimeExpiryShouldThrowException();
|
||||
walletProtectionTest.testLockWalletBeforeUnlockTimeoutExpiry();
|
||||
walletProtectionTest.testLockWalletWhenWalletAlreadyLockedShouldThrowException();
|
||||
walletProtectionTest.testUnlockWalletTimeoutOverride();
|
||||
walletProtectionTest.testSetNewWalletPassword();
|
||||
walletProtectionTest.testSetNewWalletPasswordWithIncorrectNewPasswordShouldThrowException();
|
||||
walletProtectionTest.testRemoveNewWalletPassword();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testTxFeeRateMethods(final TestInfo testInfo) {
|
||||
BtcTxFeeRateTest test = new BtcTxFeeRateTest();
|
||||
|
||||
test.testGetTxFeeRate(testInfo);
|
||||
test.testSetInvalidTxFeeRateShouldThrowException(testInfo);
|
||||
test.testSetValidTxFeeRate(testInfo);
|
||||
test.testUnsetTxFeeRate(testInfo);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario.bot;
|
||||
|
||||
import bisq.core.locale.Country;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.locale.CountryUtil.findCountryByCode;
|
||||
import static bisq.core.payment.payload.PaymentMethod.CLEAR_X_CHANGE_ID;
|
||||
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethodById;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.getProperty;
|
||||
import static java.nio.file.Files.readAllBytes;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.script.BotScript;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractBotTest extends MethodTest {
|
||||
|
||||
protected static final String BOT_SCRIPT_NAME = "bot-script.json";
|
||||
protected static BotScript botScript;
|
||||
protected static BotClient botClient;
|
||||
|
||||
protected BashScriptGenerator getBashScriptGenerator() {
|
||||
if (botScript.isUseTestHarness()) {
|
||||
PaymentAccount alicesAccount = createAlicesPaymentAccount();
|
||||
botScript.setPaymentAccountIdForCliScripts(alicesAccount.getId());
|
||||
}
|
||||
return new BashScriptGenerator(config.apiPassword,
|
||||
botScript.getApiPortForCliScripts(),
|
||||
botScript.getPaymentAccountIdForCliScripts(),
|
||||
botScript.isPrintCliScripts());
|
||||
}
|
||||
|
||||
private PaymentAccount createAlicesPaymentAccount() {
|
||||
BotPaymentAccountGenerator accountGenerator =
|
||||
new BotPaymentAccountGenerator(new BotClient(aliceClient));
|
||||
String paymentMethodId = botScript.getBotPaymentMethodId();
|
||||
if (paymentMethodId != null) {
|
||||
if (paymentMethodId.equals(CLEAR_X_CHANGE_ID)) {
|
||||
// Only Zelle test accts are supported now.
|
||||
return accountGenerator.createZellePaymentAccount(
|
||||
"Alice's Zelle Account",
|
||||
"Alice");
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
format("This test harness bot does not work with %s payment accounts yet.",
|
||||
getPaymentMethodById(paymentMethodId).getDisplayString()));
|
||||
}
|
||||
} else {
|
||||
String countryCode = botScript.getCountryCode();
|
||||
Country country = findCountryByCode(countryCode).orElseThrow(() ->
|
||||
new IllegalArgumentException(countryCode + " is not a valid iso country code."));
|
||||
return accountGenerator.createF2FPaymentAccount(country,
|
||||
"Alice's " + country.name + " F2F Account");
|
||||
}
|
||||
}
|
||||
|
||||
protected static BotScript deserializeBotScript() {
|
||||
try {
|
||||
File botScriptFile = new File(getProperty("java.io.tmpdir"), BOT_SCRIPT_NAME);
|
||||
String json = new String(readAllBytes(Paths.get(botScriptFile.getPath())));
|
||||
return new GsonBuilder().setPrettyPrinting().create().fromJson(json, BotScript.class);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("Error reading script bot file contents.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused") // This is used by the jupiter framework.
|
||||
protected static boolean botScriptExists() {
|
||||
File botScriptFile = new File(getProperty("java.io.tmpdir"), BOT_SCRIPT_NAME);
|
||||
if (botScriptFile.exists()) {
|
||||
botScriptFile.deleteOnExit();
|
||||
log.info("Enabled, found {}.", botScriptFile.getPath());
|
||||
return true;
|
||||
} else {
|
||||
log.info("Skipped, no bot script.\n\tTo generate a bot-script.json file, see BotScriptGenerator.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
77
apitest/src/test/java/bisq/apitest/scenario/bot/Bot.java
Normal file
77
apitest/src/test/java/bisq/apitest/scenario/bot/Bot.java
Normal file
|
@ -0,0 +1,77 @@
|
|||
package bisq.apitest.scenario.bot;
|
||||
|
||||
import bisq.core.locale.Country;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.locale.CountryUtil.findCountryByCode;
|
||||
import static bisq.core.payment.payload.PaymentMethod.CLEAR_X_CHANGE_ID;
|
||||
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethodById;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.script.BotScript;
|
||||
|
||||
@Slf4j
|
||||
public
|
||||
class Bot {
|
||||
|
||||
static final String MAKE = "MAKE";
|
||||
static final String TAKE = "TAKE";
|
||||
|
||||
protected final BotClient botClient;
|
||||
protected final BitcoinCliHelper bitcoinCli;
|
||||
protected final BashScriptGenerator bashScriptGenerator;
|
||||
protected final String[] actions;
|
||||
protected final long protocolStepTimeLimitInMs;
|
||||
protected final boolean stayAlive;
|
||||
protected final boolean isUsingTestHarness;
|
||||
protected final PaymentAccount paymentAccount;
|
||||
|
||||
public Bot(BotClient botClient,
|
||||
BotScript botScript,
|
||||
BitcoinCliHelper bitcoinCli,
|
||||
BashScriptGenerator bashScriptGenerator) {
|
||||
this.botClient = botClient;
|
||||
this.bitcoinCli = bitcoinCli;
|
||||
this.bashScriptGenerator = bashScriptGenerator;
|
||||
this.actions = botScript.getActions();
|
||||
this.protocolStepTimeLimitInMs = MINUTES.toMillis(botScript.getProtocolStepTimeLimitInMinutes());
|
||||
this.stayAlive = botScript.isStayAlive();
|
||||
this.isUsingTestHarness = botScript.isUseTestHarness();
|
||||
if (isUsingTestHarness)
|
||||
this.paymentAccount = createBotPaymentAccount(botScript);
|
||||
else
|
||||
this.paymentAccount = botClient.getPaymentAccount(botScript.getPaymentAccountIdForBot());
|
||||
}
|
||||
|
||||
private PaymentAccount createBotPaymentAccount(BotScript botScript) {
|
||||
BotPaymentAccountGenerator accountGenerator = new BotPaymentAccountGenerator(botClient);
|
||||
|
||||
String paymentMethodId = botScript.getBotPaymentMethodId();
|
||||
if (paymentMethodId != null) {
|
||||
if (paymentMethodId.equals(CLEAR_X_CHANGE_ID)) {
|
||||
return accountGenerator.createZellePaymentAccount("Bob's Zelle Account",
|
||||
"Bob");
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
format("This bot test does not work with %s payment accounts yet.",
|
||||
getPaymentMethodById(paymentMethodId).getDisplayString()));
|
||||
}
|
||||
} else {
|
||||
Country country = findCountry(botScript.getCountryCode());
|
||||
return accountGenerator.createF2FPaymentAccount(country, country.name + " F2F Account");
|
||||
}
|
||||
}
|
||||
|
||||
private Country findCountry(String countryCode) {
|
||||
return findCountryByCode(countryCode).orElseThrow(() ->
|
||||
new IllegalArgumentException(countryCode + " is not a valid iso country code."));
|
||||
}
|
||||
}
|
339
apitest/src/test/java/bisq/apitest/scenario/bot/BotClient.java
Normal file
339
apitest/src/test/java/bisq/apitest/scenario/bot/BotClient.java
Normal file
|
@ -0,0 +1,339 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario.bot;
|
||||
|
||||
import bisq.proto.grpc.BalancesInfo;
|
||||
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.GrpcClient;
|
||||
|
||||
/**
|
||||
* Convenience GrpcClient wrapper for bots using gRPC services.
|
||||
*
|
||||
* TODO Consider if the duplication smell is bad enough to force a BotClient user
|
||||
* to use the GrpcClient instead (and delete this class). But right now, I think it is
|
||||
* OK because moving some of the non-gRPC related methods to GrpcClient is even smellier.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings({"JavaDoc", "unused"})
|
||||
@Slf4j
|
||||
public class BotClient {
|
||||
|
||||
private static final DecimalFormat FIXED_PRICE_FMT = new DecimalFormat("###########0");
|
||||
|
||||
private final GrpcClient grpcClient;
|
||||
|
||||
public BotClient(GrpcClient grpcClient) {
|
||||
this.grpcClient = grpcClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current BSQ and BTC balance information.
|
||||
* @return BalancesInfo
|
||||
*/
|
||||
public BalancesInfo getBalance() {
|
||||
return grpcClient.getBalances();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the most recent BTC market price for the given currencyCode.
|
||||
* @param currencyCode
|
||||
* @return double
|
||||
*/
|
||||
public double getCurrentBTCMarketPrice(String currencyCode) {
|
||||
return grpcClient.getBtcPrice(currencyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the most recent BTC market price for the given currencyCode as an integer string.
|
||||
* @param currencyCode
|
||||
* @return String
|
||||
*/
|
||||
public String getCurrentBTCMarketPriceAsIntegerString(String currencyCode) {
|
||||
return FIXED_PRICE_FMT.format(getCurrentBTCMarketPrice(currencyCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all BUY and SELL offers for the given currencyCode.
|
||||
* @param currencyCode
|
||||
* @return List<OfferInfo>
|
||||
*/
|
||||
public List<OfferInfo> getOffers(String currencyCode) {
|
||||
var buyOffers = getBuyOffers(currencyCode);
|
||||
if (buyOffers.size() > 0) {
|
||||
return buyOffers;
|
||||
} else {
|
||||
return getSellOffers(currencyCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return BUY offers for the given currencyCode.
|
||||
* @param currencyCode
|
||||
* @return List<OfferInfo>
|
||||
*/
|
||||
public List<OfferInfo> getBuyOffers(String currencyCode) {
|
||||
return grpcClient.getOffers("BUY", currencyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SELL offers for the given currencyCode.
|
||||
* @param currencyCode
|
||||
* @return List<OfferInfo>
|
||||
*/
|
||||
public List<OfferInfo> getSellOffers(String currencyCode) {
|
||||
return grpcClient.getOffers("SELL", currencyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new Offer using a market based price.
|
||||
* @param paymentAccount
|
||||
* @param direction
|
||||
* @param currencyCode
|
||||
* @param amountInSatoshis
|
||||
* @param minAmountInSatoshis
|
||||
* @param priceMarginAsPercent
|
||||
* @param securityDepositAsPercent
|
||||
* @param feeCurrency
|
||||
* @return OfferInfo
|
||||
*/
|
||||
public OfferInfo createOfferAtMarketBasedPrice(PaymentAccount paymentAccount,
|
||||
String direction,
|
||||
String currencyCode,
|
||||
long amountInSatoshis,
|
||||
long minAmountInSatoshis,
|
||||
double priceMarginAsPercent,
|
||||
double securityDepositAsPercent,
|
||||
String feeCurrency) {
|
||||
return grpcClient.createMarketBasedPricedOffer(direction,
|
||||
currencyCode,
|
||||
amountInSatoshis,
|
||||
minAmountInSatoshis,
|
||||
priceMarginAsPercent,
|
||||
securityDepositAsPercent,
|
||||
paymentAccount.getId(),
|
||||
feeCurrency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new Offer using a fixed price.
|
||||
* @param paymentAccount
|
||||
* @param direction
|
||||
* @param currencyCode
|
||||
* @param amountInSatoshis
|
||||
* @param minAmountInSatoshis
|
||||
* @param fixedOfferPriceAsString
|
||||
* @param securityDepositAsPercent
|
||||
* @param feeCurrency
|
||||
* @return OfferInfo
|
||||
*/
|
||||
public OfferInfo createOfferAtFixedPrice(PaymentAccount paymentAccount,
|
||||
String direction,
|
||||
String currencyCode,
|
||||
long amountInSatoshis,
|
||||
long minAmountInSatoshis,
|
||||
String fixedOfferPriceAsString,
|
||||
double securityDepositAsPercent,
|
||||
String feeCurrency) {
|
||||
return grpcClient.createFixedPricedOffer(direction,
|
||||
currencyCode,
|
||||
amountInSatoshis,
|
||||
minAmountInSatoshis,
|
||||
fixedOfferPriceAsString,
|
||||
securityDepositAsPercent,
|
||||
paymentAccount.getId(),
|
||||
feeCurrency);
|
||||
}
|
||||
|
||||
public TradeInfo takeOffer(String offerId, PaymentAccount paymentAccount, String feeCurrency) {
|
||||
return grpcClient.takeOffer(offerId, paymentAccount.getId(), feeCurrency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a persisted Trade with the given tradeId, or throws an exception.
|
||||
* @param tradeId
|
||||
* @return TradeInfo
|
||||
*/
|
||||
public TradeInfo getTrade(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate returns true if the given exception indicates the trade with the given
|
||||
* tradeId exists, but the trade's contract has not been fully prepared.
|
||||
*/
|
||||
public final BiPredicate<Exception, String> tradeContractIsNotReady = (exception, tradeId) -> {
|
||||
if (exception.getMessage().contains("no contract was found")) {
|
||||
log.warn("Trade {} exists but is not fully prepared: {}.",
|
||||
tradeId,
|
||||
toCleanGrpcExceptionMessage(exception));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a trade's contract as a Json string, or null if the trade exists
|
||||
* but the contract is not ready.
|
||||
* @param tradeId
|
||||
* @return String
|
||||
*/
|
||||
public String getTradeContract(String tradeId) {
|
||||
try {
|
||||
var trade = grpcClient.getTrade(tradeId);
|
||||
return trade.getContractAsJson();
|
||||
} catch (Exception ex) {
|
||||
if (tradeContractIsNotReady.test(ex, tradeId))
|
||||
return null;
|
||||
else
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the trade's taker deposit fee transaction has been published.
|
||||
* @param tradeId a valid trade id
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isTakerDepositFeeTxPublished(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId).getIsPayoutPublished();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the trade's taker deposit fee transaction has been confirmed.
|
||||
* @param tradeId a valid trade id
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isTakerDepositFeeTxConfirmed(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId).getIsDepositConfirmed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the trade's 'start payment' message has been sent by the buyer.
|
||||
* @param tradeId a valid trade id
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isTradePaymentStartedSent(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId).getIsFiatSent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the trade's 'payment received' message has been sent by the seller.
|
||||
* @param tradeId a valid trade id
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isTradePaymentReceivedConfirmationSent(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId).getIsFiatReceived();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the trade's payout transaction has been published.
|
||||
* @param tradeId a valid trade id
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isTradePayoutTxPublished(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId).getIsPayoutPublished();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a 'confirm payment started message' for a trade with the given tradeId,
|
||||
* or throws an exception.
|
||||
* @param tradeId
|
||||
*/
|
||||
public void sendConfirmPaymentStartedMessage(String tradeId) {
|
||||
grpcClient.confirmPaymentStarted(tradeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a 'confirm payment received message' for a trade with the given tradeId,
|
||||
* or throws an exception.
|
||||
* @param tradeId
|
||||
*/
|
||||
public void sendConfirmPaymentReceivedMessage(String tradeId) {
|
||||
grpcClient.confirmPaymentReceived(tradeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a 'keep funds in wallet message' for a trade with the given tradeId,
|
||||
* or throws an exception.
|
||||
* @param tradeId
|
||||
*/
|
||||
public void sendKeepFundsMessage(String tradeId) {
|
||||
grpcClient.keepFunds(tradeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and save a new PaymentAccount with details in the given json.
|
||||
* @param json
|
||||
* @return PaymentAccount
|
||||
*/
|
||||
public PaymentAccount createNewPaymentAccount(String json) {
|
||||
return grpcClient.createPaymentAccount(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a persisted PaymentAccount with the given paymentAccountId, or throws
|
||||
* an exception.
|
||||
* @param paymentAccountId The id of the PaymentAccount being looked up.
|
||||
* @return PaymentAccount
|
||||
*/
|
||||
public PaymentAccount getPaymentAccount(String paymentAccountId) {
|
||||
return grpcClient.getPaymentAccounts().stream()
|
||||
.filter(a -> (a.getId().equals(paymentAccountId)))
|
||||
.findFirst()
|
||||
.orElseThrow(() ->
|
||||
new PaymentAccountNotFoundException("Could not find a payment account with id "
|
||||
+ paymentAccountId + "."));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a persisted PaymentAccount with the given accountName, or throws
|
||||
* an exception.
|
||||
* @param accountName
|
||||
* @return PaymentAccount
|
||||
*/
|
||||
public PaymentAccount getPaymentAccountWithName(String accountName) {
|
||||
var req = GetPaymentAccountsRequest.newBuilder().build();
|
||||
return grpcClient.getPaymentAccounts().stream()
|
||||
.filter(a -> (a.getAccountName().equals(accountName)))
|
||||
.findFirst()
|
||||
.orElseThrow(() ->
|
||||
new PaymentAccountNotFoundException("Could not find a payment account with name "
|
||||
+ accountName + "."));
|
||||
}
|
||||
|
||||
public String toCleanGrpcExceptionMessage(Exception ex) {
|
||||
return capitalize(ex.getMessage().replaceFirst("^[A-Z_]+: ", ""));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package bisq.apitest.scenario.bot;
|
||||
|
||||
import bisq.core.api.model.PaymentAccountForm;
|
||||
import bisq.core.locale.Country;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.payment.payload.PaymentMethod.CLEAR_X_CHANGE_ID;
|
||||
import static bisq.core.payment.payload.PaymentMethod.F2F_ID;
|
||||
|
||||
@Slf4j
|
||||
public class BotPaymentAccountGenerator {
|
||||
|
||||
private final Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
|
||||
|
||||
private final BotClient botClient;
|
||||
|
||||
public BotPaymentAccountGenerator(BotClient botClient) {
|
||||
this.botClient = botClient;
|
||||
}
|
||||
|
||||
public PaymentAccount createF2FPaymentAccount(Country country, String accountName) {
|
||||
try {
|
||||
return botClient.getPaymentAccountWithName(accountName);
|
||||
} catch (PaymentAccountNotFoundException ignored) {
|
||||
// Ignore not found exception, create a new account.
|
||||
}
|
||||
Map<String, Object> p = getPaymentAccountFormMap(F2F_ID);
|
||||
p.put("accountName", accountName);
|
||||
p.put("city", country.name + " City");
|
||||
p.put("country", country.code);
|
||||
p.put("contact", "By Semaphore");
|
||||
p.put("extraInfo", "");
|
||||
// Convert the map back to a json string and create the payment account over gRPC.
|
||||
return botClient.createNewPaymentAccount(gson.toJson(p));
|
||||
}
|
||||
|
||||
public PaymentAccount createZellePaymentAccount(String accountName, String holderName) {
|
||||
try {
|
||||
return botClient.getPaymentAccountWithName(accountName);
|
||||
} catch (PaymentAccountNotFoundException ignored) {
|
||||
// Ignore not found exception, create a new account.
|
||||
}
|
||||
Map<String, Object> p = getPaymentAccountFormMap(CLEAR_X_CHANGE_ID);
|
||||
p.put("accountName", accountName);
|
||||
p.put("emailOrMobileNr", holderName + "@zelle.com");
|
||||
p.put("holderName", holderName);
|
||||
return botClient.createNewPaymentAccount(gson.toJson(p));
|
||||
}
|
||||
|
||||
private Map<String, Object> getPaymentAccountFormMap(String paymentMethodId) {
|
||||
PaymentAccountForm paymentAccountForm = new PaymentAccountForm();
|
||||
File jsonFormTemplate = paymentAccountForm.getPaymentAccountForm(paymentMethodId);
|
||||
jsonFormTemplate.deleteOnExit();
|
||||
String jsonString = paymentAccountForm.toJsonString(jsonFormTemplate);
|
||||
//noinspection unchecked
|
||||
return (Map<String, Object>) gson.fromJson(jsonString, Object.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario.bot;
|
||||
|
||||
import bisq.common.BisqException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class InvalidRandomOfferException extends BisqException {
|
||||
public InvalidRandomOfferException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public InvalidRandomOfferException(String format, Object... args) {
|
||||
super(format, args);
|
||||
}
|
||||
|
||||
public InvalidRandomOfferException(Throwable cause, String format, Object... args) {
|
||||
super(cause, format, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario.bot;
|
||||
|
||||
import bisq.common.BisqException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class PaymentAccountNotFoundException extends BisqException {
|
||||
public PaymentAccountNotFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public PaymentAccountNotFoundException(String format, Object... args) {
|
||||
super(format, args);
|
||||
}
|
||||
|
||||
public PaymentAccountNotFoundException(Throwable cause, String format, Object... args) {
|
||||
super(cause, format, args);
|
||||
}
|
||||
}
|
177
apitest/src/test/java/bisq/apitest/scenario/bot/RandomOffer.java
Normal file
177
apitest/src/test/java/bisq/apitest/scenario/bot/RandomOffer.java
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario.bot;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.cli.CurrencyFormat.formatMarketPrice;
|
||||
import static bisq.cli.CurrencyFormat.formatSatoshis;
|
||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.payment.payload.PaymentMethod.F2F_ID;
|
||||
import static java.lang.String.format;
|
||||
import static java.math.RoundingMode.HALF_UP;
|
||||
|
||||
@Slf4j
|
||||
public class RandomOffer {
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
private static final DecimalFormat FIXED_PRICE_FMT = new DecimalFormat("###########0");
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
// If not an F2F account, keep amount <= 0.01 BTC to avoid hitting unsigned
|
||||
// acct trading limit.
|
||||
private final Supplier<Long> nextAmount = () ->
|
||||
this.getPaymentAccount().getPaymentMethod().getId().equals(F2F_ID)
|
||||
? (long) (10000000 + RANDOM.nextInt(2500000))
|
||||
: (long) (750000 + RANDOM.nextInt(250000));
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final Supplier<Long> nextMinAmount = () -> {
|
||||
boolean useMinAmount = RANDOM.nextBoolean();
|
||||
if (useMinAmount) {
|
||||
return this.getPaymentAccount().getPaymentMethod().getId().equals(F2F_ID)
|
||||
? this.getAmount() - 5000000L
|
||||
: this.getAmount() - 50000L;
|
||||
} else {
|
||||
return this.getAmount();
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final Supplier<Double> nextPriceMargin = () -> {
|
||||
boolean useZeroMargin = RANDOM.nextBoolean();
|
||||
if (useZeroMargin) {
|
||||
return 0.00;
|
||||
} else {
|
||||
BigDecimal min = BigDecimal.valueOf(-5.0).setScale(2, HALF_UP);
|
||||
BigDecimal max = BigDecimal.valueOf(5.0).setScale(2, HALF_UP);
|
||||
BigDecimal randomBigDecimal = min.add(BigDecimal.valueOf(RANDOM.nextDouble()).multiply(max.subtract(min)));
|
||||
return randomBigDecimal.setScale(2, HALF_UP).doubleValue();
|
||||
}
|
||||
};
|
||||
|
||||
private final BotClient botClient;
|
||||
@Getter
|
||||
private final PaymentAccount paymentAccount;
|
||||
@Getter
|
||||
private final String direction;
|
||||
@Getter
|
||||
private final String currencyCode;
|
||||
@Getter
|
||||
private final long amount;
|
||||
@Getter
|
||||
private final long minAmount;
|
||||
@Getter
|
||||
private final boolean useMarketBasedPrice;
|
||||
@Getter
|
||||
private final double priceMargin;
|
||||
@Getter
|
||||
private final String feeCurrency;
|
||||
|
||||
@Getter
|
||||
private String fixedOfferPrice = "0";
|
||||
@Getter
|
||||
private OfferInfo offer;
|
||||
@Getter
|
||||
private String id;
|
||||
|
||||
public RandomOffer(BotClient botClient, PaymentAccount paymentAccount) {
|
||||
this.botClient = botClient;
|
||||
this.paymentAccount = paymentAccount;
|
||||
this.direction = RANDOM.nextBoolean() ? "BUY" : "SELL";
|
||||
this.currencyCode = Objects.requireNonNull(paymentAccount.getSelectedTradeCurrency()).getCode();
|
||||
this.amount = nextAmount.get();
|
||||
this.minAmount = nextMinAmount.get();
|
||||
this.useMarketBasedPrice = RANDOM.nextBoolean();
|
||||
this.priceMargin = nextPriceMargin.get();
|
||||
this.feeCurrency = RANDOM.nextBoolean() ? "BSQ" : "BTC";
|
||||
}
|
||||
|
||||
public RandomOffer create() throws InvalidRandomOfferException {
|
||||
try {
|
||||
printDescription();
|
||||
if (useMarketBasedPrice) {
|
||||
this.offer = botClient.createOfferAtMarketBasedPrice(paymentAccount,
|
||||
direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
priceMargin,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
feeCurrency);
|
||||
} else {
|
||||
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
|
||||
direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
fixedOfferPrice,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
feeCurrency);
|
||||
}
|
||||
this.id = offer.getId();
|
||||
return this;
|
||||
} catch (Exception ex) {
|
||||
String error = format("Could not create valid %s offer for %s BTC: %s",
|
||||
currencyCode,
|
||||
formatSatoshis(amount),
|
||||
ex.getMessage());
|
||||
throw new InvalidRandomOfferException(error, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void printDescription() {
|
||||
double currentMarketPrice = botClient.getCurrentBTCMarketPrice(currencyCode);
|
||||
// Calculate a fixed price based on the random mkt price margin, even if we don't use it.
|
||||
double differenceFromMarketPrice = currentMarketPrice * scaleDownByPowerOf10(priceMargin, 2);
|
||||
double fixedOfferPriceAsDouble = direction.equals("BUY")
|
||||
? currentMarketPrice - differenceFromMarketPrice
|
||||
: currentMarketPrice + differenceFromMarketPrice;
|
||||
this.fixedOfferPrice = FIXED_PRICE_FMT.format(fixedOfferPriceAsDouble);
|
||||
String description = format("Creating new %s %s / %s offer for amount = %s BTC, min-amount = %s BTC.",
|
||||
useMarketBasedPrice ? "mkt-based-price" : "fixed-priced",
|
||||
direction,
|
||||
currencyCode,
|
||||
formatSatoshis(amount),
|
||||
formatSatoshis(minAmount));
|
||||
log.info(description);
|
||||
if (useMarketBasedPrice) {
|
||||
log.info("Offer Price Margin = {}%", priceMargin);
|
||||
log.info("Expected Offer Price = {} {}", formatMarketPrice(Double.parseDouble(fixedOfferPrice)), currencyCode);
|
||||
} else {
|
||||
|
||||
log.info("Fixed Offer Price = {} {}", fixedOfferPrice, currencyCode);
|
||||
}
|
||||
log.info("Current Market Price = {} {}", formatMarketPrice(currentMarketPrice), currencyCode);
|
||||
}
|
||||
}
|
141
apitest/src/test/java/bisq/apitest/scenario/bot/RobotBob.java
Normal file
141
apitest/src/test/java/bisq/apitest/scenario/bot/RobotBob.java
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario.bot;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.DONE;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.isShutdownCalled;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.protocol.BotProtocol;
|
||||
import bisq.apitest.scenario.bot.protocol.MakerBotProtocol;
|
||||
import bisq.apitest.scenario.bot.protocol.TakerBotProtocol;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.script.BotScript;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
|
||||
@Slf4j
|
||||
public
|
||||
class RobotBob extends Bot {
|
||||
|
||||
@Getter
|
||||
private int numTrades;
|
||||
|
||||
public RobotBob(BotClient botClient,
|
||||
BotScript botScript,
|
||||
BitcoinCliHelper bitcoinCli,
|
||||
BashScriptGenerator bashScriptGenerator) {
|
||||
super(botClient, botScript, bitcoinCli, bashScriptGenerator);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
for (String action : actions) {
|
||||
checkActionIsValid(action);
|
||||
|
||||
BotProtocol botProtocol;
|
||||
if (action.equalsIgnoreCase(MAKE)) {
|
||||
botProtocol = new MakerBotProtocol(botClient,
|
||||
paymentAccount,
|
||||
protocolStepTimeLimitInMs,
|
||||
bitcoinCli,
|
||||
bashScriptGenerator);
|
||||
} else {
|
||||
botProtocol = new TakerBotProtocol(botClient,
|
||||
paymentAccount,
|
||||
protocolStepTimeLimitInMs,
|
||||
bitcoinCli,
|
||||
bashScriptGenerator);
|
||||
}
|
||||
|
||||
botProtocol.run();
|
||||
|
||||
if (!botProtocol.getCurrentProtocolStep().equals(DONE)) {
|
||||
throw new IllegalStateException(botProtocol.getClass().getSimpleName() + " failed to complete.");
|
||||
}
|
||||
|
||||
log.info("Completed {} successful trade{}. Current Balance:\n{}",
|
||||
++numTrades,
|
||||
numTrades == 1 ? "" : "s",
|
||||
formatBalancesTbls(botClient.getBalance()));
|
||||
|
||||
if (numTrades < actions.length) {
|
||||
try {
|
||||
SECONDS.sleep(20);
|
||||
} catch (InterruptedException ignored) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
|
||||
} // end of actions loop
|
||||
|
||||
if (stayAlive)
|
||||
waitForManualShutdown();
|
||||
else
|
||||
warnCLIUserBeforeShutdown();
|
||||
}
|
||||
|
||||
private void checkActionIsValid(String action) {
|
||||
if (!action.equalsIgnoreCase(MAKE) && !action.equalsIgnoreCase(TAKE))
|
||||
throw new IllegalStateException(action + " is not a valid bot action; must be 'make' or 'take'");
|
||||
}
|
||||
|
||||
private void waitForManualShutdown() {
|
||||
String harnessOrCase = isUsingTestHarness ? "harness" : "case";
|
||||
log.info("All script actions have been completed, but the test {} will stay alive"
|
||||
+ " until a /tmp/bottest-shutdown file is detected.",
|
||||
harnessOrCase);
|
||||
log.info("When ready to shutdown the test {}, run '$ touch /tmp/bottest-shutdown'.",
|
||||
harnessOrCase);
|
||||
if (!isUsingTestHarness) {
|
||||
log.warn("You will have to manually shutdown the bitcoind and Bisq nodes"
|
||||
+ " running outside of the test harness.");
|
||||
}
|
||||
try {
|
||||
while (!isShutdownCalled()) {
|
||||
SECONDS.sleep(10);
|
||||
}
|
||||
log.warn("Manual shutdown signal received.");
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
log.warn(ex.getMessage());
|
||||
} catch (InterruptedException ignored) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
|
||||
private void warnCLIUserBeforeShutdown() {
|
||||
if (isUsingTestHarness) {
|
||||
long delayInSeconds = 30;
|
||||
log.warn("All script actions have been completed. You have {} seconds to complete any"
|
||||
+ " remaining tasks before the test harness shuts down.",
|
||||
delayInSeconds);
|
||||
try {
|
||||
SECONDS.sleep(delayInSeconds);
|
||||
} catch (InterruptedException ignored) {
|
||||
// empty
|
||||
}
|
||||
} else {
|
||||
log.info("Shutting down test case");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario.bot.protocol;
|
||||
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.*;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.currentTimeMillis;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.BotClient;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
import bisq.cli.TradeFormat;
|
||||
|
||||
@Slf4j
|
||||
public abstract class BotProtocol {
|
||||
|
||||
static final SecureRandom RANDOM = new SecureRandom();
|
||||
static final String BUY = "BUY";
|
||||
static final String SELL = "SELL";
|
||||
|
||||
protected final Supplier<Long> randomDelay = () -> (long) (2000 + RANDOM.nextInt(5000));
|
||||
|
||||
protected final AtomicLong protocolStepStartTime = new AtomicLong(0);
|
||||
protected final Consumer<ProtocolStep> initProtocolStep = (step) -> {
|
||||
currentProtocolStep = step;
|
||||
printBotProtocolStep();
|
||||
protocolStepStartTime.set(currentTimeMillis());
|
||||
};
|
||||
|
||||
@Getter
|
||||
protected ProtocolStep currentProtocolStep;
|
||||
|
||||
@Getter // Functions within 'this' need the @Getter.
|
||||
protected final BotClient botClient;
|
||||
protected final PaymentAccount paymentAccount;
|
||||
protected final String currencyCode;
|
||||
protected final long protocolStepTimeLimitInMs;
|
||||
protected final BitcoinCliHelper bitcoinCli;
|
||||
@Getter
|
||||
protected final BashScriptGenerator bashScriptGenerator;
|
||||
|
||||
public BotProtocol(BotClient botClient,
|
||||
PaymentAccount paymentAccount,
|
||||
long protocolStepTimeLimitInMs,
|
||||
BitcoinCliHelper bitcoinCli,
|
||||
BashScriptGenerator bashScriptGenerator) {
|
||||
this.botClient = botClient;
|
||||
this.paymentAccount = paymentAccount;
|
||||
this.currencyCode = Objects.requireNonNull(paymentAccount.getSelectedTradeCurrency()).getCode();
|
||||
this.protocolStepTimeLimitInMs = protocolStepTimeLimitInMs;
|
||||
this.bitcoinCli = bitcoinCli;
|
||||
this.bashScriptGenerator = bashScriptGenerator;
|
||||
this.currentProtocolStep = START;
|
||||
}
|
||||
|
||||
public abstract void run();
|
||||
|
||||
protected boolean isWithinProtocolStepTimeLimit() {
|
||||
return (currentTimeMillis() - protocolStepStartTime.get()) < protocolStepTimeLimitInMs;
|
||||
}
|
||||
|
||||
protected void checkIsStartStep() {
|
||||
if (currentProtocolStep != START) {
|
||||
throw new IllegalStateException("First bot protocol step must be " + START.name());
|
||||
}
|
||||
}
|
||||
|
||||
protected void printBotProtocolStep() {
|
||||
log.info("Starting protocol step {}. Bot will shutdown if step not completed within {} minutes.",
|
||||
currentProtocolStep.name(), MILLISECONDS.toMinutes(protocolStepTimeLimitInMs));
|
||||
|
||||
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED)) {
|
||||
log.info("Generate a btc block to trigger taker's deposit fee tx confirmation.");
|
||||
createGenerateBtcBlockScript();
|
||||
}
|
||||
}
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> waitForTakerFeeTxConfirm = (trade) -> {
|
||||
sleep(5000);
|
||||
waitForTakerFeeTxPublished(trade.getTradeId());
|
||||
waitForTakerFeeTxConfirmed(trade.getTradeId());
|
||||
return trade;
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> waitForPaymentStartedMessage = (trade) -> {
|
||||
initProtocolStep.accept(WAIT_FOR_PAYMENT_STARTED_MESSAGE);
|
||||
try {
|
||||
createPaymentStartedScript(trade);
|
||||
log.info(" Waiting for a 'payment started' message from buyer for trade with id {}.", trade.getTradeId());
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
checkIfShutdownCalled("Interrupted before checking if 'payment started' message has been sent.");
|
||||
try {
|
||||
var t = this.getBotClient().getTrade(trade.getTradeId());
|
||||
if (t.getIsFiatSent()) {
|
||||
log.info("Buyer has started payment for trade:\n{}", TradeFormat.format(t));
|
||||
return t;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex));
|
||||
}
|
||||
sleep(randomDelay.get());
|
||||
} // end while
|
||||
|
||||
throw new IllegalStateException("Payment was never sent; we won't wait any longer.");
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
throw ex; // not an error, tells bot to shutdown
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Error while waiting payment sent message.", ex);
|
||||
}
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> sendPaymentStartedMessage = (trade) -> {
|
||||
initProtocolStep.accept(SEND_PAYMENT_STARTED_MESSAGE);
|
||||
checkIfShutdownCalled("Interrupted before sending 'payment started' message.");
|
||||
this.getBotClient().sendConfirmPaymentStartedMessage(trade.getTradeId());
|
||||
return trade;
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> waitForPaymentReceivedConfirmation = (trade) -> {
|
||||
initProtocolStep.accept(WAIT_FOR_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE);
|
||||
createPaymentReceivedScript(trade);
|
||||
try {
|
||||
log.info("Waiting for a 'payment received confirmation' message from seller for trade with id {}.", trade.getTradeId());
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
checkIfShutdownCalled("Interrupted before checking if 'payment received confirmation' message has been sent.");
|
||||
try {
|
||||
var t = this.getBotClient().getTrade(trade.getTradeId());
|
||||
if (t.getIsFiatReceived()) {
|
||||
log.info("Seller has received payment for trade:\n{}", TradeFormat.format(t));
|
||||
return t;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex));
|
||||
}
|
||||
sleep(randomDelay.get());
|
||||
} // end while
|
||||
|
||||
throw new IllegalStateException("Payment was never received; we won't wait any longer.");
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
throw ex; // not an error, tells bot to shutdown
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Error while waiting payment received confirmation message.", ex);
|
||||
}
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> sendPaymentReceivedMessage = (trade) -> {
|
||||
initProtocolStep.accept(SEND_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE);
|
||||
checkIfShutdownCalled("Interrupted before sending 'payment received confirmation' message.");
|
||||
this.getBotClient().sendConfirmPaymentReceivedMessage(trade.getTradeId());
|
||||
return trade;
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> waitForPayoutTx = (trade) -> {
|
||||
initProtocolStep.accept(WAIT_FOR_PAYOUT_TX);
|
||||
try {
|
||||
log.info("Waiting on the 'payout tx published confirmation' for trade with id {}.", trade.getTradeId());
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
checkIfShutdownCalled("Interrupted before checking if payout tx has been published.");
|
||||
try {
|
||||
var t = this.getBotClient().getTrade(trade.getTradeId());
|
||||
if (t.getIsPayoutPublished()) {
|
||||
log.info("Payout tx {} has been published for trade:\n{}",
|
||||
t.getPayoutTxId(),
|
||||
TradeFormat.format(t));
|
||||
return t;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex));
|
||||
}
|
||||
sleep(randomDelay.get());
|
||||
} // end while
|
||||
|
||||
throw new IllegalStateException("Payout tx was never published; we won't wait any longer.");
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
throw ex; // not an error, tells bot to shutdown
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Error while waiting for published payout tx.", ex);
|
||||
}
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> keepFundsFromTrade = (trade) -> {
|
||||
initProtocolStep.accept(KEEP_FUNDS);
|
||||
var isBuy = trade.getOffer().getDirection().equalsIgnoreCase(BUY);
|
||||
var isSell = trade.getOffer().getDirection().equalsIgnoreCase(SELL);
|
||||
var cliUserIsSeller = (this instanceof MakerBotProtocol && isBuy) || (this instanceof TakerBotProtocol && isSell);
|
||||
if (cliUserIsSeller) {
|
||||
createKeepFundsScript(trade);
|
||||
} else {
|
||||
createGetBalanceScript();
|
||||
}
|
||||
checkIfShutdownCalled("Interrupted before closing trade with 'keep funds' command.");
|
||||
this.getBotClient().sendKeepFundsMessage(trade.getTradeId());
|
||||
return trade;
|
||||
};
|
||||
|
||||
protected void createPaymentStartedScript(TradeInfo trade) {
|
||||
File script = bashScriptGenerator.createPaymentStartedScript(trade);
|
||||
printCliHintAndOrScript(script, "The manual CLI side can send a 'payment started' message");
|
||||
}
|
||||
|
||||
protected void createPaymentReceivedScript(TradeInfo trade) {
|
||||
File script = bashScriptGenerator.createPaymentReceivedScript(trade);
|
||||
printCliHintAndOrScript(script, "The manual CLI side can sent a 'payment received confirmation' message");
|
||||
}
|
||||
|
||||
protected void createKeepFundsScript(TradeInfo trade) {
|
||||
File script = bashScriptGenerator.createKeepFundsScript(trade);
|
||||
printCliHintAndOrScript(script, "The manual CLI side can close the trade");
|
||||
}
|
||||
|
||||
protected void createGetBalanceScript() {
|
||||
File script = bashScriptGenerator.createGetBalanceScript();
|
||||
printCliHintAndOrScript(script, "The manual CLI side can view current balances");
|
||||
}
|
||||
|
||||
protected void createGenerateBtcBlockScript() {
|
||||
String newBitcoinCoreAddress = bitcoinCli.getNewBtcAddress();
|
||||
File script = bashScriptGenerator.createGenerateBtcBlockScript(newBitcoinCoreAddress);
|
||||
printCliHintAndOrScript(script, "The manual CLI side can generate 1 btc block");
|
||||
}
|
||||
|
||||
protected void printCliHintAndOrScript(File script, String hint) {
|
||||
log.info("{} by running bash script '{}'.", hint, script.getAbsolutePath());
|
||||
if (this.getBashScriptGenerator().isPrintCliScripts())
|
||||
this.getBashScriptGenerator().printCliScript(script, log);
|
||||
|
||||
sleep(5000); // Allow 5s for CLI user to read the hint.
|
||||
}
|
||||
|
||||
protected void sleep(long ms) {
|
||||
try {
|
||||
MILLISECONDS.sleep(ms);
|
||||
} catch (InterruptedException ignored) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
|
||||
private void waitForTakerFeeTxPublished(String tradeId) {
|
||||
waitForTakerDepositFee(tradeId, WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED);
|
||||
}
|
||||
|
||||
private void waitForTakerFeeTxConfirmed(String tradeId) {
|
||||
waitForTakerDepositFee(tradeId, WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED);
|
||||
}
|
||||
|
||||
private void waitForTakerDepositFee(String tradeId, ProtocolStep depositTxProtocolStep) {
|
||||
initProtocolStep.accept(depositTxProtocolStep);
|
||||
validateCurrentProtocolStep(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED, WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED);
|
||||
try {
|
||||
log.info(waitingForDepositFeeTxMsg(tradeId));
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
checkIfShutdownCalled("Interrupted before checking taker deposit fee tx is published and confirmed.");
|
||||
try {
|
||||
var trade = this.getBotClient().getTrade(tradeId);
|
||||
if (isDepositFeeTxStepComplete.test(trade))
|
||||
return;
|
||||
else
|
||||
sleep(randomDelay.get());
|
||||
} catch (Exception ex) {
|
||||
if (this.getBotClient().tradeContractIsNotReady.test(ex, tradeId))
|
||||
sleep(randomDelay.get());
|
||||
else
|
||||
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex));
|
||||
}
|
||||
} // end while
|
||||
throw new IllegalStateException(stoppedWaitingForDepositFeeTxMsg(this.getBotClient().getTrade(tradeId).getDepositTxId()));
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
throw ex; // not an error, tells bot to shutdown
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Error while waiting for taker deposit tx to be published or confirmed.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private final Predicate<TradeInfo> isDepositFeeTxStepComplete = (trade) -> {
|
||||
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) && trade.getIsDepositPublished()) {
|
||||
log.info("Taker deposit fee tx {} has been published.", trade.getDepositTxId());
|
||||
return true;
|
||||
} else if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED) && trade.getIsDepositConfirmed()) {
|
||||
log.info("Taker deposit fee tx {} has been confirmed.", trade.getDepositTxId());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private void validateCurrentProtocolStep(Enum<?>... validBotSteps) {
|
||||
for (Enum<?> validBotStep : validBotSteps) {
|
||||
if (currentProtocolStep.equals(validBotStep))
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException("Unexpected bot step: " + currentProtocolStep.name() + ".\n"
|
||||
+ "Must be one of "
|
||||
+ stream(validBotSteps).map((Enum::name)).collect(Collectors.joining(","))
|
||||
+ ".");
|
||||
}
|
||||
|
||||
private String waitingForDepositFeeTxMsg(String tradeId) {
|
||||
return format("Waiting for taker deposit fee tx for trade %s to be %s.",
|
||||
tradeId,
|
||||
currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) ? "published" : "confirmed");
|
||||
}
|
||||
|
||||
private String stoppedWaitingForDepositFeeTxMsg(String txId) {
|
||||
return format("Taker deposit fee tx %s is took too long to be %s; we won't wait any longer.",
|
||||
txId,
|
||||
currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) ? "published" : "confirmed");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package bisq.apitest.scenario.bot.protocol;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.DONE;
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.WAIT_FOR_OFFER_TAKER;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.BotClient;
|
||||
import bisq.apitest.scenario.bot.RandomOffer;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
import bisq.cli.TradeFormat;
|
||||
|
||||
@Slf4j
|
||||
public class MakerBotProtocol extends BotProtocol {
|
||||
|
||||
public MakerBotProtocol(BotClient botClient,
|
||||
PaymentAccount paymentAccount,
|
||||
long protocolStepTimeLimitInMs,
|
||||
BitcoinCliHelper bitcoinCli,
|
||||
BashScriptGenerator bashScriptGenerator) {
|
||||
super(botClient,
|
||||
paymentAccount,
|
||||
protocolStepTimeLimitInMs,
|
||||
bitcoinCli,
|
||||
bashScriptGenerator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
checkIsStartStep();
|
||||
|
||||
Function<Supplier<OfferInfo>, TradeInfo> makeTrade = waitForNewTrade.andThen(waitForTakerFeeTxConfirm);
|
||||
var trade = makeTrade.apply(randomOffer);
|
||||
|
||||
var makerIsBuyer = trade.getOffer().getDirection().equalsIgnoreCase(BUY);
|
||||
Function<TradeInfo, TradeInfo> completeFiatTransaction = makerIsBuyer
|
||||
? sendPaymentStartedMessage.andThen(waitForPaymentReceivedConfirmation)
|
||||
: waitForPaymentStartedMessage.andThen(sendPaymentReceivedMessage);
|
||||
completeFiatTransaction.apply(trade);
|
||||
|
||||
Function<TradeInfo, TradeInfo> closeTrade = waitForPayoutTx.andThen(keepFundsFromTrade);
|
||||
closeTrade.apply(trade);
|
||||
|
||||
currentProtocolStep = DONE;
|
||||
}
|
||||
|
||||
private final Supplier<OfferInfo> randomOffer = () -> {
|
||||
checkIfShutdownCalled("Interrupted before creating random offer.");
|
||||
OfferInfo offer = new RandomOffer(botClient, paymentAccount).create().getOffer();
|
||||
log.info("Created random {} offer\n{}", currencyCode, formatOfferTable(singletonList(offer), currencyCode));
|
||||
return offer;
|
||||
};
|
||||
|
||||
private final Function<Supplier<OfferInfo>, TradeInfo> waitForNewTrade = (randomOffer) -> {
|
||||
initProtocolStep.accept(WAIT_FOR_OFFER_TAKER);
|
||||
OfferInfo offer = randomOffer.get();
|
||||
createTakeOfferCliScript(offer);
|
||||
try {
|
||||
log.info("Impatiently waiting for offer {} to be taken, repeatedly calling gettrade.", offer.getId());
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
checkIfShutdownCalled("Interrupted while waiting for offer to be taken.");
|
||||
try {
|
||||
var trade = getNewTrade(offer.getId());
|
||||
if (trade.isPresent())
|
||||
return trade.get();
|
||||
else
|
||||
sleep(randomDelay.get());
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex), ex);
|
||||
}
|
||||
} // end while
|
||||
throw new IllegalStateException("Offer was never taken; we won't wait any longer.");
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
throw ex; // not an error, tells bot to shutdown
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Error while waiting for offer to be taken.", ex);
|
||||
}
|
||||
};
|
||||
|
||||
private Optional<TradeInfo> getNewTrade(String offerId) {
|
||||
try {
|
||||
var trade = botClient.getTrade(offerId);
|
||||
log.info("Offer {} was taken, new trade:\n{}", offerId, TradeFormat.format(trade));
|
||||
return Optional.of(trade);
|
||||
} catch (Exception ex) {
|
||||
// Get trade will throw a non-fatal gRPC exception if not found.
|
||||
log.info(this.getBotClient().toCleanGrpcExceptionMessage(ex));
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void createTakeOfferCliScript(OfferInfo offer) {
|
||||
File script = bashScriptGenerator.createTakeOfferScript(offer);
|
||||
printCliHintAndOrScript(script, "The manual CLI side can take the offer");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package bisq.apitest.scenario.bot.protocol;
|
||||
|
||||
public enum ProtocolStep {
|
||||
START,
|
||||
FIND_OFFER,
|
||||
TAKE_OFFER,
|
||||
WAIT_FOR_OFFER_TAKER,
|
||||
WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED,
|
||||
WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED,
|
||||
SEND_PAYMENT_STARTED_MESSAGE,
|
||||
WAIT_FOR_PAYMENT_STARTED_MESSAGE,
|
||||
SEND_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE,
|
||||
WAIT_FOR_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE,
|
||||
WAIT_FOR_PAYOUT_TX,
|
||||
KEEP_FUNDS,
|
||||
DONE
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package bisq.apitest.scenario.bot.protocol;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.DONE;
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.FIND_OFFER;
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.TAKE_OFFER;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.core.payment.payload.PaymentMethod.F2F_ID;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.BotClient;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
|
||||
@Slf4j
|
||||
public class TakerBotProtocol extends BotProtocol {
|
||||
|
||||
public TakerBotProtocol(BotClient botClient,
|
||||
PaymentAccount paymentAccount,
|
||||
long protocolStepTimeLimitInMs,
|
||||
BitcoinCliHelper bitcoinCli,
|
||||
BashScriptGenerator bashScriptGenerator) {
|
||||
super(botClient,
|
||||
paymentAccount,
|
||||
protocolStepTimeLimitInMs,
|
||||
bitcoinCli,
|
||||
bashScriptGenerator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
checkIsStartStep();
|
||||
|
||||
Function<OfferInfo, TradeInfo> takeTrade = takeOffer.andThen(waitForTakerFeeTxConfirm);
|
||||
var trade = takeTrade.apply(findOffer.get());
|
||||
|
||||
var takerIsSeller = trade.getOffer().getDirection().equalsIgnoreCase(BUY);
|
||||
Function<TradeInfo, TradeInfo> completeFiatTransaction = takerIsSeller
|
||||
? waitForPaymentStartedMessage.andThen(sendPaymentReceivedMessage)
|
||||
: sendPaymentStartedMessage.andThen(waitForPaymentReceivedConfirmation);
|
||||
completeFiatTransaction.apply(trade);
|
||||
|
||||
Function<TradeInfo, TradeInfo> closeTrade = waitForPayoutTx.andThen(keepFundsFromTrade);
|
||||
closeTrade.apply(trade);
|
||||
|
||||
currentProtocolStep = DONE;
|
||||
}
|
||||
|
||||
private final Supplier<Optional<OfferInfo>> firstOffer = () -> {
|
||||
var offers = botClient.getOffers(currencyCode);
|
||||
if (offers.size() > 0) {
|
||||
log.info("Offers found:\n{}", formatOfferTable(offers, currencyCode));
|
||||
OfferInfo offer = offers.get(0);
|
||||
log.info("Will take first offer {}", offer.getId());
|
||||
return Optional.of(offer);
|
||||
} else {
|
||||
log.info("No buy or sell {} offers found.", currencyCode);
|
||||
return Optional.empty();
|
||||
}
|
||||
};
|
||||
|
||||
private final Supplier<OfferInfo> findOffer = () -> {
|
||||
initProtocolStep.accept(FIND_OFFER);
|
||||
createMakeOfferScript();
|
||||
try {
|
||||
log.info("Impatiently waiting for at least one {} offer to be created, repeatedly calling getoffers.", currencyCode);
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
checkIfShutdownCalled("Interrupted while checking offers.");
|
||||
try {
|
||||
Optional<OfferInfo> offer = firstOffer.get();
|
||||
if (offer.isPresent())
|
||||
return offer.get();
|
||||
else
|
||||
sleep(randomDelay.get());
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException(this.getBotClient().toCleanGrpcExceptionMessage(ex), ex);
|
||||
}
|
||||
} // end while
|
||||
throw new IllegalStateException("Offer was never created; we won't wait any longer.");
|
||||
} catch (ManualBotShutdownException ex) {
|
||||
throw ex; // not an error, tells bot to shutdown
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Error while waiting for a new offer.", ex);
|
||||
}
|
||||
};
|
||||
|
||||
private final Function<OfferInfo, TradeInfo> takeOffer = (offer) -> {
|
||||
initProtocolStep.accept(TAKE_OFFER);
|
||||
checkIfShutdownCalled("Interrupted before taking offer.");
|
||||
String feeCurrency = RANDOM.nextBoolean() ? "BSQ" : "BTC";
|
||||
return botClient.takeOffer(offer.getId(), paymentAccount, feeCurrency);
|
||||
};
|
||||
|
||||
private void createMakeOfferScript() {
|
||||
String direction = RANDOM.nextBoolean() ? "BUY" : "SELL";
|
||||
String feeCurrency = RANDOM.nextBoolean() ? "BSQ" : "BTC";
|
||||
boolean createMarginPricedOffer = RANDOM.nextBoolean();
|
||||
// If not using an F2F account, don't go over possible 0.01 BTC
|
||||
// limit if account is not signed.
|
||||
String amount = paymentAccount.getPaymentMethod().getId().equals(F2F_ID)
|
||||
? "0.25"
|
||||
: "0.01";
|
||||
File script;
|
||||
if (createMarginPricedOffer) {
|
||||
script = bashScriptGenerator.createMakeMarginPricedOfferScript(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
"0.0",
|
||||
"15.0",
|
||||
feeCurrency);
|
||||
} else {
|
||||
script = bashScriptGenerator.createMakeFixedPricedOfferScript(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
botClient.getCurrentBTCMarketPriceAsIntegerString(currencyCode),
|
||||
"15.0",
|
||||
feeCurrency);
|
||||
}
|
||||
printCliHintAndOrScript(script, "The manual CLI side can create an offer");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario.bot.script;
|
||||
|
||||
import bisq.common.file.FileUtil;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.io.FileWriteMode.APPEND;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.getProperty;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.nio.file.Files.readAllBytes;
|
||||
|
||||
@Slf4j
|
||||
@Getter
|
||||
public class BashScriptGenerator {
|
||||
|
||||
private final int apiPort;
|
||||
private final String apiPassword;
|
||||
private final String paymentAccountId;
|
||||
private final String cliBase;
|
||||
private final boolean printCliScripts;
|
||||
|
||||
public BashScriptGenerator(String apiPassword,
|
||||
int apiPort,
|
||||
String paymentAccountId,
|
||||
boolean printCliScripts) {
|
||||
this.apiPassword = apiPassword;
|
||||
this.apiPort = apiPort;
|
||||
this.paymentAccountId = paymentAccountId;
|
||||
this.printCliScripts = printCliScripts;
|
||||
this.cliBase = format("./bisq-cli --password=%s --port=%d", apiPassword, apiPort);
|
||||
}
|
||||
|
||||
public File createMakeMarginPricedOfferScript(String direction,
|
||||
String currencyCode,
|
||||
String amount,
|
||||
String marketPriceMargin,
|
||||
String securityDeposit,
|
||||
String feeCurrency) {
|
||||
String makeOfferCmd = format("%s createoffer --payment-account=%s "
|
||||
+ " --direction=%s"
|
||||
+ " --currency-code=%s"
|
||||
+ " --amount=%s"
|
||||
+ " --market-price-margin=%s"
|
||||
+ " --security-deposit=%s"
|
||||
+ " --fee-currency=%s",
|
||||
cliBase,
|
||||
this.getPaymentAccountId(),
|
||||
direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
marketPriceMargin,
|
||||
securityDeposit,
|
||||
feeCurrency);
|
||||
String getOffersCmd = format("%s getmyoffers --direction=%s --currency-code=%s",
|
||||
cliBase,
|
||||
direction,
|
||||
currencyCode);
|
||||
return createCliScript("createoffer.sh",
|
||||
makeOfferCmd,
|
||||
"sleep 2",
|
||||
getOffersCmd);
|
||||
}
|
||||
|
||||
public File createMakeFixedPricedOfferScript(String direction,
|
||||
String currencyCode,
|
||||
String amount,
|
||||
String fixedPrice,
|
||||
String securityDeposit,
|
||||
String feeCurrency) {
|
||||
String makeOfferCmd = format("%s createoffer --payment-account=%s "
|
||||
+ " --direction=%s"
|
||||
+ " --currency-code=%s"
|
||||
+ " --amount=%s"
|
||||
+ " --fixed-price=%s"
|
||||
+ " --security-deposit=%s"
|
||||
+ " --fee-currency=%s",
|
||||
cliBase,
|
||||
this.getPaymentAccountId(),
|
||||
direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
fixedPrice,
|
||||
securityDeposit,
|
||||
feeCurrency);
|
||||
String getOffersCmd = format("%s getmyoffers --direction=%s --currency-code=%s",
|
||||
cliBase,
|
||||
direction,
|
||||
currencyCode);
|
||||
return createCliScript("createoffer.sh",
|
||||
makeOfferCmd,
|
||||
"sleep 2",
|
||||
getOffersCmd);
|
||||
}
|
||||
|
||||
public File createTakeOfferScript(OfferInfo offer) {
|
||||
String getOffersCmd = format("%s getoffers --direction=%s --currency-code=%s",
|
||||
cliBase,
|
||||
offer.getDirection(),
|
||||
offer.getCounterCurrencyCode());
|
||||
String takeOfferCmd = format("%s takeoffer --offer-id=%s --payment-account=%s --fee-currency=BSQ",
|
||||
cliBase,
|
||||
offer.getId(),
|
||||
this.getPaymentAccountId());
|
||||
String getTradeCmd = format("%s gettrade --trade-id=%s",
|
||||
cliBase,
|
||||
offer.getId());
|
||||
return createCliScript("takeoffer.sh",
|
||||
getOffersCmd,
|
||||
takeOfferCmd,
|
||||
"sleep 5",
|
||||
getTradeCmd);
|
||||
}
|
||||
|
||||
public File createPaymentStartedScript(TradeInfo trade) {
|
||||
String paymentStartedCmd = format("%s confirmpaymentstarted --trade-id=%s",
|
||||
cliBase,
|
||||
trade.getTradeId());
|
||||
String getTradeCmd = format("%s gettrade --trade-id=%s", cliBase, trade.getTradeId());
|
||||
return createCliScript("confirmpaymentstarted.sh",
|
||||
paymentStartedCmd,
|
||||
"sleep 2",
|
||||
getTradeCmd);
|
||||
}
|
||||
|
||||
public File createPaymentReceivedScript(TradeInfo trade) {
|
||||
String paymentStartedCmd = format("%s confirmpaymentreceived --trade-id=%s",
|
||||
cliBase,
|
||||
trade.getTradeId());
|
||||
String getTradeCmd = format("%s gettrade --trade-id=%s", cliBase, trade.getTradeId());
|
||||
return createCliScript("confirmpaymentreceived.sh",
|
||||
paymentStartedCmd,
|
||||
"sleep 2",
|
||||
getTradeCmd);
|
||||
}
|
||||
|
||||
public File createKeepFundsScript(TradeInfo trade) {
|
||||
String paymentStartedCmd = format("%s keepfunds --trade-id=%s", cliBase, trade.getTradeId());
|
||||
String getTradeCmd = format("%s gettrade --trade-id=%s", cliBase, trade.getTradeId());
|
||||
String getBalanceCmd = format("%s getbalance", cliBase);
|
||||
return createCliScript("keepfunds.sh",
|
||||
paymentStartedCmd,
|
||||
"sleep 2",
|
||||
getTradeCmd,
|
||||
getBalanceCmd);
|
||||
}
|
||||
|
||||
public File createGetBalanceScript() {
|
||||
String getBalanceCmd = format("%s getbalance", cliBase);
|
||||
return createCliScript("getbalance.sh", getBalanceCmd);
|
||||
}
|
||||
|
||||
public File createGenerateBtcBlockScript(String address) {
|
||||
String bitcoinCliCmd = format("bitcoin-cli -regtest -rpcport=19443 -rpcuser=apitest"
|
||||
+ " -rpcpassword=apitest generatetoaddress 1 \"%s\"",
|
||||
address);
|
||||
return createCliScript("genbtcblk.sh",
|
||||
bitcoinCliCmd);
|
||||
}
|
||||
|
||||
public File createCliScript(String scriptName, String... commands) {
|
||||
String filename = getProperty("java.io.tmpdir") + File.separator + scriptName;
|
||||
File oldScript = new File(filename);
|
||||
if (oldScript.exists()) {
|
||||
try {
|
||||
FileUtil.deleteFileIfExists(oldScript);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("Unable to delete old script.", ex);
|
||||
}
|
||||
}
|
||||
File script = new File(filename);
|
||||
try {
|
||||
List<CharSequence> lines = new ArrayList<>();
|
||||
lines.add("#!/bin/bash");
|
||||
lines.add("############################################################");
|
||||
lines.add("# This example CLI script may be overwritten during the test");
|
||||
lines.add("# run, and will be deleted when the test harness shuts down.");
|
||||
lines.add("# Make a copy if you want to save it.");
|
||||
lines.add("############################################################");
|
||||
lines.add("set -x");
|
||||
Collections.addAll(lines, commands);
|
||||
Files.asCharSink(script, UTF_8, APPEND).writeLines(lines);
|
||||
if (!script.setExecutable(true))
|
||||
throw new IllegalStateException("Unable to set script owner's execute permission.");
|
||||
} catch (IOException ex) {
|
||||
log.error("", ex);
|
||||
throw new IllegalStateException(ex);
|
||||
} finally {
|
||||
script.deleteOnExit();
|
||||
}
|
||||
return script;
|
||||
}
|
||||
|
||||
public void printCliScript(File cliScript,
|
||||
org.slf4j.Logger logger) {
|
||||
try {
|
||||
String contents = new String(readAllBytes(Paths.get(cliScript.getPath())));
|
||||
logger.info("CLI script {}:\n{}", cliScript.getAbsolutePath(), contents);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("Error reading CLI script contents.", ex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario.bot.script;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public
|
||||
class BotScript {
|
||||
|
||||
// Common, default is true.
|
||||
private final boolean useTestHarness;
|
||||
|
||||
// Used only with test harness. Mutually exclusive, but if both are not null,
|
||||
// the botPaymentMethodId takes precedence over countryCode.
|
||||
@Nullable
|
||||
private final String botPaymentMethodId;
|
||||
@Nullable
|
||||
private final String countryCode;
|
||||
|
||||
// Used only without test harness.
|
||||
@Nullable
|
||||
@Setter
|
||||
private String paymentAccountIdForBot;
|
||||
@Nullable
|
||||
@Setter
|
||||
private String paymentAccountIdForCliScripts;
|
||||
|
||||
// Common, used with or without test harness.
|
||||
private final int apiPortForCliScripts;
|
||||
private final String[] actions;
|
||||
private final long protocolStepTimeLimitInMinutes;
|
||||
private final boolean printCliScripts;
|
||||
private final boolean stayAlive;
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
BotScript(boolean useTestHarness,
|
||||
String botPaymentMethodId,
|
||||
String countryCode,
|
||||
String paymentAccountIdForBot,
|
||||
String paymentAccountIdForCliScripts,
|
||||
String[] actions,
|
||||
int apiPortForCliScripts,
|
||||
long protocolStepTimeLimitInMinutes,
|
||||
boolean printCliScripts,
|
||||
boolean stayAlive) {
|
||||
this.useTestHarness = useTestHarness;
|
||||
this.botPaymentMethodId = botPaymentMethodId;
|
||||
this.countryCode = countryCode != null ? countryCode.toUpperCase() : null;
|
||||
this.paymentAccountIdForBot = paymentAccountIdForBot;
|
||||
this.paymentAccountIdForCliScripts = paymentAccountIdForCliScripts;
|
||||
this.apiPortForCliScripts = apiPortForCliScripts;
|
||||
this.actions = actions;
|
||||
this.protocolStepTimeLimitInMinutes = protocolStepTimeLimitInMinutes;
|
||||
this.printCliScripts = printCliScripts;
|
||||
this.stayAlive = stayAlive;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario.bot.script;
|
||||
|
||||
import bisq.common.file.JsonFileManager;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import joptsimple.BuiltinHelpFormatter;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.lang.System.err;
|
||||
import static java.lang.System.exit;
|
||||
import static java.lang.System.getProperty;
|
||||
import static java.lang.System.out;
|
||||
|
||||
@Slf4j
|
||||
public class BotScriptGenerator {
|
||||
|
||||
private final boolean useTestHarness;
|
||||
@Nullable
|
||||
private final String countryCode;
|
||||
@Nullable
|
||||
private final String botPaymentMethodId;
|
||||
@Nullable
|
||||
private final String paymentAccountIdForBot;
|
||||
@Nullable
|
||||
private final String paymentAccountIdForCliScripts;
|
||||
private final int apiPortForCliScripts;
|
||||
private final String actions;
|
||||
private final int protocolStepTimeLimitInMinutes;
|
||||
private final boolean printCliScripts;
|
||||
private final boolean stayAlive;
|
||||
|
||||
public BotScriptGenerator(String[] args) {
|
||||
OptionParser parser = new OptionParser();
|
||||
var helpOpt = parser.accepts("help", "Print this help text.")
|
||||
.forHelp();
|
||||
OptionSpec<Boolean> useTestHarnessOpt = parser
|
||||
.accepts("use-testharness", "Use the test harness, or manually start your own nodes.")
|
||||
.withRequiredArg()
|
||||
.ofType(Boolean.class)
|
||||
.defaultsTo(true);
|
||||
OptionSpec<String> actionsOpt = parser
|
||||
.accepts("actions", "A comma delimited list with no spaces, e.g., make,take,take,make,...")
|
||||
.withRequiredArg();
|
||||
OptionSpec<String> botPaymentMethodIdOpt = parser
|
||||
.accepts("bot-payment-method",
|
||||
"The bot's (Bob) payment method id. If using the test harness,"
|
||||
+ " the id will be used to automatically create a payment account.")
|
||||
.withRequiredArg();
|
||||
OptionSpec<String> countryCodeOpt = parser
|
||||
.accepts("country-code",
|
||||
"The two letter country-code for an F2F payment account if using the test harness,"
|
||||
+ " but the bot-payment-method option takes precedence.")
|
||||
.withRequiredArg();
|
||||
OptionSpec<Integer> apiPortForCliScriptsOpt = parser
|
||||
.accepts("api-port-for-cli-scripts",
|
||||
"The api port used in bot generated bash/cli scripts.")
|
||||
.withRequiredArg()
|
||||
.ofType(Integer.class)
|
||||
.defaultsTo(9998);
|
||||
OptionSpec<String> paymentAccountIdForBotOpt = parser
|
||||
.accepts("payment-account-for-bot",
|
||||
"The bot side's payment account id, when the test harness is not used,"
|
||||
+ " and Bob & Alice accounts are not automatically created.")
|
||||
.withRequiredArg();
|
||||
OptionSpec<String> paymentAccountIdForCliScriptsOpt = parser
|
||||
.accepts("payment-account-for-cli-scripts",
|
||||
"The other side's payment account id, used in generated bash/cli scripts when"
|
||||
+ " the test harness is not used, and Bob & Alice accounts are not automatically created.")
|
||||
.withRequiredArg();
|
||||
OptionSpec<Integer> protocolStepTimeLimitInMinutesOpt = parser
|
||||
.accepts("step-time-limit", "Each protocol step's time limit in minutes")
|
||||
.withRequiredArg()
|
||||
.ofType(Integer.class)
|
||||
.defaultsTo(60);
|
||||
OptionSpec<Boolean> printCliScriptsOpt = parser
|
||||
.accepts("print-cli-scripts", "Print the generated CLI scripts from bot")
|
||||
.withRequiredArg()
|
||||
.ofType(Boolean.class)
|
||||
.defaultsTo(false);
|
||||
OptionSpec<Boolean> stayAliveOpt = parser
|
||||
.accepts("stay-alive", "Leave test harness nodes running after the last action.")
|
||||
.withRequiredArg()
|
||||
.ofType(Boolean.class)
|
||||
.defaultsTo(true);
|
||||
OptionSet options = parser.parse(args);
|
||||
|
||||
if (options.has(helpOpt)) {
|
||||
printHelp(parser, out);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (!options.has(actionsOpt)) {
|
||||
printHelp(parser, err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
this.useTestHarness = options.has(useTestHarnessOpt) ? options.valueOf(useTestHarnessOpt) : true;
|
||||
this.actions = options.valueOf(actionsOpt);
|
||||
this.apiPortForCliScripts = options.has(apiPortForCliScriptsOpt) ? options.valueOf(apiPortForCliScriptsOpt) : 9998;
|
||||
this.botPaymentMethodId = options.has(botPaymentMethodIdOpt) ? options.valueOf(botPaymentMethodIdOpt) : null;
|
||||
this.countryCode = options.has(countryCodeOpt) ? options.valueOf(countryCodeOpt) : null;
|
||||
this.paymentAccountIdForBot = options.has(paymentAccountIdForBotOpt) ? options.valueOf(paymentAccountIdForBotOpt) : null;
|
||||
this.paymentAccountIdForCliScripts = options.has(paymentAccountIdForCliScriptsOpt) ? options.valueOf(paymentAccountIdForCliScriptsOpt) : null;
|
||||
this.protocolStepTimeLimitInMinutes = options.valueOf(protocolStepTimeLimitInMinutesOpt);
|
||||
this.printCliScripts = options.valueOf(printCliScriptsOpt);
|
||||
this.stayAlive = options.valueOf(stayAliveOpt);
|
||||
|
||||
var noPaymentAccountCountryOrMethodForTestHarness = useTestHarness &&
|
||||
(!options.has(countryCodeOpt) && !options.has(botPaymentMethodIdOpt));
|
||||
if (noPaymentAccountCountryOrMethodForTestHarness) {
|
||||
log.error("When running the test harness, payment accounts are automatically generated,");
|
||||
log.error("and you must provide one of the following options:");
|
||||
log.error(" \t\t(1) --bot-payment-method=<payment-method-id> OR");
|
||||
log.error(" \t\t(2) --country-code=<country-code>");
|
||||
log.error("If the bot-payment-method option is not present, the bot will create"
|
||||
+ " a country based F2F account using the country-code.");
|
||||
log.error("If both are present, the bot-payment-method will take precedence. "
|
||||
+ "Currently, only the CLEAR_X_CHANGE_ID bot-payment-method is supported.");
|
||||
printHelp(parser, err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
var noPaymentAccountIdOrApiPortForCliScripts = !useTestHarness &&
|
||||
(!options.has(paymentAccountIdForCliScriptsOpt) || !options.has(paymentAccountIdForBotOpt));
|
||||
if (noPaymentAccountIdOrApiPortForCliScripts) {
|
||||
log.error("If not running the test harness, payment accounts are not automatically generated,");
|
||||
log.error("and you must provide three options:");
|
||||
log.error(" \t\t(1) --api-port-for-cli-scripts=<port>");
|
||||
log.error(" \t\t(2) --payment-account-for-bot=<payment-account-id>");
|
||||
log.error(" \t\t(3) --payment-account-for-cli-scripts=<payment-account-id>");
|
||||
log.error("These will be used by the bot and in CLI scripts the bot will generate when creating an offer.");
|
||||
printHelp(parser, err);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void printHelp(OptionParser parser, PrintStream stream) {
|
||||
try {
|
||||
String usage = "Examples\n--------\n"
|
||||
+ examplesUsingTestHarness()
|
||||
+ examplesNotUsingTestHarness();
|
||||
stream.println();
|
||||
parser.formatHelpWith(new HelpFormatter());
|
||||
parser.printHelpOn(stream);
|
||||
stream.println();
|
||||
stream.println(usage);
|
||||
stream.println();
|
||||
} catch (IOException ex) {
|
||||
log.error("", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String examplesUsingTestHarness() {
|
||||
@SuppressWarnings("StringBufferReplaceableByString") StringBuilder builder = new StringBuilder();
|
||||
builder.append("To generate a bot-script.json file that will start the test harness,");
|
||||
builder.append(" create F2F accounts for Bob and Alice,");
|
||||
builder.append(" and take an offer created by Alice's CLI:").append("\n");
|
||||
builder.append("\tUsage: BotScriptGenerator").append("\n");
|
||||
builder.append("\t\t").append("--use-testharness=true").append("\n");
|
||||
builder.append("\t\t").append("--country-code=<country-code>").append("\n");
|
||||
builder.append("\t\t").append("--actions=take").append("\n");
|
||||
builder.append("\n");
|
||||
builder.append("To generate a bot-script.json file that will start the test harness,");
|
||||
builder.append(" create Zelle accounts for Bob and Alice,");
|
||||
builder.append(" and create an offer to be taken by Alice's CLI:").append("\n");
|
||||
builder.append("\tUsage: BotScriptGenerator").append("\n");
|
||||
builder.append("\t\t").append("--use-testharness=true").append("\n");
|
||||
builder.append("\t\t").append("--bot-payment-method=CLEAR_X_CHANGE").append("\n");
|
||||
builder.append("\t\t").append("--actions=make").append("\n");
|
||||
builder.append("\n");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String examplesNotUsingTestHarness() {
|
||||
@SuppressWarnings("StringBufferReplaceableByString") StringBuilder builder = new StringBuilder();
|
||||
builder.append("To generate a bot-script.json file that will not start the test harness,");
|
||||
builder.append(" but will create useful bash scripts for the CLI user,");
|
||||
builder.append(" and make two offers, then take two offers:").append("\n");
|
||||
builder.append("\tUsage: BotScriptGenerator").append("\n");
|
||||
builder.append("\t\t").append("--use-testharness=false").append("\n");
|
||||
builder.append("\t\t").append("--api-port-for-cli-scripts=<port>").append("\n");
|
||||
builder.append("\t\t").append("--payment-account-for-bot=<payment-account-id>").append("\n");
|
||||
builder.append("\t\t").append("--payment-account-for-cli-scripts=<payment-account-id>").append("\n");
|
||||
builder.append("\t\t").append("--actions=make,make,take,take").append("\n");
|
||||
builder.append("\n");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String generateBotScriptTemplate() {
|
||||
return Utilities.objectToJson(new BotScript(
|
||||
useTestHarness,
|
||||
botPaymentMethodId,
|
||||
countryCode,
|
||||
paymentAccountIdForBot,
|
||||
paymentAccountIdForCliScripts,
|
||||
actions.split("\\s*,\\s*").clone(),
|
||||
apiPortForCliScripts,
|
||||
protocolStepTimeLimitInMinutes,
|
||||
printCliScripts,
|
||||
stayAlive));
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
BotScriptGenerator generator = new BotScriptGenerator(args);
|
||||
String json = generator.generateBotScriptTemplate();
|
||||
String destDir = getProperty("java.io.tmpdir");
|
||||
JsonFileManager jsonFileManager = new JsonFileManager(new File(destDir));
|
||||
jsonFileManager.writeToDisc(json, "bot-script");
|
||||
JsonFileManager.shutDownAllInstances();
|
||||
log.info("Saved {}/bot-script.json", destDir);
|
||||
log.info("bot-script.json contents\n{}", json);
|
||||
}
|
||||
|
||||
// Makes a formatter with a given overall row width of 120 and column separator width of 2.
|
||||
private static class HelpFormatter extends BuiltinHelpFormatter {
|
||||
public HelpFormatter() {
|
||||
super(120, 2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario.bot.shutdown;
|
||||
|
||||
import bisq.common.BisqException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ManualBotShutdownException extends BisqException {
|
||||
public ManualBotShutdownException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ManualBotShutdownException(String format, Object... args) {
|
||||
super(format, args);
|
||||
}
|
||||
|
||||
public ManualBotShutdownException(Throwable cause, String format, Object... args) {
|
||||
super(cause, format, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package bisq.apitest.scenario.bot.shutdown;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.common.file.FileUtil.deleteFileIfExists;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
@Slf4j
|
||||
public class ManualShutdown {
|
||||
|
||||
public static final String SHUTDOWN_FILENAME = "/tmp/bottest-shutdown";
|
||||
|
||||
private static final AtomicBoolean SHUTDOWN_CALLED = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Looks for a /tmp/bottest-shutdown file and throws a BotShutdownException if found.
|
||||
*
|
||||
* Running '$ touch /tmp/bottest-shutdown' could be used to trigger a scaffold teardown.
|
||||
*
|
||||
* This is much easier than manually shutdown down bisq apps & bitcoind.
|
||||
*/
|
||||
public static void startShutdownTimer() {
|
||||
deleteStaleShutdownFile();
|
||||
|
||||
UserThread.runPeriodically(() -> {
|
||||
File shutdownFile = new File(SHUTDOWN_FILENAME);
|
||||
if (shutdownFile.exists()) {
|
||||
log.warn("Caught manual shutdown signal: /tmp/bottest-shutdown file exists.");
|
||||
try {
|
||||
deleteFileIfExists(shutdownFile);
|
||||
} catch (IOException ex) {
|
||||
log.error("", ex);
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
SHUTDOWN_CALLED.set(true);
|
||||
}
|
||||
}, 2000, MILLISECONDS);
|
||||
}
|
||||
|
||||
public static boolean isShutdownCalled() {
|
||||
return SHUTDOWN_CALLED.get();
|
||||
}
|
||||
|
||||
public static void checkIfShutdownCalled(String warning) throws ManualBotShutdownException {
|
||||
if (isShutdownCalled())
|
||||
throw new ManualBotShutdownException(warning);
|
||||
}
|
||||
|
||||
private static void deleteStaleShutdownFile() {
|
||||
try {
|
||||
deleteFileIfExists(new File(SHUTDOWN_FILENAME));
|
||||
} catch (IOException ex) {
|
||||
log.error("", ex);
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue