diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1ffdb6..5c6a693 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,6 @@ jobs: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - # Build the .jar + # Build the .jar without running test suite - name: Build run: mvn clean && mvn install -DskipTests=true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c6f0698..bcf739a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ on: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" - build: + test: # The type of runner that the job will run on runs-on: ubuntu-latest @@ -24,6 +24,6 @@ jobs: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - # Runs the test suite [WIP] - - name: Build - run: mvn test + # Runs the test suite + - name: Test + run: mvn clean install diff --git a/.gitignore b/.gitignore index 549e00a..67ce31b 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ build/ ### VS Code ### .vscode/ + +### db +data.mv.db \ No newline at end of file diff --git a/README.md b/README.md index dd057e9..a3866b6 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If successfully settled the equivalent amount in Monero is sent 3. Run bitcoind on [regtest](https://developer.bitcoin.org/examples/testing.html) 4. Setup LND nodes for invoice generation and settling. *[Polar](https://lightningpolar.com/) is a cool tool! 5. Run Monero on [stagenet](https://monerodocs.org/infrastructure/networks/) -6. H2 db runs at host/h2-console. Execute the `src/main/resources/schema.sql` first +6. H2 db runs at host/h2-console. 7. Currently working on Bitcoin core 0.21, LND 0.12.x, Debian 10, Java 11, Maven 3.6 and Monero 0.17.2 NOTE: currently have an issue with Monero digest authentication rpc calls, so use `--disable-rpc-login` @@ -38,7 +38,7 @@ request: ```json { - "amount": 0.0123, + "amount": 0.123, "address": "54gqcJZAtgzBFnQWEQHec3RoWfmoHqL4H8sASqdQMGshfqdpG1fzT5ddCpz9y4C2MwQkB5GE2o6vUVCGKbokJJa6S6NSatn" } ``` @@ -47,12 +47,18 @@ response: ```json { - "quoteId": "e8754cd5189125b46490b30cb958792e88ae34e76b954d32ad70ced27ac21c2a", + "quoteId": "63eb4534535a4c4afa9455f7dacde8cecbbac91e2bcd390407e1b88704a9a758", "address": "54gqcJZAtgzBFnQWEQHec3RoWfmoHqL4H8sASqdQMGshfqdpG1fzT5ddCpz9y4C2MwQkB5GE2o6vUVCGKbokJJa6S6NSatn", "isValidAddress": true, - "amount": 0.0123, - "rate": 0.006363, - "invoice": "lnbcrt78260n1psw3ax6pp5ap65e4gcjyjmgeyskvxtjkre96y2ud88dw256v4dwr8dy7kzrs4qdq8d4shxuccqzpgxqzjcsp5wyywgyzhek48wdpwq3jl04jn203d07s9huwpl7dyducstjh2eqcs9qyyssq6j27kf9vzydqqhqaal2cdryzn7u4xgm3vnltvj4qsd9aqhavpwcre5q4sy0megg005gj0zycs3j3l3nvleqqxklppknjgug30sauq8cpe2sm74" + "reserveProof": { + "signature": "ReserveProofV21AhtWZDjV1SG7AcQFSxfSVWZvQB9QG99kgr2havWLjWgewkBnnKYBt3UqQycx7A9sTaNYfiCo8PLGi28kjP7f9SvN16QNUMNaLKH7kuqySYJ4kYtnPT8qPnHK72weEpQXZhmAm3ebXEjZiH9wskFnVEfVjeCBegqcAVNsXjBZHfv95NZBoE4MgKZvfDT2ank1cqLj7VLUyC4pGVR2Y8bNdv8R1gjjQEQFo6r4YFcPKUz59k6yQ1iokWr6ZJwEauMviEk5CNEK8XYUr47TWJTzM5S3whFW5NhDZFeQ1fdsHTHbV332kwcHoDjGf3ZKaeGa5hNMHbb1XjjM5MURdHR6N59vHXPkN3xTnmZd2k1d6Dg8btwutBZujBBzWT5QNswm1V4ewutYTBBcg1cT8XsZh5MtG7cpobgaHGYYxEtGSfpD9R3agCJBpF5EZ9vsm5", + "proofAddress": "56fK1PpmCjz5CEiAb8dbpyQZtpJ8s4B2WaXPqyFD2FU9DUobuSDJZztEPeppvAqT2DPWcdp7qtW6KasCbYoWJC7qBcwWrSH" + }, + "minSwapAmt": 10000, + "maxSwapAmt": 10000000, + "amount": 0.123, + "rate": 0.00629129, + "invoice": "lnbcrt773820n1ps0xdf8pp5v0452dzntfxy47552hma4n0gem9m4jg790xnjpq8uxugwp9f5avqdq8d4shxuccqzpgxqzjcsp5a75z3kfuwvas78t2a8rmm7j04su4e7t2dwh02x3e0dvwpc6w4urs9qyyssqqqryuthw0sgmtpwymmqjue89ltsre8vnh9uzrey9fs46tynqfk4rxnq5jwyjwvq3vksndfklxa578540zhuu9dprjweyezqjhcg9n8qp068g75" } ``` @@ -66,19 +72,54 @@ request: ```json { - "hash": "e8754cd5189125b46490b30cb958792e88ae34e76b954d32ad70ced27ac21c2a" + "hash": "63eb4534535a4c4afa9455f7dacde8cecbbac91e2bcd390407e1b88704a9a758" } ``` response: +lncli + +```bash +user@server:~$ lncli -n regtest payinvoice $PAY_REQ +Payment hash: 63eb4534535a4c4afa9455f7dacde8cecbbac91e2bcd390407e1b88704a9a758 +Description: mass +Amount (in satoshis): 77382 +Fee limit (in satoshis): 77382 +Destination: 03e420f400087f0640ee6dfd5b0b589908133c8cf36a737e2d0c3c908661477597 +Confirm payment (yes/no): yes ++------------+--------------+--------------+--------------+-----+----------+-----------------+----------------------+ +| HTLC_STATE | ATTEMPT_TIME | RESOLVE_TIME | RECEIVER_AMT | FEE | TIMELOCK | CHAN_OUT | ROUTE | ++------------+--------------+--------------+--------------+-----+----------+-----------------+----------------------+ +| SUCCEEDED | 0.041 | 33.868 | 77382 | 0 | 792 | 713583046557696 | 03e420f400087f0640ee | ++------------+--------------+--------------+--------------+-----+----------+-----------------+----------------------+ +Amount + fee: 77382 + 0 sat +Payment hash: 63eb4534535a4c4afa9455f7dacde8cecbbac91e2bcd390407e1b88704a9a758 +Payment status: SUCCEEDED, preimage: cb4605aa339a21d70e20db617a2853214759999cac90c35e5a65fd2462bc0447 +``` + ```json + { - "hash": "e8754cd5189125b46490b30cb958792e88ae34e76b954d32ad70ced27ac21c2a", - "txId": "fc30f5dceccc9a5514af8ec6d01e2bd8405282382a973ed8185d8d2ac8a03934" + "hash": "63eb4534535a4c4afa9455f7dacde8cecbbac91e2bcd390407e1b88704a9a758", + "metadata": "02000102000b8e8ee801a5a31b..." } + ``` +relay the transaction with [relay_tx](https://web.getmonero.org/resources/developer-guides/wallet-rpc.html#relay_tx) + +## Tests + +MASS uses JUnit5 - [junit-jupiter](https://junit.org/junit5/) framework + +Run `mvn clean install` from the root directory + +View test coverage with web browser `./target/site/jacoco/index.htm` + +![image](https://user-images.githubusercontent.com/13033037/126047819-09fe351a-be62-4bf9-bd5f-cb3580862c6e.png) + + ## TODOs -Refund / Cancel logic, Tests, etc +See [milestones](https://github.com/hyahatiph-labs/mass/milestones) diff --git a/api.http b/api.http index a991789..3a2537d 100644 --- a/api.http +++ b/api.http @@ -9,7 +9,7 @@ Content-Type: application/json { "amount": 0.123, - "address": "54gqcJZAtgzBFnQWEQHec3RoWfmoHqL4H8sASqdQMGshfqdpG1fzT5ddCpz9y4C2MwQkB5GE2o6vUVCGKbokJJa6S6NSatn" + "address": "56fK1PpmCjz5CEiAb8dbpyQZtpJ8s4B2WaXPqyFD2FU9DUobuSDJZztEPeppvAqT2DPWcdp7qtW6KasCbYoWJC7qBcwWrSH" } ### Validate Monero address @@ -57,7 +57,7 @@ POST http://localhost:6789/swap/xmr Content-Type: application/json { - "hash": "fbbdbe08b60d66514dfa295f9f192413bb549f217479fabf5ae3887f1ccdc1a2" + "hash": "2161997ea4adcaa2806c0a0e66f30b3ac48ed9f969108742a6b163148f5c7b6c" } ### get balances of xmr wallet diff --git a/pom.xml b/pom.xml index 237d427..6fc6f6b 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,8 @@ org.hiahatf mass - 0.0.1-SNAPSHOT + + 0.1.0-beta mass lightning powered xmr swaps @@ -39,6 +40,65 @@ runtime true + + + org.springframework.boot + spring-boot-starter-test + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.jupiter + junit-jupiter-engine + 5.7.2 + test + + + org.junit.platform + junit-platform-runner + 1.2.0 + test + + + org.junit.vintage + junit-vintage-engine + 5.2.0 + test + + + org.mockito + mockito-core + 3.11.2 + test + + + org.mockito + mockito-junit-jupiter + 2.23.0 + test + + + com.squareup.okhttp3 + okhttp + 4.0.1 + test + + + com.squareup.okhttp3 + mockwebserver + 4.0.1 + test + + + io.projectreactor + reactor-test + test + 3.2.3.RELEASE + + org.projectlombok lombok @@ -80,6 +140,7 @@ 30.1.1-jre + @@ -89,6 +150,13 @@ pom import + + org.junit + junit-bom + 5.7.2 + pom + import + @@ -106,6 +174,61 @@ + + maven-surefire-plugin + 2.19.1 + + + org.junit.platform + junit-platform-surefire-provider + 1.0.1 + + + + + org.jacoco + jacoco-maven-plugin + 0.8.7 + + + + prepare-agent + + + + report + prepare-package + + report + + + + jacoco-check + + check + + + + + + BUNDLE + + + LINE + COVEREDRATIO + 0.80 + + + + + + + **/MassApplication.class + + + + + diff --git a/src/main/java/org/hiahatf/mass/controllers/SwapController.java b/src/main/java/org/hiahatf/mass/controllers/SwapController.java index 8a0781b..fdee508 100644 --- a/src/main/java/org/hiahatf/mass/controllers/SwapController.java +++ b/src/main/java/org/hiahatf/mass/controllers/SwapController.java @@ -25,7 +25,7 @@ public class SwapController extends BaseController { /** * Swap Controller constructor dependency injection - * @param quoteService + * @param service */ @Autowired public SwapController(SwapService service) { @@ -40,7 +40,7 @@ public SwapController(SwapService service) { */ @PostMapping(Constants.XMR_SWAP_PATH) @ResponseStatus(HttpStatus.CREATED) - public Mono fetchMoneroQuote(@RequestBody SwapRequest request) { + public Mono fetchMoneroSwap(@RequestBody SwapRequest request) { return swapService.processMoneroSwap(request); } diff --git a/src/main/java/org/hiahatf/mass/models/Constants.java b/src/main/java/org/hiahatf/mass/models/Constants.java index b5ad561..a217ddb 100644 --- a/src/main/java/org/hiahatf/mass/models/Constants.java +++ b/src/main/java/org/hiahatf/mass/models/Constants.java @@ -57,9 +57,9 @@ public final class Constants { public static final String PARSE_RATE_MSG = "parsed rate: {} => real rate: {}"; // quote service values - public static final String SHA_256 = "SHA-256"; public static final Long COIN = 100000000L; + public static final String RP_ADDRESS = "${rp-address}"; // swap service values public static final String MIN_PAY = "${min-pay}"; diff --git a/src/main/java/org/hiahatf/mass/models/monero/Quote.java b/src/main/java/org/hiahatf/mass/models/monero/Quote.java index a531ba0..4a7f14b 100644 --- a/src/main/java/org/hiahatf/mass/models/monero/Quote.java +++ b/src/main/java/org/hiahatf/mass/models/monero/Quote.java @@ -20,7 +20,7 @@ public class Quote { // proof of address validity private Boolean isValidAddress; // reserve proof - private String reserveProof; + private ReserveProof reserveProof; // minimum swap amount in satoshis private Long minSwapAmt; // maximum swap amount in satoshis diff --git a/src/main/java/org/hiahatf/mass/models/monero/ReserveProof.java b/src/main/java/org/hiahatf/mass/models/monero/ReserveProof.java new file mode 100644 index 0000000..9ec1c13 --- /dev/null +++ b/src/main/java/org/hiahatf/mass/models/monero/ReserveProof.java @@ -0,0 +1,22 @@ +package org.hiahatf.mass.models.monero; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * POJO for the quote proof + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ReserveProof { + // proof signature + private String signature; + // proof address + // configure in application.yml + // TODO: dynamic configuration + private String proofAddress; +} diff --git a/src/main/java/org/hiahatf/mass/services/monero/QuoteService.java b/src/main/java/org/hiahatf/mass/services/monero/QuoteService.java index b4ce657..78bc27a 100644 --- a/src/main/java/org/hiahatf/mass/services/monero/QuoteService.java +++ b/src/main/java/org/hiahatf/mass/services/monero/QuoteService.java @@ -18,6 +18,7 @@ import org.hiahatf.mass.models.Constants; import org.hiahatf.mass.models.monero.Quote; import org.hiahatf.mass.models.monero.Request; +import org.hiahatf.mass.models.monero.ReserveProof; import org.hiahatf.mass.models.monero.XmrQuoteTable; import org.hiahatf.mass.repo.QuoteRepository; import org.hiahatf.mass.services.rate.RateService; @@ -43,6 +44,7 @@ public class QuoteService { private Lightning lightning; private MassUtil massUtil; private QuoteRepository quoteRepository; + private String proofAddress; private Long minPay; private Long maxPay; @@ -50,7 +52,8 @@ public class QuoteService { public QuoteService(RateService rateService, MassUtil massUtil, Monero moneroRpc, Lightning lightning, QuoteRepository quoteRepository, @Value(Constants.MIN_PAY) long minPay, - @Value(Constants.MAX_PAY) long maxPay) { + @Value(Constants.MAX_PAY) long maxPay, + @Value(Constants.RP_ADDRESS) String rpAddress){ this.rateService = rateService; this.massUtil = massUtil; this.moneroRpc = moneroRpc; @@ -58,6 +61,7 @@ public QuoteService(RateService rateService, MassUtil massUtil, this.quoteRepository = quoteRepository; this.minPay = minPay; this.maxPay = maxPay; + this.proofAddress = rpAddress; } /** @@ -67,22 +71,21 @@ public QuoteService(RateService rateService, MassUtil massUtil, * @return Mono */ public Mono processMoneroQuote(Request request) { - return rateService.getMoneroRate().flatMap(r -> { - Double rate = massUtil.parseMoneroRate(r); - Double value = (rate * request.getAmount()) * Constants.COIN; - /* - * The quote amount is validated before a response is sent. - * Minimum and maximum payments are configured via the MASS - * application.yml. There is no limit on requests. The amount - * is also validated with Monero reserve proof. - */ - return validateInboundLiquidity(value).flatMap(l -> { - if(l) { - return generateReserveProof(request, value, rate); - } - // edge case, this should never happen... - return Mono.error(new MassException(Constants.UNK_ERROR)); - }); + String rate = rateService.getMoneroRate(); + Double parsedRate = massUtil.parseMoneroRate(rate); + Double value = (parsedRate* request.getAmount()) * Constants.COIN; + /* + * The quote amount is validated before a response is sent. + * Minimum and maximum payments are configured via the MASS + * application.yml. There is no limit on requests. The amount + * is also validated with Monero reserve proof. + */ + return validateInboundLiquidity(value).flatMap(l -> { + if(l) { + return generateReserveProof(request, value, parsedRate); + } + // edge case, this should never happen... + return Mono.error(new MassException(Constants.UNK_ERROR)); }); } @@ -131,8 +134,7 @@ private Mono validateInboundLiquidity(Double value) { */ private Mono generateReserveProof(Request request, Double value, Double rate) { - return moneroRpc.getReserveProof(request.getAddress(), - request.getAmount()).flatMap(r -> { + return moneroRpc.getReserveProof(request.getAmount()).flatMap(r -> { if(r.getResult() == null) { return Mono.error( new MassException(Constants.RESERVE_PROOF_ERROR) @@ -223,6 +225,9 @@ private void persistQuote(Request request, byte[] preimage, */ private Mono generateMoneroQuote(Double value, byte[] hash, Request request, Double rate, Boolean v, String proof) { + ReserveProof reserveProof = ReserveProof.builder() + .proofAddress(proofAddress) + .signature(proof).build(); try { return lightning.generateInvoice(value, hash).flatMap(i -> { Quote quote = Quote.builder() @@ -231,7 +236,7 @@ private Mono generateMoneroQuote(Double value, byte[] hash, .isValidAddress(v) .amount(request.getAmount()) .invoice(i.getPayment_request()) - .reserveProof(proof) + .reserveProof(reserveProof) .rate(rate) .minSwapAmt(minPay) .maxSwapAmt(maxPay) diff --git a/src/main/java/org/hiahatf/mass/services/rate/RateService.java b/src/main/java/org/hiahatf/mass/services/rate/RateService.java index 0ab9cd6..335ea0f 100644 --- a/src/main/java/org/hiahatf/mass/services/rate/RateService.java +++ b/src/main/java/org/hiahatf/mass/services/rate/RateService.java @@ -20,7 +20,7 @@ public class RateService { private Logger logger = LoggerFactory.getLogger(RateService.class); private static final int FREQUENCY = 600000; private static final int INITIAL_DELAY = 10000; - private Mono moneroRate; + private String moneroRate; private String xmrPriceUrl; @@ -32,7 +32,7 @@ public RateService(@Value(Constants.RATE_HOST) String url) { * Accessor for the Monero rate * @return monero rate */ - public Mono getMoneroRate() { + public String getMoneroRate() { return this.moneroRate; } @@ -54,7 +54,8 @@ public void updateMoneroRate() { .build()) .retrieve() .bodyToMono(String.class); - this.moneroRate = xmrRate.retry(1); + // normally wouldn't use block, but is needed here to cache price data + this.moneroRate = xmrRate.retry(1).block(); } } diff --git a/src/main/java/org/hiahatf/mass/services/rpc/Monero.java b/src/main/java/org/hiahatf/mass/services/rpc/Monero.java index aa44295..43227b1 100644 --- a/src/main/java/org/hiahatf/mass/services/rpc/Monero.java +++ b/src/main/java/org/hiahatf/mass/services/rpc/Monero.java @@ -101,11 +101,10 @@ public Mono transfer(String address, Double amount) { * --rpc-disable-login flag. * TODO: roll custom digest authentication support * @param value - * @param address * @return Mono */ public Mono - getReserveProof(String address, Double amount) { + getReserveProof(Double amount) { // build request Double piconeroAmt = amount * PICONERO; GetReserveProofParameters parameters = GetReserveProofParameters diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json index f761ec5..f0de1ba 100644 --- a/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -33,5 +33,10 @@ "name": "max-pay", "type": "java.lang.Long", "description": "The maximum amount in satoshis accepted" + }, + { + "name": "rp-address", + "type": "java.lang.String", + "description": "Address to share for reserve proof validation" } ]} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c8136dc..16ed69c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,8 +5,10 @@ host: price: https://min-api.cryptocompare.com monero: http://localhost:18082 lightning: https://localhost:8180 +# reserve proof address +rp-address: 56fK1PpmCjz5CEiAb8dbpyQZtpJ8s4B2WaXPqyFD2FU9DUobuSDJZztEPeppvAqT2DPWcdp7qtW6KasCbYoWJC7qBcwWrSH # macaroon path -macaroon-path: /home/nigellchristian/.lnd2/data/chain/bitcoin/regtest/admin.macaroon +macaroon-path: /home/rimuru/.lnd-regtest-2/data/chain/bitcoin/regtest/admin.macaroon # markup so we can pay the bills markup: 0.01 # payment thresholds in satoshis @@ -23,8 +25,10 @@ server.error.include-stacktrace: never # not a big fan of h2 # TODO: database upgrade spring.datasource: - url: jdbc:h2:file:/data - driverClassName: org.h2.Driver + url: jdbc:h2:file:./data + driverClassName: org.h2.Driver username: sa - password: pass -spring.jpa.database-platform: org.hibernate.dialect.H2Dialect + password: pass +spring.jpa: + database-platform: org.hibernate.dialect.H2Dialect + hibernate.ddl-auto: update diff --git a/src/main/resources/data b/src/main/resources/data deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/java/org/hiahatf/mass/MassApplicationTests.java b/src/test/java/org/hiahatf/mass/MassApplicationTests.java deleted file mode 100644 index b8cb68f..0000000 --- a/src/test/java/org/hiahatf/mass/MassApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.hiahatf.mass; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class MassApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/java/org/hiahatf/mass/controllers/HealthControllerTest.java b/src/test/java/org/hiahatf/mass/controllers/HealthControllerTest.java new file mode 100644 index 0000000..de9864e --- /dev/null +++ b/src/test/java/org/hiahatf/mass/controllers/HealthControllerTest.java @@ -0,0 +1,40 @@ +package org.hiahatf.mass.controllers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import java.io.IOException; + +import org.hiahatf.mass.models.lightning.Info; +import org.hiahatf.mass.services.rpc.Lightning; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import reactor.core.publisher.Mono; + +@ExtendWith(MockitoExtension.class) +@RunWith(JUnitPlatform.class) +public class HealthControllerTest { + + @Mock + Lightning lightning; + + @InjectMocks + HealthController controller = new HealthController(lightning); + + @Test + @DisplayName("Health Controller Test") + public void fetchMoneroQuoteTest() throws IOException { + Info info = Info.builder().version("v0.0.0").build(); + when(lightning.getInfo()).thenReturn(Mono.just(info)); + Mono testInfo = controller.ping(); + assertEquals(info.getVersion(), testInfo.block().getVersion()); + } + +} diff --git a/src/test/java/org/hiahatf/mass/controllers/QuoteControllerTest.java b/src/test/java/org/hiahatf/mass/controllers/QuoteControllerTest.java new file mode 100644 index 0000000..4d96640 --- /dev/null +++ b/src/test/java/org/hiahatf/mass/controllers/QuoteControllerTest.java @@ -0,0 +1,41 @@ +package org.hiahatf.mass.controllers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import org.hiahatf.mass.models.monero.Quote; +import org.hiahatf.mass.models.monero.Request; +import org.hiahatf.mass.services.monero.QuoteService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import reactor.core.publisher.Mono; + +@ExtendWith(MockitoExtension.class) +@RunWith(JUnitPlatform.class) +public class QuoteControllerTest { + + @Mock + QuoteService quoteService; + + @InjectMocks + QuoteController controller = new QuoteController(quoteService); + + @Test + @DisplayName("Quote Controller Test") + public void fetchMoneroQuoteTest() { + String address = "54xxx"; + Request request = Request.builder().address(address).build(); + Quote quote = Quote.builder().address(address).build(); + when(quoteService.processMoneroQuote(request)).thenReturn(Mono.just(quote)); + Mono testQuote = controller.fetchMoneroQuote(request); + assertEquals(quote.getAddress(), testQuote.block().getAddress()); + } + +} diff --git a/src/test/java/org/hiahatf/mass/controllers/SwapControllerTest.java b/src/test/java/org/hiahatf/mass/controllers/SwapControllerTest.java new file mode 100644 index 0000000..006fd10 --- /dev/null +++ b/src/test/java/org/hiahatf/mass/controllers/SwapControllerTest.java @@ -0,0 +1,41 @@ +package org.hiahatf.mass.controllers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import org.hiahatf.mass.models.monero.SwapRequest; +import org.hiahatf.mass.models.monero.SwapResponse; +import org.hiahatf.mass.services.monero.SwapService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import reactor.core.publisher.Mono; + +@ExtendWith(MockitoExtension.class) +@RunWith(JUnitPlatform.class) +public class SwapControllerTest { + + @Mock + SwapService swapService; + + @InjectMocks + SwapController controller = new SwapController(swapService); + + @Test + @DisplayName("Swap Controller Test") + public void fetchMoneroQuoteTest() { + String hash = "hash"; + SwapRequest request = SwapRequest.builder().hash(hash).build(); + SwapResponse swap = SwapResponse.builder().hash(hash).build(); + when(swapService.processMoneroSwap(request)).thenReturn(Mono.just(swap)); + Mono testSwap = controller.fetchMoneroSwap(request); + assertEquals(swap.getHash(), testSwap.block().getHash()); + } + +} diff --git a/src/test/java/org/hiahatf/mass/service/monero/QuoteServiceTest.java b/src/test/java/org/hiahatf/mass/service/monero/QuoteServiceTest.java new file mode 100644 index 0000000..b35006f --- /dev/null +++ b/src/test/java/org/hiahatf/mass/service/monero/QuoteServiceTest.java @@ -0,0 +1,172 @@ +package org.hiahatf.mass.service.monero; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import java.io.IOException; + +import javax.net.ssl.SSLException; + +import org.hiahatf.mass.models.lightning.AddHoldInvoiceResponse; +import org.hiahatf.mass.models.lightning.Amount; +import org.hiahatf.mass.models.lightning.Liquidity; +import org.hiahatf.mass.models.monero.Quote; +import org.hiahatf.mass.models.monero.Request; +import org.hiahatf.mass.models.monero.proof.GetProofResult; +import org.hiahatf.mass.models.monero.proof.GetReserveProofResponse; +import org.hiahatf.mass.models.monero.validate.ValidateAddressResponse; +import org.hiahatf.mass.models.monero.validate.ValidateAddressResult; +import org.hiahatf.mass.repo.QuoteRepository; +import org.hiahatf.mass.services.monero.QuoteService; +import org.hiahatf.mass.services.rate.RateService; +import org.hiahatf.mass.services.rpc.Lightning; +import org.hiahatf.mass.services.rpc.Monero; +import org.hiahatf.mass.util.MassUtil; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +/** + * Tests for Monero Quote Service + */ +@ExtendWith(MockitoExtension.class) +@RunWith(JUnitPlatform.class) +public class QuoteServiceTest { + + @Mock + private RateService rateService; + @Mock + private MassUtil util; + @Mock + private Monero moneroRpc; + @Mock + private Lightning lightning; + @Mock + private MassUtil massUtil; + @Mock + private QuoteRepository quoteRepository; + private final Long minPay = 10000L; + private final Long maxPay = 1000000L; + @InjectMocks + private QuoteService quoteService = new QuoteService(rateService, massUtil, + moneroRpc, lightning, quoteRepository, minPay, maxPay, "54rpvxxx"); + + @Test + @DisplayName("Monero Quote Service Test") + public void processQuoteTest() throws SSLException, IOException { + String prs = "proofresultsigxxx"; + // build test data + Request req = Request.builder().address("54xxx") + .amount(0.1).build(); + Amount amt = Amount.builder().sat("100000").build(); + Liquidity liquidity = Liquidity.builder() + .remote_balance(amt).build(); + GetProofResult getProofResult = GetProofResult.builder() + .signature(prs).build(); + GetReserveProofResponse reserveProof = GetReserveProofResponse + .builder().result(getProofResult) + .build(); + ValidateAddressResult validateAddressResult = ValidateAddressResult.builder() + .valid(true).build(); + ValidateAddressResponse validateAddressResponse = ValidateAddressResponse.builder() + .result(validateAddressResult).build(); + AddHoldInvoiceResponse addHoldInvoiceResponse = AddHoldInvoiceResponse.builder() + .payment_request("lntest123xxx").build(); + // mocks + when(rateService.getMoneroRate()).thenReturn("{BTC: 0.00777}"); + when(massUtil.parseMoneroRate(anyString())).thenReturn(0.008); + when(lightning.fetchBalance()).thenReturn(Mono.just(liquidity)); + when(moneroRpc.getReserveProof(req.getAmount())).thenReturn(Mono.just(reserveProof)); + when(moneroRpc.validateAddress(req.getAddress())) + .thenReturn(Mono.just(validateAddressResponse)); + when(lightning.generateInvoice(any(), any())).thenReturn(Mono.just(addHoldInvoiceResponse)); + Mono testQuote = quoteService.processMoneroQuote(req); + + StepVerifier.create(testQuote) + .expectNextMatches(r -> r.getReserveProof().getSignature() + .equals(prs)) + .verifyComplete(); + } + + @Test + @DisplayName("Monero Payment Threshold Error Test") + public void paymentThresholdErrorTest() throws SSLException, IOException { + // build test data + Request req = Request.builder().address("54xxx") + .amount(100.0).build(); + // mocks + when(rateService.getMoneroRate()).thenReturn("{BTC: 0.00777}"); + when(massUtil.parseMoneroRate(anyString())).thenReturn(0.008); + try { + Quote test = quoteService.processMoneroQuote(req).block(); + assertNotNull(test); + } catch (Exception e) { + String expectedError = "org.hiahatf.mass.exception.MassException: " + + "Payment threshold error. (min: 10000, max: 1000000 satoshis)"; + assertEquals(expectedError, e.getMessage()); + } + } + + @Test + @DisplayName("Monero Swap Liquidity Error Test") + public void liquidityErrorTest() throws SSLException, IOException { + // build test data + Request req = Request.builder().address("54xxx") + .amount(0.1).build(); + Amount amt = Amount.builder().sat("10").build(); + Liquidity liquidity = Liquidity.builder() + .remote_balance(amt).build(); + // mocks + when(rateService.getMoneroRate()).thenReturn("{BTC: 0.00777}"); + when(massUtil.parseMoneroRate(anyString())).thenReturn(0.008); + when(lightning.fetchBalance()).thenReturn(Mono.just(liquidity)); + try { + Quote test = quoteService.processMoneroQuote(req).block(); + assertNotNull(test); + } catch (Exception e) { + String expectedError = "org.hiahatf.mass.exception.MassException: " + + "Liquidity validation error"; + assertEquals(expectedError, e.getMessage()); + } + } + + @Test + @DisplayName("Monero Swap Reserve Proof Error Test") + public void reserveProofErrorTest() throws SSLException, IOException { + // build test data + Request req = Request.builder().address("54xxx") + .amount(0.1).build(); + Amount amt = Amount.builder().sat("100000").build(); + Liquidity liquidity = Liquidity.builder() + .remote_balance(amt).build(); + GetReserveProofResponse reserveProof = GetReserveProofResponse + .builder().result(null) + .build(); + // mocks + when(rateService.getMoneroRate()).thenReturn("{BTC: 0.00777}"); + when(massUtil.parseMoneroRate(anyString())).thenReturn(0.008); + when(lightning.fetchBalance()).thenReturn(Mono.just(liquidity)); + when(moneroRpc.getReserveProof(req.getAmount())).thenReturn(Mono.just(reserveProof)); + try { + Quote test = quoteService.processMoneroQuote(req).block(); + assertNotNull(test); + } catch (Exception e) { + String expectedError = "org.hiahatf.mass.exception.MassException: " + + "Reserve proof error"; + assertEquals(expectedError, e.getMessage()); + } + } + +} diff --git a/src/test/java/org/hiahatf/mass/service/monero/SwapServiceTest.java b/src/test/java/org/hiahatf/mass/service/monero/SwapServiceTest.java new file mode 100644 index 0000000..a1deae9 --- /dev/null +++ b/src/test/java/org/hiahatf/mass/service/monero/SwapServiceTest.java @@ -0,0 +1,93 @@ +package org.hiahatf.mass.service.monero; + +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.Optional; + +import javax.net.ssl.SSLException; + +import org.hiahatf.mass.models.lightning.InvoiceLookupResponse; +import org.hiahatf.mass.models.lightning.InvoiceState; +import org.hiahatf.mass.models.monero.SwapRequest; +import org.hiahatf.mass.models.monero.SwapResponse; +import org.hiahatf.mass.models.monero.XmrQuoteTable; +import org.hiahatf.mass.models.monero.transfer.TransferResponse; +import org.hiahatf.mass.models.monero.transfer.TransferResult; +import org.hiahatf.mass.repo.QuoteRepository; +import org.hiahatf.mass.services.monero.SwapService; +import org.hiahatf.mass.services.rpc.Lightning; +import org.hiahatf.mass.services.rpc.Monero; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +/** + * Tests for Monero Swap Service + */ +@ExtendWith(MockitoExtension.class) +@RunWith(JUnitPlatform.class) +public class SwapServiceTest { + + @Mock + QuoteRepository quoteRepository; + @Mock + Lightning lightning; + @Mock + Monero monero; + @Mock + ResponseEntity entity; + @InjectMocks + SwapService swapService; + + @Test + @DisplayName("Monero Swap Service Test") + public void processMoneroSwapTest() throws SSLException, IOException { + String metadata = "expectedMetadata000"; + SwapRequest swapRequest = SwapRequest.builder() + .hash("hash").build(); + Optional table = Optional.of(XmrQuoteTable.builder() + .amount(0.1) + .payment_hash(new byte[32]) + .preimage(new byte[32]) + .quote_id("qid") + .xmr_address("54xxx") + .build()); + InvoiceLookupResponse invoiceLookupResponse = InvoiceLookupResponse + .builder() + .state(InvoiceState.ACCEPTED) + .build(); + TransferResult result = TransferResult.builder() + .tx_metadata(metadata) + .build(); + TransferResponse transferResponse = TransferResponse.builder() + .result(result) + .build(); + + // mocks + when(quoteRepository.findById(swapRequest.getHash())).thenReturn(table); + when(lightning.lookupInvoice(table.get().getQuote_id())) + .thenReturn(Mono.just(invoiceLookupResponse)); + when(monero.transfer(table.get().getXmr_address(), table.get().getAmount())) + .thenReturn(Mono.just(transferResponse)); + when(entity.getStatusCode()).thenReturn(HttpStatus.OK); + when(lightning.handleInvoice(table.get(), true)).thenReturn(Mono.just(entity)); + Mono testRes = swapService.processMoneroSwap(swapRequest); + + StepVerifier.create(testRes) + .expectNextMatches(r -> r.getMetadata() + .equals(metadata)) + .verifyComplete(); + } + +} diff --git a/src/test/java/org/hiahatf/mass/service/rate/RateServiceTest.java b/src/test/java/org/hiahatf/mass/service/rate/RateServiceTest.java new file mode 100644 index 0000000..d0dfd94 --- /dev/null +++ b/src/test/java/org/hiahatf/mass/service/rate/RateServiceTest.java @@ -0,0 +1,73 @@ +package org.hiahatf.mass.service.rate; + + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.util.HashMap; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.net.HttpHeaders; + +import org.hiahatf.mass.services.rate.RateService; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import io.netty.handler.codec.http.HttpHeaderValues; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; + +/** + * Tests for Rate Service + */ +@ExtendWith(MockitoExtension.class) +@RunWith(JUnitPlatform.class) +public class RateServiceTest { + + public static MockWebServer mockBackEnd; + private ObjectMapper objectMapper = new ObjectMapper(); + private RateService rateService; + + @BeforeAll + static void setUp() throws IOException { + mockBackEnd = new MockWebServer(); + mockBackEnd.start(); + } + + @AfterAll + static void tearDown() throws IOException { + mockBackEnd.shutdown(); + } + + @BeforeEach + void initialize() { + String baseUrl = String.format("http://localhost:%s", + mockBackEnd.getPort()); + rateService = new RateService(baseUrl); + } + + @Test + @DisplayName("Rate Service Test") + public void getRateTest() throws JsonProcessingException { + String expectedRate = "{\"BTC\":\"0.00777\"}"; + HashMap res = new HashMap<>(); + res.put("BTC", "0.00777"); + mockBackEnd.enqueue(new MockResponse() + .setBody(objectMapper.writeValueAsString(res)) + .addHeader(HttpHeaders.CONTENT_TYPE, + HttpHeaderValues.APPLICATION_JSON.toString())); + + rateService.updateMoneroRate(); + String testRate = rateService.getMoneroRate(); + assertEquals(expectedRate, testRate); + } + +} diff --git a/src/test/java/org/hiahatf/mass/service/rpc/LightningTest.java b/src/test/java/org/hiahatf/mass/service/rpc/LightningTest.java new file mode 100644 index 0000000..d2a6793 --- /dev/null +++ b/src/test/java/org/hiahatf/mass/service/rpc/LightningTest.java @@ -0,0 +1,165 @@ +package org.hiahatf.mass.service.rpc; + +import java.io.IOException; + +import javax.net.ssl.SSLException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.net.HttpHeaders; + +import org.hiahatf.mass.models.lightning.AddHoldInvoiceResponse; +import org.hiahatf.mass.models.lightning.Amount; +import org.hiahatf.mass.models.lightning.Info; +import org.hiahatf.mass.models.lightning.InvoiceLookupResponse; +import org.hiahatf.mass.models.lightning.InvoiceState; +import org.hiahatf.mass.models.lightning.Liquidity; +import org.hiahatf.mass.models.monero.XmrQuoteTable; +import org.hiahatf.mass.services.rpc.Lightning; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import io.netty.handler.codec.http.HttpHeaderValues; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +/** + * Tests for Lightning RPC Service + */ +@ExtendWith(MockitoExtension.class) +@RunWith(JUnitPlatform.class) +public class LightningTest { + + public static MockWebServer mockBackEnd; + private ObjectMapper objectMapper = new ObjectMapper(); + private Lightning lightning; + private String testMacaroonPath = + "src/test/resources/test.macroon"; + + @BeforeAll + static void setUp() throws IOException { + mockBackEnd = new MockWebServer(); + mockBackEnd.start(); + } + + @AfterAll + static void tearDown() throws IOException { + mockBackEnd.shutdown(); + } + + @BeforeEach + void initialize() { + String baseUrl = String.format("http://localhost:%s", + mockBackEnd.getPort()); + lightning = new Lightning(baseUrl, testMacaroonPath); + } + + @Test + @DisplayName("Get Info Test") + public void getInfoTest() throws JsonProcessingException, IOException, + SSLException { + String version = "v.0.0.0-test"; + Info info = Info.builder().version(version).build(); + mockBackEnd.enqueue(new MockResponse() + .setBody(objectMapper.writeValueAsString(info)) + .addHeader(HttpHeaders.CONTENT_TYPE, + HttpHeaderValues.APPLICATION_JSON.toString())); + Mono testRes = lightning.getInfo(); + + StepVerifier.create(testRes) + .expectNextMatches(r -> r.getVersion() + .equals(version)) + .verifyComplete(); + } + + @Test + @DisplayName("Lookup Invoice Test") + public void lookupInvoiceTest() throws JsonProcessingException, IOException, + SSLException { + InvoiceLookupResponse res = InvoiceLookupResponse.builder() + .state(InvoiceState.ACCEPTED).build(); + mockBackEnd.enqueue(new MockResponse() + .setBody(objectMapper.writeValueAsString(res)) + .addHeader(HttpHeaders.CONTENT_TYPE, + HttpHeaderValues.APPLICATION_JSON.toString())); + Mono testRes = lightning.lookupInvoice("hash"); + + StepVerifier.create(testRes) + .expectNextMatches(r -> r.getState() + .equals(InvoiceState.ACCEPTED)) + .verifyComplete(); + } + + @Test + @DisplayName("Generate Invoice Test") + public void generateInvoiceTest() throws JsonProcessingException, IOException, + SSLException { + String expectedPayReq = "lntest"; + Double amount = 0.1; + byte[] hash = new byte[32]; + AddHoldInvoiceResponse res = AddHoldInvoiceResponse.builder() + .payment_request(expectedPayReq).build(); + mockBackEnd.enqueue(new MockResponse() + .setBody(objectMapper.writeValueAsString(res)) + .addHeader(HttpHeaders.CONTENT_TYPE, + HttpHeaderValues.APPLICATION_JSON.toString())); + Mono testRes = lightning.generateInvoice(amount, hash); + + StepVerifier.create(testRes) + .expectNextMatches(r -> r.getPayment_request() + .equals(expectedPayReq)) + .verifyComplete(); + } + + @Test + @DisplayName("Handle Invoice Test") + public void handleInvoiceTest() throws JsonProcessingException, IOException, + SSLException { + XmrQuoteTable quote = XmrQuoteTable.builder() + .amount(0.1) + .quote_id("qid") + .build(); + mockBackEnd.enqueue(new MockResponse() + .setResponseCode(HttpStatus.OK.value()) + .addHeader(HttpHeaders.CONTENT_TYPE, + HttpHeaderValues.APPLICATION_JSON.toString())); + Mono> testRes = lightning.handleInvoice(quote, true); + + StepVerifier.create(testRes) + .expectNextMatches(r -> r.getStatusCode() + .equals(HttpStatus.OK)) + .verifyComplete(); + } + + @Test + @DisplayName("Fetch Balance Test") + public void fetchBalanceTest() throws JsonProcessingException, IOException, + SSLException { + Amount amount = Amount.builder().msat("10000").sat("10").build(); + Liquidity liquidity = Liquidity.builder() + .local_balance(amount).remote_balance(amount).build(); + mockBackEnd.enqueue(new MockResponse() + .setBody(objectMapper.writeValueAsString(liquidity)) + .addHeader(HttpHeaders.CONTENT_TYPE, + HttpHeaderValues.APPLICATION_JSON.toString())); + Mono testRes = lightning.fetchBalance(); + + + StepVerifier.create(testRes) + .expectNextMatches(r -> r.getLocal_balance() + .equals(amount)) + .verifyComplete(); + } + +} diff --git a/src/test/java/org/hiahatf/mass/service/rpc/MoneroTest.java b/src/test/java/org/hiahatf/mass/service/rpc/MoneroTest.java new file mode 100644 index 0000000..4d3ef88 --- /dev/null +++ b/src/test/java/org/hiahatf/mass/service/rpc/MoneroTest.java @@ -0,0 +1,117 @@ +package org.hiahatf.mass.service.rpc; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.hiahatf.mass.models.monero.proof.GetProofResult; +import org.hiahatf.mass.models.monero.proof.GetReserveProofResponse; +import org.hiahatf.mass.models.monero.transfer.TransferResponse; +import org.hiahatf.mass.models.monero.transfer.TransferResult; +import org.hiahatf.mass.models.monero.validate.ValidateAddressResponse; +import org.hiahatf.mass.models.monero.validate.ValidateAddressResult; +import org.hiahatf.mass.services.rpc.Monero; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +/** + * Tests for Monero RPC Service + */ +@ExtendWith(MockitoExtension.class) +@RunWith(JUnitPlatform.class) +public class MoneroTest { + + public static MockWebServer mockBackEnd; + private ObjectMapper objectMapper = new ObjectMapper(); + private Monero monero; + + @BeforeAll + static void setUp() throws IOException { + mockBackEnd = new MockWebServer(); + mockBackEnd.start(); + } + + @AfterAll + static void tearDown() throws IOException { + mockBackEnd.shutdown(); + } + + @BeforeEach + void initialize() { + String baseUrl = String.format("http://localhost:%s", + mockBackEnd.getPort()); + monero = new Monero(baseUrl); + } + + @Test + @DisplayName("Monero Validate Address Test") + public void validateAddressTest() throws JsonProcessingException { + String address = "54testAddress"; + ValidateAddressResult result = ValidateAddressResult.builder() + .valid(true).build(); + ValidateAddressResponse res = ValidateAddressResponse.builder() + .result(result).build(); + mockBackEnd.enqueue(new MockResponse() + .setBody(objectMapper.writeValueAsString(res)) + .addHeader("Content-Type", "application/json")); + Mono testRes = monero.validateAddress(address); + + StepVerifier.create(testRes) + .expectNextMatches(r -> r.getResult() + .equals(result)) + .verifyComplete(); + } + + @Test + @DisplayName("Monero Transfer Test") + public void transferTest() throws JsonProcessingException { + String address = "54testAddress"; + Double amount = 0.1; + TransferResult result = TransferResult.builder() + .tx_hash("hash").build(); + TransferResponse response = TransferResponse.builder() + .result(result).build(); + mockBackEnd.enqueue(new MockResponse() + .setBody(objectMapper.writeValueAsString(response)) + .addHeader("Content-Type", "application/json")); + Mono testRes = monero.transfer(address, amount); + + StepVerifier.create(testRes) + .expectNextMatches(r -> r.getResult() + .equals(result)) + .verifyComplete(); + } + + @Test + @DisplayName("Monero Reserve Proof Test") + public void reserveProofTest() throws JsonProcessingException { + Double amount = 0.1; + GetProofResult result = GetProofResult.builder() + .signature("reserveProofTest").build(); + GetReserveProofResponse response = GetReserveProofResponse.builder() + .result(result).build(); + mockBackEnd.enqueue(new MockResponse() + .setBody(objectMapper.writeValueAsString(response)) + .addHeader("Content-Type", "application/json")); + Mono testRes = monero.getReserveProof(amount); + + StepVerifier.create(testRes) + .expectNextMatches(r -> r.getResult() + .equals(result)) + .verifyComplete(); + } + +} diff --git a/src/test/java/org/hiahatf/mass/util/MassUtilTest.java b/src/test/java/org/hiahatf/mass/util/MassUtilTest.java new file mode 100644 index 0000000..84def8d --- /dev/null +++ b/src/test/java/org/hiahatf/mass/util/MassUtilTest.java @@ -0,0 +1,24 @@ +package org.hiahatf.mass.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Test class for the utility methods + */ +public class MassUtilTest { + + private MassUtil util = new MassUtil(0.01); + + @Test + @DisplayName("Parse Rate Test") + public void parseRateTest() { + String data = "{BTC: 0.0076543}"; + Double testRate = util.parseMoneroRate(data); + Double expectedRate = 0.007730843; + assertEquals(expectedRate, testRate); + } + +} diff --git a/src/test/resources/test.macroon b/src/test/resources/test.macroon new file mode 100644 index 0000000..621e8f1 --- /dev/null +++ b/src/test/resources/test.macroon @@ -0,0 +1 @@ +used for the Lightning rpc test \ No newline at end of file