diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/ChannelService.java b/backend/src/main/java/de/cotto/lndmanagej/service/ChannelService.java index 79db9e6d..f6d733fc 100644 --- a/backend/src/main/java/de/cotto/lndmanagej/service/ChannelService.java +++ b/backend/src/main/java/de/cotto/lndmanagej/service/ChannelService.java @@ -5,6 +5,7 @@ import com.github.benmanes.caffeine.cache.LoadingCache; import de.cotto.lndmanagej.caching.CacheBuilder; import de.cotto.lndmanagej.grpc.GrpcChannels; import de.cotto.lndmanagej.grpc.GrpcClosedChannels; +import de.cotto.lndmanagej.model.Channel; import de.cotto.lndmanagej.model.ChannelId; import de.cotto.lndmanagej.model.ClosedChannel; import de.cotto.lndmanagej.model.ForceClosedChannel; @@ -12,7 +13,10 @@ import de.cotto.lndmanagej.model.ForceClosingChannel; import de.cotto.lndmanagej.model.LocalChannel; import de.cotto.lndmanagej.model.LocalOpenChannel; import de.cotto.lndmanagej.model.Pubkey; +import de.cotto.lndmanagej.model.TransactionHash; import de.cotto.lndmanagej.model.WaitingCloseChannel; +import de.cotto.lndmanagej.transactions.model.Transaction; +import de.cotto.lndmanagej.transactions.service.TransactionService; import org.springframework.stereotype.Component; import java.time.Duration; @@ -32,17 +36,23 @@ public class ChannelService { private static final Duration CACHE_REFRESH = Duration.ofSeconds(30); private final GrpcChannels grpcChannels; + private final TransactionService transactionService; private final LoadingCache> localOpenChannelsCache; private final LoadingCache> closedChannelsCache; private final LoadingCache> forceClosingChannelsCache; private final LoadingCache> waitingCloseChannelsCache; - public ChannelService(GrpcChannels grpcChannels, GrpcClosedChannels grpcClosedChannels) { + public ChannelService( + TransactionService transactionService, + GrpcChannels grpcChannels, + GrpcClosedChannels grpcClosedChannels + ) { this.grpcChannels = grpcChannels; localOpenChannelsCache = new CacheBuilder() .withRefresh(CACHE_REFRESH) .withExpiry(CACHE_EXPIRY) .build(grpcChannels::getChannels); + this.transactionService = transactionService; closedChannelsCache = new CacheBuilder() .withRefresh(CACHE_REFRESH) .withExpiry(CACHE_EXPIRY) @@ -163,4 +173,9 @@ public class ChannelService { forceClosingChannels ).parallel().map(Supplier::get).flatMap(Collection::stream); } + + public Optional getOpenHeight(Channel channel) { + TransactionHash openTransactionHash = channel.getChannelPoint().getTransactionHash(); + return transactionService.getTransaction(openTransactionHash).map(Transaction::blockHeight); + } } diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/NodeWarningsProvider.java b/backend/src/main/java/de/cotto/lndmanagej/service/NodeWarningsProvider.java new file mode 100644 index 00000000..dc7b508a --- /dev/null +++ b/backend/src/main/java/de/cotto/lndmanagej/service/NodeWarningsProvider.java @@ -0,0 +1,10 @@ +package de.cotto.lndmanagej.service; + +import de.cotto.lndmanagej.model.Pubkey; +import de.cotto.lndmanagej.model.warnings.NodeWarning; + +import java.util.stream.Stream; + +public interface NodeWarningsProvider { + Stream getNodeWarnings(Pubkey pubkey); +} diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/NodeWarningsService.java b/backend/src/main/java/de/cotto/lndmanagej/service/NodeWarningsService.java index 4f0b47b6..13692f10 100644 --- a/backend/src/main/java/de/cotto/lndmanagej/service/NodeWarningsService.java +++ b/backend/src/main/java/de/cotto/lndmanagej/service/NodeWarningsService.java @@ -1,52 +1,26 @@ package de.cotto.lndmanagej.service; -import de.cotto.lndmanagej.model.NodeOnlineChangesWarning; -import de.cotto.lndmanagej.model.NodeOnlinePercentageWarning; -import de.cotto.lndmanagej.model.NodeWarning; import de.cotto.lndmanagej.model.NodeWarnings; import de.cotto.lndmanagej.model.Pubkey; +import de.cotto.lndmanagej.model.warnings.NodeWarning; import org.springframework.stereotype.Component; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Stream; +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; @Component public class NodeWarningsService { - private static final int ONLINE_PERCENTAGE_THRESHOLD = 80; - private static final int ONLINE_CHANGES_THRESHOLD = 50; + private final Collection providers; - private final OnlinePeersService onlinePeersService; - - public NodeWarningsService(OnlinePeersService onlinePeersService) { - this.onlinePeersService = onlinePeersService; + public NodeWarningsService(Collection providers) { + this.providers = providers; } public NodeWarnings getNodeWarnings(Pubkey pubkey) { - List warnings = Stream.of( - (Function>) this::getOnlinePercentageWarning, - this::getOnlineChangesWarning - ).map(function -> function.apply(pubkey)) - .flatMap(Optional::stream) - .toList(); + Set warnings = providers.stream() + .flatMap(provider -> provider.getNodeWarnings(pubkey)) + .collect(Collectors.toSet()); return new NodeWarnings(warnings); } - - private Optional getOnlinePercentageWarning(Pubkey pubkey) { - int percentage = onlinePeersService.getOnlinePercentageLastWeek(pubkey); - if (percentage < ONLINE_PERCENTAGE_THRESHOLD) { - return Optional.of(new NodeOnlinePercentageWarning(percentage)); - } - return Optional.empty(); - } - - private Optional getOnlineChangesWarning(Pubkey pubkey) { - int changes = onlinePeersService.getChangesLastWeek(pubkey); - if (changes > ONLINE_CHANGES_THRESHOLD) { - return Optional.of(new NodeOnlineChangesWarning(changes)); - } - return Optional.empty(); - } - } diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/warnings/NodeFlowWarningsProvider.java b/backend/src/main/java/de/cotto/lndmanagej/service/warnings/NodeFlowWarningsProvider.java new file mode 100644 index 00000000..167ba2b2 --- /dev/null +++ b/backend/src/main/java/de/cotto/lndmanagej/service/warnings/NodeFlowWarningsProvider.java @@ -0,0 +1,82 @@ +package de.cotto.lndmanagej.service.warnings; + +import de.cotto.lndmanagej.model.Coins; +import de.cotto.lndmanagej.model.FlowReport; +import de.cotto.lndmanagej.model.Pubkey; +import de.cotto.lndmanagej.model.warnings.NodeNoFlowWarning; +import de.cotto.lndmanagej.model.warnings.NodeWarning; +import de.cotto.lndmanagej.service.ChannelService; +import de.cotto.lndmanagej.service.FlowService; +import de.cotto.lndmanagej.service.NodeWarningsProvider; +import de.cotto.lndmanagej.service.OwnNodeService; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.stream.Stream; + +@Component +public class NodeFlowWarningsProvider implements NodeWarningsProvider { + private static final int EXPECTED_MINUTES_PER_BLOCK = 10; + private static final int MINUTES_PER_DAY = 1_440; + private static final int MINIMUM_DAYS_FOR_WARNING = 14; + private static final int MAX_DAYS_TO_CONSIDER = 60; + private final FlowService flowService; + private final ChannelService channelService; + private final OwnNodeService ownNodeService; + + public NodeFlowWarningsProvider( + FlowService flowService, + ChannelService channelService, + OwnNodeService ownNodeService + ) { + this.flowService = flowService; + this.channelService = channelService; + this.ownNodeService = ownNodeService; + } + + @Override + public Stream getNodeWarnings(Pubkey pubkey) { + return Stream.of(getNoFlowWarning(pubkey)).flatMap(Optional::stream); + } + + private Optional getNoFlowWarning(Pubkey pubkey) { + int daysWithoutFlow = getDaysWithoutFlow(pubkey); + if (daysWithoutFlow < MINIMUM_DAYS_FOR_WARNING) { + return Optional.empty(); + } + return Optional.of(new NodeNoFlowWarning(daysWithoutFlow)); + } + + private int getDaysWithoutFlow(Pubkey pubkey) { + int daysToConsider = getDaysToConsider(pubkey); + int daysToCheck = MINIMUM_DAYS_FOR_WARNING; + while (noFlow(pubkey, daysToCheck) && daysToCheck <= daysToConsider) { + daysToCheck++; + } + return daysToCheck - 1; + } + + private int getDaysToConsider(Pubkey pubkey) { + OptionalInt openHeightOldestOpenChannel = channelService.getOpenChannelsWith(pubkey).stream() + .map(channelService::getOpenHeight) + .flatMap(Optional::stream) + .mapToInt(h -> h) + .max(); + if (openHeightOldestOpenChannel.isEmpty()) { + return 0; + } + int channelAgeInBlocks = ownNodeService.getBlockHeight() - openHeightOldestOpenChannel.getAsInt(); + int channelAgeInDays = (int) Math.ceil(channelAgeInBlocks * 1.0 * EXPECTED_MINUTES_PER_BLOCK / MINUTES_PER_DAY); + return Math.min(MAX_DAYS_TO_CONSIDER, channelAgeInDays); + } + + private boolean noFlow(Pubkey pubkey, int days) { + Duration maxAge = Duration.ofDays(days); + FlowReport flowReport = flowService.getFlowReportForPeer(pubkey, maxAge); + Coins absoluteFlow = flowReport.totalSent().add(flowReport.totalReceived()); + return absoluteFlow.isNonPositive(); + } + +} diff --git a/backend/src/main/java/de/cotto/lndmanagej/service/warnings/NodeOnlineWarningsProvider.java b/backend/src/main/java/de/cotto/lndmanagej/service/warnings/NodeOnlineWarningsProvider.java new file mode 100644 index 00000000..e593f9e3 --- /dev/null +++ b/backend/src/main/java/de/cotto/lndmanagej/service/warnings/NodeOnlineWarningsProvider.java @@ -0,0 +1,52 @@ +package de.cotto.lndmanagej.service.warnings; + +import de.cotto.lndmanagej.model.Pubkey; +import de.cotto.lndmanagej.model.warnings.NodeOnlineChangesWarning; +import de.cotto.lndmanagej.model.warnings.NodeOnlinePercentageWarning; +import de.cotto.lndmanagej.model.warnings.NodeWarning; +import de.cotto.lndmanagej.service.NodeWarningsProvider; +import de.cotto.lndmanagej.service.OnlinePeersService; +import org.springframework.stereotype.Component; + +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +@Component +public class NodeOnlineWarningsProvider implements NodeWarningsProvider { + private static final int ONLINE_PERCENTAGE_THRESHOLD = 80; + private static final int ONLINE_CHANGES_THRESHOLD = 50; + + private final OnlinePeersService onlinePeersService; + + public NodeOnlineWarningsProvider( + OnlinePeersService onlinePeersService + ) { + this.onlinePeersService = onlinePeersService; + } + + @Override + public Stream getNodeWarnings(Pubkey pubkey) { + return Stream.of( + (Function>) this::getOnlinePercentageWarning, + this::getOnlineChangesWarning + ).map(function -> function.apply(pubkey)) + .flatMap(Optional::stream); + } + + private Optional getOnlinePercentageWarning(Pubkey pubkey) { + int percentage = onlinePeersService.getOnlinePercentageLastWeek(pubkey); + if (percentage < ONLINE_PERCENTAGE_THRESHOLD) { + return Optional.of(new NodeOnlinePercentageWarning(percentage)); + } + return Optional.empty(); + } + + private Optional getOnlineChangesWarning(Pubkey pubkey) { + int changes = onlinePeersService.getChangesLastWeek(pubkey); + if (changes > ONLINE_CHANGES_THRESHOLD) { + return Optional.of(new NodeOnlineChangesWarning(changes)); + } + return Optional.empty(); + } +} diff --git a/backend/src/test/java/de/cotto/lndmanagej/service/ChannelServiceTest.java b/backend/src/test/java/de/cotto/lndmanagej/service/ChannelServiceTest.java index c10b0f34..61cb8e3b 100644 --- a/backend/src/test/java/de/cotto/lndmanagej/service/ChannelServiceTest.java +++ b/backend/src/test/java/de/cotto/lndmanagej/service/ChannelServiceTest.java @@ -2,6 +2,7 @@ package de.cotto.lndmanagej.service; import de.cotto.lndmanagej.grpc.GrpcChannels; import de.cotto.lndmanagej.grpc.GrpcClosedChannels; +import de.cotto.lndmanagej.transactions.service.TransactionService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -30,6 +31,8 @@ import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2; import static de.cotto.lndmanagej.model.WaitingCloseChannelFixtures.WAITING_CLOSE_CHANNEL; import static de.cotto.lndmanagej.model.WaitingCloseChannelFixtures.WAITING_CLOSE_CHANNEL_2; import static de.cotto.lndmanagej.model.WaitingCloseChannelFixtures.WAITING_CLOSE_CHANNEL_TO_NODE_3; +import static de.cotto.lndmanagej.transactions.model.TransactionFixtures.BLOCK_HEIGHT; +import static de.cotto.lndmanagej.transactions.model.TransactionFixtures.TRANSACTION; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @@ -38,6 +41,9 @@ class ChannelServiceTest { @InjectMocks private ChannelService channelService; + @Mock + private TransactionService transactionService; + @Mock private GrpcChannels grpcChannels; @@ -268,4 +274,18 @@ class ChannelServiceTest { WAITING_CLOSE_CHANNEL ); } + + @Test + void getOpenHeight_unknown_transaction() { + when(transactionService.getTransaction(LOCAL_OPEN_CHANNEL.getChannelPoint().getTransactionHash())) + .thenReturn(Optional.empty()); + assertThat(channelService.getOpenHeight(LOCAL_OPEN_CHANNEL)).isEmpty(); + } + + @Test + void getOpenHeight() { + when(transactionService.getTransaction(LOCAL_OPEN_CHANNEL.getChannelPoint().getTransactionHash())) + .thenReturn(Optional.of(TRANSACTION)); + assertThat(channelService.getOpenHeight(LOCAL_OPEN_CHANNEL)).contains(BLOCK_HEIGHT); + } } \ No newline at end of file diff --git a/backend/src/test/java/de/cotto/lndmanagej/service/NodeDetailsServiceTest.java b/backend/src/test/java/de/cotto/lndmanagej/service/NodeDetailsServiceTest.java index 8a2aa919..f77d9cc1 100644 --- a/backend/src/test/java/de/cotto/lndmanagej/service/NodeDetailsServiceTest.java +++ b/backend/src/test/java/de/cotto/lndmanagej/service/NodeDetailsServiceTest.java @@ -24,13 +24,13 @@ import static de.cotto.lndmanagej.model.NodeDetailsFixtures.NODE_DETAILS; import static de.cotto.lndmanagej.model.NodeDetailsFixtures.NODE_DETAILS_EMPTY; import static de.cotto.lndmanagej.model.NodeFixtures.NODE; import static de.cotto.lndmanagej.model.NodeFixtures.NODE_PEER; -import static de.cotto.lndmanagej.model.NodeWarningsFixtures.NODE_WARNINGS; import static de.cotto.lndmanagej.model.OnChainCostsFixtures.ON_CHAIN_COSTS; import static de.cotto.lndmanagej.model.OnlineReportFixtures.ONLINE_REPORT; import static de.cotto.lndmanagej.model.OnlineReportFixtures.ONLINE_REPORT_OFFLINE; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; import static de.cotto.lndmanagej.model.RebalanceReportFixtures.REBALANCE_REPORT; import static de.cotto.lndmanagej.model.WaitingCloseChannelFixtures.WAITING_CLOSE_CHANNEL_TO_NODE_3; +import static de.cotto.lndmanagej.model.warnings.NodeWarningsFixtures.NODE_WARNINGS; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; diff --git a/backend/src/test/java/de/cotto/lndmanagej/service/NodeWarningsServiceTest.java b/backend/src/test/java/de/cotto/lndmanagej/service/NodeWarningsServiceTest.java deleted file mode 100644 index 3d21c319..00000000 --- a/backend/src/test/java/de/cotto/lndmanagej/service/NodeWarningsServiceTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.cotto.lndmanagej.service; - -import de.cotto.lndmanagej.model.NodeOnlineChangesWarning; -import de.cotto.lndmanagej.model.NodeOnlinePercentageWarning; -import de.cotto.lndmanagej.model.NodeWarnings; -import org.junit.jupiter.api.BeforeEach; -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 static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class NodeWarningsServiceTest { - @InjectMocks - private NodeWarningsService nodeWarningsService; - - @Mock - private OnlinePeersService onlinePeersService; - - @BeforeEach - void setUp() { - when(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).thenReturn(80); - when(onlinePeersService.getChangesLastWeek(PUBKEY)).thenReturn(50); - } - - @Test - void getNodeWarnings_online_below_threshold() { - when(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).thenReturn(79); - assertThat(nodeWarningsService.getNodeWarnings(PUBKEY)) - .isEqualTo(new NodeWarnings(new NodeOnlinePercentageWarning(79))); - } - - @Test - void getNodeWarnings_online_changes_above_threshold() { - when(onlinePeersService.getChangesLastWeek(PUBKEY)).thenReturn(51); - assertThat(nodeWarningsService.getNodeWarnings(PUBKEY)) - .isEqualTo(new NodeWarnings(new NodeOnlineChangesWarning(51))); - } - - @Test - void getNodeWarnings_all_warnings() { - when(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).thenReturn(79); - when(onlinePeersService.getChangesLastWeek(PUBKEY)).thenReturn(51); - assertThat(nodeWarningsService.getNodeWarnings(PUBKEY)) - .isEqualTo(new NodeWarnings(new NodeOnlinePercentageWarning(79), new NodeOnlineChangesWarning(51))); - } - - @Test - void getNodeWarnings_ok() { - assertThat(nodeWarningsService.getNodeWarnings(PUBKEY)).isEqualTo(NodeWarnings.NONE); - } -} \ No newline at end of file diff --git a/backend/src/test/java/de/cotto/lndmanagej/service/warnings/NodeFlowWarningsProviderTest.java b/backend/src/test/java/de/cotto/lndmanagej/service/warnings/NodeFlowWarningsProviderTest.java new file mode 100644 index 00000000..7c66fffd --- /dev/null +++ b/backend/src/test/java/de/cotto/lndmanagej/service/warnings/NodeFlowWarningsProviderTest.java @@ -0,0 +1,96 @@ +package de.cotto.lndmanagej.service.warnings; + +import de.cotto.lndmanagej.model.FlowReport; +import de.cotto.lndmanagej.model.warnings.NodeNoFlowWarning; +import de.cotto.lndmanagej.service.ChannelService; +import de.cotto.lndmanagej.service.FlowService; +import de.cotto.lndmanagej.service.OwnNodeService; +import org.junit.jupiter.api.BeforeEach; +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.time.Duration; +import java.util.Optional; +import java.util.Set; + +import static de.cotto.lndmanagej.model.FlowReportFixtures.FLOW_REPORT; +import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL; +import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; +import static de.cotto.lndmanagej.transactions.model.TransactionFixtures.BLOCK_HEIGHT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class NodeFlowWarningsProviderTest { + private static final int EXPECTED_BLOCKS_PER_DAY = 144; + @InjectMocks + private NodeFlowWarningsProvider warningsProvider; + + @Mock + private FlowService flowService; + + @Mock + private OwnNodeService ownNodeService; + + @Mock + private ChannelService channelService; + + @BeforeEach + void setUp() { + lenient().when(flowService.getFlowReportForPeer(eq(PUBKEY), any())).thenReturn(FlowReport.EMPTY); + lenient().when(ownNodeService.getBlockHeight()).thenReturn(800_000); + } + + @Test + void getNodeWarnings_no_flow() { + when(channelService.getOpenChannelsWith(PUBKEY)).thenReturn(Set.of(LOCAL_OPEN_CHANNEL)); + when(channelService.getOpenHeight(LOCAL_OPEN_CHANNEL)).thenReturn(Optional.of(BLOCK_HEIGHT)); + assertThat(warningsProvider.getNodeWarnings(PUBKEY)).containsExactly(new NodeNoFlowWarning(60)); + } + + @Test + void getNodeWarnings_no_flow_very_recent_channel() { + mockOpenChannelWithAgeInBlocks(50); + assertThat(warningsProvider.getNodeWarnings(PUBKEY)).isEmpty(); + } + + @Test + void getNodeWarnings_no_flow_young_channel() { + mockOpenChannelWithAgeInBlocks(17 * EXPECTED_BLOCKS_PER_DAY); + assertThat(warningsProvider.getNodeWarnings(PUBKEY)).containsExactly(new NodeNoFlowWarning(17)); + } + + @Test + void getNodeWarnings_no_flow_no_open_channel() { + when(channelService.getOpenChannelsWith(PUBKEY)).thenReturn(Set.of()); + assertThat(warningsProvider.getNodeWarnings(PUBKEY)).isEmpty(); + } + + @Test + void getNodeWarnings_no_flow_then_some_flow() { + mockOpenChannelWithAgeInBlocks(100 * EXPECTED_BLOCKS_PER_DAY); + when(flowService.getFlowReportForPeer(PUBKEY, Duration.ofDays(14))).thenReturn(FlowReport.EMPTY); + when(flowService.getFlowReportForPeer(PUBKEY, Duration.ofDays(15))).thenReturn(FlowReport.EMPTY); + when(flowService.getFlowReportForPeer(PUBKEY, Duration.ofDays(16))).thenReturn(FLOW_REPORT); + assertThat(warningsProvider.getNodeWarnings(PUBKEY)).containsExactly(new NodeNoFlowWarning(15)); + } + + @Test + void getNodeWarnings_ok() { + mockOpenChannelWithAgeInBlocks(100 * EXPECTED_BLOCKS_PER_DAY); + when(flowService.getFlowReportForPeer(any(), any())).thenReturn(FLOW_REPORT); + assertThat(warningsProvider.getNodeWarnings(PUBKEY)).isEmpty(); + } + + private void mockOpenChannelWithAgeInBlocks(int channelAgeInBlocks) { + when(channelService.getOpenChannelsWith(PUBKEY)).thenReturn(Set.of(LOCAL_OPEN_CHANNEL)); + when(channelService.getOpenHeight(LOCAL_OPEN_CHANNEL)).thenReturn(Optional.of(BLOCK_HEIGHT)); + when(ownNodeService.getBlockHeight()).thenReturn(BLOCK_HEIGHT + channelAgeInBlocks); + } +} \ No newline at end of file diff --git a/backend/src/test/java/de/cotto/lndmanagej/service/warnings/NodeOnlineWarningsProviderTest.java b/backend/src/test/java/de/cotto/lndmanagej/service/warnings/NodeOnlineWarningsProviderTest.java new file mode 100644 index 00000000..d1f9d46f --- /dev/null +++ b/backend/src/test/java/de/cotto/lndmanagej/service/warnings/NodeOnlineWarningsProviderTest.java @@ -0,0 +1,49 @@ +package de.cotto.lndmanagej.service.warnings; + +import de.cotto.lndmanagej.model.warnings.NodeOnlineChangesWarning; +import de.cotto.lndmanagej.model.warnings.NodeOnlinePercentageWarning; +import de.cotto.lndmanagej.service.OnlinePeersService; +import org.junit.jupiter.api.BeforeEach; +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 static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class NodeOnlineWarningsProviderTest { + @InjectMocks + private NodeOnlineWarningsProvider warningsProvider; + + @Mock + private OnlinePeersService onlinePeersService; + + @BeforeEach + void setUp() { + when(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).thenReturn(80); + when(onlinePeersService.getChangesLastWeek(PUBKEY)).thenReturn(50); + } + + @Test + void getNodeWarnings_online_below_threshold() { + when(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).thenReturn(79); + assertThat(warningsProvider.getNodeWarnings(PUBKEY)) + .containsExactly(new NodeOnlinePercentageWarning(79)); + } + + @Test + void getNodeWarnings_online_changes_above_threshold() { + when(onlinePeersService.getChangesLastWeek(PUBKEY)).thenReturn(51); + assertThat(warningsProvider.getNodeWarnings(PUBKEY)) + .containsExactly(new NodeOnlineChangesWarning(51)); + } + + @Test + void getNodeWarnings_ok() { + assertThat(warningsProvider.getNodeWarnings(PUBKEY)).isEmpty(); + } +} \ No newline at end of file diff --git a/backend/src/test/java/de/cotto/lndmanagej/service/warnings/NodeWarningsServiceTest.java b/backend/src/test/java/de/cotto/lndmanagej/service/warnings/NodeWarningsServiceTest.java new file mode 100644 index 00000000..c159742c --- /dev/null +++ b/backend/src/test/java/de/cotto/lndmanagej/service/warnings/NodeWarningsServiceTest.java @@ -0,0 +1,55 @@ +package de.cotto.lndmanagej.service.warnings; + +import de.cotto.lndmanagej.model.NodeWarnings; +import de.cotto.lndmanagej.service.NodeWarningsProvider; +import de.cotto.lndmanagej.service.NodeWarningsService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Set; +import java.util.stream.Stream; + +import static de.cotto.lndmanagej.model.NodeWarningFixtures.NODE_NO_FLOW_WARNING; +import static de.cotto.lndmanagej.model.NodeWarningFixtures.NODE_ONLINE_CHANGES_WARNING; +import static de.cotto.lndmanagej.model.NodeWarningFixtures.NODE_ONLINE_PERCENTAGE_WARNING; +import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class NodeWarningsServiceTest { + private NodeWarningsService nodeWarningsService; + private NodeWarningsProvider provider1; + private NodeWarningsProvider provider2; + + @BeforeEach + void setUp() { + provider1 = mock(NodeWarningsProvider.class); + provider2 = mock(NodeWarningsProvider.class); + nodeWarningsService = new NodeWarningsService(Set.of(provider1, provider2)); + } + + @Test + void getNodeWarnings_no_warning() { + when(provider1.getNodeWarnings(PUBKEY)).thenReturn(Stream.of()); + when(provider2.getNodeWarnings(PUBKEY)).thenReturn(Stream.of()); + assertThat(nodeWarningsService.getNodeWarnings(PUBKEY)).isEqualTo(NodeWarnings.NONE); + } + + @Test + void getNodeWarnings() { + when(provider1.getNodeWarnings(PUBKEY)) + .thenReturn(Stream.of(NODE_ONLINE_PERCENTAGE_WARNING, NODE_ONLINE_CHANGES_WARNING)); + when(provider2.getNodeWarnings(PUBKEY)) + .thenReturn(Stream.of(NODE_NO_FLOW_WARNING)); + NodeWarnings expected = new NodeWarnings( + NODE_NO_FLOW_WARNING, + NODE_ONLINE_PERCENTAGE_WARNING, + NODE_ONLINE_CHANGES_WARNING + ); + assertThat(nodeWarningsService.getNodeWarnings(PUBKEY)).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/model/src/main/java/de/cotto/lndmanagej/model/NodeOnlineChangesWarning.java b/model/src/main/java/de/cotto/lndmanagej/model/NodeOnlineChangesWarning.java deleted file mode 100644 index 187f64af..00000000 --- a/model/src/main/java/de/cotto/lndmanagej/model/NodeOnlineChangesWarning.java +++ /dev/null @@ -1,4 +0,0 @@ -package de.cotto.lndmanagej.model; - -public record NodeOnlineChangesWarning(int changes) implements NodeWarning { -} diff --git a/model/src/main/java/de/cotto/lndmanagej/model/NodeOnlinePercentageWarning.java b/model/src/main/java/de/cotto/lndmanagej/model/NodeOnlinePercentageWarning.java deleted file mode 100644 index d1e7f051..00000000 --- a/model/src/main/java/de/cotto/lndmanagej/model/NodeOnlinePercentageWarning.java +++ /dev/null @@ -1,4 +0,0 @@ -package de.cotto.lndmanagej.model; - -public record NodeOnlinePercentageWarning(int onlinePercentage) implements NodeWarning { -} diff --git a/model/src/main/java/de/cotto/lndmanagej/model/NodeWarning.java b/model/src/main/java/de/cotto/lndmanagej/model/NodeWarning.java deleted file mode 100644 index 5457af97..00000000 --- a/model/src/main/java/de/cotto/lndmanagej/model/NodeWarning.java +++ /dev/null @@ -1,4 +0,0 @@ -package de.cotto.lndmanagej.model; - -public interface NodeWarning { -} diff --git a/model/src/main/java/de/cotto/lndmanagej/model/NodeWarnings.java b/model/src/main/java/de/cotto/lndmanagej/model/NodeWarnings.java index 55f80fbd..5dec43f2 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/NodeWarnings.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/NodeWarnings.java @@ -1,12 +1,19 @@ package de.cotto.lndmanagej.model; -import java.util.Arrays; -import java.util.List; +import de.cotto.lndmanagej.model.warnings.NodeWarning; -public record NodeWarnings(List warnings) { +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +public record NodeWarnings(Set warnings) { public static final NodeWarnings NONE = new NodeWarnings(); public NodeWarnings(NodeWarning... nodeWarnings) { - this(Arrays.stream(nodeWarnings).toList()); + this(Arrays.stream(nodeWarnings).collect(Collectors.toSet())); + } + + public Set descriptions() { + return warnings.stream().map(NodeWarning::description).collect(Collectors.toSet()); } } diff --git a/model/src/main/java/de/cotto/lndmanagej/model/warnings/NodeNoFlowWarning.java b/model/src/main/java/de/cotto/lndmanagej/model/warnings/NodeNoFlowWarning.java new file mode 100644 index 00000000..92042b4c --- /dev/null +++ b/model/src/main/java/de/cotto/lndmanagej/model/warnings/NodeNoFlowWarning.java @@ -0,0 +1,8 @@ +package de.cotto.lndmanagej.model.warnings; + +public record NodeNoFlowWarning(int days) implements NodeWarning { + @Override + public String description() { + return "No flow in the past " + days + " days"; + } +} diff --git a/model/src/main/java/de/cotto/lndmanagej/model/warnings/NodeOnlineChangesWarning.java b/model/src/main/java/de/cotto/lndmanagej/model/warnings/NodeOnlineChangesWarning.java new file mode 100644 index 00000000..cfce23be --- /dev/null +++ b/model/src/main/java/de/cotto/lndmanagej/model/warnings/NodeOnlineChangesWarning.java @@ -0,0 +1,8 @@ +package de.cotto.lndmanagej.model.warnings; + +public record NodeOnlineChangesWarning(int changes) implements NodeWarning { + @Override + public String description() { + return "Node changed between online and offline " + changes + " times"; + } +} diff --git a/model/src/main/java/de/cotto/lndmanagej/model/warnings/NodeOnlinePercentageWarning.java b/model/src/main/java/de/cotto/lndmanagej/model/warnings/NodeOnlinePercentageWarning.java new file mode 100644 index 00000000..fd10ed31 --- /dev/null +++ b/model/src/main/java/de/cotto/lndmanagej/model/warnings/NodeOnlinePercentageWarning.java @@ -0,0 +1,8 @@ +package de.cotto.lndmanagej.model.warnings; + +public record NodeOnlinePercentageWarning(int onlinePercentage) implements NodeWarning { + @Override + public String description() { + return "Node has been online " + onlinePercentage + "% in the last week"; + } +} diff --git a/model/src/main/java/de/cotto/lndmanagej/model/warnings/NodeWarning.java b/model/src/main/java/de/cotto/lndmanagej/model/warnings/NodeWarning.java new file mode 100644 index 00000000..cd07e177 --- /dev/null +++ b/model/src/main/java/de/cotto/lndmanagej/model/warnings/NodeWarning.java @@ -0,0 +1,5 @@ +package de.cotto.lndmanagej.model.warnings; + +public interface NodeWarning { + String description(); +} diff --git a/model/src/test/java/de/cotto/lndmanagej/model/NodeWarningsTest.java b/model/src/test/java/de/cotto/lndmanagej/model/NodeWarningsTest.java deleted file mode 100644 index d13abcb2..00000000 --- a/model/src/test/java/de/cotto/lndmanagej/model/NodeWarningsTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.cotto.lndmanagej.model; - -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static de.cotto.lndmanagej.model.NodeWarningFixtures.NODE_ONLINE_CHANGES_WARNING; -import static de.cotto.lndmanagej.model.NodeWarningFixtures.NODE_ONLINE_PERCENTAGE_WARNING; -import static de.cotto.lndmanagej.model.NodeWarningsFixtures.NODE_WARNINGS; -import static org.assertj.core.api.Assertions.assertThat; - -class NodeWarningsTest { - @Test - void warnings() { - assertThat(NODE_WARNINGS.warnings()) - .containsExactly(NODE_ONLINE_PERCENTAGE_WARNING, NODE_ONLINE_CHANGES_WARNING); - } - - @Test - void none() { - assertThat(NodeWarnings.NONE).isEqualTo(new NodeWarnings(List.of())); - } -} \ No newline at end of file diff --git a/model/src/test/java/de/cotto/lndmanagej/model/warnings/NodeNoFlowWarningTest.java b/model/src/test/java/de/cotto/lndmanagej/model/warnings/NodeNoFlowWarningTest.java new file mode 100644 index 00000000..f22ed601 --- /dev/null +++ b/model/src/test/java/de/cotto/lndmanagej/model/warnings/NodeNoFlowWarningTest.java @@ -0,0 +1,18 @@ +package de.cotto.lndmanagej.model.warnings; + +import org.junit.jupiter.api.Test; + +import static de.cotto.lndmanagej.model.NodeWarningFixtures.NODE_NO_FLOW_WARNING; +import static org.assertj.core.api.Assertions.assertThat; + +class NodeNoFlowWarningTest { + @Test + void days() { + assertThat(NODE_NO_FLOW_WARNING.days()).isEqualTo(16); + } + + @Test + void description() { + assertThat(NODE_NO_FLOW_WARNING.description()).isEqualTo("No flow in the past 16 days"); + } +} \ No newline at end of file diff --git a/model/src/test/java/de/cotto/lndmanagej/model/NodeOnlineChangesWarningTest.java b/model/src/test/java/de/cotto/lndmanagej/model/warnings/NodeOnlineChangesWarningTest.java similarity index 59% rename from model/src/test/java/de/cotto/lndmanagej/model/NodeOnlineChangesWarningTest.java rename to model/src/test/java/de/cotto/lndmanagej/model/warnings/NodeOnlineChangesWarningTest.java index fe2abf61..823333b4 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/NodeOnlineChangesWarningTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/warnings/NodeOnlineChangesWarningTest.java @@ -1,4 +1,4 @@ -package de.cotto.lndmanagej.model; +package de.cotto.lndmanagej.model.warnings; import org.junit.jupiter.api.Test; @@ -10,4 +10,10 @@ class NodeOnlineChangesWarningTest { void name() { assertThat(NODE_ONLINE_CHANGES_WARNING.changes()).isEqualTo(123); } + + @Test + void description() { + assertThat(NODE_ONLINE_CHANGES_WARNING.description()) + .isEqualTo("Node changed between online and offline 123 times"); + } } \ No newline at end of file diff --git a/model/src/test/java/de/cotto/lndmanagej/model/NodeOnlinePercentageWarningTest.java b/model/src/test/java/de/cotto/lndmanagej/model/warnings/NodeOnlinePercentageWarningTest.java similarity index 63% rename from model/src/test/java/de/cotto/lndmanagej/model/NodeOnlinePercentageWarningTest.java rename to model/src/test/java/de/cotto/lndmanagej/model/warnings/NodeOnlinePercentageWarningTest.java index 70f90f21..b46237c0 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/NodeOnlinePercentageWarningTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/warnings/NodeOnlinePercentageWarningTest.java @@ -1,4 +1,4 @@ -package de.cotto.lndmanagej.model; +package de.cotto.lndmanagej.model.warnings; import org.junit.jupiter.api.Test; @@ -10,4 +10,9 @@ class NodeOnlinePercentageWarningTest { void onlinePercentage() { assertThat(NODE_ONLINE_PERCENTAGE_WARNING.onlinePercentage()).isEqualTo(51); } + + @Test + void description() { + assertThat(NODE_ONLINE_PERCENTAGE_WARNING.description()).isEqualTo("Node has been online 51% in the last week"); + } } \ No newline at end of file diff --git a/model/src/test/java/de/cotto/lndmanagej/model/warnings/NodeWarningsTest.java b/model/src/test/java/de/cotto/lndmanagej/model/warnings/NodeWarningsTest.java new file mode 100644 index 00000000..39570fae --- /dev/null +++ b/model/src/test/java/de/cotto/lndmanagej/model/warnings/NodeWarningsTest.java @@ -0,0 +1,37 @@ +package de.cotto.lndmanagej.model.warnings; + +import de.cotto.lndmanagej.model.NodeWarnings; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static de.cotto.lndmanagej.model.NodeWarningFixtures.NODE_NO_FLOW_WARNING; +import static de.cotto.lndmanagej.model.NodeWarningFixtures.NODE_ONLINE_CHANGES_WARNING; +import static de.cotto.lndmanagej.model.NodeWarningFixtures.NODE_ONLINE_PERCENTAGE_WARNING; +import static de.cotto.lndmanagej.model.warnings.NodeWarningsFixtures.NODE_WARNINGS; +import static org.assertj.core.api.Assertions.assertThat; + +class NodeWarningsTest { + @Test + void warnings() { + assertThat(NODE_WARNINGS.warnings()).containsExactlyInAnyOrder( + NODE_ONLINE_PERCENTAGE_WARNING, + NODE_ONLINE_CHANGES_WARNING, + NODE_NO_FLOW_WARNING + ); + } + + @Test + void descriptions() { + assertThat(NODE_WARNINGS.descriptions()).containsExactlyInAnyOrder( + NODE_ONLINE_PERCENTAGE_WARNING.description(), + NODE_ONLINE_CHANGES_WARNING.description(), + NODE_NO_FLOW_WARNING.description() + ); + } + + @Test + void none() { + assertThat(NodeWarnings.NONE).isEqualTo(new NodeWarnings(Set.of())); + } +} \ No newline at end of file diff --git a/model/src/testFixtures/java/de/cotto/lndmanagej/model/NodeDetailsFixtures.java b/model/src/testFixtures/java/de/cotto/lndmanagej/model/NodeDetailsFixtures.java index 71eec968..fb49fa73 100644 --- a/model/src/testFixtures/java/de/cotto/lndmanagej/model/NodeDetailsFixtures.java +++ b/model/src/testFixtures/java/de/cotto/lndmanagej/model/NodeDetailsFixtures.java @@ -10,12 +10,12 @@ import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_4; import static de.cotto.lndmanagej.model.FeeReportFixtures.FEE_REPORT; import static de.cotto.lndmanagej.model.FlowReportFixtures.FLOW_REPORT; import static de.cotto.lndmanagej.model.NodeFixtures.ALIAS; -import static de.cotto.lndmanagej.model.NodeWarningsFixtures.NODE_WARNINGS; import static de.cotto.lndmanagej.model.OnChainCostsFixtures.ON_CHAIN_COSTS; import static de.cotto.lndmanagej.model.OnlineReportFixtures.ONLINE_REPORT; import static de.cotto.lndmanagej.model.OnlineReportFixtures.ONLINE_REPORT_OFFLINE; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; import static de.cotto.lndmanagej.model.RebalanceReportFixtures.REBALANCE_REPORT; +import static de.cotto.lndmanagej.model.warnings.NodeWarningsFixtures.NODE_WARNINGS; public class NodeDetailsFixtures { public static final NodeDetails NODE_DETAILS = new NodeDetails( diff --git a/model/src/testFixtures/java/de/cotto/lndmanagej/model/NodeWarningFixtures.java b/model/src/testFixtures/java/de/cotto/lndmanagej/model/NodeWarningFixtures.java index 69b14c59..dc98b689 100644 --- a/model/src/testFixtures/java/de/cotto/lndmanagej/model/NodeWarningFixtures.java +++ b/model/src/testFixtures/java/de/cotto/lndmanagej/model/NodeWarningFixtures.java @@ -1,8 +1,14 @@ package de.cotto.lndmanagej.model; +import de.cotto.lndmanagej.model.warnings.NodeNoFlowWarning; +import de.cotto.lndmanagej.model.warnings.NodeOnlineChangesWarning; +import de.cotto.lndmanagej.model.warnings.NodeOnlinePercentageWarning; + public class NodeWarningFixtures { public static final NodeOnlinePercentageWarning NODE_ONLINE_PERCENTAGE_WARNING = new NodeOnlinePercentageWarning(51); public static final NodeOnlineChangesWarning NODE_ONLINE_CHANGES_WARNING = new NodeOnlineChangesWarning(123); + public static final NodeNoFlowWarning NODE_NO_FLOW_WARNING = + new NodeNoFlowWarning(16); } diff --git a/model/src/testFixtures/java/de/cotto/lndmanagej/model/NodeWarningsFixtures.java b/model/src/testFixtures/java/de/cotto/lndmanagej/model/warnings/NodeWarningsFixtures.java similarity index 58% rename from model/src/testFixtures/java/de/cotto/lndmanagej/model/NodeWarningsFixtures.java rename to model/src/testFixtures/java/de/cotto/lndmanagej/model/warnings/NodeWarningsFixtures.java index 05e4cdca..ff0968b1 100644 --- a/model/src/testFixtures/java/de/cotto/lndmanagej/model/NodeWarningsFixtures.java +++ b/model/src/testFixtures/java/de/cotto/lndmanagej/model/warnings/NodeWarningsFixtures.java @@ -1,11 +1,15 @@ -package de.cotto.lndmanagej.model; +package de.cotto.lndmanagej.model.warnings; +import de.cotto.lndmanagej.model.NodeWarnings; + +import static de.cotto.lndmanagej.model.NodeWarningFixtures.NODE_NO_FLOW_WARNING; import static de.cotto.lndmanagej.model.NodeWarningFixtures.NODE_ONLINE_CHANGES_WARNING; import static de.cotto.lndmanagej.model.NodeWarningFixtures.NODE_ONLINE_PERCENTAGE_WARNING; public class NodeWarningsFixtures { public static final NodeWarnings NODE_WARNINGS = new NodeWarnings( NODE_ONLINE_PERCENTAGE_WARNING, - NODE_ONLINE_CHANGES_WARNING + NODE_ONLINE_CHANGES_WARNING, + NODE_NO_FLOW_WARNING ); } diff --git a/settings.gradle b/settings.gradle index d24af1ce..5ddff46b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,14 +1,11 @@ rootProject.name = 'lnd-manageJ' -include 'application' +include 'model' include 'backend' include 'balances' include 'caching' include 'forwarding-history' -include 'grpc-adapter' -include 'grpc-client' include 'hardcoded' include 'invoices' -include 'model' include 'onlinepeers' include 'payments' include 'privatechannels' @@ -16,3 +13,6 @@ include 'selfpayments' include 'statistics' include 'transactions' include 'web' +include 'grpc-adapter' +include 'grpc-client' +include 'application' diff --git a/web/src/integrationTest/java/de/cotto/lndmanagej/controller/NodeControllerIT.java b/web/src/integrationTest/java/de/cotto/lndmanagej/controller/NodeControllerIT.java index 9369332e..7ef14232 100644 --- a/web/src/integrationTest/java/de/cotto/lndmanagej/controller/NodeControllerIT.java +++ b/web/src/integrationTest/java/de/cotto/lndmanagej/controller/NodeControllerIT.java @@ -109,8 +109,11 @@ class NodeControllerIT { .andExpect(jsonPath("$.balance.remoteAvailable", is("203"))) .andExpect(jsonPath("$.feeReport.earned", is("1234"))) .andExpect(jsonPath("$.feeReport.sourced", is("567"))) - .andExpect(jsonPath("$.nodeWarnings[0].onlinePercentage", is(51))) - .andExpect(jsonPath("$.nodeWarnings[1].changes", is(123))) + .andExpect(jsonPath("$.nodeWarnings", is(List.of( + "Node has been online 51% in the last week", + "Node changed between online and offline 123 times", + "No flow in the past 16 days" + )))) .andExpect(jsonPath("$.onChainCosts.openCosts", is("1000"))) .andExpect(jsonPath("$.onChainCosts.closeCosts", is("2000"))) .andExpect(jsonPath("$.onChainCosts.sweepCosts", is("3000"))) diff --git a/web/src/integrationTest/java/de/cotto/lndmanagej/controller/WarningsControllerIT.java b/web/src/integrationTest/java/de/cotto/lndmanagej/controller/WarningsControllerIT.java index 20206f20..916e2c46 100644 --- a/web/src/integrationTest/java/de/cotto/lndmanagej/controller/WarningsControllerIT.java +++ b/web/src/integrationTest/java/de/cotto/lndmanagej/controller/WarningsControllerIT.java @@ -9,10 +9,10 @@ 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 static de.cotto.lndmanagej.model.NodeWarningsFixtures.NODE_WARNINGS; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; +import static de.cotto.lndmanagej.model.warnings.NodeWarningsFixtures.NODE_WARNINGS; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasSize; -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; @@ -35,7 +35,11 @@ class WarningsControllerIT { void getWarningsForNode() throws Exception { when(nodeWarningsService.getNodeWarnings(PUBKEY)).thenReturn(NODE_WARNINGS); mockMvc.perform(get(NODE_PREFIX + "/warnings")) - .andExpect(jsonPath("$.nodeWarnings[0].onlinePercentage", is(51))); + .andExpect(jsonPath("$.nodeWarnings", containsInAnyOrder( + "No flow in the past 16 days", + "Node has been online 51% in the last week", + "Node changed between online and offline 123 times" + ))); } @Test diff --git a/web/src/main/java/de/cotto/lndmanagej/controller/dto/NodeDetailsDto.java b/web/src/main/java/de/cotto/lndmanagej/controller/dto/NodeDetailsDto.java index fc2d215a..ec0274c0 100644 --- a/web/src/main/java/de/cotto/lndmanagej/controller/dto/NodeDetailsDto.java +++ b/web/src/main/java/de/cotto/lndmanagej/controller/dto/NodeDetailsDto.java @@ -4,10 +4,10 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import de.cotto.lndmanagej.model.ChannelId; import de.cotto.lndmanagej.model.NodeDetails; -import de.cotto.lndmanagej.model.NodeWarning; import de.cotto.lndmanagej.model.Pubkey; import java.util.List; +import java.util.Set; public record NodeDetailsDto( @JsonSerialize(using = ToStringSerializer.class) Pubkey node, @@ -22,7 +22,7 @@ public record NodeDetailsDto( FeeReportDto feeReport, FlowReportDto flowReport, RebalanceReportDto rebalanceReport, - List nodeWarnings + Set nodeWarnings ) { public static NodeDetailsDto createFromModel(NodeDetails nodeDetails) { return new NodeDetailsDto( @@ -38,7 +38,7 @@ public record NodeDetailsDto( FeeReportDto.createFromModel(nodeDetails.feeReport()), FlowReportDto.createFromModel(nodeDetails.flowReport()), RebalanceReportDto.createFromModel(nodeDetails.rebalanceReport()), - nodeDetails.nodeWarnings().warnings() + nodeDetails.nodeWarnings().descriptions() ); } } diff --git a/web/src/main/java/de/cotto/lndmanagej/controller/dto/NodeWarningsDto.java b/web/src/main/java/de/cotto/lndmanagej/controller/dto/NodeWarningsDto.java index b0bee1a4..7a2cb8e8 100644 --- a/web/src/main/java/de/cotto/lndmanagej/controller/dto/NodeWarningsDto.java +++ b/web/src/main/java/de/cotto/lndmanagej/controller/dto/NodeWarningsDto.java @@ -1,12 +1,11 @@ package de.cotto.lndmanagej.controller.dto; -import de.cotto.lndmanagej.model.NodeWarning; import de.cotto.lndmanagej.model.NodeWarnings; -import java.util.List; +import java.util.Set; -public record NodeWarningsDto(List nodeWarnings) { +public record NodeWarningsDto(Set nodeWarnings) { public static NodeWarningsDto createFromModel(NodeWarnings nodeWarnings) { - return new NodeWarningsDto(nodeWarnings.warnings()); + return new NodeWarningsDto(nodeWarnings.descriptions()); } } diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/WarningsControllerTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/WarningsControllerTest.java index eee1041b..3ba86b3e 100644 --- a/web/src/test/java/de/cotto/lndmanagej/controller/WarningsControllerTest.java +++ b/web/src/test/java/de/cotto/lndmanagej/controller/WarningsControllerTest.java @@ -8,8 +8,8 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import static de.cotto.lndmanagej.model.NodeWarningsFixtures.NODE_WARNINGS; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; +import static de.cotto.lndmanagej.model.warnings.NodeWarningsFixtures.NODE_WARNINGS; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/dto/NodeDetailsDtoTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/dto/NodeDetailsDtoTest.java index 37af7276..d8996cf8 100644 --- a/web/src/test/java/de/cotto/lndmanagej/controller/dto/NodeDetailsDtoTest.java +++ b/web/src/test/java/de/cotto/lndmanagej/controller/dto/NodeDetailsDtoTest.java @@ -13,11 +13,11 @@ import static de.cotto.lndmanagej.model.FeeReportFixtures.FEE_REPORT; import static de.cotto.lndmanagej.model.FlowReportFixtures.FLOW_REPORT; import static de.cotto.lndmanagej.model.NodeDetailsFixtures.NODE_DETAILS; import static de.cotto.lndmanagej.model.NodeFixtures.ALIAS; -import static de.cotto.lndmanagej.model.NodeWarningsFixtures.NODE_WARNINGS; import static de.cotto.lndmanagej.model.OnChainCostsFixtures.ON_CHAIN_COSTS; import static de.cotto.lndmanagej.model.OnlineReportFixtures.ONLINE_REPORT; import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY; import static de.cotto.lndmanagej.model.RebalanceReportFixtures.REBALANCE_REPORT; +import static de.cotto.lndmanagej.model.warnings.NodeWarningsFixtures.NODE_WARNINGS; import static org.assertj.core.api.Assertions.assertThat; class NodeDetailsDtoTest { @@ -36,7 +36,7 @@ class NodeDetailsDtoTest { FeeReportDto.createFromModel(FEE_REPORT), FlowReportDto.createFromModel(FLOW_REPORT), RebalanceReportDto.createFromModel(REBALANCE_REPORT), - NODE_WARNINGS.warnings() + NODE_WARNINGS.descriptions() ); assertThat(NodeDetailsDto.createFromModel(NODE_DETAILS)).isEqualTo(expected); } diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/dto/NodeWarningsDtoTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/dto/NodeWarningsDtoTest.java index 6447adcd..ccd759e2 100644 --- a/web/src/test/java/de/cotto/lndmanagej/controller/dto/NodeWarningsDtoTest.java +++ b/web/src/test/java/de/cotto/lndmanagej/controller/dto/NodeWarningsDtoTest.java @@ -2,13 +2,18 @@ package de.cotto.lndmanagej.controller.dto; import org.junit.jupiter.api.Test; -import static de.cotto.lndmanagej.model.NodeWarningsFixtures.NODE_WARNINGS; +import java.util.Set; + +import static de.cotto.lndmanagej.model.warnings.NodeWarningsFixtures.NODE_WARNINGS; import static org.assertj.core.api.Assertions.assertThat; class NodeWarningsDtoTest { @Test void createFromModel() { - assertThat(NodeWarningsDto.createFromModel(NODE_WARNINGS)) - .isEqualTo(new NodeWarningsDto(NODE_WARNINGS.warnings())); + assertThat(NodeWarningsDto.createFromModel(NODE_WARNINGS)).isEqualTo(new NodeWarningsDto(Set.of( + "No flow in the past 16 days", + "Node has been online 51% in the last week", + "Node changed between online and offline 123 times" + ))); } } \ No newline at end of file