diff --git a/.travis.yml b/.travis.yml index e13823f..eeb37ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ language: groovy +jdk: + - oraclejdk8 sudo: false after_success: - mvn -q -e clean test jacoco:report coveralls:report diff --git a/pom.xml b/pom.xml index 8cc59a9..6f548ce 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ pl.jug.torun xenia-api - 0.0.1-SNAPSHOT + 2.0.1-SNAPSHOT jar Xenia API diff --git a/src/main/groovy/pl/jug/torun/xenia/draw/DrawController.groovy b/src/main/groovy/pl/jug/torun/xenia/draw/DrawController.groovy index b652fe6..b42921c 100644 --- a/src/main/groovy/pl/jug/torun/xenia/draw/DrawController.groovy +++ b/src/main/groovy/pl/jug/torun/xenia/draw/DrawController.groovy @@ -20,9 +20,12 @@ final class DrawController { } @RequestMapping(method = RequestMethod.GET) - public DrawResult drawWinnerCandidate(@PathVariable("giveAway") GiveAway giveAway, @RequestParam(value = "absent", required = false) Member absentMember) { + public DrawResult drawWinnerCandidate(@PathVariable("giveAway") GiveAway giveAway, @RequestParam(value = "absent", required = false) Member absentMember, + @RequestParam(value = "skipped", required = false) Member skippedMember) { if (absentMember) { drawService.markMemberAsAbsentForCurrentDraw(absentMember, giveAway.event) + } else if (skippedMember) { + drawService.setGiveAwaySkippedForMember(skippedMember, giveAway) } return drawService.drawWinnerCandidate(giveAway) diff --git a/src/main/groovy/pl/jug/torun/xenia/draw/DrawService.groovy b/src/main/groovy/pl/jug/torun/xenia/draw/DrawService.groovy index 6786417..fc01aab 100644 --- a/src/main/groovy/pl/jug/torun/xenia/draw/DrawService.groovy +++ b/src/main/groovy/pl/jug/torun/xenia/draw/DrawService.groovy @@ -11,16 +11,21 @@ import pl.jug.torun.xenia.events.Event import pl.jug.torun.xenia.meetup.Member import pl.jug.torun.xenia.meetup.MemberRepository +import static org.apache.commons.lang.StringUtils.isNotBlank + @Slf4j @Service class DrawService { private final DrawResultRepository drawResultRepository private final AttendeeRepository attendeeRepository private final MemberRepository memberRepository + private final SkippedGiveAwayContainer skippedGiveAwayContainer @Autowired - DrawService(DrawResultRepository drawResultRepository, AttendeeRepository attendeeRepository, MemberRepository memberRepository) { + DrawService(DrawResultRepository drawResultRepository, SkippedGiveAwayContainer skippedGiveAwayContainer, AttendeeRepository attendeeRepository, + MemberRepository memberRepository) { this.drawResultRepository = drawResultRepository + this.skippedGiveAwayContainer = skippedGiveAwayContainer this.attendeeRepository = attendeeRepository this.memberRepository = memberRepository } @@ -53,8 +58,12 @@ class DrawService { log.debug '{} members have won this prize already...', membersWhoHaveWonThisPrizeAlready.size() members.removeAll(membersWhoHaveWonThisPrizeAlready) + Set membersWithSkippedGiveAway = skippedGiveAwayContainer.getMembersByGiveAway(giveAway) + log.debug '{} members have skipped this prize already...', membersWithSkippedGiveAway.size() + members.removeAll(membersWithSkippedGiveAway) + if (giveAway.emailRequired) { - members = members.findAll { it.email != null && !it.email.empty } + members = members.findAll { isNotBlank(it.email) } } return members } @@ -77,4 +86,10 @@ class DrawService { attendeeRepository.save(attendee) } } + + public void setGiveAwaySkippedForMember(Member member, GiveAway giveAway) { + log.debug 'Member {} skipped the draw result for {}', member, giveAway + skippedGiveAwayContainer.addSkippedMember(giveAway, member) + } + } diff --git a/src/main/groovy/pl/jug/torun/xenia/draw/SkippedGiveAwayContainer.groovy b/src/main/groovy/pl/jug/torun/xenia/draw/SkippedGiveAwayContainer.groovy new file mode 100644 index 0000000..439309c --- /dev/null +++ b/src/main/groovy/pl/jug/torun/xenia/draw/SkippedGiveAwayContainer.groovy @@ -0,0 +1,23 @@ +package pl.jug.torun.xenia.draw + +import org.springframework.stereotype.Component +import pl.jug.torun.xenia.meetup.Member + +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentSkipListSet + +@Component +class SkippedGiveAwayContainer { + + private final Map> skippedGiveWays = new ConcurrentHashMap<>() + + Set getMembersByGiveAway(GiveAway giveaway) { + (skippedGiveWays[giveaway] ?: new HashSet<>()).asImmutable() + } + + void addSkippedMember(GiveAway giveAway, Member member) { + skippedGiveWays.computeIfAbsent(giveAway) { + new ConcurrentSkipListSet({ m1, m2 -> m1.id.compareTo(m2.id) }) + }.add(member) + } +} \ No newline at end of file diff --git a/src/test/groovy/pl/jug/torun/xenia/draw/DrawServiceSpec.groovy b/src/test/groovy/pl/jug/torun/xenia/draw/DrawServiceSpec.groovy index d001cd2..b5d49d4 100644 --- a/src/test/groovy/pl/jug/torun/xenia/draw/DrawServiceSpec.groovy +++ b/src/test/groovy/pl/jug/torun/xenia/draw/DrawServiceSpec.groovy @@ -4,10 +4,7 @@ import org.joda.time.DateTime import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.test.context.ContextConfiguration -import pl.jug.torun.xenia.events.Attendee -import pl.jug.torun.xenia.events.AttendeeRepository -import pl.jug.torun.xenia.events.Event -import pl.jug.torun.xenia.events.EventRepository +import pl.jug.torun.xenia.events.* import pl.jug.torun.xenia.meetup.Member import pl.jug.torun.xenia.meetup.MemberRepository import pl.jug.torun.xenia.prizes.Prize @@ -18,6 +15,8 @@ import spock.lang.Subject import java.util.concurrent.atomic.AtomicLong +import static org.apache.commons.lang.StringUtils.isNotBlank + @Stepwise @DataJpaTest @ContextConfiguration @@ -53,7 +52,7 @@ class DrawServiceSpec extends Specification { private AtomicLong counter = new AtomicLong(0L) def setup() { - drawService = new DrawService(drawResultRepository, attendeeRepository, memberRepository) + drawService = new DrawService(drawResultRepository, new SkippedGiveAwayContainer(), attendeeRepository, memberRepository) giveAwayController = new GiveAwayController(giveAwayRepository, prizeRepository, drawResultRepository) event = event("Test event", DateTime.parse("2015-04-02T20:00:00")) } @@ -80,7 +79,7 @@ class DrawServiceSpec extends Specification { thrown IllegalStateException } - def "should confirm that giveaway that requires email can be drawn by attendess with email only"() { + def "should confirm that giveaway that requires email can be drawn by attendees with email only"() { setup: GiveAway giveAway = giveAwayWithPrizeAndAmount("License", 1, true) attendee("John") @@ -94,7 +93,7 @@ class DrawServiceSpec extends Specification { DrawResult result = drawService.drawWinnerCandidate(giveAway) then: - result.member.email != null && result.member.email != "" + isNotBlank(result.member.email) where: i << (1..RANDOM_TESTS_RUN_LIMIT) @@ -204,6 +203,21 @@ class DrawServiceSpec extends Specification { i << (1..RANDOM_TESTS_RUN_LIMIT) } + def "should not allow attendee who skipped a giveaway to win same prize again during the same event"() { + setup: + GiveAway giveAway = giveAwayWithPrizeAndAmount("Something", 1) + attendee("Paul").with { + drawService.setGiveAwaySkippedForMember(it.member, giveAway) + } + attendee("Roger") + + when: + Member winner = drawService.drawWinnerCandidate(giveAway).member + + then: + winner.name == "Roger" + } + def "should draw all giveaways and confirm winners"() { setup: GiveAway firstGiveAway = giveAwayWithPrizeAndAmount("Spring Boot in Action ebook", 2) @@ -247,7 +261,18 @@ class DrawServiceSpec extends Specification { i << (1..RANDOM_TESTS_RUN_LIMIT) } + def "should mark attendee as absent"() { + given: + Attendee attendee = attendee("Roger") + when: + drawService.markMemberAsAbsentForCurrentDraw(attendee.member, event) + then: + storedAttendee(attendee).absent + } + private Attendee storedAttendee(Attendee attendee) { + return attendeeRepository.findAllByMemberId(attendee.member.id)[0] + } private GiveAway giveAwayWithAmount(int amount) { return giveAwayWithPrizeAndAmount("Test", amount) @@ -274,7 +299,7 @@ class DrawServiceSpec extends Specification { } private Attendee absent(Attendee attendee) { - attendee.absent = true - return attendeeRepository.save(attendee) + drawService.markMemberAsAbsentForCurrentDraw(attendee.member, event) + return attendee } } diff --git a/src/test/groovy/pl/jug/torun/xenia/draw/SkippedGiveAwayContainerSpec.groovy b/src/test/groovy/pl/jug/torun/xenia/draw/SkippedGiveAwayContainerSpec.groovy new file mode 100644 index 0000000..f214f99 --- /dev/null +++ b/src/test/groovy/pl/jug/torun/xenia/draw/SkippedGiveAwayContainerSpec.groovy @@ -0,0 +1,46 @@ +package pl.jug.torun.xenia.draw + +import pl.jug.torun.xenia.meetup.Member +import spock.lang.Specification + +class SkippedGiveAwayContainerSpec extends Specification { + + def "should add skipped members for same giveaway"() { + given: + SkippedGiveAwayContainer container = new SkippedGiveAwayContainer() + Member firstMember = new Member(id: 100) + Member nextMember = new Member(id: 200) + GiveAway giveAway = new GiveAway(id: 1000) + + when: + container.addSkippedMember(giveAway, firstMember) + container.addSkippedMember(giveAway, nextMember) + + then: + container.getMembersByGiveAway(giveAway).with { + it.size() == 2 && it[0] == firstMember && it[1] == nextMember + } + } + + def "should add skipped members for different giveaways"() { + given: + SkippedGiveAwayContainer container = new SkippedGiveAwayContainer() + Member firstMember = new Member(id: 100) + Member nextMember = new Member(id: 200) + GiveAway firstGiveAway = new GiveAway(id: 1000) + GiveAway nextGiveAway = new GiveAway(id: 2000) + + when: + container.addSkippedMember(firstGiveAway, firstMember) + container.addSkippedMember(nextGiveAway, nextMember) + + then: + container.getMembersByGiveAway(firstGiveAway).with { + it.size() == 1 && it[0] == firstMember + } + container.getMembersByGiveAway(nextGiveAway).with { + it.size() == 1 && it[0] == nextMember + } + } + +}