--- /dev/null
+package de.juplo.kafka.wordcount.top10;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
+
+
+public class RankingTest
+{
+ @DisplayName("A newly created instance is empty")
+ @Test
+ public void testNewRankingIsEmpty()
+ {
+ Ranking ranking = new Ranking();
+ assertThat(ranking.getEntries()).isEmpty();
+ }
+
+ @DisplayName("An instance that was build from an empty ranking is empty")
+ @Test
+ public void testRankingOfYieldsExpectedResultForEmptyList()
+ {
+ Ranking ranking = new Ranking();
+ assertThat(ranking.getEntries()).isEmpty();
+ }
+
+ @DisplayName("An instance that was build from a valid ranking contains the expected entries")
+ @ParameterizedTest
+ @MethodSource("validRankingsProvider")
+ public void testRankingOfYieldsExpectedResultsForValidRankings(List<Entry> entryList)
+ {
+ Ranking ranking = Ranking.of(toArray(entryList));
+ assertThat(ranking.getEntries()).containsExactlyElementsOf(entryList);
+ }
+
+ @DisplayName("The builder fails for invalid rankings")
+ @ParameterizedTest
+ @MethodSource("invalidRankingsProvider")
+ public void testRankingOfThrowsExceptionForInvalidRankings(List<Entry> entryList)
+ {
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> Ranking.of(toArray(entryList)));
+ }
+
+ @DisplayName("Adding a new word with highest ranking, pushes all other words down")
+ @ParameterizedTest
+ @MethodSource("validRankingsProvider")
+ public void testAddingNewWordWithHighestRanking(List<Entry> entryList)
+ {
+ Ranking ranking = Ranking.of(toArray(entryList));
+ Entry newEntry = Entry.of("NEW!", rankingForPosition(-1));
+ ranking.add(newEntry);
+ assertThat(ranking.getEntries()[0]).isEqualTo(newEntry);
+ for (int i = 0; i < entryList.size() && i < Ranking.MAX_ENTRIES - 1; i++)
+ {
+ assertThat(ranking.getEntries()[i + 1]).isEqualTo(entryList.get(i));
+ }
+ }
+
+ @DisplayName("Adding a new word with an existent ranking, pushes all words with lower ranking down")
+ @ParameterizedTest
+ @MethodSource("validRankingsProvider")
+ public void testAddingNewWordWithExistingRanking(List<Entry> entryList)
+ {
+ for (int position = 0; position < entryList.size(); position++ )
+ {
+ Ranking ranking = Ranking.of(toArray(entryList));
+ Entry newEntry = Entry.of("NEW!", rankingForPosition(position));
+ ranking.add(newEntry);
+ for (int i = 0; i < entryList.size() && i < Ranking.MAX_ENTRIES - 1; i++)
+ {
+ if (i < position)
+ {
+ assertThat(ranking.getEntries()[i]).isEqualTo(entryList.get(i));
+ }
+ if (i == position)
+ {
+ assertThat(ranking.getEntries()[i]).isEqualTo(entryList.get(i));
+ assertThat(ranking.getEntries()[i + 1]).isEqualTo(newEntry);
+ }
+ if (i > position)
+ {
+ assertThat(ranking.getEntries()[i + 1]).isEqualTo(entryList.get(i));
+ }
+ }
+ }
+ }
+
+ @DisplayName("Adding a highest ranking for an existing word shifts it to the first place")
+ @ParameterizedTest
+ @ValueSource(ints = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })
+ public void testAddingExistingWordWithHighestRanking(int position)
+ {
+ Ranking ranking = Ranking.of(toArray(VALID_RANKINGS[0]));
+ String word = wordForPosition(position);
+ Entry highestEntry = Entry.of(word, 100l);
+ ranking.add(highestEntry);
+ List<Entry> expectedEntries = Stream
+ .concat(
+ Stream.of(highestEntry),
+ VALID_RANKINGS[0]
+ .stream()
+ .filter(entry -> !entry.getWord().equals(word)))
+ .toList();
+ assertThat(ranking.getEntries()).containsExactlyElementsOf(expectedEntries);
+ }
+
+ @DisplayName("Adding an existing word with unchanged ranking changes nothing")
+ @ParameterizedTest
+ @ValueSource(ints = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })
+ public void testAddingExistingWordWithUnchangedRanking(int position)
+ {
+ Ranking ranking = Ranking.of(toArray(VALID_RANKINGS[0]));
+ Entry unchangedEntry = Entry.of(
+ wordForPosition(position),
+ rankingForPosition(position));
+ ranking.add(unchangedEntry);
+ assertThat(ranking.getEntries()).containsExactlyElementsOf(VALID_RANKINGS[0]);
+ }
+
+ @DisplayName("Adding an existing word with a lower ranking fails")
+ @ParameterizedTest
+ @MethodSource("validRankingsProvider")
+ public void testAddingExistingWordWithLowerRankingFails(List<Entry> entryList)
+ {
+ Ranking ranking = Ranking.of(toArray(entryList));
+ entryList.forEach(entry ->
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> ranking.add(Entry.of(entry.getWord(), entry.getCounter() - 1))));
+ }
+
+
+ Entry[] toArray(List<Entry> entryList)
+ {
+ return entryList.toArray(size -> new Entry[size]);
+ }
+
+ static String wordForPosition(int position)
+ {
+ return Integer.toString(position+1);
+ }
+
+ static long rankingForPosition(int position)
+ {
+ return (long)Ranking.MAX_ENTRIES * 2 - position;
+ }
+
+ static Stream<List<Entry>> validRankingsProvider()
+ {
+ return Stream.of(VALID_RANKINGS);
+ }
+
+ static Stream<List<Entry>> invalidRankingsProvider()
+ {
+ return Stream.of(INVALID_RANKINGS);
+ }
+
+ static String[] WORDS = new String[Ranking.MAX_ENTRIES];
+ static List<Entry>[] VALID_RANKINGS = new List[Ranking.MAX_ENTRIES];
+
+ static
+ {
+ for (int i = 0; i < Ranking.MAX_ENTRIES; i++)
+ {
+ List<Entry> ranking = new LinkedList<>();
+ String word = null;
+ for (int position = 0; position <= i; position++)
+ {
+ word = wordForPosition(position);
+ Entry entry = Entry.of(word, rankingForPosition(position));
+ ranking.add(entry);
+ }
+ WORDS[i] = word;
+ VALID_RANKINGS[Ranking.MAX_ENTRIES - (i + 1)] = ranking;
+ }
+ }
+
+ static List<Entry>[] INVALID_RANKINGS = new List[] {
+ List.of(
+ Entry.of("Platz eins", 1l),
+ Entry.of("Platz zwei", 2l)),
+ List.of(
+ Entry.of("Platz eins", 1111111111l),
+ Entry.of("Platz zwei", 222222222l),
+ Entry.of("Platz eins", 1l)),
+ List.of(
+ Entry.of("Platz eins", 11l),
+ Entry.of("Platz eins", 1l)),
+ List.of(
+ Entry.of("Platz eins", 1111111111l),
+ Entry.of("Platz zwei", 222222222l),
+ Entry.of("Platz eins", 11111111l),
+ Entry.of("Platz zwei", 2222222l),
+ Entry.of("Platz fünf", 555555l)),
+ List.of(
+ Entry.of("Platz eins", 1111111111l),
+ Entry.of("Platz zwei", 222222222l),
+ Entry.of("Platz drei", 33333333l),
+ Entry.of("Platz vier", 4444444l),
+ Entry.of("Platz eins", 111111l),
+ Entry.of("Platz sechs", 66666l)),
+ List.of(
+ Entry.of("Platz eins", 1111111111l),
+ Entry.of("Platz zwei", 222222222l),
+ Entry.of("Platz drei", 33333333l),
+ Entry.of("Platz vier", 4444444l),
+ Entry.of("Platz fünf", 555555l),
+ Entry.of("Platz sechs", 66666l),
+ Entry.of("Platz eins", 1l)),
+ List.of(
+ Entry.of("Platz eins", 1111111111l),
+ Entry.of("Platz zwei", 222222222l),
+ Entry.of("Platz drei", 33333333l),
+ Entry.of("Platz vier", 4444444l),
+ Entry.of("Platz fünf", 555555l),
+ Entry.of("Platz sechs", 66666l),
+ Entry.of("Platz sieben", 7777l),
+ Entry.of("Platz acht", 888l),
+ Entry.of("Platz neun", 99l),
+ Entry.of("Platz 10", 6l),
+ Entry.of("Platz 11", 3l))};
+}