Skip to content

Commit ac9f5b3

Browse files
test: add unit tests for DeviceCodeOAuthFlow
Test cases: - Construction with all fields populated - Null refreshUrl (optional field) - Empty scopes map with full field assertions - Null validation for required fields (deviceAuthorizationUrl, tokenUrl, scopes) - Record equality, hashCode consistency, and inequality edge cases - Scopes map immutability (defensive copy via Map.copyOf) Also fix: add Map.copyOf() defensive copy in DeviceCodeOAuthFlow compact constructor Fixes #607
1 parent c12888d commit ac9f5b3

2 files changed

Lines changed: 108 additions & 0 deletions

File tree

spec/src/main/java/io/a2a/spec/DeviceCodeOAuthFlow.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ public record DeviceCodeOAuthFlow(String deviceAuthorizationUrl, String tokenUrl
3333
Assert.checkNotNullParam("deviceAuthorizationUrl", deviceAuthorizationUrl);
3434
Assert.checkNotNullParam("tokenUrl", tokenUrl);
3535
Assert.checkNotNullParam("scopes", scopes);
36+
scopes = Map.copyOf(scopes);
3637
}
3738
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package io.a2a.spec;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
5+
import static org.junit.jupiter.api.Assertions.assertNull;
6+
import static org.junit.jupiter.api.Assertions.assertThrows;
7+
8+
import java.util.Map;
9+
10+
import org.junit.jupiter.api.Test;
11+
12+
/**
13+
* Unit tests for {@link DeviceCodeOAuthFlow}.
14+
* <p>
15+
* Tests cover construction with valid parameters, null validation of required
16+
* fields, and handling of the optional {@code refreshUrl} field.
17+
*
18+
* @see DeviceCodeOAuthFlow
19+
*/
20+
class DeviceCodeOAuthFlowTest {
21+
22+
private static final String DEVICE_AUTH_URL = "https://auth.example.com/device/code";
23+
private static final String TOKEN_URL = "https://auth.example.com/token";
24+
private static final String REFRESH_URL = "https://auth.example.com/refresh";
25+
private static final Map<String, String> SCOPES = Map.of("read", "Read access", "write", "Write access");
26+
27+
@Test
28+
void testConstruction_withAllFields() {
29+
DeviceCodeOAuthFlow flow = new DeviceCodeOAuthFlow(DEVICE_AUTH_URL, TOKEN_URL, REFRESH_URL, SCOPES);
30+
31+
assertEquals(DEVICE_AUTH_URL, flow.deviceAuthorizationUrl());
32+
assertEquals(TOKEN_URL, flow.tokenUrl());
33+
assertEquals(REFRESH_URL, flow.refreshUrl());
34+
assertEquals(SCOPES, flow.scopes());
35+
}
36+
37+
@Test
38+
void testConstruction_withNullRefreshUrl() {
39+
DeviceCodeOAuthFlow flow = new DeviceCodeOAuthFlow(DEVICE_AUTH_URL, TOKEN_URL, null, SCOPES);
40+
41+
assertEquals(DEVICE_AUTH_URL, flow.deviceAuthorizationUrl());
42+
assertEquals(TOKEN_URL, flow.tokenUrl());
43+
assertNull(flow.refreshUrl());
44+
assertEquals(SCOPES, flow.scopes());
45+
}
46+
47+
@Test
48+
void testConstruction_withEmptyScopes() {
49+
Map<String, String> emptyScopes = Map.of();
50+
DeviceCodeOAuthFlow flow = new DeviceCodeOAuthFlow(DEVICE_AUTH_URL, TOKEN_URL, null, emptyScopes);
51+
52+
assertEquals(DEVICE_AUTH_URL, flow.deviceAuthorizationUrl());
53+
assertEquals(TOKEN_URL, flow.tokenUrl());
54+
assertNull(flow.refreshUrl());
55+
assertEquals(emptyScopes, flow.scopes());
56+
}
57+
58+
@Test
59+
void testConstruction_nullDeviceAuthorizationUrl_throwsException() {
60+
assertThrows(IllegalArgumentException.class,
61+
() -> new DeviceCodeOAuthFlow(null, TOKEN_URL, REFRESH_URL, SCOPES));
62+
}
63+
64+
@Test
65+
void testConstruction_nullTokenUrl_throwsException() {
66+
assertThrows(IllegalArgumentException.class,
67+
() -> new DeviceCodeOAuthFlow(DEVICE_AUTH_URL, null, REFRESH_URL, SCOPES));
68+
}
69+
70+
@Test
71+
void testConstruction_nullScopes_throwsException() {
72+
assertThrows(IllegalArgumentException.class,
73+
() -> new DeviceCodeOAuthFlow(DEVICE_AUTH_URL, TOKEN_URL, REFRESH_URL, null));
74+
}
75+
76+
@Test
77+
void testEqualityAndHashCode() {
78+
DeviceCodeOAuthFlow flow1 = new DeviceCodeOAuthFlow(DEVICE_AUTH_URL, TOKEN_URL, REFRESH_URL, SCOPES);
79+
DeviceCodeOAuthFlow flow2 = new DeviceCodeOAuthFlow(DEVICE_AUTH_URL, TOKEN_URL, REFRESH_URL, SCOPES);
80+
DeviceCodeOAuthFlow flow3 = new DeviceCodeOAuthFlow(DEVICE_AUTH_URL, TOKEN_URL, null, SCOPES);
81+
DeviceCodeOAuthFlow flow4 = new DeviceCodeOAuthFlow(DEVICE_AUTH_URL, TOKEN_URL, null, SCOPES);
82+
83+
// Test for equality and hashCode consistency
84+
assertEquals(flow1, flow2);
85+
assertEquals(flow1.hashCode(), flow2.hashCode());
86+
assertEquals(flow3, flow4);
87+
assertEquals(flow3.hashCode(), flow4.hashCode());
88+
89+
// Test for inequality with different field values
90+
assertNotEquals(flow1, flow3);
91+
assertNotEquals(flow1, new DeviceCodeOAuthFlow("https://other.com", TOKEN_URL, REFRESH_URL, SCOPES));
92+
assertNotEquals(flow1, null);
93+
assertNotEquals(flow1, "not a flow");
94+
}
95+
96+
@Test
97+
void testScopesImmutability() {
98+
Map<String, String> mutableScopes = new java.util.HashMap<>();
99+
mutableScopes.put("read", "Read access");
100+
DeviceCodeOAuthFlow flow = new DeviceCodeOAuthFlow(DEVICE_AUTH_URL, TOKEN_URL, REFRESH_URL, mutableScopes);
101+
102+
// Modifying the original map should not affect the record
103+
mutableScopes.put("write", "Write access");
104+
assertNotEquals(mutableScopes.size(), flow.scopes().size(),
105+
"Record should be immutable and perform a defensive copy of the scopes map");
106+
}
107+
}

0 commit comments

Comments
 (0)