diff --git a/core/src/main/java/bisq/core/dao/governance/ballot/BallotListService.java b/core/src/main/java/bisq/core/dao/governance/ballot/BallotListService.java index 7a871b40f1..7d7c589208 100644 --- a/core/src/main/java/bisq/core/dao/governance/ballot/BallotListService.java +++ b/core/src/main/java/bisq/core/dao/governance/ballot/BallotListService.java @@ -17,6 +17,8 @@ package bisq.core.dao.governance.ballot; +import bisq.common.proto.persistable.PersistedDataHost; +import bisq.common.storage.Storage; import bisq.core.app.BisqEnvironment; import bisq.core.dao.DaoSetupService; import bisq.core.dao.governance.period.PeriodService; @@ -25,22 +27,17 @@ import bisq.core.dao.governance.proposal.ProposalValidator; import bisq.core.dao.governance.proposal.storage.appendonly.ProposalPayload; import bisq.core.dao.state.model.governance.Ballot; import bisq.core.dao.state.model.governance.BallotList; +import bisq.core.dao.state.model.governance.Proposal; import bisq.core.dao.state.model.governance.Vote; - -import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; - -import javax.inject.Inject; - -import javafx.collections.ListChangeListener; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; - +import javafx.collections.ListChangeListener.Change; +import javafx.collections.ObservableList; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; +import javax.inject.Inject; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; /** * Takes the proposals from the append only store and makes Ballots out of it (vote is null). @@ -76,28 +73,41 @@ public class BallotListService implements PersistedDataHost, DaoSetupService { /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void addListeners() { - proposalService.getProposalPayloads().addListener((ListChangeListener) c -> { - c.next(); - if (c.wasAdded()) { - c.getAddedSubList().stream() - .map(ProposalPayload::getProposal) - .filter(proposal -> ballotList.stream() - .noneMatch(ballot -> ballot.getProposal().equals(proposal))) - .forEach(proposal -> { - Ballot ballot = new Ballot(proposal); // vote is null - log.info("We create a new ballot with a proposal and add it to our list. " + - "Vote is null at that moment. proposalTxId={}", proposal.getTxId()); - if (!ballotList.contains(ballot)) { - ballotList.add(ballot); - listeners.forEach(l -> l.onListChanged(ballotList.getList())); - } else { - log.warn("Ballot already exist on our ballotList"); - } - }); - persist(); - } - }); + public final void addListeners() { + ObservableList payloads = proposalService.getProposalPayloads(); + payloads.addListener(this::onChanged); + } + + private void onChanged(Change change) { + change.next(); + if (change.wasAdded()) { + List addedPayloads = change.getAddedSubList(); + addedPayloads.stream() + .map(ProposalPayload::getProposal) + .filter(this::isNewProposal) + .forEach(this::registerProposalAsBallot); + persist(); + } + } + + private boolean isNewProposal(Proposal proposal) { + return ballotList.stream() + .map(Ballot::getProposal) + .noneMatch(proposal::equals); + } + + private void registerProposalAsBallot(Proposal proposal) { + Ballot ballot = new Ballot(proposal); // vote is null + if (log.isInfoEnabled()) { + log.info("We create a new ballot with a proposal and add it to our list. " + + "Vote is null at that moment. proposalTxId={}", proposal.getTxId()); + } + if (ballotList.contains(ballot)) { + log.warn("Ballot {} already exists on our ballotList", ballot); + } else { + ballotList.add(ballot); + listeners.forEach(listener -> listener.onListChanged(ballotList.getList())); + } } @Override diff --git a/core/src/test/java/bisq/core/dao/governance/ballot/BallotListServiceTest.java b/core/src/test/java/bisq/core/dao/governance/ballot/BallotListServiceTest.java new file mode 100644 index 0000000000..ff908fb5bf --- /dev/null +++ b/core/src/test/java/bisq/core/dao/governance/ballot/BallotListServiceTest.java @@ -0,0 +1,48 @@ +package bisq.core.dao.governance.ballot; + +import bisq.common.storage.Storage; +import bisq.core.dao.governance.ballot.BallotListService.BallotListChangeListener; +import bisq.core.dao.governance.period.PeriodService; +import bisq.core.dao.governance.proposal.ProposalService; +import bisq.core.dao.governance.proposal.ProposalValidator; +import bisq.core.dao.governance.proposal.storage.appendonly.ProposalPayload; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({PeriodService.class, ProposalPayload.class}) +public class BallotListServiceTest { + @Test + @SuppressWarnings("unchecked") + public void testAddListenersWhenNewPayloadAdded() { + // given + ObservableList payloads = FXCollections.observableArrayList(); + + ProposalService proposalService = mock(ProposalService.class); + when(proposalService.getProposalPayloads()).thenReturn(payloads); + + BallotListService service = new BallotListService(proposalService, mock(PeriodService.class), + mock(ProposalValidator.class), mock(Storage.class)); + + BallotListChangeListener listener = mock(BallotListChangeListener.class); + service.addListener(listener); + + service.addListeners(); + + // when + payloads.add(mock(ProposalPayload.class, RETURNS_DEEP_STUBS)); + + // then + verify(listener).onListChanged(any()); + } +}