Skip to content

Commit a7f5267

Browse files
authored
test: coverage for non-generated code (#72)
1 parent 5a2b3de commit a7f5267

File tree

10 files changed

+213
-72
lines changed

10 files changed

+213
-72
lines changed

.github/workflows/auto-merge-dependabot.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
steps:
1414
- name: Fetch Dependabot metadata
1515
id: dependabot-metadata
16-
uses: dependabot/fetch-metadata@dbb049abf0d677abbd7f7eee0375145b417fdd34
16+
uses: dependabot/fetch-metadata@dbb049abf0d677abbd7f7eee0375145b417fdd34 # v2.2.0
1717
- name: Enable auto-merge for minor updates
1818
if: ${{ steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' }}
1919
env:

.github/workflows/build.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ jobs:
1919
timeout-minutes: 1
2020
steps:
2121
- name: Checkout repository
22-
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
22+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
2323
- name: Setup Java
24-
uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018
24+
uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 # v4.2.2
2525
with:
2626
distribution: temurin
2727
java-version: 17
@@ -40,15 +40,15 @@ jobs:
4040
security-events: write
4141
steps:
4242
- name: Checkout repository
43-
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
43+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
4444
- name: Setup Java
45-
uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018
45+
uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 # v4.2.2
4646
with:
4747
distribution: temurin
4848
java-version: 17
4949
cache: maven
5050
- name: Initialize CodeQL
51-
uses: github/codeql-action/init@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a
51+
uses: github/codeql-action/init@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
5252
with:
5353
languages: java
5454
queries: security-and-quality
@@ -57,4 +57,4 @@ jobs:
5757
- name: Compile project
5858
run: ./mvnw compile
5959
- name: Perform CodeQL Analysis
60-
uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a
60+
uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ jobs:
1313
packages: write
1414
steps:
1515
- name: Checkout repository
16-
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
16+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
1717
- name: Setup Java
18-
uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018
18+
uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 # v4.2.2
1919
with:
2020
distribution: temurin
2121
java-version: 17

codewars-sdk-client/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@
5050
<artifactId>junit-jupiter</artifactId>
5151
<scope>test</scope>
5252
</dependency>
53+
<dependency>
54+
<groupId>org.mockito</groupId>
55+
<artifactId>mockito-core</artifactId>
56+
<scope>test</scope>
57+
</dependency>
5358
</dependencies>
5459

5560
<build>

codewars-sdk-client/src/main/java/dev/noid/codewars/client/CodewarsClient.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import dev.noid.codewars.client.model.CompletedChallenges;
99
import dev.noid.codewars.client.model.User;
1010
import java.util.List;
11-
import java.util.function.IntFunction;
11+
import java.util.Map;
1212

1313
public final class CodewarsClient {
1414

@@ -41,9 +41,11 @@ public List<AuthoredChallenge> listAuthoredChallenges(String idOrUsername) throw
4141

4242
public List<CompletedChallenge> listCompletedChallenges(String idOrUsername) throws ApiException {
4343
CompletedChallenges firstPage = usersApi.listCompletedChallenges(idOrUsername, 0);
44-
List<CompletedChallenge> recentChallenges = firstPage.getData();
45-
IntFunction<List<CompletedChallenge>> pageLoader = page -> usersApi.listCompletedChallenges(idOrUsername, page).getData();
46-
return new LazyList<>(pageLoader, recentChallenges, recentChallenges.size(), firstPage.getTotalItems());
44+
return new PaginatedList<>(
45+
page -> usersApi.listCompletedChallenges(idOrUsername, page).getData(),
46+
firstPage.getTotalPages(),
47+
firstPage.getTotalItems(),
48+
Map.of(0, firstPage.getData()));
4749
}
4850

4951
public CodeChallenge getCodeChallenge(String idOrSlag) throws ApiException {

codewars-sdk-client/src/main/java/dev/noid/codewars/client/LazyList.java

Lines changed: 0 additions & 52 deletions
This file was deleted.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dev.noid.codewars.client;
2+
3+
import java.util.AbstractList;
4+
import java.util.List;
5+
import java.util.Map;
6+
import java.util.function.IntFunction;
7+
8+
class PaginatedList<E> extends AbstractList<E> {
9+
10+
private final IntFunction<List<E>> pageLoader;
11+
private final List<E>[] cache;
12+
private final int totalItems;
13+
private final int pageSize;
14+
15+
PaginatedList(IntFunction<List<E>> pageLoader, int totalPages, int totalItems, Map<Integer, List<E>> preloaded) {
16+
this.pageLoader = pageLoader;
17+
this.cache = new List[totalPages];
18+
preloaded.forEach((pageNumber, page) -> cache[pageNumber] = page);
19+
this.totalItems = totalItems;
20+
this.pageSize = (int) Math.ceil((double) totalItems / totalPages);
21+
}
22+
23+
@Override
24+
public E get(int i) {
25+
if (i < 0 || i >= size()) {
26+
throw new IndexOutOfBoundsException();
27+
}
28+
29+
int pageNumber = Math.floorDiv(i, pageSize);
30+
List<E> page = cache[pageNumber];
31+
if (page == null) {
32+
page = pageLoader.apply(pageNumber);
33+
cache[pageNumber] = page;
34+
}
35+
return page.get(i % pageSize);
36+
}
37+
38+
@Override
39+
public int size() {
40+
return totalItems;
41+
}
42+
}

codewars-sdk-client/src/test/java/dev/noid/codewars/client/LazyListTest.java

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package dev.noid.codewars.client;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
import static org.mockito.ArgumentMatchers.anyInt;
7+
8+
import java.util.List;
9+
import java.util.Map;
10+
import org.junit.jupiter.api.Test;
11+
import org.mockito.Mockito;
12+
13+
class PaginatedListTest {
14+
15+
private final List<List<String>> remotePages = Mockito.spy(List.of(
16+
List.of("a", "b", "c", "d", "e"),
17+
List.of("f", "g", "h", "i", "j"),
18+
List.of("k", "l", "m", "n", "o"),
19+
List.of("p", "q", "r", "s", "t"),
20+
List.of("u", "v", "w", "x", "y"),
21+
List.of("z")
22+
));
23+
24+
@Test
25+
void load_all_remote_pages() {
26+
assertEquals(List.of(
27+
"a", "b", "c", "d", "e",
28+
"f", "g", "h", "i", "j",
29+
"k", "l", "m", "n", "o",
30+
"p", "q", "r", "s", "t",
31+
"u", "v", "w", "x", "y",
32+
"z"
33+
), getList(6, 26, Map.of()));
34+
35+
Mockito.verify(remotePages, Mockito.times(remotePages.size())).get(anyInt());
36+
for (int page = 0; page < remotePages.size(); page++) {
37+
Mockito.verify(remotePages, Mockito.times(1)).get(page);
38+
}
39+
}
40+
41+
@Test
42+
void preload_first_page() {
43+
assertEquals(List.of(
44+
"1", "2", "3", "4", "5",
45+
"f", "g", "h", "i", "j",
46+
"k", "l", "m", "n", "o",
47+
"p", "q", "r", "s", "t",
48+
"u", "v", "w", "x", "y",
49+
"z"
50+
), getList(6, 26, Map.of(0, List.of("1", "2", "3", "4", "5"))));
51+
52+
Mockito.verify(remotePages, Mockito.times(remotePages.size() - 1)).get(anyInt());
53+
for (int page = 1; page < remotePages.size(); page++) {
54+
Mockito.verify(remotePages, Mockito.times(1)).get(page);
55+
}
56+
}
57+
58+
@Test
59+
void preload_last_page() {
60+
assertEquals(List.of(
61+
"a", "b", "c", "d", "e",
62+
"f", "g", "h", "i", "j",
63+
"k", "l", "m", "n", "o",
64+
"p", "q", "r", "s", "t",
65+
"u", "v", "w", "x", "y",
66+
"1"
67+
), getList(6, 26, Map.of(5, List.of("1", "2", "3", "4", "5"))));
68+
69+
Mockito.verify(remotePages, Mockito.times(remotePages.size() - 1)).get(anyInt());
70+
for (int page = 0; page < remotePages.size() - 1; page++) {
71+
Mockito.verify(remotePages, Mockito.times(1)).get(page);
72+
}
73+
}
74+
75+
@Test
76+
void load_no_remote_pages() {
77+
Map<Integer, List<String>> preloaded = Map.of(
78+
0, List.of("1", "2", "3", "4", "5"),
79+
1, List.of("6", "7", "8", "9", "0"),
80+
2, List.of("~", "!", "@", "#", "$"),
81+
3, List.of("%", "^", "&", "*", "("),
82+
4, List.of(")", "_", "+", "`", "-"),
83+
5, List.of("=")
84+
);
85+
86+
assertEquals(List.of(
87+
"1", "2", "3", "4", "5",
88+
"6", "7", "8", "9", "0",
89+
"~", "!", "@", "#", "$",
90+
"%", "^", "&", "*", "(",
91+
")", "_", "+", "`", "-",
92+
"="
93+
), getList(6, 26, preloaded));
94+
95+
Mockito.verify(remotePages, Mockito.times(0)).get(anyInt());
96+
}
97+
98+
@Test
99+
void limit_pages_access() {
100+
assertEquals(List.of(
101+
"a", "b", "c", "d", "e",
102+
"f", "g", "h", "i", "j",
103+
"k", "l", "m"
104+
), getList(3, 13, Map.of()));
105+
106+
Mockito.verify(remotePages, Mockito.times(3)).get(anyInt());
107+
Mockito.verify(remotePages, Mockito.times(1)).get(0);
108+
Mockito.verify(remotePages, Mockito.times(1)).get(1);
109+
Mockito.verify(remotePages, Mockito.times(1)).get(2);
110+
}
111+
112+
@Test
113+
void read_from_empty() {
114+
List<String> list = getList(0, 0, Map.of());
115+
assertTrue(list.isEmpty());
116+
assertEquals(List.of(), list);
117+
}
118+
119+
@Test
120+
void constant_size() {
121+
List<String> list = getList(6, 26, Map.of());
122+
assertEquals(26, list.size());
123+
for (String ignored : list) {
124+
assertEquals(26, list.size());
125+
}
126+
assertEquals(26, list.size());
127+
}
128+
129+
@Test
130+
void immutability() {
131+
List<String> list = getList(1, 1, Map.of());
132+
assertThrows(UnsupportedOperationException.class, () -> list.add(0, "!"));
133+
assertThrows(UnsupportedOperationException.class, () -> list.set(0, "!"));
134+
assertThrows(UnsupportedOperationException.class, () -> list.remove(0));
135+
assertThrows(UnsupportedOperationException.class, () -> list.remove("a"));
136+
}
137+
138+
private List<String> getList(int totalPages, int totalItems, Map<Integer, List<String>> preloaded) {
139+
return new PaginatedList<>(remotePages::get, totalPages, totalItems, preloaded);
140+
}
141+
}

pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<jsr305.version>3.0.2</jsr305.version>
3939
<swagger.version>1.6.14</swagger.version>
4040
<junit.version>5.10.3</junit.version>
41+
<mockito.version>5.12.0</mockito.version>
4142
</properties>
4243

4344
<dependencyManagement>
@@ -63,6 +64,15 @@
6364
<groupId>org.junit.jupiter</groupId>
6465
<artifactId>junit-jupiter</artifactId>
6566
<version>${junit.version}</version>
67+
<type>pom</type>
68+
<scope>import</scope>
69+
</dependency>
70+
<dependency>
71+
<groupId>org.mockito</groupId>
72+
<artifactId>mockito-bom</artifactId>
73+
<version>${mockito.version}</version>
74+
<type>pom</type>
75+
<scope>import</scope>
6676
</dependency>
6777
</dependencies>
6878
</dependencyManagement>

0 commit comments

Comments
 (0)