add self payments endpoints

This commit is contained in:
Carsten Otto
2021-12-09 12:35:01 +01:00
parent 75bb974224
commit fb8fd3134f
44 changed files with 808 additions and 66 deletions

View File

@@ -8,6 +8,7 @@ dependencies {
runtimeOnly project(':invoices')
runtimeOnly project(':payments')
runtimeOnly project(':statistics')
runtimeOnly project(':selfpayments')
runtimeOnly 'org.postgresql:postgresql'
testRuntimeOnly 'com.h2database:h2'
testImplementation project(':backend')

View File

@@ -8,6 +8,7 @@ dependencies {
implementation project(':grpc-adapter')
implementation project(':model')
implementation project(':transactions')
implementation project(':selfpayments')
testImplementation testFixtures(project(':model'))
testImplementation testFixtures(project(':transactions'))
}

View File

@@ -0,0 +1,25 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.SelfPayment;
import de.cotto.lndmanagej.selfpayments.SelfPaymentsDao;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class SelfPaymentsService {
private final SelfPaymentsDao dao;
public SelfPaymentsService(SelfPaymentsDao dao) {
this.dao = dao;
}
public List<SelfPayment> getSelfPaymentsToChannel(ChannelId channelId) {
return dao.getSelfPaymentsToChannel(channelId);
}
public List<SelfPayment> getSelfPaymentsFromChannel(ChannelId channelId) {
return dao.getSelfPaymentsFromChannel(channelId).stream().distinct().toList();
}
}

View File

@@ -0,0 +1,42 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.selfpayments.SelfPaymentsDao;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class SelfPaymentsServiceTest {
@InjectMocks
private SelfPaymentsService selfPaymentsService;
@Mock
private SelfPaymentsDao selfPaymentsDao;
@Test
void getSelfPaymentsToChannel() {
when(selfPaymentsDao.getSelfPaymentsToChannel(CHANNEL_ID)).thenReturn(List.of(SELF_PAYMENT));
assertThat(selfPaymentsService.getSelfPaymentsToChannel(CHANNEL_ID)).containsExactly(SELF_PAYMENT);
}
@Test
void getSelfPaymentsFromChannel() {
when(selfPaymentsDao.getSelfPaymentsFromChannel(CHANNEL_ID)).thenReturn(List.of(SELF_PAYMENT));
assertThat(selfPaymentsService.getSelfPaymentsFromChannel(CHANNEL_ID)).containsExactly(SELF_PAYMENT);
}
@Test
void getSelfPaymentsFromChannel_no_duplicates() {
when(selfPaymentsDao.getSelfPaymentsFromChannel(CHANNEL_ID)).thenReturn(List.of(SELF_PAYMENT, SELF_PAYMENT));
assertThat(selfPaymentsService.getSelfPaymentsFromChannel(CHANNEL_ID)).containsExactly(SELF_PAYMENT);
}
}

View File

@@ -10,7 +10,6 @@ import lnrpc.ListInvoiceResponse;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HexFormat;
import java.util.Iterator;
import java.util.List;
@@ -19,6 +18,8 @@ import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.time.ZoneOffset.UTC;
@Component
public class GrpcInvoices {
private static final int LIMIT = 1_000;
@@ -62,7 +63,7 @@ public class GrpcInvoices {
return new SettledInvoice(
lndInvoice.getAddIndex(),
lndInvoice.getSettleIndex(),
LocalDateTime.ofEpochSecond(lndInvoice.getSettleDate(), 0, ZoneOffset.UTC),
LocalDateTime.ofEpochSecond(lndInvoice.getSettleDate(), 0, UTC).atZone(UTC),
HEX_FORMAT.formatHex(lndInvoice.getRHash().toByteArray()),
Coins.ofMilliSatoshis(lndInvoice.getAmtPaidMsat()),
lndInvoice.getMemo(),

View File

@@ -13,7 +13,6 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.annotation.Nullable;
import java.time.ZoneOffset;
import java.util.Collections;
import java.util.HexFormat;
import java.util.LinkedHashMap;
@@ -250,7 +249,7 @@ class GrpcInvoicesTest {
.setSettleIndex(settledInvoice.settleIndex())
.setRHash(ByteString.copyFrom(HEX_FORMAT.parseHex(settledInvoice.hash())))
.setMemo(settledInvoice.memo())
.setSettleDate(settledInvoice.settleDate().toEpochSecond(ZoneOffset.UTC))
.setSettleDate(settledInvoice.settleDate().toEpochSecond())
.setAmtPaidMsat(settledInvoice.amountPaid().milliSatoshis())
.addHtlcs(htlc)
.build();

View File

@@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Optional;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
@@ -59,7 +60,7 @@ class SettledInvoicesRepositoryIT {
return SettledInvoiceJpaDto.createFromModel(new SettledInvoice(
addIndex,
settleIndex,
LocalDateTime.MIN,
LocalDateTime.MIN.atZone(ZoneOffset.UTC),
"",
Coins.NONE,
"",

View File

@@ -11,10 +11,12 @@ import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.Optional;
import static java.time.ZoneOffset.UTC;
@Entity
@Table(name = "settled_invoices", indexes = {@Index(unique = true, columnList = "settleIndex")})
public class SettledInvoiceJpaDto {
@@ -47,7 +49,7 @@ public class SettledInvoiceJpaDto {
SettledInvoiceJpaDto jpaDto = new SettledInvoiceJpaDto();
jpaDto.addIndex = settledInvoice.addIndex();
jpaDto.settleIndex = settledInvoice.settleIndex();
jpaDto.settleDate = settledInvoice.settleDate().toEpochSecond(ZoneOffset.UTC);
jpaDto.settleDate = settledInvoice.settleDate().toEpochSecond();
jpaDto.hash = settledInvoice.hash();
jpaDto.amountPaid = settledInvoice.amountPaid().milliSatoshis();
jpaDto.memo = settledInvoice.memo();
@@ -57,7 +59,7 @@ public class SettledInvoiceJpaDto {
}
public SettledInvoice toModel() {
LocalDateTime localDateTime = LocalDateTime.ofEpochSecond(settleDate, 0, ZoneOffset.UTC);
ZonedDateTime dateTime = LocalDateTime.ofEpochSecond(settleDate, 0, UTC).atZone(UTC);
Optional<ChannelId> channelId;
if (receivedVia > 0) {
channelId = Optional.of(ChannelId.fromShortChannelId(receivedVia));
@@ -67,7 +69,7 @@ public class SettledInvoiceJpaDto {
return new SettledInvoice(
addIndex,
settleIndex,
localDateTime,
dateTime,
Objects.requireNonNull(hash),
Coins.ofMilliSatoshis(amountPaid),
Objects.requireNonNull(memo),

View File

@@ -4,10 +4,10 @@ import de.cotto.lndmanagej.model.SettledInvoice;
import de.cotto.lndmanagej.model.SettledInvoiceFixtures;
import org.junit.jupiter.api.Test;
import java.time.ZoneOffset;
import java.util.Optional;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.KEYSEND_MESSAGE;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.SETTLED_INVOICE;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.SETTLED_INVOICE_KEYSEND;
@@ -21,13 +21,12 @@ class SettledSettledInvoiceJpaDtoTest {
SettledInvoiceJpaDto jpaDto = SettledInvoiceJpaDto.createFromModel(SETTLED_INVOICE);
assertThat(jpaDto.getAddIndex()).isEqualTo(SETTLED_INVOICE.addIndex());
assertThat(jpaDto.getSettleIndex()).isEqualTo(SETTLED_INVOICE.settleIndex());
assertThat(jpaDto.getSettleDate())
.isEqualTo(SETTLED_INVOICE.settleDate().toEpochSecond(ZoneOffset.UTC));
assertThat(jpaDto.getSettleDate()).isEqualTo(SETTLED_INVOICE.settleDate().toEpochSecond());
assertThat(jpaDto.getHash()).isEqualTo(SETTLED_INVOICE.hash());
assertThat(jpaDto.getAmountPaid()).isEqualTo(SETTLED_INVOICE.amountPaid().milliSatoshis());
assertThat(jpaDto.getMemo()).isEqualTo(SETTLED_INVOICE.memo());
assertThat(jpaDto.getKeysendMessage()).isNull();
assertThat(jpaDto.getReceivedVia()).isEqualTo(CHANNEL_ID.getShortChannelId());
assertThat(jpaDto.getReceivedVia()).isEqualTo(CHANNEL_ID_2.getShortChannelId());
}
@Test

View File

@@ -2,6 +2,7 @@ package de.cotto.lndmanagej.model;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
public record Payment(
long index,
@@ -11,4 +12,7 @@ public record Payment(
Coins fees,
List<PaymentRoute> routes
) {
public Optional<ChannelId> getFirstChannel() {
return routes.stream().flatMap(route -> route.hops().stream()).map(PaymentHop::channelId).findFirst();
}
}

View File

@@ -0,0 +1,24 @@
package de.cotto.lndmanagej.model;
import java.time.ZonedDateTime;
import java.util.Optional;
public record SelfPayment(
String memo,
ZonedDateTime settleDate,
Coins value,
Coins fees,
Optional<ChannelId> firstChannel,
Optional<ChannelId> lastChannel
) {
public SelfPayment(Payment payment, SettledInvoice settledInvoice) {
this(
settledInvoice.memo(),
settledInvoice.settleDate(),
payment.value(),
payment.fees(),
payment.getFirstChannel(),
settledInvoice.receivedVia()
);
}
}

View File

@@ -1,12 +1,14 @@
package de.cotto.lndmanagej.model;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Optional;
public record SettledInvoice(
long addIndex,
long settleIndex,
LocalDateTime settleDate,
ZonedDateTime settleDate,
String hash,
Coins amountPaid,
String memo,
@@ -16,7 +18,7 @@ public record SettledInvoice(
public static final SettledInvoice INVALID = new SettledInvoice(
-1,
-1,
LocalDateTime.MIN,
LocalDateTime.MIN.atZone(ZoneOffset.UTC),
"",
Coins.NONE,
"",

View File

@@ -2,18 +2,18 @@ package de.cotto.lndmanagej.model;
import org.junit.jupiter.api.Test;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_4;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_4;
import static org.assertj.core.api.Assertions.assertThat;
class PaymentHopTest {
@Test
void channelId() {
assertThat(PAYMENT_HOP.channelId()).isEqualTo(CHANNEL_ID);
assertThat(PAYMENT_HOP_CHANNEL_4.channelId()).isEqualTo(CHANNEL_ID_4);
}
@Test
void amount() {
assertThat(PAYMENT_HOP.amount()).isEqualTo(Coins.ofSatoshis(1));
assertThat(PAYMENT_HOP_CHANNEL_4.amount()).isEqualTo(Coins.ofSatoshis(4));
}
}

View File

@@ -4,16 +4,18 @@ import org.junit.jupiter.api.Test;
import java.util.List;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_2;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_3;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_2;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_3;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_4;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE_4_TO_2;
import static org.assertj.core.api.Assertions.assertThat;
class PaymentRouteTest {
@Test
void hops() {
assertThat(PAYMENT_ROUTE.hops()).isEqualTo(List.of(PAYMENT_HOP, PAYMENT_HOP_2, PAYMENT_HOP_3));
assertThat(PAYMENT_ROUTE_4_TO_2.hops()).isEqualTo(
List.of(PAYMENT_HOP_CHANNEL_4, PAYMENT_HOP_CHANNEL_3, PAYMENT_HOP_CHANNEL_2)
);
}
}

View File

@@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
import java.util.List;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_4;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT_2;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT_CREATION_DATE_TIME;
@@ -11,8 +12,8 @@ import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT_FEES;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT_HASH;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT_INDEX;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT_VALUE;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE_2;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE_3_TO_1;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE_4_TO_1;
import static org.assertj.core.api.Assertions.assertThat;
class PaymentTest {
@@ -43,6 +44,11 @@ class PaymentTest {
@Test
void routes() {
assertThat(PAYMENT_2.routes()).isEqualTo(List.of(PAYMENT_ROUTE, PAYMENT_ROUTE_2));
assertThat(PAYMENT_2.routes()).isEqualTo(List.of(PAYMENT_ROUTE_4_TO_1, PAYMENT_ROUTE_3_TO_1));
}
@Test
void getFirstChannel() {
assertThat(PAYMENT.getFirstChannel()).contains(CHANNEL_ID_4);
}
}

View File

@@ -0,0 +1,42 @@
package de.cotto.lndmanagej.model;
import org.junit.jupiter.api.Test;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT_2;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT_2;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.SETTLED_INVOICE;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.SETTLED_INVOICE_2;
import static org.assertj.core.api.Assertions.assertThat;
class SelfPaymentTest {
@Test
void memo() {
assertThat(SELF_PAYMENT.memo()).isEqualTo(SETTLED_INVOICE.memo());
}
@Test
void settleDate() {
assertThat(SELF_PAYMENT_2.settleDate()).isEqualTo(SETTLED_INVOICE_2.settleDate());
}
@Test
void value() {
assertThat(SELF_PAYMENT_2.value()).isEqualTo(PAYMENT_2.value());
}
@Test
void fees() {
assertThat(SELF_PAYMENT_2.fees()).isEqualTo(PAYMENT_2.fees());
}
@Test
void firstChannel() {
assertThat(SELF_PAYMENT_2.firstChannel()).isEqualTo(PAYMENT_2.getFirstChannel());
}
@Test
void lastChannel() {
assertThat(SELF_PAYMENT_2.lastChannel()).isEqualTo(SETTLED_INVOICE_2.receivedVia());
}
}

View File

@@ -3,9 +3,10 @@ package de.cotto.lndmanagej.model;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Optional;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.ADD_INDEX;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.AMOUNT_PAID;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.HASH;
@@ -23,7 +24,7 @@ class SettledInvoiceTest {
SettledInvoice expected = new SettledInvoice(
-1,
-1,
LocalDateTime.MIN,
LocalDateTime.MIN.atZone(ZoneOffset.UTC),
"",
Coins.NONE,
"",
@@ -81,6 +82,6 @@ class SettledInvoiceTest {
@Test
void receivedVia() {
assertThat(SETTLED_INVOICE.receivedVia()).contains(CHANNEL_ID);
assertThat(SETTLED_INVOICE.receivedVia()).contains(CHANNEL_ID_2);
}
}

View File

@@ -0,0 +1,13 @@
package de.cotto.lndmanagej;
import de.cotto.lndmanagej.model.SelfPayment;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT_2;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.SETTLED_INVOICE;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.SETTLED_INVOICE_2;
public class SelfPaymentFixtures {
public static final SelfPayment SELF_PAYMENT = new SelfPayment(PAYMENT, SETTLED_INVOICE);
public static final SelfPayment SELF_PAYMENT_2 = new SelfPayment(PAYMENT_2, SETTLED_INVOICE_2);
}

View File

@@ -3,25 +3,33 @@ package de.cotto.lndmanagej.model;
import java.time.LocalDateTime;
import java.util.List;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE_2;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE_3_TO_1;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE_4_TO_1;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE_4_TO_2;
public class PaymentFixtures {
public static final int PAYMENT_INDEX = 2;
public static final int PAYMENT_INDEX_2 = 3;
public static final String PAYMENT_HASH = "abc";
public static final int PAYMENT_INDEX_3 = 4;
public static final String PAYMENT_HASH = "1234";
public static final String PAYMENT_HASH_2 = "aaa0";
public static final String PAYMENT_HASH_3 = "aaa1";
public static final Coins PAYMENT_VALUE = Coins.ofSatoshis(1_000_000);
public static final Coins PAYMENT_FEES = Coins.ofMilliSatoshis(10);
private static final List<PaymentRoute> ONE_ROUTE = List.of(PAYMENT_ROUTE);
private static final List<PaymentRoute> TWO_ROUTES = List.of(PAYMENT_ROUTE, PAYMENT_ROUTE_2);
private static final List<PaymentRoute> ONE_ROUTE_4_TO_2 = List.of(PAYMENT_ROUTE_4_TO_2);
private static final List<PaymentRoute> TWO_ROUTES = List.of(PAYMENT_ROUTE_4_TO_1, PAYMENT_ROUTE_3_TO_1);
public static final LocalDateTime PAYMENT_CREATION_DATE_TIME =
LocalDateTime.of(2021, 12, 5, 22, 22, 22, 500_000_000);
public static final Payment PAYMENT = new Payment(
PAYMENT_INDEX, PAYMENT_HASH, PAYMENT_CREATION_DATE_TIME, PAYMENT_VALUE, PAYMENT_FEES, ONE_ROUTE
PAYMENT_INDEX, PAYMENT_HASH, PAYMENT_CREATION_DATE_TIME, PAYMENT_VALUE, PAYMENT_FEES, ONE_ROUTE_4_TO_2
);
public static final Payment PAYMENT_2 = new Payment(
PAYMENT_INDEX_2, PAYMENT_HASH, PAYMENT_CREATION_DATE_TIME, PAYMENT_VALUE, PAYMENT_FEES, TWO_ROUTES
PAYMENT_INDEX_2, PAYMENT_HASH_2, PAYMENT_CREATION_DATE_TIME, PAYMENT_VALUE, PAYMENT_FEES, TWO_ROUTES
);
public static final Payment PAYMENT_3 = new Payment(
PAYMENT_INDEX_3, PAYMENT_HASH_3, PAYMENT_CREATION_DATE_TIME, PAYMENT_VALUE, PAYMENT_FEES, TWO_ROUTES
);
}

View File

@@ -3,9 +3,11 @@ package de.cotto.lndmanagej.model;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_3;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_4;
public class PaymentHopFixtures {
public static final PaymentHop PAYMENT_HOP = new PaymentHop(CHANNEL_ID, Coins.ofSatoshis(1));
public static final PaymentHop PAYMENT_HOP_2 = new PaymentHop(CHANNEL_ID_2, Coins.ofSatoshis(2));
public static final PaymentHop PAYMENT_HOP_3 = new PaymentHop(CHANNEL_ID_3, Coins.ofSatoshis(3));
public static final PaymentHop PAYMENT_HOP_CHANNEL_1 = new PaymentHop(CHANNEL_ID, Coins.ofSatoshis(1));
public static final PaymentHop PAYMENT_HOP_CHANNEL_2 = new PaymentHop(CHANNEL_ID_2, Coins.ofSatoshis(2));
public static final PaymentHop PAYMENT_HOP_CHANNEL_3 = new PaymentHop(CHANNEL_ID_3, Coins.ofSatoshis(3));
public static final PaymentHop PAYMENT_HOP_CHANNEL_4 = new PaymentHop(CHANNEL_ID_4, Coins.ofSatoshis(4));
}

View File

@@ -2,13 +2,16 @@ package de.cotto.lndmanagej.model;
import java.util.List;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_2;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_3;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_1;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_2;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_3;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_4;
public class PaymentRouteFixtures {
public static final PaymentRoute PAYMENT_ROUTE =
new PaymentRoute(List.of(PAYMENT_HOP, PAYMENT_HOP_2, PAYMENT_HOP_3));
public static final PaymentRoute PAYMENT_ROUTE_2 =
new PaymentRoute(List.of(PAYMENT_HOP, PAYMENT_HOP_2));
public static final PaymentRoute PAYMENT_ROUTE_4_TO_2 =
new PaymentRoute(List.of(PAYMENT_HOP_CHANNEL_4, PAYMENT_HOP_CHANNEL_3, PAYMENT_HOP_CHANNEL_2));
public static final PaymentRoute PAYMENT_ROUTE_4_TO_1 =
new PaymentRoute(List.of(PAYMENT_HOP_CHANNEL_4, PAYMENT_HOP_CHANNEL_3, PAYMENT_HOP_CHANNEL_1));
public static final PaymentRoute PAYMENT_ROUTE_3_TO_1 =
new PaymentRoute(List.of(PAYMENT_HOP_CHANNEL_3, PAYMENT_HOP_CHANNEL_1));
}

View File

@@ -1,18 +1,24 @@
package de.cotto.lndmanagej.model;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Optional;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_3;
public class SettledInvoiceFixtures {
public static final LocalDateTime SETTLE_DATE = LocalDateTime.of(2021, 12, 2, 16, 4, 30);
public static final ZonedDateTime SETTLE_DATE = ZonedDateTime.of(2021, 12, 2, 16, 4, 30, 0, ZoneOffset.UTC);
public static final int ADD_INDEX = 2;
public static final int ADD_INDEX_2 = 1;
public static final int ADD_INDEX_3 = 3;
public static final int SETTLE_INDEX = 1;
public static final int SETTLE_INDEX_2 = 2;
public static final int SETTLE_INDEX_3 = 3;
public static final String HASH = "1234";
public static final String HASH_2 = "aaa0";
public static final String HASH_3 = "010101";
public static final Coins AMOUNT_PAID = Coins.ofMilliSatoshis(123);
public static final Coins AMOUNT_PAID_2 = Coins.ofMilliSatoshis(4_567);
public static final String MEMO = "this is a memo";
@@ -27,7 +33,7 @@ public class SettledInvoiceFixtures {
AMOUNT_PAID,
MEMO,
Optional.empty(),
Optional.of(CHANNEL_ID)
Optional.of(CHANNEL_ID_2)
);
public static final SettledInvoice SETTLED_INVOICE_NO_CHANNEL_ID = new SettledInvoice(
@@ -49,7 +55,7 @@ public class SettledInvoiceFixtures {
AMOUNT_PAID,
MEMO,
Optional.of(KEYSEND_MESSAGE),
Optional.of(CHANNEL_ID)
Optional.of(CHANNEL_ID_2)
);
public static final SettledInvoice SETTLED_INVOICE_2 = new SettledInvoice(
@@ -62,4 +68,15 @@ public class SettledInvoiceFixtures {
Optional.empty(),
Optional.of(CHANNEL_ID)
);
public static final SettledInvoice SETTLED_INVOICE_3 = new SettledInvoice(
ADD_INDEX_3,
SETTLE_INDEX_3,
SETTLE_DATE.plusSeconds(2),
HASH_3,
AMOUNT_PAID_2,
MEMO_2,
Optional.empty(),
Optional.of(CHANNEL_ID_3)
);
}

View File

@@ -7,7 +7,7 @@ import de.cotto.lndmanagej.model.PaymentHop;
import javax.persistence.Embeddable;
@Embeddable
public class PaymentHopJpaDto {
class PaymentHopJpaDto {
private long channelId;
private long amount;

View File

@@ -16,7 +16,7 @@ import java.util.Objects;
@Entity
@Table(name = "payments")
class PaymentJpaDto {
public class PaymentJpaDto {
@Id
private long paymentIndex;

View File

@@ -9,19 +9,21 @@ import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OrderColumn;
import javax.persistence.Table;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name = "payment_routes")
public class PaymentRouteJpaDto {
class PaymentRouteJpaDto {
@Id
@GeneratedValue
@SuppressWarnings("unused")
private long routeId;
@Nullable
@OrderColumn
@ElementCollection
@CollectionTable(name = "payment_route_hops")
private List<PaymentHopJpaDto> hops;

View File

@@ -4,18 +4,19 @@ import de.cotto.lndmanagej.model.Coins;
import org.junit.jupiter.api.Test;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_1;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_4;
import static org.assertj.core.api.Assertions.assertThat;
class PaymentHopJpaDtoTest {
@Test
void toModel() {
assertThat(new PaymentHopJpaDto(CHANNEL_ID.getShortChannelId(), Coins.ofSatoshis(1).milliSatoshis()).toModel())
.isEqualTo(PAYMENT_HOP);
.isEqualTo(PAYMENT_HOP_CHANNEL_1);
}
@Test
void createFromModel() {
assertThat(PaymentHopJpaDto.createFromModel(PAYMENT_HOP).toModel()).isEqualTo(PAYMENT_HOP);
assertThat(PaymentHopJpaDto.createFromModel(PAYMENT_HOP_CHANNEL_4).toModel()).isEqualTo(PAYMENT_HOP_CHANNEL_4);
}
}

View File

@@ -4,10 +4,10 @@ import org.junit.jupiter.api.Test;
import java.util.List;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_2;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_3;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_2;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_3;
import static de.cotto.lndmanagej.model.PaymentHopFixtures.PAYMENT_HOP_CHANNEL_4;
import static de.cotto.lndmanagej.model.PaymentRouteFixtures.PAYMENT_ROUTE_4_TO_2;
import static org.assertj.core.api.Assertions.assertThat;
class PaymentRouteJpaDtoTest {
@@ -15,15 +15,15 @@ class PaymentRouteJpaDtoTest {
void toModel() {
assertThat(new PaymentRouteJpaDto(
List.of(
PaymentHopJpaDto.createFromModel(PAYMENT_HOP),
PaymentHopJpaDto.createFromModel(PAYMENT_HOP_2),
PaymentHopJpaDto.createFromModel(PAYMENT_HOP_3)
PaymentHopJpaDto.createFromModel(PAYMENT_HOP_CHANNEL_4),
PaymentHopJpaDto.createFromModel(PAYMENT_HOP_CHANNEL_3),
PaymentHopJpaDto.createFromModel(PAYMENT_HOP_CHANNEL_2)
)
).toModel()).isEqualTo(PAYMENT_ROUTE);
).toModel()).isEqualTo(PAYMENT_ROUTE_4_TO_2);
}
@Test
void createFromModel() {
assertThat(PaymentRouteJpaDto.createFromModel(PAYMENT_ROUTE).toModel()).isEqualTo(PAYMENT_ROUTE);
assertThat(PaymentRouteJpaDto.createFromModel(PAYMENT_ROUTE_4_TO_2).toModel()).isEqualTo(PAYMENT_ROUTE_4_TO_2);
}
}

12
selfpayments/build.gradle Normal file
View File

@@ -0,0 +1,12 @@
plugins {
id 'lnd-manageJ.java-library-conventions'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation project(':model')
implementation project(':invoices')
implementation project(':payments')
testRuntimeOnly 'com.h2database:h2'
testFixturesApi testFixtures(project(':model'))
}

View File

@@ -0,0 +1,10 @@
package de.cotto.lndmanagej;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootConfiguration {
public SpringBootConfiguration() {
// default constructor
}
}

View File

@@ -0,0 +1,72 @@
package de.cotto.lndmanagej.selfpayments.persistence;
import de.cotto.lndmanagej.invoices.persistence.SettledInvoiceJpaDto;
import de.cotto.lndmanagej.invoices.persistence.SettledInvoicesRepository;
import de.cotto.lndmanagej.payments.persistence.PaymentJpaDto;
import de.cotto.lndmanagej.payments.persistence.PaymentsRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT_2;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_3;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_4;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT_2;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT_3;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.SETTLED_INVOICE;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.SETTLED_INVOICE_2;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.SETTLED_INVOICE_3;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
class SelfPaymentsRepositoryIT {
@Autowired
private SelfPaymentsRepository repository;
@Autowired
private SettledInvoicesRepository invoicesRepository;
@Autowired
private PaymentsRepository paymentsRepository;
@BeforeEach
void setUp() {
paymentsRepository.save(PaymentJpaDto.createFromModel(PAYMENT));
paymentsRepository.save(PaymentJpaDto.createFromModel(PAYMENT_2));
paymentsRepository.save(PaymentJpaDto.createFromModel(PAYMENT_3));
invoicesRepository.save(SettledInvoiceJpaDto.createFromModel(SETTLED_INVOICE));
invoicesRepository.save(SettledInvoiceJpaDto.createFromModel(SETTLED_INVOICE_2));
invoicesRepository.save(SettledInvoiceJpaDto.createFromModel(SETTLED_INVOICE_3));
}
@Test
void getSelfPayments() {
assertThat(repository.getAllSelfPayments()).map(SelfPaymentJpaDto::toModel)
.containsExactlyInAnyOrder(SELF_PAYMENT, SELF_PAYMENT_2);
}
@Test
void getSelfPaymentsToChannel() {
assertThat(repository.getSelfPaymentsToChannel(CHANNEL_ID_2.getShortChannelId()))
.map(SelfPaymentJpaDto::toModel)
.containsExactly(SELF_PAYMENT);
}
@Test
void getSelfPaymentsFromChannel() {
assertThat(repository.getSelfPaymentsFromChannel(CHANNEL_ID_3.getShortChannelId()))
.map(SelfPaymentJpaDto::toModel)
.containsExactly(SELF_PAYMENT_2);
}
@Test
void getSelfPaymentsFromChannel_two_payments() {
assertThat(repository.getSelfPaymentsFromChannel(CHANNEL_ID_4.getShortChannelId()))
.map(SelfPaymentJpaDto::toModel)
.containsExactly(SELF_PAYMENT, SELF_PAYMENT_2);
}
}

View File

@@ -0,0 +1,14 @@
package de.cotto.lndmanagej.selfpayments;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.SelfPayment;
import java.util.List;
public interface SelfPaymentsDao {
List<SelfPayment> getAllSelfPayments();
List<SelfPayment> getSelfPaymentsToChannel(ChannelId channelId);
List<SelfPayment> getSelfPaymentsFromChannel(ChannelId channelId);
}

View File

@@ -0,0 +1,14 @@
package de.cotto.lndmanagej.selfpayments.persistence;
import de.cotto.lndmanagej.invoices.persistence.SettledInvoiceJpaDto;
import de.cotto.lndmanagej.model.SelfPayment;
import de.cotto.lndmanagej.payments.persistence.PaymentJpaDto;
public record SelfPaymentJpaDto(
PaymentJpaDto payment,
SettledInvoiceJpaDto settledInvoice
) {
public SelfPayment toModel() {
return new SelfPayment(payment.toModel(), settledInvoice.toModel());
}
}

View File

@@ -0,0 +1,38 @@
package de.cotto.lndmanagej.selfpayments.persistence;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.SelfPayment;
import de.cotto.lndmanagej.selfpayments.SelfPaymentsDao;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
import java.util.List;
@Component
@Transactional
public class SelfPaymentsDaoImpl implements SelfPaymentsDao {
private final SelfPaymentsRepository repository;
public SelfPaymentsDaoImpl(SelfPaymentsRepository repository) {
this.repository = repository;
}
@Override
public List<SelfPayment> getAllSelfPayments() {
return toModel(repository.getAllSelfPayments());
}
@Override
public List<SelfPayment> getSelfPaymentsToChannel(ChannelId channelId) {
return toModel(repository.getSelfPaymentsToChannel(channelId.getShortChannelId()));
}
@Override
public List<SelfPayment> getSelfPaymentsFromChannel(ChannelId channelId) {
return toModel(repository.getSelfPaymentsFromChannel(channelId.getShortChannelId()));
}
private List<SelfPayment> toModel(List<SelfPaymentJpaDto> selfPayments) {
return selfPayments.stream().map(SelfPaymentJpaDto::toModel).toList();
}
}

View File

@@ -0,0 +1,46 @@
package de.cotto.lndmanagej.selfpayments.persistence;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.List;
public interface SelfPaymentsRepository extends JpaRepository<SelfPaymentsRepository.DummyEntity, Long> {
@Query("SELECT NEW de.cotto.lndmanagej.selfpayments.persistence.SelfPaymentJpaDto(p, i) " +
"FROM PaymentJpaDto p " +
"JOIN SettledInvoiceJpaDto i ON p.hash = i.hash " +
"ORDER BY i.settleDate ASC")
List<SelfPaymentJpaDto> getAllSelfPayments();
@Query("SELECT NEW de.cotto.lndmanagej.selfpayments.persistence.SelfPaymentJpaDto(p, i) " +
"FROM PaymentJpaDto p " +
"JOIN SettledInvoiceJpaDto i ON p.hash = i.hash " +
"WHERE i.receivedVia = ?1 " +
"ORDER BY i.settleDate ASC")
List<SelfPaymentJpaDto> getSelfPaymentsToChannel(long channelId);
@Query("SELECT NEW de.cotto.lndmanagej.selfpayments.persistence.SelfPaymentJpaDto(p, i) " +
"FROM PaymentJpaDto p " +
"JOIN SettledInvoiceJpaDto i ON p.hash = i.hash " +
"JOIN p.routes route " +
"JOIN route.hops hop " +
"WHERE hop.channelId = ?1 AND INDEX(hop) = 0 " +
"ORDER BY i.settleDate ASC")
List<SelfPaymentJpaDto> getSelfPaymentsFromChannel(long channelId);
@Entity
@Table(name = "dummy")
class DummyEntity {
@Id
@SuppressWarnings("unused")
private long dummyId;
public DummyEntity() {
// for JPA
}
}
}

View File

@@ -0,0 +1,37 @@
package de.cotto.lndmanagej.selfpayments.persistence;
import de.cotto.lndmanagej.invoices.persistence.SettledInvoiceJpaDto;
import de.cotto.lndmanagej.payments.persistence.PaymentJpaDto;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.SETTLED_INVOICE;
import static org.assertj.core.api.Assertions.assertThat;
class SelfPaymentJpaDtoTest {
private SelfPaymentJpaDto selfPaymentJpaDto;
@BeforeEach
void setUp() {
SettledInvoiceJpaDto invoice = SettledInvoiceJpaDto.createFromModel(SETTLED_INVOICE);
PaymentJpaDto payment = PaymentJpaDto.createFromModel(PAYMENT);
selfPaymentJpaDto = new SelfPaymentJpaDto(payment, invoice);
}
@Test
void toModel() {
assertThat(selfPaymentJpaDto.toModel()).isEqualTo(SELF_PAYMENT);
}
@Test
void payment() {
assertThat(selfPaymentJpaDto.payment().toModel()).isEqualTo(PAYMENT);
}
@Test
void settledInvoice() {
assertThat(selfPaymentJpaDto.settledInvoice().toModel()).isEqualTo(SETTLED_INVOICE);
}
}

View File

@@ -0,0 +1,65 @@
package de.cotto.lndmanagej.selfpayments.persistence;
import de.cotto.lndmanagej.invoices.persistence.SettledInvoiceJpaDto;
import de.cotto.lndmanagej.model.Payment;
import de.cotto.lndmanagej.model.SettledInvoice;
import de.cotto.lndmanagej.payments.persistence.PaymentJpaDto;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT_2;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT;
import static de.cotto.lndmanagej.model.PaymentFixtures.PAYMENT_2;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.SETTLED_INVOICE;
import static de.cotto.lndmanagej.model.SettledInvoiceFixtures.SETTLED_INVOICE_2;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class SelfPaymentsDaoImplTest {
@InjectMocks
private SelfPaymentsDaoImpl selfPaymentsDaoImpl;
@Mock
private SelfPaymentsRepository repository;
@Test
void getAllSelfPayments_empty() {
assertThat(selfPaymentsDaoImpl.getAllSelfPayments()).isEmpty();
}
@Test
void getAllSelfPayments() {
when(repository.getAllSelfPayments())
.thenReturn(List.of(getDto(PAYMENT, SETTLED_INVOICE), getDto(PAYMENT_2, SETTLED_INVOICE_2)));
assertThat(selfPaymentsDaoImpl.getAllSelfPayments()).containsExactlyInAnyOrder(SELF_PAYMENT, SELF_PAYMENT_2);
}
@Test
void getSelfPaymentsToChannel() {
when(repository.getSelfPaymentsToChannel(CHANNEL_ID.getShortChannelId()))
.thenReturn(List.of(getDto(PAYMENT, SETTLED_INVOICE)));
assertThat(selfPaymentsDaoImpl.getSelfPaymentsToChannel(CHANNEL_ID)).containsExactlyInAnyOrder(SELF_PAYMENT);
}
@Test
void getSelfPaymentsFromChannel() {
when(repository.getSelfPaymentsFromChannel(CHANNEL_ID.getShortChannelId()))
.thenReturn(List.of(getDto(PAYMENT, SETTLED_INVOICE)));
assertThat(selfPaymentsDaoImpl.getSelfPaymentsFromChannel(CHANNEL_ID)).containsExactlyInAnyOrder(SELF_PAYMENT);
}
private SelfPaymentJpaDto getDto(Payment payment, SettledInvoice settledInvoice) {
return new SelfPaymentJpaDto(
PaymentJpaDto.createFromModel(payment),
SettledInvoiceJpaDto.createFromModel(settledInvoice)
);
}
}

View File

@@ -0,0 +1,13 @@
package de.cotto.lndmanagej.selfpayments.persistence;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class SelfPaymentsRepositoryTest {
@Test
void dummyEntity() {
// the class is required to define a repository
assertThat(new SelfPaymentsRepository.DummyEntity()).isNotNull();
}
}

View File

@@ -8,6 +8,7 @@ include 'grpc-client'
include 'invoices'
include 'model'
include 'payments'
include 'selfpayments'
include 'statistics'
include 'transactions'
include 'web'

View File

@@ -0,0 +1,67 @@
package de.cotto.lndmanagej.controller;
import de.cotto.lndmanagej.model.ChannelIdResolver;
import de.cotto.lndmanagej.service.SelfPaymentsService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import java.util.List;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT_2;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_4;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.Is.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@WebMvcTest(controllers = SelfPaymentsController.class)
class SelfPaymentsControllerIT {
private static final String CHANNEL_PREFIX = "/api/channel/" + CHANNEL_ID.getShortChannelId() + "/";
@Autowired
private MockMvc mockMvc;
@MockBean
@SuppressWarnings("unused")
private ChannelIdResolver channelIdResolver;
@MockBean
private SelfPaymentsService selfPaymentsService;
@Test
void getSelfPaymentsFromChannel() throws Exception {
when(selfPaymentsService.getSelfPaymentsFromChannel(CHANNEL_ID))
.thenReturn(List.of(SELF_PAYMENT_2, SELF_PAYMENT));
mockMvc.perform(get(CHANNEL_PREFIX + "/self-payments-from-channel/"))
.andExpect(jsonPath("$[0].memo", is(SELF_PAYMENT_2.memo())))
.andExpect(jsonPath("$[1].memo", is(SELF_PAYMENT.memo())));
}
@Test
void getSelfPaymentsToChannel() throws Exception {
when(selfPaymentsService.getSelfPaymentsToChannel(CHANNEL_ID))
.thenReturn(List.of(SELF_PAYMENT, SELF_PAYMENT_2));
mockMvc.perform(get(CHANNEL_PREFIX + "/self-payments-to-channel/"))
.andExpect(jsonPath("$[0].memo", is(SELF_PAYMENT.memo())))
.andExpect(jsonPath("$[0].settleDate", is(SELF_PAYMENT.settleDate().toString())))
.andExpect(jsonPath("$[0].value", is(String.valueOf(SELF_PAYMENT.value().milliSatoshis()))))
.andExpect(jsonPath("$[0].fees", is(String.valueOf(SELF_PAYMENT.fees().milliSatoshis()))))
.andExpect(jsonPath("$[0].firstChannel", is(CHANNEL_ID_4.toString())))
.andExpect(jsonPath("$[0].lastChannel", is(CHANNEL_ID_2.toString())))
.andExpect(jsonPath("$[1].memo", is(SELF_PAYMENT_2.memo())))
.andExpect(jsonPath("$[1].settleDate", is(SELF_PAYMENT_2.settleDate().toString())))
.andExpect(jsonPath("$[1].value", is(String.valueOf(SELF_PAYMENT_2.value().milliSatoshis()))))
.andExpect(jsonPath("$[1].fees", is(String.valueOf(SELF_PAYMENT_2.fees().milliSatoshis()))))
.andExpect(jsonPath("$[1].firstChannel", is(not(empty()))))
.andExpect(jsonPath("$[1].lastChannel", is(CHANNEL_ID.toString())));
}
}

View File

@@ -0,0 +1,41 @@
package de.cotto.lndmanagej.controller;
import com.codahale.metrics.annotation.Timed;
import de.cotto.lndmanagej.controller.dto.ObjectMapperConfiguration;
import de.cotto.lndmanagej.controller.dto.SelfPaymentDto;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.service.SelfPaymentsService;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/")
@Import(ObjectMapperConfiguration.class)
public class SelfPaymentsController {
private final SelfPaymentsService selfPaymentsService;
public SelfPaymentsController(SelfPaymentsService selfPaymentsService) {
this.selfPaymentsService = selfPaymentsService;
}
@Timed
@GetMapping("/channel/{channelId}/self-payments-to-channel")
public List<SelfPaymentDto> getSelfPaymentsToChannel(@PathVariable ChannelId channelId) {
return selfPaymentsService.getSelfPaymentsToChannel(channelId).stream()
.map(SelfPaymentDto::createFromModel)
.toList();
}
@Timed
@GetMapping("/channel/{channelId}/self-payments-from-channel")
public List<SelfPaymentDto> getSelfPaymentsFromChannel(@PathVariable ChannelId channelId) {
return selfPaymentsService.getSelfPaymentsFromChannel(channelId).stream()
.map(SelfPaymentDto::createFromModel)
.toList();
}
}

View File

@@ -10,6 +10,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.time.ZonedDateTime;
@Configuration
public class ObjectMapperConfiguration {
public ObjectMapperConfiguration() {
@@ -23,6 +25,7 @@ public class ObjectMapperConfiguration {
module.addSerializer(Pubkey.class, new ToStringSerializer());
module.addSerializer(ChannelId.class, new ToStringSerializer());
module.addSerializer(ChannelPoint.class, new ToStringSerializer());
module.addSerializer(ZonedDateTime.class, new ToStringSerializer());
return new ObjectMapper().registerModule(module);
}
}

View File

@@ -0,0 +1,26 @@
package de.cotto.lndmanagej.controller.dto;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.SelfPayment;
import java.time.ZonedDateTime;
public record SelfPaymentDto(
ZonedDateTime settleDate,
String memo,
String value,
String fees,
ChannelId firstChannel,
ChannelId lastChannel
) {
public static SelfPaymentDto createFromModel(SelfPayment selfPayment) {
return new SelfPaymentDto(
selfPayment.settleDate(),
selfPayment.memo(),
String.valueOf(selfPayment.value().milliSatoshis()),
String.valueOf(selfPayment.fees().milliSatoshis()),
selfPayment.firstChannel().orElseThrow(),
selfPayment.lastChannel().orElseThrow()
);
}
}

View File

@@ -0,0 +1,44 @@
package de.cotto.lndmanagej.controller;
import de.cotto.lndmanagej.controller.dto.SelfPaymentDto;
import de.cotto.lndmanagej.service.SelfPaymentsService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT_2;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class SelfPaymentsControllerTest {
@InjectMocks
private SelfPaymentsController selfPaymentsController;
@Mock
private SelfPaymentsService service;
@Test
void getSelfPaymentsToChannel() {
when(service.getSelfPaymentsToChannel(CHANNEL_ID)).thenReturn(List.of(SELF_PAYMENT, SELF_PAYMENT_2));
assertThat(selfPaymentsController.getSelfPaymentsToChannel(CHANNEL_ID)).containsExactly(
SelfPaymentDto.createFromModel(SELF_PAYMENT),
SelfPaymentDto.createFromModel(SELF_PAYMENT_2)
);
}
@Test
void getSelfPaymentsFromChannel() {
when(service.getSelfPaymentsFromChannel(CHANNEL_ID)).thenReturn(List.of(SELF_PAYMENT, SELF_PAYMENT_2));
assertThat(selfPaymentsController.getSelfPaymentsFromChannel(CHANNEL_ID)).containsExactly(
SelfPaymentDto.createFromModel(SELF_PAYMENT),
SelfPaymentDto.createFromModel(SELF_PAYMENT_2)
);
}
}

View File

@@ -0,0 +1,41 @@
package de.cotto.lndmanagej.controller.dto;
import org.junit.jupiter.api.Test;
import static de.cotto.lndmanagej.SelfPaymentFixtures.SELF_PAYMENT;
import static org.assertj.core.api.Assertions.assertThat;
class SelfPaymentDtoTest {
private final SelfPaymentDto dto = SelfPaymentDto.createFromModel(SELF_PAYMENT);
@Test
void settleDate() {
assertThat(dto.settleDate()).isEqualTo(SELF_PAYMENT.settleDate());
}
@Test
void memo() {
assertThat(dto.memo()).isEqualTo(SELF_PAYMENT.memo());
}
@Test
void value() {
assertThat(dto.value()).isEqualTo(String.valueOf(SELF_PAYMENT.value().milliSatoshis()));
}
@Test
void fees() {
assertThat(dto.fees()).isEqualTo(String.valueOf(SELF_PAYMENT.fees().milliSatoshis()));
}
@Test
void firstChannel() {
assertThat(dto.firstChannel()).isEqualTo(SELF_PAYMENT.firstChannel().orElseThrow());
}
@Test
void lastChannel() {
assertThat(dto.lastChannel()).isEqualTo(SELF_PAYMENT.lastChannel().orElseThrow());
}
}