diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/FlowService.java b/backend/src/main/java/de/cotto/lndmanagej/service/FlowService.java index 8a414835..ccc7e199 100644 --- a/backend/src/main/java/de/cotto/lndmanagej/service/FlowService.java +++ b/backend/src/main/java/de/cotto/lndmanagej/service/FlowService.java @@ -76,10 +76,8 @@ public class FlowService { Coins rebalanceSupportReceived = rebalanceService.getSupportAsTargetAmountToChannel(channelId, maxAge); Coins rebalanceFeesSent = rebalanceService.getSourceCostsForChannel(channelId, maxAge); Coins rebalanceSupportFeesSent = rebalanceService.getSupportAsSourceCostsFromChannel(channelId, maxAge); - Coins receivedViaPayments = settledInvoicesService.getAmountReceivedViaChannel(channelId, maxAge) - .subtract(rebalanceReceived) - .subtract(rebalanceSupportReceived) - .maximum(Coins.NONE); + Coins receivedViaPayments = + settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(channelId, maxAge); return new FlowReport( forwardedSent, diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/SettledInvoicesService.java b/backend/src/main/java/de/cotto/lndmanagej/service/SettledInvoicesService.java index fedc6944..bc84e68e 100644 --- a/backend/src/main/java/de/cotto/lndmanagej/service/SettledInvoicesService.java +++ b/backend/src/main/java/de/cotto/lndmanagej/service/SettledInvoicesService.java @@ -16,8 +16,8 @@ public class SettledInvoicesService { this.dao = dao; } - public Coins getAmountReceivedViaChannel(ChannelId channelId, Duration maxAge) { - return dao.getInvoicesPaidVia(channelId, maxAge).stream() + public Coins getAmountReceivedViaChannelWithoutSelfPayments(ChannelId channelId, Duration maxAge) { + return dao.getInvoicesWithoutSelfPaymentsPaidVia(channelId, maxAge).stream() .map(SettledInvoice::receivedVia) .map(receivedVia -> receivedVia.getOrDefault(channelId, Coins.NONE)) .reduce(Coins.NONE, Coins::add); diff --git a/backend/src/test/java/de/cotto/lndmanagej/service/FlowServiceTest.java b/backend/src/test/java/de/cotto/lndmanagej/service/FlowServiceTest.java index f113e605..94af8440 100644 --- a/backend/src/test/java/de/cotto/lndmanagej/service/FlowServiceTest.java +++ b/backend/src/test/java/de/cotto/lndmanagej/service/FlowServiceTest.java @@ -60,7 +60,8 @@ class FlowServiceTest { lenient().when(rebalanceService.getSourceCostsForChannel(any())).thenReturn(Coins.NONE); lenient().when(rebalanceService.getSourceCostsForChannel(any(), any())).thenReturn(Coins.NONE); lenient().when(rebalanceService.getSupportAsSourceCostsFromChannel(any(), any())).thenReturn(Coins.NONE); - lenient().when(settledInvoicesService.getAmountReceivedViaChannel(any(), any())).thenReturn(Coins.NONE); + lenient().when(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(any(), any())) + .thenReturn(Coins.NONE); } @Test @@ -79,9 +80,9 @@ class FlowServiceTest { mockRebalanceFromTo(DEFAULT_MAX_AGE, CHANNEL_ID_2, 3, 4); mockRebalanceSupportFromTo(DEFAULT_MAX_AGE, CHANNEL_ID, 555, 6); mockRebalanceSupportFromTo(DEFAULT_MAX_AGE, CHANNEL_ID_2, 7, 888); - when(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID, DEFAULT_MAX_AGE)) + when(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(CHANNEL_ID, DEFAULT_MAX_AGE)) .thenReturn(Coins.ofMilliSatoshis(1_500_000)); - when(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID_2, DEFAULT_MAX_AGE)) + when(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(CHANNEL_ID_2, DEFAULT_MAX_AGE)) .thenReturn(Coins.ofMilliSatoshis(915_000)); FlowReport flowReport = new FlowReport( Coins.ofSatoshis(1_000 + 50), @@ -93,7 +94,7 @@ class FlowServiceTest { Coins.ofSatoshis(555 + 7), Coins.ofMilliSatoshis(555 + 7), Coins.ofSatoshis(6 + 888), - Coins.ofMilliSatoshis(1_500_000 + 915_000 - 2_000 - 4_000 - 6_000 - 888_000) + Coins.ofMilliSatoshis(1_500_000 + 915_000) ); assertThat(flowService.getFlowReportForPeer(PUBKEY)).isEqualTo(flowReport); verify(forwardingEventsService).getEventsWithOutgoingChannel(CHANNEL_ID, DEFAULT_MAX_AGE); @@ -113,9 +114,9 @@ class FlowServiceTest { mockRebalanceFromTo(maxAge, CHANNEL_ID_2, 333, 4); mockRebalanceSupportFromTo(maxAge, CHANNEL_ID, 5, 6); mockRebalanceSupportFromTo(maxAge, CHANNEL_ID_2, 7, 8); - when(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID, maxAge)) + when(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(CHANNEL_ID, maxAge)) .thenReturn(Coins.ofMilliSatoshis(1_500_000)); - when(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID_2, maxAge)) + when(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(CHANNEL_ID_2, maxAge)) .thenReturn(Coins.ofMilliSatoshis(15_000)); when(channelService.getAllChannelsWith(PUBKEY)).thenReturn(Set.of(LOCAL_OPEN_CHANNEL, CLOSED_CHANNEL_2)); FlowReport flowReport = new FlowReport( @@ -128,7 +129,7 @@ class FlowServiceTest { Coins.ofSatoshis(5 + 7), Coins.ofMilliSatoshis(5 + 7), Coins.ofSatoshis(6 + 8), - Coins.ofMilliSatoshis(1_500_000 + 15_000 - 2_000 - 4_000 - 6_000 - 8_000) + Coins.ofMilliSatoshis(1_500_000 + 15_000) ); assertThat(flowService.getFlowReportForPeer(PUBKEY, maxAge)).isEqualTo(flowReport); } @@ -139,7 +140,7 @@ class FlowServiceTest { mockReceived(DEFAULT_MAX_AGE, CHANNEL_ID, 9_001L); mockRebalanceFromTo(DEFAULT_MAX_AGE, CHANNEL_ID, 100, 200); mockRebalanceSupportFromTo(DEFAULT_MAX_AGE, CHANNEL_ID, 5, 6); - when(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID, DEFAULT_MAX_AGE)) + when(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(CHANNEL_ID, DEFAULT_MAX_AGE)) .thenReturn(Coins.ofMilliSatoshis(1_500_000)); FlowReport flowReport = new FlowReport( Coins.ofSatoshis(1_050), @@ -151,7 +152,7 @@ class FlowServiceTest { Coins.ofSatoshis(5), Coins.ofMilliSatoshis(5), Coins.ofSatoshis(6), - Coins.ofMilliSatoshis(1_500_000 - 200_000 - 6_000) + Coins.ofMilliSatoshis(1_500_000) ); assertThat(flowService.getFlowReportForChannel(CHANNEL_ID)).isEqualTo(flowReport); verify(forwardingEventsService).getEventsWithOutgoingChannel(CHANNEL_ID, DEFAULT_MAX_AGE); @@ -165,7 +166,7 @@ class FlowServiceTest { mockReceived(maxAge, CHANNEL_ID, 1_000L, 8_001L); mockRebalanceFromTo(maxAge, CHANNEL_ID, 101, 201); mockRebalanceSupportFromTo(maxAge, CHANNEL_ID, 5, 6); - when(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID, maxAge)) + when(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(CHANNEL_ID, maxAge)) .thenReturn(Coins.ofMilliSatoshis(1_500_000)); FlowReport flowReport = new FlowReport( Coins.ofSatoshis(1_000 + 50), @@ -177,7 +178,7 @@ class FlowServiceTest { Coins.ofSatoshis(5), Coins.ofMilliSatoshis(5), Coins.ofSatoshis(6), - Coins.ofMilliSatoshis(1_500_000 - 201_000 - 6_000) + Coins.ofMilliSatoshis(1_500_000) ); assertThat(flowService.getFlowReportForChannel(CHANNEL_ID, maxAge)).isEqualTo(flowReport); } @@ -185,7 +186,7 @@ class FlowServiceTest { @Test void getFlowReportForChannel_includes_receivedViaPayments() { Coins expectedReceivedViaPayments = Coins.ofMilliSatoshis(1); - when(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID, DEFAULT_MAX_AGE)) + when(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(CHANNEL_ID, DEFAULT_MAX_AGE)) .thenReturn(expectedReceivedViaPayments); assertThat(flowService.getFlowReportForChannel(CHANNEL_ID).receivedViaPayments()) .isEqualTo(expectedReceivedViaPayments); @@ -196,24 +197,12 @@ class FlowServiceTest { Coins expectedReceivedViaPayments = Coins.NONE; Coins expectedRebalanceReceived = Coins.ofSatoshis(1); mockRebalanceFromTo(DEFAULT_MAX_AGE, CHANNEL_ID, 0, expectedRebalanceReceived.satoshis()); - when(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID, DEFAULT_MAX_AGE)) + when(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(CHANNEL_ID, DEFAULT_MAX_AGE)) .thenReturn(expectedReceivedViaPayments); assertThat(flowService.getFlowReportForChannel(CHANNEL_ID).receivedViaPayments()) .isEqualTo(expectedReceivedViaPayments); } - @Test - void getFlowReportForChannel_self_payments_do_not_count_for_receivedViaPayments() { - Coins expectedReceivedViaPayments = Coins.ofSatoshis(3); - Coins expectedRebalanceReceived = Coins.ofSatoshis(1); - - mockRebalanceFromTo(DEFAULT_MAX_AGE, CHANNEL_ID, 0, expectedRebalanceReceived.satoshis()); - when(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID, DEFAULT_MAX_AGE)) - .thenReturn(expectedReceivedViaPayments.add(expectedRebalanceReceived)); - assertThat(flowService.getFlowReportForChannel(CHANNEL_ID).receivedViaPayments()) - .isEqualTo(expectedReceivedViaPayments); - } - private void mockReceived(Duration maxAge, ChannelId channelId, Long... amounts) { List events = createListOfEvents(channelId, CHANNEL_ID_2, amounts); when(forwardingEventsService.getEventsWithIncomingChannel(channelId, maxAge)).thenReturn(events); diff --git a/backend/src/test/java/de/cotto/lndmanagej/service/SettledInvoicesServiceTest.java b/backend/src/test/java/de/cotto/lndmanagej/service/SettledInvoicesServiceTest.java index 82a9a974..9731606e 100644 --- a/backend/src/test/java/de/cotto/lndmanagej/service/SettledInvoicesServiceTest.java +++ b/backend/src/test/java/de/cotto/lndmanagej/service/SettledInvoicesServiceTest.java @@ -31,30 +31,31 @@ class SettledInvoicesServiceTest { private SettledInvoicesDao dao; @Test - void getAmountReceivedViaChannel_no_settled_invoices() { - assertThat(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID, MAX_AGE)) + void getAmountReceivedViaChannelWithoutSelfPayments_no_settled_invoices() { + assertThat(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(CHANNEL_ID, MAX_AGE)) .isEqualTo(Coins.NONE); } @Test - void getAmountReceivedViaChannel() { + void getAmountReceivedViaChannelWithoutSelfPayments() { Coins amountReceivedPerInvoice = Coins.ofMilliSatoshis(CHANNEL_ID.getShortChannelId()); - when(dao.getInvoicesPaidVia(CHANNEL_ID, MAX_AGE)).thenReturn(List.of(SETTLED_INVOICE)); - assertThat(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID, MAX_AGE)) + when(dao.getInvoicesWithoutSelfPaymentsPaidVia(CHANNEL_ID, MAX_AGE)).thenReturn(List.of(SETTLED_INVOICE)); + assertThat(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(CHANNEL_ID, MAX_AGE)) .isEqualTo(amountReceivedPerInvoice); } @Test - void getAmountReceivedViaChannel_two_invoices() { + void getAmountReceivedViaChannelWithoutSelfPayments_two_invoices() { Coins amountReceivedPerInvoice = Coins.ofMilliSatoshis(CHANNEL_ID.getShortChannelId()); Coins expected = amountReceivedPerInvoice.add(amountReceivedPerInvoice); - when(dao.getInvoicesPaidVia(CHANNEL_ID, MAX_AGE)).thenReturn(List.of(SETTLED_INVOICE, SETTLED_INVOICE_2)); - assertThat(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID, MAX_AGE)) + when(dao.getInvoicesWithoutSelfPaymentsPaidVia(CHANNEL_ID, MAX_AGE)) + .thenReturn(List.of(SETTLED_INVOICE, SETTLED_INVOICE_2)); + assertThat(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(CHANNEL_ID, MAX_AGE)) .isEqualTo(expected); } @Test - void getAmountReceivedViaChannel_invoice_paid_via_two_channels() { + void getAmountReceivedViaChannelWithoutSelfPayments_invoice_paid_via_two_channels() { Coins oneMilliSatoshi = Coins.ofMilliSatoshis(1); SettledInvoice invoice = new SettledInvoice( SETTLED_INVOICE.addIndex(), @@ -66,8 +67,8 @@ class SettledInvoicesServiceTest { SETTLED_INVOICE.keysendMessage(), Map.of(CHANNEL_ID, oneMilliSatoshi, CHANNEL_ID_2, AMOUNT_PAID.subtract(oneMilliSatoshi)) ); - when(dao.getInvoicesPaidVia(CHANNEL_ID, MAX_AGE)).thenReturn(List.of(invoice)); - assertThat(settledInvoicesService.getAmountReceivedViaChannel(CHANNEL_ID, MAX_AGE)) + when(dao.getInvoicesWithoutSelfPaymentsPaidVia(CHANNEL_ID, MAX_AGE)).thenReturn(List.of(invoice)); + assertThat(settledInvoicesService.getAmountReceivedViaChannelWithoutSelfPayments(CHANNEL_ID, MAX_AGE)) .isEqualTo(oneMilliSatoshi); } } diff --git a/invoices/build.gradle b/invoices/build.gradle index 5a3090a6..27461cf2 100644 --- a/invoices/build.gradle +++ b/invoices/build.gradle @@ -8,8 +8,10 @@ dependencies { implementation project(':model') implementation project(':caching') implementation project(':grpc-adapter') + implementation project(':payments') testFixturesApi testFixtures(project(':model')) integrationTestRuntimeOnly 'com.h2database:h2' + integrationTestImplementation project(':payments') integrationTestImplementation 'org.springframework.boot:spring-boot-starter-data-jpa' integrationTestImplementation testFixtures(project(':model')) } diff --git a/invoices/src/integrationTest/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesRepositoryIT.java b/invoices/src/integrationTest/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesRepositoryIT.java index 8f07d4c5..80288486 100644 --- a/invoices/src/integrationTest/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesRepositoryIT.java +++ b/invoices/src/integrationTest/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesRepositoryIT.java @@ -2,7 +2,11 @@ package de.cotto.lndmanagej.invoices.persistence; import de.cotto.lndmanagej.model.ChannelId; import de.cotto.lndmanagej.model.Coins; +import de.cotto.lndmanagej.model.Payment; import de.cotto.lndmanagej.model.SettledInvoice; +import de.cotto.lndmanagej.payments.persistence.PaymentJpaDto; +import de.cotto.lndmanagej.payments.persistence.PaymentsRepository; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -23,6 +27,9 @@ class SettledInvoicesRepositoryIT { @Autowired private SettledInvoicesRepository repository; + @Autowired + private PaymentsRepository paymentsRepository; + @Test void getMaxSettledIndexWithoutGaps_no_invoice() { assertThat(repository.getMaxSettledIndexWithoutGaps()).isEqualTo(0); @@ -61,70 +68,87 @@ class SettledInvoicesRepositoryIT { assertThat(repository.getMaxAddIndex()).isEqualTo(3L); } - @Test - void findAllByReceivedViaChannelIdAndSettleDateAfter_no_invoice() { - assertThat(repository.findAllByReceivedViaChannelIdAndSettleDateAfter(CHANNEL_ID.getShortChannelId(), 0)) - .isEmpty(); - } + @Nested + class GetInvoicesWithoutSelfPaymentsPaidVia { + @Test + void no_invoice() { + assertThat(repository.getInvoicesWithoutSelfPaymentsPaidVia(CHANNEL_ID.getShortChannelId(), 0)) + .isEmpty(); + } - @Test - void findAllByReceivedViaChannelIdAndSettleDateAfter_wrong_channel_id() { - repository.save(invoice(1, 1, Map.of(CHANNEL_ID, Coins.ofSatoshis(100)))); - assertThat(repository.findAllByReceivedViaChannelIdAndSettleDateAfter(CHANNEL_ID_2.getShortChannelId(), 0)) - .isEmpty(); - } + @Test + void wrong_channel_id() { + repository.save(invoice(1, 1, Map.of(CHANNEL_ID, Coins.ofSatoshis(100)))); + assertThat(repository.getInvoicesWithoutSelfPaymentsPaidVia(CHANNEL_ID_2.getShortChannelId(), 0)) + .isEmpty(); + } - @Test - void findAllByReceivedViaChannelIdAndSettleDateAfter_expected_channel_id() { - Map expected = Map.of(CHANNEL_ID, Coins.ofSatoshis(100)); - repository.save(invoice(1, 1, expected)); - assertReceivedVia(CHANNEL_ID, List.of(expected)); - } + @Test + void expected_channel_id() { + Map expected = Map.of(CHANNEL_ID, Coins.ofSatoshis(100)); + repository.save(invoice(1, 1, expected)); + assertReceivedVia(CHANNEL_ID, List.of(expected)); + } - @Test - void findAllByReceivedViaChannelIdAndSettleDateAfter_just_within_max_age() { - Map tooOld = Map.of(CHANNEL_ID, Coins.ofSatoshis(100)); - SettledInvoiceJpaDto invoice = invoice(1, 1, tooOld); - repository.save(invoice); - long timestamp = invoice.getSettleDate() - 1; - List invoices = - repository.findAllByReceivedViaChannelIdAndSettleDateAfter(CHANNEL_ID.getShortChannelId(), timestamp); - assertThat(invoices).isNotEmpty(); - } + @Test + void just_within_max_age() { + Map tooOld = Map.of(CHANNEL_ID, Coins.ofSatoshis(100)); + SettledInvoiceJpaDto invoice = invoice(1, 1, tooOld); + repository.save(invoice); + long timestamp = invoice.getSettleDate() - 1; + List invoices = + repository.getInvoicesWithoutSelfPaymentsPaidVia(CHANNEL_ID.getShortChannelId(), timestamp); + assertThat(invoices).isNotEmpty(); + } - @Test - void findAllByReceivedViaChannelIdAndSettleDateAfter_expected_channel_id_but_too_old() { - Map tooOld = Map.of(CHANNEL_ID, Coins.ofSatoshis(100)); - SettledInvoiceJpaDto invoice = invoice(1, 1, tooOld); - repository.save(invoice); - long timestamp = invoice.getSettleDate(); - List invoices = - repository.findAllByReceivedViaChannelIdAndSettleDateAfter(CHANNEL_ID.getShortChannelId(), timestamp); - assertThat(invoices).isEmpty(); - } + @Test + void too_old() { + Map tooOld = Map.of(CHANNEL_ID, Coins.ofSatoshis(100)); + SettledInvoiceJpaDto invoice = invoice(1, 1, tooOld); + repository.save(invoice); + long timestamp = invoice.getSettleDate(); + List invoices = + repository.getInvoicesWithoutSelfPaymentsPaidVia(CHANNEL_ID.getShortChannelId(), timestamp); + assertThat(invoices).isEmpty(); + } - @Test - void findAllByReceivedViaChannelIdAndSettleDateAfter_one_of_two_channels_matches() { - Map expected = Map.of(CHANNEL_ID, Coins.ofSatoshis(100), CHANNEL_ID_2, Coins.ofSatoshis(50)); - repository.save(invoice(1, 1, expected)); - assertReceivedVia(CHANNEL_ID, List.of(expected)); - } + @Test + void self_payment() { + Map receivedVia = Map.of(CHANNEL_ID, Coins.ofSatoshis(100)); + SettledInvoiceJpaDto invoice = invoice(1, 1, receivedVia); + paymentsRepository.save(payment(invoice.getHash())); + repository.save(invoice); + List invoices = + repository.getInvoicesWithoutSelfPaymentsPaidVia(CHANNEL_ID.getShortChannelId(), 0); + assertThat(invoices).isEmpty(); + } - @Test - void findAllByReceivedViaChannelIdAndSettleDateAfter_several_invoices() { - Map expected1 = Map.of(CHANNEL_ID, Coins.ofSatoshis(100), CHANNEL_ID_2, Coins.ofSatoshis(50)); - Map expected2 = Map.of(CHANNEL_ID_3, Coins.ofSatoshis(3), CHANNEL_ID_2, Coins.ofSatoshis(4)); - repository.save(invoice(1, 1, expected1)); - repository.save(invoice(2, 2, Map.of(CHANNEL_ID, Coins.ofSatoshis(1), CHANNEL_ID_3, Coins.ofSatoshis(2)))); - repository.save(invoice(3, 3, expected2)); - assertReceivedVia(CHANNEL_ID_2, List.of(expected1, expected2)); - } + @Test + void one_of_two_channels_matches() { + Map expected = + Map.of(CHANNEL_ID, Coins.ofSatoshis(100), CHANNEL_ID_2, Coins.ofSatoshis(50)); + repository.save(invoice(1, 1, expected)); + assertReceivedVia(CHANNEL_ID, List.of(expected)); + } - private void assertReceivedVia(ChannelId channelId, List> expected) { - assertThat(repository.findAllByReceivedViaChannelIdAndSettleDateAfter(channelId.getShortChannelId(), 0)) - .map(SettledInvoiceJpaDto::toModel) - .map(SettledInvoice::receivedVia) - .isEqualTo(expected); + @Test + void several_invoices() { + Map expected1 = + Map.of(CHANNEL_ID, Coins.ofSatoshis(100), CHANNEL_ID_2, Coins.ofSatoshis(50)); + Map expected2 = + Map.of(CHANNEL_ID_3, Coins.ofSatoshis(3), CHANNEL_ID_2, Coins.ofSatoshis(4)); + repository.save(invoice(1, 1, expected1)); + repository.save(invoice(2, 2, Map.of(CHANNEL_ID, Coins.ofSatoshis(1), CHANNEL_ID_3, Coins.ofSatoshis(2)))); + repository.save(invoice(3, 3, expected2)); + assertReceivedVia(CHANNEL_ID_2, List.of(expected1, expected2)); + } + + private void assertReceivedVia(ChannelId channelId, List> expected) { + assertThat(repository.getInvoicesWithoutSelfPaymentsPaidVia(channelId.getShortChannelId(), 0)) + .map(SettledInvoiceJpaDto::toModel) + .map(SettledInvoice::receivedVia) + .isEqualTo(expected); + } } private SettledInvoiceJpaDto invoice(int addIndex, int settleIndex) { @@ -144,4 +168,15 @@ class SettledInvoicesRepositoryIT { ) ); } + + private PaymentJpaDto payment(String hash) { + return PaymentJpaDto.createFromModel(new Payment( + 0, + hash, + LocalDateTime.of(2020, 1, 2, 3, 4), + Coins.NONE, + Coins.NONE, + List.of()) + ); + } } diff --git a/invoices/src/main/java/de/cotto/lndmanagej/invoices/SettledInvoicesDao.java b/invoices/src/main/java/de/cotto/lndmanagej/invoices/SettledInvoicesDao.java index 55d3fb88..4835b987 100644 --- a/invoices/src/main/java/de/cotto/lndmanagej/invoices/SettledInvoicesDao.java +++ b/invoices/src/main/java/de/cotto/lndmanagej/invoices/SettledInvoicesDao.java @@ -16,5 +16,5 @@ public interface SettledInvoicesDao { long getSettleIndexOffset(); - List getInvoicesPaidVia(ChannelId channelId, Duration maxAge); + List getInvoicesWithoutSelfPaymentsPaidVia(ChannelId channelId, Duration maxAge); } diff --git a/invoices/src/main/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesDaoImpl.java b/invoices/src/main/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesDaoImpl.java index 5fd83273..d342d192 100644 --- a/invoices/src/main/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesDaoImpl.java +++ b/invoices/src/main/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesDaoImpl.java @@ -44,13 +44,11 @@ public class SettledInvoicesDaoImpl implements SettledInvoicesDao { } @Override - public List getInvoicesPaidVia(ChannelId channelId, Duration maxAge) { - return repository.findAllByReceivedViaChannelIdAndSettleDateAfter( + public List getInvoicesWithoutSelfPaymentsPaidVia(ChannelId channelId, Duration maxAge) { + return repository.getInvoicesWithoutSelfPaymentsPaidVia( channelId.getShortChannelId(), getAfterEpochMilliSeconds(maxAge) - ).stream() - .map(SettledInvoiceJpaDto::toModel) - .toList(); + ).stream().map(SettledInvoiceJpaDto::toModel).toList(); } private long getAfterEpochMilliSeconds(Duration maxAge) { diff --git a/invoices/src/main/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesRepository.java b/invoices/src/main/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesRepository.java index 27bb3376..7b56c13e 100644 --- a/invoices/src/main/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesRepository.java +++ b/invoices/src/main/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesRepository.java @@ -13,5 +13,9 @@ public interface SettledInvoicesRepository extends JpaRepository findAllByReceivedViaChannelIdAndSettleDateAfter(long channelId, long timestamp); + @Query("SELECT s FROM SettledInvoiceJpaDto s " + + "JOIN s.receivedVia v " + + "LEFT JOIN PaymentJpaDto p ON (s.hash = p.hash) " + + "WHERE s.settleDate > ?2 AND v.channelId = ?1 AND p IS NULL") + List getInvoicesWithoutSelfPaymentsPaidVia(long channelId, long timestamp); } diff --git a/invoices/src/test/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesDaoImplTest.java b/invoices/src/test/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesDaoImplTest.java index d198bcb7..a94468de 100644 --- a/invoices/src/test/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesDaoImplTest.java +++ b/invoices/src/test/java/de/cotto/lndmanagej/invoices/persistence/SettledInvoicesDaoImplTest.java @@ -82,23 +82,23 @@ class SettledInvoicesDaoImplTest { @Test void getInvoicesPaidVia_empty() { - assertThat(dao.getInvoicesPaidVia(CHANNEL_ID, Duration.ofMinutes(1))).isEmpty(); + assertThat(dao.getInvoicesWithoutSelfPaymentsPaidVia(CHANNEL_ID, Duration.ofMinutes(1))).isEmpty(); } @Test void getInvoicesPaidVia() { SettledInvoiceJpaDto jpaDto = SettledInvoiceJpaDto.createFromModel(SETTLED_INVOICE); - when(repository.findAllByReceivedViaChannelIdAndSettleDateAfter(eq(CHANNEL_ID.getShortChannelId()), anyLong())) + when(repository.getInvoicesWithoutSelfPaymentsPaidVia(eq(CHANNEL_ID.getShortChannelId()), anyLong())) .thenReturn(List.of(jpaDto)); - assertThat(dao.getInvoicesPaidVia(CHANNEL_ID, MAX_AGE)).containsExactly(SETTLED_INVOICE); + assertThat(dao.getInvoicesWithoutSelfPaymentsPaidVia(CHANNEL_ID, MAX_AGE)).containsExactly(SETTLED_INVOICE); } @Test void getInvoicesPaidVia_with_max_age() { Duration maxAge = Duration.ofDays(17); long expectedTimestamp = Instant.now().minus(maxAge).getEpochSecond() * 1_000; - dao.getInvoicesPaidVia(CHANNEL_ID, maxAge); - verify(repository).findAllByReceivedViaChannelIdAndSettleDateAfter( + dao.getInvoicesWithoutSelfPaymentsPaidVia(CHANNEL_ID, maxAge); + verify(repository).getInvoicesWithoutSelfPaymentsPaidVia( anyLong(), longThat(timestamp -> Math.abs(expectedTimestamp - timestamp) <= 5_000) );