add online report

This commit is contained in:
Carsten Otto
2021-12-24 13:48:39 +01:00
parent ddc156e566
commit c3eead54bc
63 changed files with 454 additions and 102 deletions

View File

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

View File

@@ -7,6 +7,7 @@ import de.cotto.lndmanagej.model.FeeReport;
import de.cotto.lndmanagej.model.Node;
import de.cotto.lndmanagej.model.NodeDetails;
import de.cotto.lndmanagej.model.OnChainCosts;
import de.cotto.lndmanagej.model.OnlineReport;
import de.cotto.lndmanagej.model.Pubkey;
import de.cotto.lndmanagej.model.RebalanceReport;
import org.springframework.stereotype.Component;
@@ -24,6 +25,7 @@ public class NodeDetailsService {
private final BalanceService balanceService;
private final FeeService feeService;
private final RebalanceService rebalanceService;
private final OnlinePeersService onlinePeersService;
public NodeDetailsService(
ChannelService channelService,
@@ -31,7 +33,8 @@ public class NodeDetailsService {
OnChainCostService onChainCostService,
BalanceService balanceService,
FeeService feeService,
RebalanceService rebalanceService
RebalanceService rebalanceService,
OnlinePeersService onlinePeersService
) {
this.channelService = channelService;
this.nodeService = nodeService;
@@ -39,10 +42,12 @@ public class NodeDetailsService {
this.balanceService = balanceService;
this.feeService = feeService;
this.rebalanceService = rebalanceService;
this.onlinePeersService = onlinePeersService;
}
public NodeDetails getDetails(Pubkey pubkey) {
CompletableFuture<Node> node = getNode(pubkey);
CompletableFuture<OnlineReport> onlineReport = node.thenApply(onlinePeersService::getOnlineReport);
CompletableFuture<OnChainCosts> onChainCosts = getOnChainCosts(pubkey);
CompletableFuture<BalanceInformation> balanceInformation = getBalanceInformation(pubkey);
CompletableFuture<FeeReport> feeReport = getFeeReport(pubkey);
@@ -65,7 +70,7 @@ public class NodeDetailsService {
forceClosingChannelIds,
onChainCosts.get(),
balanceInformation.get(),
node.get().online(),
onlineReport.get(),
feeReport.get(),
rebalanceReport.get()
);

View File

@@ -0,0 +1,33 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.model.Node;
import de.cotto.lndmanagej.model.OnlineReport;
import de.cotto.lndmanagej.model.OnlineStatus;
import de.cotto.lndmanagej.onlinepeers.OnlinePeersDao;
import org.springframework.stereotype.Component;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
@Component
public class OnlinePeersService {
private final OnlinePeersDao dao;
public OnlinePeersService(OnlinePeersDao dao) {
this.dao = dao;
}
public OnlineReport getOnlineReport(Node node) {
boolean online = node.online();
OnlineStatus mostRecentOnlineStatus = dao.getMostRecentOnlineStatus(node.pubkey()).orElse(null);
if (mostRecentOnlineStatus != null && mostRecentOnlineStatus.online() == online) {
return OnlineReport.createFromStatus(mostRecentOnlineStatus);
}
return new OnlineReport(online, now());
}
private ZonedDateTime now() {
return ZonedDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS);
}
}

View File

@@ -2,7 +2,6 @@ package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.FeeReport;
import de.cotto.lndmanagej.model.NodeDetailsFixtures;
import de.cotto.lndmanagej.model.OnChainCosts;
import de.cotto.lndmanagej.model.RebalanceReport;
import org.junit.jupiter.api.Test;
@@ -18,9 +17,13 @@ import static de.cotto.lndmanagej.model.CoopClosedChannelFixtures.CLOSED_CHANNEL
import static de.cotto.lndmanagej.model.FeeReportFixtures.FEE_REPORT;
import static de.cotto.lndmanagej.model.ForceClosingChannelFixtures.FORCE_CLOSING_CHANNEL_4;
import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL;
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.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;
@@ -50,6 +53,9 @@ class NodeDetailsServiceTest {
@Mock
private RebalanceService rebalanceService;
@Mock
private OnlinePeersService onlinePeersService;
@Test
void getDetails_no_channel() {
when(nodeService.getNode(PUBKEY)).thenReturn(NODE);
@@ -57,7 +63,8 @@ class NodeDetailsServiceTest {
when(onChainCostService.getOnChainCostsForPeer(PUBKEY)).thenReturn(OnChainCosts.NONE);
when(feeService.getFeeReportForPeer(PUBKEY)).thenReturn(FeeReport.EMPTY);
when(rebalanceService.getReportForPeer(PUBKEY)).thenReturn(RebalanceReport.EMPTY);
assertThat(nodeDetailsService.getDetails(PUBKEY)).isEqualTo(NodeDetailsFixtures.NODE_DETAILS_EMPTY);
when(onlinePeersService.getOnlineReport(NODE_PEER)).thenReturn(ONLINE_REPORT_OFFLINE);
assertThat(nodeDetailsService.getDetails(PUBKEY)).isEqualTo(NODE_DETAILS_EMPTY);
}
@Test
@@ -71,6 +78,7 @@ class NodeDetailsServiceTest {
when(channelService.getForceClosingChannelsWith(PUBKEY)).thenReturn(Set.of(FORCE_CLOSING_CHANNEL_4));
when(feeService.getFeeReportForPeer(PUBKEY)).thenReturn(FEE_REPORT);
when(rebalanceService.getReportForPeer(PUBKEY)).thenReturn(REBALANCE_REPORT);
assertThat(nodeDetailsService.getDetails(PUBKEY)).isEqualTo(NodeDetailsFixtures.NODE_DETAILS);
when(onlinePeersService.getOnlineReport(NODE_PEER)).thenReturn(ONLINE_REPORT);
assertThat(nodeDetailsService.getDetails(PUBKEY)).isEqualTo(NODE_DETAILS);
}
}

View File

@@ -0,0 +1,65 @@
package de.cotto.lndmanagej.service;
import de.cotto.lndmanagej.model.OnlineReport;
import de.cotto.lndmanagej.onlinepeers.OnlinePeersDao;
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.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Optional;
import static de.cotto.lndmanagej.model.NodeFixtures.NODE;
import static de.cotto.lndmanagej.model.NodeFixtures.NODE_PEER;
import static de.cotto.lndmanagej.model.OnlineReportFixtures.ONLINE_REPORT;
import static de.cotto.lndmanagej.model.OnlineStatusFixtures.ONLINE_STATUS;
import static de.cotto.lndmanagej.model.OnlineStatusFixtures.ONLINE_STATUS_OFFLINE;
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 OnlinePeersServiceTest {
@InjectMocks
private OnlinePeersService onlinePeersService;
@Mock
private OnlinePeersDao dao;
@Test
void with_time_if_given_status_matches_last_known_status() {
when(dao.getMostRecentOnlineStatus(PUBKEY)).thenReturn(Optional.of(ONLINE_STATUS));
assertThat(onlinePeersService.getOnlineReport(NODE_PEER)).isEqualTo(ONLINE_REPORT);
}
@Test
void with_current_time_if_given_status_does_not_match_last_known_status() {
when(dao.getMostRecentOnlineStatus(PUBKEY)).thenReturn(Optional.of(ONLINE_STATUS_OFFLINE));
OnlineReport report = onlinePeersService.getOnlineReport(NODE_PEER);
assertThat(report.online()).isTrue();
assertVeryRecentSince(report);
}
@Test
void with_current_time_if_no_persisted_status_known() {
OnlineReport report = onlinePeersService.getOnlineReport(NODE);
assertThat(report.online()).isFalse();
assertVeryRecentSince(report);
}
@Test
void with_current_state_if_no_persisted_status_known() {
OnlineReport report = onlinePeersService.getOnlineReport(NODE_PEER);
assertThat(report.online()).isTrue();
assertVeryRecentSince(report);
}
private void assertVeryRecentSince(OnlineReport report) {
assertThat(report.since())
.isAfter(ZonedDateTime.now(ZoneOffset.UTC).minusSeconds(1))
.asString().hasSize(20);
}
}

10
balances/build.gradle Normal file
View File

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

View File

@@ -1,13 +1,13 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.balances.persistence;
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.balances.BalancesFixtures.BALANCES;
import static de.cotto.lndmanagej.balances.BalancesFixtures.BALANCES_OLD;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2;
import static de.cotto.lndmanagej.statistics.StatisticsFixtures.BALANCES;
import static de.cotto.lndmanagej.statistics.StatisticsFixtures.BALANCES_OLD;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics;
package de.cotto.lndmanagej.balances;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.ChannelId;

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics;
package de.cotto.lndmanagej.balances;
import de.cotto.lndmanagej.model.ChannelId;

View File

@@ -1,8 +1,8 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.balances.persistence;
import de.cotto.lndmanagej.balances.Balances;
import de.cotto.lndmanagej.balances.BalancesDao;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.statistics.Balances;
import de.cotto.lndmanagej.statistics.BalancesDao;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.balances.persistence;
import javax.annotation.Nullable;
import java.io.Serial;

View File

@@ -1,10 +1,10 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.balances.persistence;
import com.google.common.annotations.VisibleForTesting;
import de.cotto.lndmanagej.balances.Balances;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.Coins;
import de.cotto.lndmanagej.statistics.Balances;
import javax.persistence.Entity;
import javax.persistence.Id;

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.balances.persistence;
import org.springframework.data.jpa.repository.JpaRepository;

View File

@@ -1,6 +1,5 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.balances.persistence;
import de.cotto.lndmanagej.statistics.StatisticsFixtures;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -10,9 +9,10 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.time.ZoneOffset;
import java.util.Optional;
import static de.cotto.lndmanagej.balances.BalancesFixtures.BALANCES;
import static de.cotto.lndmanagej.balances.BalancesFixtures.TIMESTAMP;
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.BALANCE_INFORMATION;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.statistics.StatisticsFixtures.BALANCES;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.verify;
@@ -31,7 +31,7 @@ class BalancesDaoImplTest {
void saveStatistics() {
dao.saveBalances(BALANCES);
verify(balancesRepository).save(argThat(jpaDto ->
jpaDto.getTimestamp() == StatisticsFixtures.TIMESTAMP.toEpochSecond(ZoneOffset.UTC)
jpaDto.getTimestamp() == TIMESTAMP.toEpochSecond(ZoneOffset.UTC)
));
verify(balancesRepository).save(argThat(jpaDto ->
jpaDto.getChannelId() == CHANNEL_ID.getShortChannelId()));

View File

@@ -1,15 +1,14 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.balances.persistence;
import nl.jqno.equalsverifier.EqualsVerifier;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class BalancesIdTest {
@Test
void test_default_constructor() {
// required for JPA
assertThat(new BalancesId()).isNotNull();
Assertions.assertThat(new BalancesId()).isNotNull();
}
@Test

View File

@@ -1,13 +1,13 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.balances.persistence;
import org.junit.jupiter.api.Test;
import java.time.ZoneOffset;
import static de.cotto.lndmanagej.balances.BalancesFixtures.BALANCES;
import static de.cotto.lndmanagej.balances.BalancesFixtures.TIMESTAMP;
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.BALANCE_INFORMATION;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
import static de.cotto.lndmanagej.statistics.StatisticsFixtures.BALANCES;
import static de.cotto.lndmanagej.statistics.StatisticsFixtures.TIMESTAMP;
import static org.assertj.core.api.Assertions.assertThat;
class BalancesJpaDtoTest {

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics;
package de.cotto.lndmanagej.balances;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
@@ -8,7 +8,7 @@ import static de.cotto.lndmanagej.model.BalanceInformationFixtures.BALANCE_INFOR
import static de.cotto.lndmanagej.model.BalanceInformationFixtures.BALANCE_INFORMATION_2;
import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID;
public class StatisticsFixtures {
public class BalancesFixtures {
public static final LocalDateTime TIMESTAMP = LocalDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS);
public static final Balances BALANCES =
new Balances(TIMESTAMP, CHANNEL_ID, BALANCE_INFORMATION);

View File

@@ -11,7 +11,7 @@ public record NodeDetails(
List<ChannelId> pendingForceClosingChannels,
OnChainCosts onChainCosts,
BalanceInformation balanceInformation,
boolean online,
OnlineReport onlineReport,
FeeReport feeReport,
RebalanceReport rebalanceReport
) {

View File

@@ -0,0 +1,9 @@
package de.cotto.lndmanagej.model;
import java.time.ZonedDateTime;
public record OnlineReport(boolean online, ZonedDateTime since) {
public static OnlineReport createFromStatus(OnlineStatus onlineStatus) {
return new OnlineReport(onlineStatus.online(), onlineStatus.since());
}
}

View File

@@ -0,0 +1,6 @@
package de.cotto.lndmanagej.model;
import java.time.ZonedDateTime;
public record OnlineStatus(boolean online, ZonedDateTime since) {
}

View File

@@ -11,6 +11,7 @@ import static de.cotto.lndmanagej.model.FeeReportFixtures.FEE_REPORT;
import static de.cotto.lndmanagej.model.NodeDetailsFixtures.NODE_DETAILS;
import static de.cotto.lndmanagej.model.NodeFixtures.ALIAS;
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 org.assertj.core.api.Assertions.assertThat;
@@ -57,11 +58,12 @@ class NodeDetailsTest {
}
@Test
void online() {
assertThat(NODE_DETAILS.online()).isTrue();
void onlineReport() {
assertThat(NODE_DETAILS.onlineReport()).isEqualTo(ONLINE_REPORT);
}
@Test
void feeReport() {
assertThat(NODE_DETAILS.feeReport()).isEqualTo(FEE_REPORT);
}

View File

@@ -0,0 +1,28 @@
package de.cotto.lndmanagej.model;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import static de.cotto.lndmanagej.model.OnlineReportFixtures.ONLINE_REPORT;
import static de.cotto.lndmanagej.model.OnlineReportFixtures.TIMESTAMP;
import static de.cotto.lndmanagej.model.OnlineStatusFixtures.ONLINE_STATUS;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MockitoExtension.class)
class OnlineReportTest {
@Test
void online() {
assertThat(ONLINE_REPORT.online()).isTrue();
}
@Test
void since() {
assertThat(ONLINE_REPORT.since()).isEqualTo(TIMESTAMP);
}
@Test
void createFromOnlineStatus() {
assertThat(OnlineReport.createFromStatus(ONLINE_STATUS)).isEqualTo(ONLINE_REPORT);
}
}

View File

@@ -0,0 +1,25 @@
package de.cotto.lndmanagej.model;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import static de.cotto.lndmanagej.model.OnlineStatusFixtures.ONLINE_STATUS;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MockitoExtension.class)
class OnlineStatusTest {
@Test
void online() {
assertThat(ONLINE_STATUS.online()).isTrue();
}
@Test
void since() {
assertThat(ONLINE_STATUS.since())
.isEqualTo(ZonedDateTime.of(2021, 12, 23, 1, 2, 3, 0, ZoneOffset.UTC));
}
}

View File

@@ -10,6 +10,8 @@ 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.NodeFixtures.ALIAS;
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;
@@ -23,7 +25,7 @@ public class NodeDetailsFixtures {
List.of(CHANNEL_ID_4),
ON_CHAIN_COSTS,
BALANCE_INFORMATION_2,
true,
ONLINE_REPORT,
FEE_REPORT,
REBALANCE_REPORT
);
@@ -36,7 +38,7 @@ public class NodeDetailsFixtures {
List.of(),
OnChainCosts.NONE,
BalanceInformation.EMPTY,
false,
ONLINE_REPORT_OFFLINE,
FeeReport.EMPTY,
RebalanceReport.EMPTY
);

View File

@@ -0,0 +1,12 @@
package de.cotto.lndmanagej.model;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import static java.time.ZoneOffset.UTC;
public class OnlineReportFixtures {
public static final ZonedDateTime TIMESTAMP = LocalDateTime.of(2021, 12, 23, 1, 2, 3).atZone(UTC);
public static final OnlineReport ONLINE_REPORT = new OnlineReport(true, TIMESTAMP);
public static final OnlineReport ONLINE_REPORT_OFFLINE = new OnlineReport(false, TIMESTAMP);
}

View File

@@ -0,0 +1,20 @@
package de.cotto.lndmanagej.model;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import static java.time.ZoneOffset.UTC;
public class OnlineStatusFixtures {
private static final ZonedDateTime TIMESTAMP = LocalDateTime.of(2021, 12, 23, 1, 2, 3).atZone(UTC);
public static final OnlineStatus ONLINE_STATUS = new OnlineStatus(
true,
TIMESTAMP
);
public static final OnlineStatus ONLINE_STATUS_OFFLINE = new OnlineStatus(
false,
TIMESTAMP
);
}

10
onlinepeers/build.gradle Normal file
View File

@@ -0,0 +1,10 @@
plugins {
id 'lnd-manageJ.java-library-conventions'
}
dependencies {
implementation project(':model')
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
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

@@ -1,11 +1,11 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.onlinepeers.persistence;
import org.junit.jupiter.api.Test;
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.time.ZonedDateTime;
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY;
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2;
@@ -13,7 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
class OnlinePeersRepositoryIT {
private static final LocalDateTime TIMESTAMP = LocalDateTime.now(ZoneOffset.UTC);
private static final ZonedDateTime TIMESTAMP = ZonedDateTime.now(ZoneOffset.UTC);
@Autowired
private OnlinePeersRepository repository;

View File

@@ -0,0 +1,13 @@
package de.cotto.lndmanagej.onlinepeers;
import de.cotto.lndmanagej.model.OnlineStatus;
import de.cotto.lndmanagej.model.Pubkey;
import java.time.ZonedDateTime;
import java.util.Optional;
public interface OnlinePeersDao {
void saveOnlineStatus(Pubkey pubkey, boolean online, ZonedDateTime timestamp);
Optional<OnlineStatus> getMostRecentOnlineStatus(Pubkey pubkey);
}

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.onlinepeers.persistence;
import javax.annotation.Nullable;
import java.io.Serial;

View File

@@ -1,5 +1,6 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.onlinepeers.persistence;
import de.cotto.lndmanagej.model.OnlineStatus;
import de.cotto.lndmanagej.model.Pubkey;
import javax.annotation.CheckForNull;
@@ -9,7 +10,9 @@ import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import static java.time.ZoneOffset.UTC;
@Entity
@IdClass(OnlinePeerId.class)
@@ -30,10 +33,15 @@ public class OnlinePeerJpaDto {
// for JPA
}
public OnlinePeerJpaDto(Pubkey pubkey, boolean online, LocalDateTime timestamp) {
public OnlinePeerJpaDto(Pubkey pubkey, boolean online, ZonedDateTime timestamp) {
this.pubkey = pubkey.toString();
this.online = online;
this.timestamp = timestamp.toEpochSecond(ZoneOffset.UTC);
this.timestamp = timestamp.toEpochSecond();
}
public OnlineStatus toModel() {
ZonedDateTime zonedDateTime = LocalDateTime.ofEpochSecond(timestamp, 0, UTC).atZone(UTC);
return new OnlineStatus(online, zonedDateTime);
}
public boolean isOnline() {

View File

@@ -1,11 +1,12 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.onlinepeers.persistence;
import de.cotto.lndmanagej.model.OnlineStatus;
import de.cotto.lndmanagej.model.Pubkey;
import de.cotto.lndmanagej.statistics.OnlinePeersDao;
import de.cotto.lndmanagej.onlinepeers.OnlinePeersDao;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Optional;
@Component
@@ -18,12 +19,12 @@ class OnlinePeersDaoImpl implements OnlinePeersDao {
}
@Override
public void saveOnlineStatus(Pubkey pubkey, boolean online, LocalDateTime timestamp) {
public void saveOnlineStatus(Pubkey pubkey, boolean online, ZonedDateTime timestamp) {
repository.save(new OnlinePeerJpaDto(pubkey, online, timestamp));
}
@Override
public Optional<Boolean> getMostRecentOnlineStatus(Pubkey pubkey) {
return repository.findTopByPubkeyOrderByTimestampDesc(pubkey.toString()).map(OnlinePeerJpaDto::isOnline);
public Optional<OnlineStatus> getMostRecentOnlineStatus(Pubkey pubkey) {
return repository.findTopByPubkeyOrderByTimestampDesc(pubkey.toString()).map(OnlinePeerJpaDto::toModel);
}
}

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.onlinepeers.persistence;
import org.springframework.data.jpa.repository.JpaRepository;

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.onlinepeers.persistence;
import nl.jqno.equalsverifier.EqualsVerifier;
import org.junit.jupiter.api.Test;

View File

@@ -0,0 +1,19 @@
package de.cotto.lndmanagej.onlinepeers.persistence;
import de.cotto.lndmanagej.model.OnlineStatus;
import org.junit.jupiter.api.Test;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY;
import static org.assertj.core.api.Assertions.assertThat;
class OnlinePeerJpaDtoTest {
@Test
void toModel() {
ZonedDateTime zonedDateTime = ZonedDateTime.of(2021, 12, 23, 1, 2, 3, 0, ZoneOffset.UTC);
OnlinePeerJpaDto onlinePeerJpaDto = new OnlinePeerJpaDto(PUBKEY, true, zonedDateTime);
assertThat(onlinePeerJpaDto.toModel()).isEqualTo(new OnlineStatus(true, zonedDateTime));
}
}

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.onlinepeers.persistence;
import de.cotto.lndmanagej.model.Pubkey;
import org.junit.jupiter.api.Test;
@@ -7,11 +7,11 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.Optional;
import static de.cotto.lndmanagej.model.OnlineStatusFixtures.ONLINE_STATUS;
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY;
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2;
import static org.assertj.core.api.Assertions.assertThat;
@@ -21,7 +21,7 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class OnlinePeersDaoImplTest {
private static final LocalDateTime TIMESTAMP = LocalDateTime.now(ZoneOffset.UTC);
private static final ZonedDateTime TIMESTAMP = ONLINE_STATUS.since();
@InjectMocks
private OnlinePeersDaoImpl dao;
@@ -50,7 +50,7 @@ class OnlinePeersDaoImplTest {
void getMostRecentOnlineStatus() {
OnlinePeerJpaDto dto = new OnlinePeerJpaDto(PUBKEY, true, TIMESTAMP);
when(repository.findTopByPubkeyOrderByTimestampDesc(PUBKEY.toString())).thenReturn(Optional.of(dto));
assertThat(dao.getMostRecentOnlineStatus(PUBKEY)).contains(true);
assertThat(dao.getMostRecentOnlineStatus(PUBKEY)).contains(ONLINE_STATUS);
}
private void verifySave(Pubkey pubkey, boolean expected) {

View File

@@ -0,0 +1,11 @@
plugins {
id 'lnd-manageJ.java-library-conventions'
}
dependencies {
implementation project(':caching')
implementation project(':model')
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
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

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.privatechannels.persistence;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics;
package de.cotto.lndmanagej.privatechannels;
import de.cotto.lndmanagej.model.ChannelId;

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics;
package de.cotto.lndmanagej.privatechannels;
import com.codahale.metrics.annotation.Timed;
import com.github.benmanes.caffeine.cache.LoadingCache;

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.privatechannels.persistence;
import de.cotto.lndmanagej.model.ChannelId;

View File

@@ -1,7 +1,7 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.privatechannels.persistence;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.statistics.PrivateChannelsDao;
import de.cotto.lndmanagej.privatechannels.PrivateChannelsDao;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.privatechannels.persistence;
import org.springframework.data.jpa.repository.JpaRepository;

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics;
package de.cotto.lndmanagej.privatechannels;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

View File

@@ -1,4 +1,4 @@
package de.cotto.lndmanagej.statistics.persistence;
package de.cotto.lndmanagej.privatechannels.persistence;
import de.cotto.lndmanagej.model.ChannelId;
import org.junit.jupiter.api.Test;

View File

@@ -1,6 +1,7 @@
rootProject.name = 'lnd-manageJ'
include 'application'
include 'backend'
include 'balances'
include 'caching'
include 'forwarding-history'
include 'grpc-adapter'
@@ -8,7 +9,9 @@ include 'grpc-client'
include 'hardcoded'
include 'invoices'
include 'model'
include 'onlinepeers'
include 'payments'
include 'privatechannels'
include 'selfpayments'
include 'statistics'
include 'transactions'

View File

@@ -4,9 +4,10 @@ plugins {
dependencies {
implementation project(':backend')
implementation project(':model')
implementation project(':balances')
implementation project(':caching')
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testRuntimeOnly 'com.h2database:h2'
implementation project(':model')
implementation project(':onlinepeers')
implementation project(':privatechannels')
testFixturesApi testFixtures(project(':model'))
}

View File

@@ -1,5 +1,7 @@
package de.cotto.lndmanagej.statistics;
import de.cotto.lndmanagej.balances.Balances;
import de.cotto.lndmanagej.balances.BalancesDao;
import de.cotto.lndmanagej.model.BalanceInformation;
import de.cotto.lndmanagej.model.LocalOpenChannel;
import de.cotto.lndmanagej.service.ChannelService;
@@ -12,11 +14,11 @@ import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Component
public class BalanceStatisticsService {
public class BalancesUpdater {
private final ChannelService channelService;
private final BalancesDao balancesDao;
public BalanceStatisticsService(ChannelService channelService, BalancesDao balancesDao) {
public BalancesUpdater(ChannelService channelService, BalancesDao balancesDao) {
this.channelService = channelService;
this.balancesDao = balancesDao;
}

View File

@@ -1,12 +0,0 @@
package de.cotto.lndmanagej.statistics;
import de.cotto.lndmanagej.model.Pubkey;
import java.time.LocalDateTime;
import java.util.Optional;
public interface OnlinePeersDao {
void saveOnlineStatus(Pubkey pubkey, boolean online, LocalDateTime timestamp);
Optional<Boolean> getMostRecentOnlineStatus(Pubkey pubkey);
}

View File

@@ -1,13 +1,17 @@
package de.cotto.lndmanagej.statistics;
import de.cotto.lndmanagej.model.LocalChannel;
import de.cotto.lndmanagej.model.Node;
import de.cotto.lndmanagej.model.OnlineStatus;
import de.cotto.lndmanagej.onlinepeers.OnlinePeersDao;
import de.cotto.lndmanagej.service.ChannelService;
import de.cotto.lndmanagej.service.NodeService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Component
@@ -24,12 +28,20 @@ public class OnlinePeersUpdater {
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES)
public void storePeerOnlineStatus() {
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
channelService.getOpenChannels().stream()
.map(LocalChannel::getRemotePubkey)
.distinct()
.map(nodeService::getNode)
.filter(node -> dao.getMostRecentOnlineStatus(node.pubkey()).orElse(!node.online()) != node.online())
.filter(this::shouldUpdate)
.forEach(node -> dao.saveOnlineStatus(node.pubkey(), node.online(), now));
}
private boolean shouldUpdate(Node node) {
Optional<OnlineStatus> mostRecentOnlineStatus = dao.getMostRecentOnlineStatus(node.pubkey());
if (mostRecentOnlineStatus.isEmpty()) {
return true;
}
return mostRecentOnlineStatus.get().online() != node.online();
}
}

View File

@@ -1,5 +1,6 @@
package de.cotto.lndmanagej.statistics;
import de.cotto.lndmanagej.privatechannels.PrivateChannelsDao;
import de.cotto.lndmanagej.service.ChannelService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

View File

@@ -1,5 +1,7 @@
package de.cotto.lndmanagej.statistics;
import de.cotto.lndmanagej.balances.Balances;
import de.cotto.lndmanagej.balances.BalancesDao;
import de.cotto.lndmanagej.model.ChannelId;
import de.cotto.lndmanagej.model.LocalOpenChannel;
import de.cotto.lndmanagej.service.ChannelService;
@@ -26,10 +28,10 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class BalanceStatisticsServiceTest {
class BalancesUpdaterTest {
private static final LocalDateTime LOCAL_DATE_TIME = LocalDateTime.now(ZoneOffset.UTC);
@InjectMocks
private BalanceStatisticsService balanceStatisticsService;
private BalancesUpdater balancesUpdater;
@Mock
private ChannelService channelService;
@@ -43,7 +45,7 @@ class BalanceStatisticsServiceTest {
LOCAL_OPEN_CHANNEL,
LOCAL_OPEN_CHANNEL_MORE_BALANCE_2
));
balanceStatisticsService.storeBalances();
balancesUpdater.storeBalances();
verify(dao).saveBalances(argThat(withBalanceInformation(LOCAL_OPEN_CHANNEL_MORE_BALANCE_2)));
verify(dao).saveBalances(argThat(withChannelId(LOCAL_OPEN_CHANNEL_MORE_BALANCE_2)));
verify(dao).saveBalances(argThat(withBalanceInformation(LOCAL_OPEN_CHANNEL)));
@@ -57,7 +59,7 @@ class BalanceStatisticsServiceTest {
ChannelId channelId = LOCAL_OPEN_CHANNEL.getId();
Balances balances = new Balances(LOCAL_DATE_TIME, channelId, BALANCE_INFORMATION_2);
when(dao.getMostRecentBalances(channelId)).thenReturn(Optional.of(balances));
balanceStatisticsService.storeBalances();
balancesUpdater.storeBalances();
verify(dao).saveBalances(any());
}
@@ -67,7 +69,7 @@ class BalanceStatisticsServiceTest {
ChannelId channelId = LOCAL_OPEN_CHANNEL.getId();
Balances balances = new Balances(LOCAL_DATE_TIME, channelId, LOCAL_OPEN_CHANNEL.getBalanceInformation());
when(dao.getMostRecentBalances(channelId)).thenReturn(Optional.of(balances));
balanceStatisticsService.storeBalances();
balancesUpdater.storeBalances();
verify(dao, never()).saveBalances(any());
}

View File

@@ -1,5 +1,6 @@
package de.cotto.lndmanagej.statistics;
import de.cotto.lndmanagej.onlinepeers.OnlinePeersDao;
import de.cotto.lndmanagej.service.ChannelService;
import de.cotto.lndmanagej.service.NodeService;
import org.junit.jupiter.api.BeforeEach;
@@ -17,6 +18,8 @@ import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHAN
import static de.cotto.lndmanagej.model.NodeFixtures.NODE_2;
import static de.cotto.lndmanagej.model.NodeFixtures.NODE_2_PEER;
import static de.cotto.lndmanagej.model.NodeFixtures.NODE_3_PEER;
import static de.cotto.lndmanagej.model.OnlineStatusFixtures.ONLINE_STATUS;
import static de.cotto.lndmanagej.model.OnlineStatusFixtures.ONLINE_STATUS_OFFLINE;
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2;
import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_3;
import static org.mockito.ArgumentMatchers.any;
@@ -65,7 +68,7 @@ class OnlinePeersUpdaterTest {
@Test
void saveOnlineStatus_online_to_offline() {
when(dao.getMostRecentOnlineStatus(PUBKEY_2)).thenReturn(Optional.of(true));
when(dao.getMostRecentOnlineStatus(PUBKEY_2)).thenReturn(Optional.of(ONLINE_STATUS));
when(nodeService.getNode(PUBKEY_2)).thenReturn(NODE_2);
onlinePeersUpdater.storePeerOnlineStatus();
@@ -75,7 +78,7 @@ class OnlinePeersUpdaterTest {
@Test
void saveOnlineStatus_offline_to_online() {
when(dao.getMostRecentOnlineStatus(PUBKEY_2)).thenReturn(Optional.of(false));
when(dao.getMostRecentOnlineStatus(PUBKEY_2)).thenReturn(Optional.of(ONLINE_STATUS_OFFLINE));
when(nodeService.getNode(PUBKEY_2)).thenReturn(NODE_2_PEER);
onlinePeersUpdater.storePeerOnlineStatus();
@@ -85,7 +88,7 @@ class OnlinePeersUpdaterTest {
@Test
void saveOnlineStatus_still_offline() {
when(dao.getMostRecentOnlineStatus(PUBKEY_2)).thenReturn(Optional.of(false));
when(dao.getMostRecentOnlineStatus(PUBKEY_2)).thenReturn(Optional.of(ONLINE_STATUS_OFFLINE));
when(nodeService.getNode(PUBKEY_2)).thenReturn(NODE_2);
onlinePeersUpdater.storePeerOnlineStatus();
@@ -95,7 +98,7 @@ class OnlinePeersUpdaterTest {
@Test
void saveOnlineStatus_still_online() {
when(dao.getMostRecentOnlineStatus(PUBKEY_2)).thenReturn(Optional.of(true));
when(dao.getMostRecentOnlineStatus(PUBKEY_2)).thenReturn(Optional.of(ONLINE_STATUS));
when(nodeService.getNode(PUBKEY_2)).thenReturn(NODE_2_PEER);
onlinePeersUpdater.storePeerOnlineStatus();

View File

@@ -1,5 +1,6 @@
package de.cotto.lndmanagej.statistics;
import de.cotto.lndmanagej.privatechannels.PrivateChannelsDao;
import de.cotto.lndmanagej.service.ChannelService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

View File

@@ -100,7 +100,8 @@ class NodeControllerIT {
.andExpect(jsonPath("$.onChainCosts.openCosts", is("1000")))
.andExpect(jsonPath("$.onChainCosts.closeCosts", is("2000")))
.andExpect(jsonPath("$.onChainCosts.sweepCosts", is("3000")))
.andExpect(jsonPath("$.online", is(true)));
.andExpect(jsonPath("$.onlineReport.online", is(true)))
.andExpect(jsonPath("$.onlineReport.since", is("2021-12-23T01:02:03Z")));
}
@Test

View File

@@ -15,9 +15,7 @@ public record ClosedChannelDetailsDto(String initiator, int height, boolean forc
}
public static ClosedChannelDetailsDto createFromModel(LocalChannel localChannel) {
boolean closed = localChannel instanceof ClosedChannel;
if (closed) {
ClosedChannel closedChannel = (ClosedChannel) localChannel;
if (localChannel instanceof ClosedChannel closedChannel) {
boolean forceClosed = closedChannel instanceof ForceClosedChannel;
boolean breach = forceClosed && closedChannel instanceof BreachForceClosedChannel;
return new ClosedChannelDetailsDto(

View File

@@ -17,7 +17,7 @@ public record NodeDetailsDto(
List<ChannelId> pendingForceClosingChannels,
OnChainCostsDto onChainCosts,
BalanceInformationDto balance,
boolean online,
OnlineReportDto onlineReport,
FeeReportDto feeReport,
RebalanceReportDto rebalanceReport
) {
@@ -31,7 +31,7 @@ public record NodeDetailsDto(
nodeDetails.pendingForceClosingChannels(),
OnChainCostsDto.createFromModel(nodeDetails.onChainCosts()),
BalanceInformationDto.createFromModel(nodeDetails.balanceInformation()),
nodeDetails.online(),
OnlineReportDto.createFromModel(nodeDetails.onlineReport()),
FeeReportDto.createFromModel(nodeDetails.feeReport()),
RebalanceReportDto.createFromModel(nodeDetails.rebalanceReport())
);

View File

@@ -0,0 +1,12 @@
package de.cotto.lndmanagej.controller.dto;
import de.cotto.lndmanagej.model.OnlineReport;
import java.time.format.DateTimeFormatter;
public record OnlineReportDto(boolean online, String since) {
public static OnlineReportDto createFromModel(OnlineReport onlineReport) {
String formattedDateTime = onlineReport.since().format(DateTimeFormatter.ISO_INSTANT);
return new OnlineReportDto(onlineReport.online(), formattedDateTime);
}
}

View File

@@ -13,6 +13,7 @@ import static de.cotto.lndmanagej.model.FeeReportFixtures.FEE_REPORT;
import static de.cotto.lndmanagej.model.NodeDetailsFixtures.NODE_DETAILS;
import static de.cotto.lndmanagej.model.NodeFixtures.ALIAS;
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 org.assertj.core.api.Assertions.assertThat;
@@ -29,7 +30,7 @@ class NodeDetailsDtoTest {
List.of(CHANNEL_ID_4),
OnChainCostsDto.createFromModel(ON_CHAIN_COSTS),
BalanceInformationDto.createFromModel(BALANCE_INFORMATION_2),
true,
OnlineReportDto.createFromModel(ONLINE_REPORT),
FeeReportDto.createFromModel(FEE_REPORT),
RebalanceReportDto.createFromModel(REBALANCE_REPORT)
);

View File

@@ -0,0 +1,20 @@
package de.cotto.lndmanagej.controller.dto;
import org.junit.jupiter.api.Test;
import static de.cotto.lndmanagej.model.OnlineReportFixtures.ONLINE_REPORT;
import static org.assertj.core.api.Assertions.assertThat;
class OnlineReportDtoTest {
@Test
void createFromModel() {
assertThat(OnlineReportDto.createFromModel(ONLINE_REPORT)).isEqualTo(
new OnlineReportDto(ONLINE_REPORT.online(), ONLINE_REPORT.since().toString())
);
}
@Test
void since() {
assertThat(OnlineReportDto.createFromModel(ONLINE_REPORT).since()).isEqualTo("2021-12-23T01:02:03Z");
}
}