Skip to content

Commit a7fc2c1

Browse files
committed
add: tests
Signed-off-by: alperozturk <alper_ozturk@proton.me>
1 parent 3321ef3 commit a7fc2c1

File tree

4 files changed

+278
-13
lines changed

4 files changed

+278
-13
lines changed

lib/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.github.spotbugs.snom.Confidence
1010
import com.github.spotbugs.snom.Effort
1111
import com.github.spotbugs.snom.SpotBugsTask
12+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
1213

1314
repositories {
1415
google()
@@ -17,6 +18,7 @@ repositories {
1718
maven { url = 'https://plugins.gradle.org/m2/' }
1819
}
1920

21+
apply plugin: "kotlin-android"
2022
apply plugin: 'com.android.library'
2123
apply plugin: "com.github.spotbugs"
2224
apply plugin: "io.gitlab.arturbosch.detekt"
@@ -61,6 +63,12 @@ android {
6163
targetCompatibility JavaVersion.VERSION_17
6264
}
6365

66+
kotlin {
67+
compilerOptions {
68+
jvmTarget = JvmTarget.JVM_17
69+
}
70+
}
71+
6472
publishing {
6573
singleVariant('release') {
6674
withSourcesJar()

lib/src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ public static class PlainHeader implements Serializable {
251251
private String name;
252252
private String value;
253253

254-
PlainHeader(String name, String value) {
254+
public PlainHeader(String name, String value) {
255255
this.name = name;
256256
this.value = value;
257257
}

lib/src/main/java/com/nextcloud/android/sso/helper/Retrofit2Helper.java

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919

2020
import java.lang.reflect.Type;
2121
import java.util.Arrays;
22-
import java.util.Collections;
22+
import java.util.HashSet;
23+
import java.util.List;
2324
import java.util.Optional;
24-
import java.util.stream.Collectors;
25+
import java.util.Set;
2526

2627
import okhttp3.Headers;
2728
import okhttp3.Protocol;
@@ -51,16 +52,8 @@ public Response<T> execute() {
5152
try {
5253
final var response = nextcloudAPI.performNetworkRequestV2(nextcloudRequest);
5354
final T body = nextcloudAPI.convertStreamToTargetEntity(response.getBody(), resType);
54-
final var headerMap = Optional.ofNullable(response.getPlainHeaders())
55-
.map(headers -> headers
56-
.stream()
57-
.collect(Collectors.toMap(
58-
AidlNetworkRequest.PlainHeader::getName,
59-
AidlNetworkRequest.PlainHeader::getValue,
60-
(existingValue, newValue) -> existingValue + ", " + newValue)))
61-
.orElse(Collections.emptyMap());
62-
63-
return Response.success(body, Headers.of(headerMap));
55+
final var headers = buildHeaders(response.getPlainHeaders());
56+
return Response.success(body, headers);
6457

6558
} catch (NextcloudHttpRequestFailedException e) {
6659
return convertExceptionToResponse(e.getStatusCode(), Optional.ofNullable(e.getCause()).orElse(e));
@@ -127,4 +120,35 @@ private Response<T> convertExceptionToResponse(int statusCode, @NonNull Throwabl
127120
}
128121
};
129122
}
123+
124+
/**
125+
* This preserves all distinct header values without combining them.
126+
*
127+
* @param plainHeaders List of headers from the response
128+
* @return Headers object
129+
*/
130+
public static Headers buildHeaders(List<AidlNetworkRequest.PlainHeader> plainHeaders) {
131+
if (plainHeaders == null || plainHeaders.isEmpty()) {
132+
return new Headers.Builder().build();
133+
}
134+
135+
final Headers.Builder builder = new Headers.Builder();
136+
final Set<String> seen = new HashSet<>();
137+
138+
for (var header : plainHeaders) {
139+
final String name = header.getName();
140+
final String value = header.getValue();
141+
142+
// Create a unique key for name:value combination
143+
final String key = name.toLowerCase() + ":" + value;
144+
145+
// Only add if we haven't seen this exact name:value combination before
146+
if (!seen.contains(key)) {
147+
builder.add(name, value);
148+
seen.add(key);
149+
}
150+
}
151+
152+
return builder.build();
153+
}
130154
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
package com.nextcloud.android.sso.helper
2+
3+
import com.nextcloud.android.sso.api.AidlNetworkRequest
4+
import com.nextcloud.android.sso.helper.Retrofit2Helper.buildHeaders
5+
import org.junit.Assert.assertEquals
6+
import org.junit.Assert.assertTrue
7+
import org.junit.Test
8+
9+
class Retrofit2HelperTest {
10+
11+
@Test
12+
fun testBuildHeadersWhenGivenNullHeadersShouldReturnEmptyHeaders() {
13+
val headers = buildHeaders(null)
14+
assertEquals(0, headers.size)
15+
}
16+
17+
@Test
18+
fun testBuildHeadersWhenGivenEmptyHeadersListShouldReturnEmptyHeaders() {
19+
val headers = buildHeaders(emptyList())
20+
assertEquals(0, headers.size)
21+
}
22+
23+
@Test
24+
fun testBuildHeadersWhenGivenSingleHeaderShouldReturnThatHeader() {
25+
val plainHeaders = listOf(
26+
createPlainHeader("Content-Type", "application/json")
27+
)
28+
29+
val headers = buildHeaders(plainHeaders)
30+
31+
assertEquals(1, headers.size)
32+
assertEquals("application/json", headers["Content-Type"])
33+
}
34+
35+
@Test
36+
fun testBuildHeadersWhenGivenDuplicateHeadersShouldRemoveDuplicates() {
37+
val plainHeaders = listOf(
38+
createPlainHeader("X-Robots-Tag", "noindex, nofollow"),
39+
createPlainHeader("X-Robots-Tag", "noindex, nofollow"),
40+
createPlainHeader("X-Robots-Tag", "noindex, nofollow")
41+
)
42+
43+
val headers = buildHeaders(plainHeaders)
44+
45+
assertEquals(1, headers.size)
46+
assertEquals("noindex, nofollow", headers["X-Robots-Tag"])
47+
}
48+
49+
@Test
50+
fun testBuildHeadersWhenGivenMultipleDistinctValuesShouldKeepAllValues() {
51+
val plainHeaders = listOf(
52+
createPlainHeader("Set-Cookie", "session=abc123"),
53+
createPlainHeader("Set-Cookie", "token=xyz789"),
54+
createPlainHeader("Set-Cookie", "user=john")
55+
)
56+
57+
val headers = buildHeaders(plainHeaders)
58+
59+
assertEquals(3, headers.size)
60+
val cookies = headers.values("Set-Cookie")
61+
assertEquals(3, cookies.size)
62+
assertTrue(cookies.contains("session=abc123"))
63+
assertTrue(cookies.contains("token=xyz789"))
64+
assertTrue(cookies.contains("user=john"))
65+
}
66+
67+
@Test
68+
fun testBuildHeadersWhenGivenMixedDuplicateAndDistinctValuesShouldHandleCorrectly() {
69+
val plainHeaders = listOf(
70+
createPlainHeader("Set-Cookie", "session=abc"),
71+
createPlainHeader("Set-Cookie", "session=abc"),
72+
createPlainHeader("Set-Cookie", "token=xyz"),
73+
createPlainHeader("X-Robots-Tag", "noindex"),
74+
createPlainHeader("X-Robots-Tag", "noindex")
75+
)
76+
77+
val headers = buildHeaders(plainHeaders)
78+
79+
assertEquals(3, headers.size)
80+
81+
val cookies = headers.values("Set-Cookie")
82+
assertEquals(2, cookies.size)
83+
assertTrue(cookies.contains("session=abc"))
84+
assertTrue(cookies.contains("token=xyz"))
85+
86+
assertEquals("noindex", headers["X-Robots-Tag"])
87+
}
88+
89+
@Test
90+
fun testBuildHeadersWhenGivenCaseInsensitiveHeaderNamesShouldTreatAsSame() {
91+
val plainHeaders = listOf(
92+
createPlainHeader("Content-Type", "application/json"),
93+
createPlainHeader("content-type", "application/json"),
94+
createPlainHeader("CONTENT-TYPE", "application/json")
95+
)
96+
97+
val headers = buildHeaders(plainHeaders)
98+
99+
assertEquals(1, headers.size)
100+
assertEquals("application/json", headers["Content-Type"])
101+
}
102+
103+
@Test
104+
fun testBuildHeadersWhenGivenCaseInsensitiveHeaderNamesWithDifferentValuesShouldKeepAll() {
105+
val plainHeaders = listOf(
106+
createPlainHeader("Content-Type", "application/json"),
107+
createPlainHeader("content-type", "text/html"),
108+
createPlainHeader("CONTENT-TYPE", "application/xml")
109+
)
110+
111+
val headers = buildHeaders(plainHeaders)
112+
113+
assertEquals(3, headers.size)
114+
val values = headers.values("Content-Type")
115+
assertTrue(values.contains("application/json"))
116+
assertTrue(values.contains("text/html"))
117+
assertTrue(values.contains("application/xml"))
118+
}
119+
120+
@Test
121+
fun testBuildHeadersWhenGivenSameNameDifferentValuesShouldKeepAll() {
122+
val plainHeaders = listOf(
123+
createPlainHeader("Accept", "text/html"),
124+
createPlainHeader("Accept", "application/json"),
125+
createPlainHeader("Accept", "text/plain")
126+
)
127+
128+
val headers = buildHeaders(plainHeaders)
129+
130+
assertEquals(3, headers.size)
131+
val accepts = headers.values("Accept")
132+
assertEquals(3, accepts.size)
133+
assertTrue(accepts.contains("text/html"))
134+
assertTrue(accepts.contains("application/json"))
135+
assertTrue(accepts.contains("text/plain"))
136+
}
137+
138+
@Test
139+
fun testBuildHeadersWhenGivenComplexScenarioShouldHandleAllCasesCorrectly() {
140+
val plainHeaders = listOf(
141+
createPlainHeader("Content-Type", "application/json"),
142+
createPlainHeader("Set-Cookie", "session=abc"),
143+
createPlainHeader("Set-Cookie", "token=xyz"),
144+
createPlainHeader("Set-Cookie", "session=abc"),
145+
createPlainHeader("X-Robots-Tag", "noindex, nofollow"),
146+
createPlainHeader("X-Robots-Tag", "noindex, nofollow"),
147+
createPlainHeader("Cache-Control", "no-cache"),
148+
createPlainHeader("cache-control", "no-cache"),
149+
createPlainHeader("Accept", "text/html"),
150+
createPlainHeader("Accept", "application/json")
151+
)
152+
153+
val headers = buildHeaders(plainHeaders)
154+
155+
assertEquals(7, headers.size)
156+
157+
assertEquals("application/json", headers["Content-Type"])
158+
159+
val cookies = headers.values("Set-Cookie")
160+
assertEquals(2, cookies.size)
161+
assertTrue(cookies.contains("session=abc"))
162+
assertTrue(cookies.contains("token=xyz"))
163+
164+
assertEquals("noindex, nofollow", headers["X-Robots-Tag"])
165+
assertEquals("no-cache", headers["Cache-Control"])
166+
167+
val accepts = headers.values("Accept")
168+
assertEquals(2, accepts.size)
169+
assertTrue(accepts.contains("text/html"))
170+
assertTrue(accepts.contains("application/json"))
171+
}
172+
173+
@Test
174+
fun testBuildHeadersWhenGivenHeadersWithWhitespaceShouldPreserveExactValue() {
175+
val plainHeaders = listOf(
176+
createPlainHeader("X-Custom", "value with spaces"),
177+
createPlainHeader("X-Custom", "value with spaces")
178+
)
179+
180+
val headers = buildHeaders(plainHeaders)
181+
182+
assertEquals(1, headers.size)
183+
assertEquals("value with spaces", headers["X-Custom"])
184+
}
185+
186+
@Test
187+
fun testBuildHeadersWhenGivenHeadersWithSpecialCharactersShouldPreserveExactValue() {
188+
val plainHeaders = listOf(
189+
createPlainHeader("Authorization", "Bearer eyJhbGc..."),
190+
createPlainHeader("X-Special", "value=test;path=/;secure")
191+
)
192+
193+
val headers = buildHeaders(plainHeaders)
194+
195+
assertEquals(2, headers.size)
196+
assertEquals("Bearer eyJhbGc...", headers["Authorization"])
197+
assertEquals("value=test;path=/;secure", headers["X-Special"])
198+
}
199+
200+
@Test
201+
fun testBuildHeadersWhenGivenEmptyHeaderValueShouldHandleCorrectly() {
202+
val plainHeaders = listOf(
203+
createPlainHeader("X-Empty", ""),
204+
createPlainHeader("X-Empty", "")
205+
)
206+
207+
val headers = buildHeaders(plainHeaders)
208+
209+
assertEquals(1, headers.size)
210+
assertEquals("", headers["X-Empty"])
211+
}
212+
213+
@Test
214+
fun testBuildHeadersWhenGivenMultipleHeadersWithSomeEmptyValuesShouldHandleCorrectly() {
215+
val plainHeaders = listOf(
216+
createPlainHeader("X-Test", "value1"),
217+
createPlainHeader("X-Test", ""),
218+
createPlainHeader("X-Test", "value2"),
219+
createPlainHeader("X-Test", "")
220+
)
221+
222+
val headers = buildHeaders(plainHeaders)
223+
224+
assertEquals(3, headers.size)
225+
val values = headers.values("X-Test")
226+
assertEquals(3, values.size)
227+
assertTrue(values.contains("value1"))
228+
assertTrue(values.contains(""))
229+
assertTrue(values.contains("value2"))
230+
}
231+
232+
private fun createPlainHeader(name: String, value: String) = AidlNetworkRequest.PlainHeader(name, value)
233+
}

0 commit comments

Comments
 (0)