diff --git a/model/src/main/java/de/cotto/lndmanagej/model/BreachForceClosedChannel.java b/model/src/main/java/de/cotto/lndmanagej/model/BreachForceClosedChannel.java index b66743a2..e4da7bc2 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/BreachForceClosedChannel.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/BreachForceClosedChannel.java @@ -23,4 +23,9 @@ public class BreachForceClosedChannel extends ForceClosedChannel { resolutions ); } + + @Override + public boolean isBreach() { + return true; + } } diff --git a/model/src/main/java/de/cotto/lndmanagej/model/ForceClosedChannel.java b/model/src/main/java/de/cotto/lndmanagej/model/ForceClosedChannel.java index 660b6470..be03258b 100644 --- a/model/src/main/java/de/cotto/lndmanagej/model/ForceClosedChannel.java +++ b/model/src/main/java/de/cotto/lndmanagej/model/ForceClosedChannel.java @@ -42,6 +42,10 @@ public class ForceClosedChannel extends ClosedChannel { return this; } + public boolean isBreach() { + return false; + } + @Override public boolean equals(Object other) { if (this == other) { diff --git a/model/src/test/java/de/cotto/lndmanagej/model/BreachForceClosedChannelTest.java b/model/src/test/java/de/cotto/lndmanagej/model/BreachForceClosedChannelTest.java index d5e14093..c0d4c277 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/BreachForceClosedChannelTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/BreachForceClosedChannelTest.java @@ -110,6 +110,11 @@ class BreachForceClosedChannelTest { assertThat(FORCE_CLOSED_CHANNEL_BREACH.getResolutions()).containsExactlyInAnyOrder(RESOLUTION_2); } + @Test + void isBreach() { + assertThat(FORCE_CLOSED_CHANNEL_BREACH.isBreach()).isTrue(); + } + @Test void testEquals() { EqualsVerifier.forClass(BreachForceClosedChannel.class).usingGetClass().verify(); diff --git a/model/src/test/java/de/cotto/lndmanagej/model/ForceClosedChannelTest.java b/model/src/test/java/de/cotto/lndmanagej/model/ForceClosedChannelTest.java index 588410da..0c04c2d7 100644 --- a/model/src/test/java/de/cotto/lndmanagej/model/ForceClosedChannelTest.java +++ b/model/src/test/java/de/cotto/lndmanagej/model/ForceClosedChannelTest.java @@ -123,6 +123,11 @@ class ForceClosedChannelTest { assertThat(FORCE_CLOSED_CHANNEL.getResolutions()).containsExactlyInAnyOrder(RESOLUTION, RESOLUTION_2); } + @Test + void isBreach() { + assertThat(FORCE_CLOSED_CHANNEL.isBreach()).isFalse(); + } + @Test void testEquals() { EqualsVerifier.forClass(ForceClosedChannel.class).usingGetClass().verify(); diff --git a/web/src/integrationTest/java/de/cotto/lndmanagej/controller/ChannelControllerIT.java b/web/src/integrationTest/java/de/cotto/lndmanagej/controller/ChannelControllerIT.java index f90db463..84d8cdde 100644 --- a/web/src/integrationTest/java/de/cotto/lndmanagej/controller/ChannelControllerIT.java +++ b/web/src/integrationTest/java/de/cotto/lndmanagej/controller/ChannelControllerIT.java @@ -25,6 +25,8 @@ import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID; import static de.cotto.lndmanagej.model.ChannelIdFixtures.CHANNEL_ID_2; import static de.cotto.lndmanagej.model.ChannelPointFixtures.CHANNEL_POINT; import static de.cotto.lndmanagej.model.CoopClosedChannelFixtures.CLOSED_CHANNEL; +import static de.cotto.lndmanagej.model.ForceClosedChannelFixtures.FORCE_CLOSED_CHANNEL; +import static de.cotto.lndmanagej.model.ForceClosedChannelFixtures.FORCE_CLOSED_CHANNEL_BREACH; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL_2; import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL_PRIVATE; @@ -230,7 +232,29 @@ class ChannelControllerIT { when(channelService.getClosedChannel(CHANNEL_ID)).thenReturn(Optional.of(CLOSED_CHANNEL)); mockMvc.perform(get(CHANNEL_PREFIX + "/close-details")) .andExpect(jsonPath("$.initiator", is("REMOTE"))) - .andExpect(jsonPath("$.height", is(987_654))); + .andExpect(jsonPath("$.height", is(987_654))) + .andExpect(jsonPath("$.force", is(false))) + .andExpect(jsonPath("$.breach", is(false))); + } + + @Test + void getCloseDetails_force() throws Exception { + when(channelService.getClosedChannel(CHANNEL_ID)).thenReturn(Optional.of(FORCE_CLOSED_CHANNEL)); + mockMvc.perform(get(CHANNEL_PREFIX + "/close-details")) + .andExpect(jsonPath("$.initiator", is("REMOTE"))) + .andExpect(jsonPath("$.height", is(987_654))) + .andExpect(jsonPath("$.force", is(true))) + .andExpect(jsonPath("$.breach", is(false))); + } + + @Test + void getCloseDetails_breach() throws Exception { + when(channelService.getClosedChannel(CHANNEL_ID)).thenReturn(Optional.of(FORCE_CLOSED_CHANNEL_BREACH)); + mockMvc.perform(get(CHANNEL_PREFIX + "/close-details")) + .andExpect(jsonPath("$.breach", is(true))) + .andExpect(jsonPath("$.initiator", is("REMOTE"))) + .andExpect(jsonPath("$.height", is(987_654))) + .andExpect(jsonPath("$.force", is(true))); } @Test diff --git a/web/src/main/java/de/cotto/lndmanagej/controller/ChannelController.java b/web/src/main/java/de/cotto/lndmanagej/controller/ChannelController.java index a52a1d80..802b094a 100644 --- a/web/src/main/java/de/cotto/lndmanagej/controller/ChannelController.java +++ b/web/src/main/java/de/cotto/lndmanagej/controller/ChannelController.java @@ -118,7 +118,7 @@ public class ChannelController { if (closedChannel == null) { throw new NotFoundException(); } - return new ClosedChannelDetailsDto(closedChannel.getCloseInitiator(), closedChannel.getCloseHeight()); + return ClosedChannelDetailsDto.createFromModel(closedChannel); } @Timed @@ -151,11 +151,6 @@ public class ChannelController { } private ClosedChannelDetailsDto getCloseDetailsForChannel(LocalChannel localChannel) { - if (localChannel.isClosed()) { - ClosedChannel closedChannel = localChannel.getAsClosedChannel(); - return new ClosedChannelDetailsDto(closedChannel.getCloseInitiator(), closedChannel.getCloseHeight()); - } else { - return ClosedChannelDetailsDto.UNKNOWN; - } + return ClosedChannelDetailsDto.createFromModel(localChannel); } } diff --git a/web/src/main/java/de/cotto/lndmanagej/controller/dto/ClosedChannelDetailsDto.java b/web/src/main/java/de/cotto/lndmanagej/controller/dto/ClosedChannelDetailsDto.java index f0f444c2..42d52b45 100644 --- a/web/src/main/java/de/cotto/lndmanagej/controller/dto/ClosedChannelDetailsDto.java +++ b/web/src/main/java/de/cotto/lndmanagej/controller/dto/ClosedChannelDetailsDto.java @@ -1,11 +1,28 @@ package de.cotto.lndmanagej.controller.dto; import de.cotto.lndmanagej.model.CloseInitiator; +import de.cotto.lndmanagej.model.ClosedChannel; +import de.cotto.lndmanagej.model.LocalChannel; -public record ClosedChannelDetailsDto(String initiator, int height) { - public static final ClosedChannelDetailsDto UNKNOWN = new ClosedChannelDetailsDto("", 0); +public record ClosedChannelDetailsDto(String initiator, int height, boolean force, boolean breach) { + public static final ClosedChannelDetailsDto UNKNOWN = + new ClosedChannelDetailsDto("", 0, false, false); - public ClosedChannelDetailsDto(CloseInitiator initiator, int height) { - this(initiator.toString(), height); + public ClosedChannelDetailsDto(CloseInitiator initiator, int height, boolean force, boolean breach) { + this(initiator.toString(), height, force, breach); + } + + public static ClosedChannelDetailsDto createFromModel(LocalChannel localChannel) { + boolean closed = localChannel.isClosed(); + if (closed) { + ClosedChannel closedChannel = localChannel.getAsClosedChannel(); + boolean forceClosed = closedChannel.isForceClosed(); + boolean breach = forceClosed && closedChannel.getAsForceClosedChannel().isBreach(); + return new ClosedChannelDetailsDto( + closedChannel.getCloseInitiator(), closedChannel.getCloseHeight(), forceClosed, breach + ); + } else { + return UNKNOWN; + } } } diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/ChannelControllerTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/ChannelControllerTest.java index 7e5b745a..300457a6 100644 --- a/web/src/test/java/de/cotto/lndmanagej/controller/ChannelControllerTest.java +++ b/web/src/test/java/de/cotto/lndmanagej/controller/ChannelControllerTest.java @@ -8,7 +8,6 @@ import de.cotto.lndmanagej.controller.dto.FeeReportDto; import de.cotto.lndmanagej.controller.dto.OffChainCostsDto; import de.cotto.lndmanagej.controller.dto.PoliciesDto; import de.cotto.lndmanagej.model.BalanceInformation; -import de.cotto.lndmanagej.model.CloseInitiator; import de.cotto.lndmanagej.model.Coins; import de.cotto.lndmanagej.model.FeeReport; import de.cotto.lndmanagej.model.LocalChannel; @@ -54,7 +53,7 @@ class ChannelControllerTest { private static final OffChainCostsDto OFF_CHAIN_COSTS = new OffChainCostsDto(SOURCE_COSTS, TARGET_COSTS); private static final PoliciesDto FEE_CONFIGURATION_DTO = PoliciesDto.createFromModel(POLICIES); private static final ClosedChannelDetailsDto CLOSED_CHANNEL_DETAILS_DTO = - new ClosedChannelDetailsDto(CloseInitiator.REMOTE, 987_654); + ClosedChannelDetailsDto.createFromModel(CLOSED_CHANNEL); private static final FeeReport FEE_REPORT = new FeeReport(Coins.ofMilliSatoshis(1_234), Coins.ofMilliSatoshis(567)); @InjectMocks @@ -158,17 +157,13 @@ class ChannelControllerTest { @Test void getDetails_closed() throws NotFoundException { - ChannelDetailsDto expectedDetails = mockForChannelWithoutPolicies( - CLOSED_CHANNEL, - CLOSED_CHANNEL.getCloseInitiator().toString(), - CLOSED_CHANNEL.getCloseHeight() - ); + ChannelDetailsDto expectedDetails = mockForChannelWithoutPolicies(CLOSED_CHANNEL); assertThat(channelController.getDetails(CHANNEL_ID)).isEqualTo(expectedDetails); } @Test void getDetails_waiting_close() throws NotFoundException { - ChannelDetailsDto expectedDetails = mockForChannelWithoutPolicies(WAITING_CLOSE_CHANNEL, "", 0); + ChannelDetailsDto expectedDetails = mockForChannelWithoutPolicies(WAITING_CLOSE_CHANNEL); assertThat(channelController.getDetails(CHANNEL_ID)).isEqualTo(expectedDetails); } @@ -216,11 +211,7 @@ class ChannelControllerTest { assertThat(channelController.getFeeReport(CHANNEL_ID)).isEqualTo(FeeReportDto.createFromModel(FEE_REPORT)); } - private ChannelDetailsDto mockForChannelWithoutPolicies( - LocalChannel channel, - String closeInitiator, - int closeHeight - ) { + private ChannelDetailsDto mockForChannelWithoutPolicies(LocalChannel channel) { when(nodeService.getAlias(PUBKEY_2)).thenReturn(ALIAS_2); when(channelService.getLocalChannel(CHANNEL_ID)).thenReturn(Optional.of(channel)); when(balanceService.getBalanceInformation(CHANNEL_ID)).thenReturn(Optional.empty()); @@ -231,7 +222,7 @@ class ChannelControllerTest { ON_CHAIN_COSTS, OFF_CHAIN_COSTS, PoliciesDto.EMPTY, - new ClosedChannelDetailsDto(closeInitiator, closeHeight), + ClosedChannelDetailsDto.createFromModel(channel), FEE_REPORT ); } diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/dto/ChannelDetailsDtoTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/dto/ChannelDetailsDtoTest.java index 50fbf33a..4fd95f33 100644 --- a/web/src/test/java/de/cotto/lndmanagej/controller/dto/ChannelDetailsDtoTest.java +++ b/web/src/test/java/de/cotto/lndmanagej/controller/dto/ChannelDetailsDtoTest.java @@ -24,7 +24,8 @@ class ChannelDetailsDtoTest { new OnChainCosts(Coins.ofSatoshis(1), Coins.ofSatoshis(2), Coins.ofSatoshis(3)); private static final OffChainCostsDto OFF_CHAIN_COSTS = new OffChainCostsDto(Coins.ofSatoshis(3), Coins.ofSatoshis(4)); - private static final ClosedChannelDetailsDto CLOSE_DETAILS = new ClosedChannelDetailsDto("abc", 123); + private static final ClosedChannelDetailsDto CLOSE_DETAILS = + ClosedChannelDetailsDto.createFromModel(CLOSED_CHANNEL); private static final FeeReport FEE_REPORT = new FeeReport(Coins.ofMilliSatoshis(1234), Coins.ofMilliSatoshis(567)); private static final ChannelDetailsDto CHANNEL_DETAILS_DTO = new ChannelDetailsDto( diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/dto/ChannelDtoTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/dto/ChannelDtoTest.java index cf615256..6a8bae74 100644 --- a/web/src/test/java/de/cotto/lndmanagej/controller/dto/ChannelDtoTest.java +++ b/web/src/test/java/de/cotto/lndmanagej/controller/dto/ChannelDtoTest.java @@ -16,7 +16,8 @@ import static de.cotto.lndmanagej.model.PubkeyFixtures.PUBKEY_2; import static org.assertj.core.api.Assertions.assertThat; class ChannelDtoTest { - private static final ClosedChannelDetailsDto CLOSE_DETAILS = new ClosedChannelDetailsDto("abc", 123); + private static final ClosedChannelDetailsDto CLOSE_DETAILS = + ClosedChannelDetailsDto.createFromModel(CLOSED_CHANNEL); private static final ChannelDto CHANNEL_DTO = new ChannelDto(CLOSED_CHANNEL, CLOSE_DETAILS); @Test diff --git a/web/src/test/java/de/cotto/lndmanagej/controller/dto/ClosedChannelDetailsDtoTest.java b/web/src/test/java/de/cotto/lndmanagej/controller/dto/ClosedChannelDetailsDtoTest.java index ef715155..5aee58cd 100644 --- a/web/src/test/java/de/cotto/lndmanagej/controller/dto/ClosedChannelDetailsDtoTest.java +++ b/web/src/test/java/de/cotto/lndmanagej/controller/dto/ClosedChannelDetailsDtoTest.java @@ -3,11 +3,75 @@ package de.cotto.lndmanagej.controller.dto; import de.cotto.lndmanagej.model.CloseInitiator; import org.junit.jupiter.api.Test; +import static de.cotto.lndmanagej.model.CoopClosedChannelFixtures.CLOSED_CHANNEL; +import static de.cotto.lndmanagej.model.ForceClosedChannelFixtures.FORCE_CLOSED_CHANNEL; +import static de.cotto.lndmanagej.model.ForceClosedChannelFixtures.FORCE_CLOSED_CHANNEL_BREACH; +import static de.cotto.lndmanagej.model.ForceClosingChannelFixtures.FORCE_CLOSING_CHANNEL; +import static de.cotto.lndmanagej.model.LocalOpenChannelFixtures.LOCAL_OPEN_CHANNEL; import static org.assertj.core.api.Assertions.assertThat; class ClosedChannelDetailsDtoTest { + + private static final ClosedChannelDetailsDto DTO = + new ClosedChannelDetailsDto(CloseInitiator.LOCAL, 987_654, true, false); + @Test void initiator() { - assertThat(new ClosedChannelDetailsDto(CloseInitiator.LOCAL, 987_654).initiator()).isEqualTo("LOCAL"); + assertThat(DTO.initiator()).isEqualTo("LOCAL"); + } + + @Test + void height() { + assertThat(DTO.height()).isEqualTo(987_654); + } + + @Test + void forceClose() { + assertThat(DTO.force()).isEqualTo(true); + } + + @Test + void createFromModel_open() { + assertThat(ClosedChannelDetailsDto.createFromModel(LOCAL_OPEN_CHANNEL)) + .isEqualTo(ClosedChannelDetailsDto.UNKNOWN); + } + + @Test + void createFromModel_closing() { + assertThat(ClosedChannelDetailsDto.createFromModel(FORCE_CLOSING_CHANNEL)) + .isEqualTo(ClosedChannelDetailsDto.UNKNOWN); + } + + @Test + void createFromModel_coop_closed() { + ClosedChannelDetailsDto expected = new ClosedChannelDetailsDto( + CLOSED_CHANNEL.getCloseInitiator().toString(), + CLOSED_CHANNEL.getCloseHeight(), + false, + false + ); + assertThat(ClosedChannelDetailsDto.createFromModel(CLOSED_CHANNEL)).isEqualTo(expected); + } + + @Test + void createFromModel_force_closed() { + ClosedChannelDetailsDto expected = new ClosedChannelDetailsDto( + FORCE_CLOSED_CHANNEL.getCloseInitiator().toString(), + FORCE_CLOSED_CHANNEL.getCloseHeight(), + true, + false + ); + assertThat(ClosedChannelDetailsDto.createFromModel(FORCE_CLOSED_CHANNEL)).isEqualTo(expected); + } + + @Test + void createFromModel_breach() { + ClosedChannelDetailsDto expected = new ClosedChannelDetailsDto( + FORCE_CLOSED_CHANNEL_BREACH.getCloseInitiator().toString(), + FORCE_CLOSED_CHANNEL_BREACH.getCloseHeight(), + true, + true + ); + assertThat(ClosedChannelDetailsDto.createFromModel(FORCE_CLOSED_CHANNEL_BREACH)).isEqualTo(expected); } } \ No newline at end of file