package io.kaif.service.impl;
import static io.kaif.model.vote.VoteState.DOWN;
import static io.kaif.model.vote.VoteState.EMPTY;
import static io.kaif.model.vote.VoteState.UP;
import static java.util.Arrays.asList;
import static org.junit.Assert.*;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import com.google.common.collect.Sets;
import io.kaif.flake.FlakeId;
import io.kaif.model.account.Account;
import io.kaif.model.account.AccountDao;
import io.kaif.model.account.AccountStats;
import io.kaif.model.article.Article;
import io.kaif.model.article.ArticleDao;
import io.kaif.model.debate.Debate;
import io.kaif.model.debate.DebateDao;
import io.kaif.model.vote.ArticleVoter;
import io.kaif.model.vote.DebateVoter;
import io.kaif.model.vote.HonorRoll;
import io.kaif.model.vote.HonorRollDao;
import io.kaif.model.zone.Zone;
import io.kaif.model.zone.ZoneInfo;
import io.kaif.test.DbIntegrationTests;
import io.kaif.web.support.AccessDeniedException;
public class VoteServiceImplTest extends DbIntegrationTests {
@Autowired
private VoteServiceImpl service;
@Autowired
private AccountDao accountDao;
@Autowired
private ArticleDao articleDao;
@Autowired
private DebateDao debateDao;
@Autowired
private HonorRollDao honorRollDao;
private Zone zone;
private FlakeId articleId;
private FlakeId debateId;
private Account voter;
private Account debater;
private Article article;
private ZoneInfo zoneInfo;
private Account author;
@Before
public void setUp() throws Exception {
zoneInfo = savedZoneDefault("hacker");
author = savedAccountCitizen("hc1");
article = savedArticle(zoneInfo, author, "new cython 3");
Debate debate = savedDebate(article, "it is slow", null);
voter = savedAccountCitizen("vt");
zone = zoneInfo.getZone();
articleId = article.getArticleId();
debateId = debate.getDebateId();
debater = accountDao.findById(debate.getDebaterId()).get();
}
@Test
public void vote_accessDenied() throws Exception {
Account tourist = savedAccountTourist("no_permit");
try {
service.voteArticle(UP, articleId, tourist, EMPTY, 100);
fail("AccessDeniedException expected");
} catch (AccessDeniedException expected) {
}
try {
service.voteDebate(UP, debateId, tourist, EMPTY, 100);
fail("AccessDeniedException expected");
} catch (AccessDeniedException expected) {
}
}
@Test
public void articleNotAllowDownVote() throws Exception {
try {
service.voteArticle(DOWN, articleId, voter, EMPTY, 100);
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException expected) {
}
}
@Test
public void listArticleVoters() throws Exception {
assertEquals(0, service.listArticleVoters(voter, Collections.emptyList()).size());
service.voteArticle(UP, articleId, voter, EMPTY, 100);
Article a2 = savedArticle(zoneInfo, savedAccountCitizen("author2"), "title vote");
service.voteArticle(UP, a2.getArticleId(), voter, EMPTY, 200);
Set<FlakeId> actual = service.listArticleVoters(voter, asList(articleId, a2.getArticleId()))
.stream()
.map(ArticleVoter::getArticleId)
.collect(Collectors.toSet());
assertEquals(Sets.newHashSet(articleId, a2.getArticleId()), actual);
}
@Test
public void upVoteArticle() throws Exception {
service.voteArticle(UP, articleId, voter, EMPTY, 100);
assertArticleTotalVote(1);
assertArticleRotateVoteStats(1);
List<ArticleVoter> votes = service.listArticleVoters(voter, asList(articleId));
assertEquals(1, votes.size());
ArticleVoter vote = votes.get(0);
assertEquals(voter.getAccountId(), vote.getVoterId());
assertEquals(articleId, vote.getArticleId());
assertEquals(100, vote.getPreviousCount());
assertEquals(UP, vote.getVoteState());
assertNotNull(vote.getUpdateTime());
}
@Test
public void listUpVotedArticles() throws Exception {
assertEquals(0, service.listUpVotedArticles(voter, null).size());
savedArticle(zoneInfo, savedAccountCitizen("other2"), "not voted");
Article a3 = savedArticle(zoneInfo, savedAccountCitizen("other3"), "title 3");
service.voteArticle(UP, a3.getArticleId(), voter, EMPTY, 100);
service.voteArticle(UP, articleId, voter, EMPTY, 100);
assertEquals(asList(a3, article), service.listUpVotedArticles(voter, null));
assertEquals(asList(article), service.listUpVotedArticles(voter, a3.getArticleId()));
articleDao.markAsDeleted(article);
assertEquals("up voted article should include deleted one",
asList(a3, article),
service.listUpVotedArticles(voter, null));
}
@Test
public void upVoteArticle_ignore_duplicate() throws Exception {
service.voteArticle(UP, articleId, voter, EMPTY, 100);
service.voteArticle(UP, articleId, voter, UP, 100);
assertArticleTotalVote(1);
assertArticleRotateVoteStats(1);
}
@Test
public void upVoteArticle_not_allow_wrong_previous_state() throws Exception {
service.voteArticle(UP, articleId, voter, EMPTY, 100);
//wrong previous state:
try {
service.voteArticle(UP, articleId, voter, EMPTY, 100);
fail("DuplicateKeyException expected");
} catch (DuplicateKeyException expected) {
}
}
@Test
public void cancelVoteArticle_no_effect_if_not_exist() throws Exception {
service.voteArticle(EMPTY, articleId, voter, EMPTY, 10);
assertArticleTotalVote(0);
assertArticleRotateVoteStats(0);
List<ArticleVoter> votes = service.listArticleVoters(voter, asList(articleId));
assertEquals(EMPTY, votes.get(0).getVoteState());
}
@Test
public void upVoteArticle_allow_on_canceled_vote() throws Exception {
service.voteArticle(UP, articleId, voter, EMPTY, 100);
service.voteArticle(EMPTY, articleId, voter, UP, 20);
service.voteArticle(UP, articleId, voter, EMPTY, 102);
assertArticleTotalVote(1);
assertArticleRotateVoteStats(1);
List<ArticleVoter> votes = service.listArticleVoters(voter, asList(articleId));
assertEquals(1, votes.size());
ArticleVoter vote = votes.get(0);
assertEquals(UP, vote.getVoteState());
assertEquals(102, vote.getPreviousCount());
}
@Test
public void touristVoteDoNotCountInTotalVote() throws Exception {
ZoneInfo zoneTourist = savedZoneTourist("test");
Zone z = zoneTourist.getZone();
Account tourist = savedAccountTourist("guest-a");
Article testArticle = savedArticle(zoneTourist, tourist, "test article");
Debate testDebate = savedDebate(testArticle, "foo", null);
service.voteDebate(UP, testDebate.getDebateId(), tourist, EMPTY, 100);
Debate changedDebate = debateDao.findDebate(testDebate.getDebateId()).get();
assertEquals(0, changedDebate.getDownVote());
assertEquals(1, changedDebate.getUpVote());
AccountStats stats = accountDao.loadStats(debater.getUsername());
assertEquals(0, stats.getDebateDownVoted());
assertEquals(0, stats.getDebateUpVoted());
}
@Test
public void articleSelfVoteDoNotCountInRotateScore() throws Exception {
service.voteArticle(UP, articleId, author, EMPTY, 100);
Article changed = articleDao.findArticle(articleId).get();
assertEquals(1, changed.getUpVote());
AccountStats stats = accountDao.loadStats(author.getUsername());
assertEquals(0, stats.getArticleUpVoted());
assertEquals(Optional.<HonorRoll>empty(),
honorRollDao.findHonorRoll(author.getAccountId(),
zone,
Instant.ofEpochMilli(article.getArticleId().epochMilli())));
}
@Test
public void debateSelfVoteDoNotCountInTotalVote() throws Exception {
service.voteDebate(UP, debateId, debater, EMPTY, 20);
AccountStats stats = accountDao.loadStats(debater.getUsername());
assertEquals(0, stats.getDebateDownVoted());
assertEquals(0, stats.getDebateUpVoted());
assertEquals(Optional.empty(),
honorRollDao.findHonorRoll(debater.getAccountId(),
zone,
Instant.ofEpochMilli(debateId.epochMilli())));
}
@Test
public void cancelVoteArticle() throws Exception {
service.voteArticle(UP, articleId, voter, EMPTY, 100);
service.voteArticle(EMPTY, articleId, voter, UP, 0);
assertArticleTotalVote(0);
List<ArticleVoter> votes = service.listArticleVoters(voter, asList(articleId));
assertEquals(1, votes.size());
ArticleVoter vote = votes.get(0);
assertEquals(EMPTY, vote.getVoteState());
assertEquals(0, vote.getPreviousCount());
}
@Test
public void cancelVoteArticle_twice() throws Exception {
service.voteArticle(UP, articleId, voter, EMPTY, 100);
service.voteArticle(EMPTY, articleId, voter, UP, 100);
service.voteArticle(EMPTY, articleId, voter, EMPTY, 100);
assertEquals(0, articleDao.findArticle(articleId).get().getUpVote());
}
@Test
public void voteDebate_ignore_duplicate() throws Exception {
service.voteDebate(UP, debateId, voter, EMPTY, 20);
assertDebateTotalVote(1, 0);
assertDebateRotateVoteStats(1, 0);
service.voteDebate(UP, debateId, voter, UP, 20);
assertDebateTotalVote(1, 0);
assertDebateRotateVoteStats(1, 0);
assertEquals(UP, service.listDebateVoters(voter, articleId).get(0).getVoteState());
}
@Test
public void listDebateVotersByIds() throws Exception {
assertEquals(0, service.listDebateVotersByIds(voter, Collections.emptyList()).size());
service.voteDebate(UP, debateId, voter, EMPTY, 20);
Article a2 = savedArticle(zoneInfo, author, "another article");
Debate d2 = savedDebate(a2, "foo", null);
service.voteDebate(UP, d2.getDebateId(), voter, EMPTY, 20);
List<DebateVoter> debateVoters = service.listDebateVotersByIds(voter,
asList(debateId, d2.getDebateId()));
assertEquals(2, debateVoters.size());
assertEquals(debateId, debateVoters.get(0).getDebateId());
assertEquals(d2.getDebateId(), debateVoters.get(1).getDebateId());
}
@Test
public void upVoteDebate() throws Exception {
service.voteDebate(UP, debateId, voter, EMPTY, 20);
assertDebateTotalVote(1, 0);
assertDebateRotateVoteStats(1, 0);
List<DebateVoter> debateVoters = service.listDebateVoters(voter, articleId);
assertEquals(1, debateVoters.size());
DebateVoter debateVoter = debateVoters.get(0);
assertEquals(voter.getAccountId(), debateVoter.getVoterId());
assertEquals(articleId, debateVoter.getArticleId());
assertEquals(UP, debateVoter.getVoteState());
assertNotNull(debateVoter.getUpdateTime());
assertEquals(20, debateVoter.getPreviousCount());
}
@Test
public void cancelVoteDebate_no_change_if_not_exist() throws Exception {
service.voteDebate(EMPTY, debateId, voter, EMPTY, 0);
assertDebateTotalVote(0, 0);
assertDebateRotateVoteStats(0, 0);
assertEquals(EMPTY, service.listDebateVoters(voter, articleId).get(0).getVoteState());
}
@Test
public void voteDebate_not_allow_wrong_previous_state() throws Exception {
service.voteDebate(DOWN, debateId, voter, EMPTY, 20);
//wrong previous state:
try {
service.voteDebate(EMPTY, debateId, voter, UP, 0);
fail("DuplicateKeyException expected");
} catch (DuplicateKeyException expected) {
}
}
@Test
public void voteDebate_up_then_cancel() throws Exception {
service.voteDebate(UP, debateId, voter, EMPTY, 20);
service.voteDebate(EMPTY, debateId, voter, UP, 0);
assertDebateTotalVote(0, 0);
assertDebateRotateVoteStats(0, 0);
DebateVoter debateVoter = service.listDebateVoters(voter, articleId).get(0);
assertEquals(EMPTY, debateVoter.getVoteState());
assertEquals(0, debateVoter.getPreviousCount());
}
@Test
public void downVoteDebate() throws Exception {
service.voteDebate(DOWN, debateId, voter, EMPTY, 20);
assertDebateTotalVote(0, 1);
assertDebateRotateVoteStats(0, 1);
List<DebateVoter> debateVoters = service.listDebateVoters(voter, articleId);
DebateVoter debateVoter = debateVoters.get(0);
assertEquals(DOWN, debateVoter.getVoteState());
assertNotNull(debateVoter.getUpdateTime());
assertEquals(20, debateVoter.getPreviousCount());
}
@Test
public void debate_upVote_then_downVote() throws Exception {
service.voteDebate(UP, debateId, voter, EMPTY, 20);
service.voteDebate(DOWN, debateId, voter, UP, 49);
assertDebateTotalVote(0, 1);
assertDebateRotateVoteStats(0, 1);
List<DebateVoter> debateVoters = service.listDebateVoters(voter, articleId);
DebateVoter debateVoter = debateVoters.get(0);
assertEquals(DOWN, debateVoter.getVoteState());
assertEquals(49, debateVoter.getPreviousCount());
}
@Test
public void debateVoteChain_up_down_up_cancel_down() throws Exception {
assertDebateTotalVote(0, 0);
service.voteDebate(UP, debateId, voter, EMPTY, 20);
assertDebateTotalVote(1, 0);
assertDebateRotateVoteStats(1, 0);
service.voteDebate(DOWN, debateId, voter, UP, 49);
assertDebateTotalVote(0, 1);
assertDebateRotateVoteStats(0, 1);
service.voteDebate(UP, debateId, voter, DOWN, 30);
assertDebateTotalVote(1, 0);
assertDebateRotateVoteStats(1, 0);
service.voteDebate(EMPTY, debateId, voter, UP, 0);
assertDebateTotalVote(0, 0);
assertDebateRotateVoteStats(0, 0);
service.voteDebate(DOWN, debateId, voter, EMPTY, 90);
assertDebateTotalVote(0, 1);
assertDebateRotateVoteStats(0, 1);
DebateVoter debateVoter = service.listDebateVoters(voter, articleId).get(0);
assertEquals(DOWN, debateVoter.getVoteState());
assertEquals(90, debateVoter.getPreviousCount());
}
private void assertDebateTotalVote(long upVote, long downVote) {
Debate changedDebate = debateDao.findDebate(debateId).get();
assertEquals(downVote, changedDebate.getDownVote());
assertEquals(upVote, changedDebate.getUpVote());
AccountStats stats = accountDao.loadStats(debater.getUsername());
assertEquals(downVote, stats.getDebateDownVoted());
assertEquals(upVote, stats.getDebateUpVoted());
}
private void assertArticleTotalVote(long upVote) {
Article changed = articleDao.findArticle(articleId).get();
assertEquals(upVote, changed.getUpVote());
AccountStats stats = accountDao.loadStats(author.getUsername());
assertEquals(upVote, stats.getArticleUpVoted());
}
private void assertDebateRotateVoteStats(long upVoteDebate, long downVoteDebate) {
HonorRoll stats = honorRollDao.findHonorRoll(debater.getAccountId(),
zone,
Instant.ofEpochMilli(debateId.epochMilli())).get();
assertEquals(upVoteDebate, stats.getDebateUpVoted());
assertEquals(downVoteDebate, stats.getDebateDownVoted());
}
private void assertArticleRotateVoteStats(long upVoteArticle) {
HonorRoll stats = honorRollDao.findHonorRoll(author.getAccountId(),
zone,
Instant.ofEpochMilli(article.getArticleId().epochMilli())).get();
assertEquals(upVoteArticle, stats.getArticleUpVoted());
}
}