30
30
import java .net .URL ;
31
31
import java .net .UnknownHostException ;
32
32
import java .time .Duration ;
33
+ import java .util .Optional ;
33
34
import java .util .concurrent .CompletionException ;
34
35
import java .util .stream .Collectors ;
35
36
import org .junit .jupiter .api .Test ;
@@ -58,44 +59,81 @@ class ExternalUrlKeyReaderTest {
58
59
private final String [] expectedKeys =
59
60
new String [] {publicKey1 .toHexString (), publicKey2 .toHexString ()};
60
61
62
+ private ExternalUrlKeyReader reader ;
63
+
64
+ @ Test
65
+ void malformedUrlString () {
66
+ final String invalidUrl = "invalid:url" ;
67
+ assertThatThrownBy (() -> new ExternalUrlKeyReader (invalidUrl , mapper , Optional .empty ()))
68
+ .isInstanceOf (InvalidConfigurationException .class )
69
+ .hasMessageContaining ("Failed to load public keys from invalid URL: " + invalidUrl )
70
+ .hasRootCauseInstanceOf (MalformedURLException .class );
71
+ verifyNoInteractions (mapper );
72
+ }
73
+
61
74
@ Test
62
- void readKeys_validUrlReturnsValidKeys () throws IOException {
63
- when (mapper .readValue (any (URL .class ), eq (String [].class ))).thenReturn (expectedKeys );
64
- final ExternalUrlKeyReader reader = new ExternalUrlKeyReader (VALID_URL , mapper , asyncRunner );
75
+ void readKeys_retryDisabled_validUrlReturnsValidKeys () throws IOException {
76
+ initialise (expectedKeys , false );
65
77
66
78
assertThat (reader .readKeys ()).contains (publicKey1 , publicKey2 );
67
- verify ( mapper ). readValue ( any ( URL . class ), eq ( String []. class ) );
79
+ verifyReadCall ( );
68
80
}
69
81
70
82
@ Test
71
- void readKeys_validUrlReturnsEmptyKeys () throws IOException {
72
- when (mapper .readValue (any (URL .class ), eq (String [].class ))).thenReturn (new String [] {});
73
- final ExternalUrlKeyReader reader = new ExternalUrlKeyReader (VALID_URL , mapper , asyncRunner );
83
+ void readKeys_retryDisabled_validUrlReturnsEmptyKeys () throws IOException {
84
+ initialise (new String [] {}, false );
74
85
75
86
assertThat (reader .readKeys ()).isEmpty ();
76
- verify ( mapper ). readValue ( any ( URL . class ), eq ( String []. class ) );
87
+ verifyReadCall ( );
77
88
}
78
89
79
90
@ Test
80
- void readKeys_validUrlReturnsInvalidKeys () throws IOException {
81
- when (mapper .readValue (any (URL .class ), eq (String [].class )))
82
- .thenReturn (new String [] {"invalid" , "keys" });
83
- final ExternalUrlKeyReader reader = new ExternalUrlKeyReader (VALID_URL , mapper , asyncRunner );
91
+ void readKeys_retryDisabled_validUrlReturnsInvalidKeys () throws IOException {
92
+ initialise (new String [] {"invalid" , "keys" }, false );
84
93
85
94
assertThatThrownBy (() -> reader .readKeys ().collect (Collectors .toSet ()))
86
95
.isInstanceOf (IllegalArgumentException .class )
87
96
.hasMessage ("Invalid odd-length hex binary representation" );
88
- verify ( mapper ). readValue ( any ( URL . class ), eq ( String []. class ) );
97
+ verifyReadCall ( );
89
98
}
90
99
91
100
@ Test
92
- void readKeys_malformedUrlString () {
93
- final String invalidUrl = "invalid:url" ;
94
- assertThatThrownBy (() -> new ExternalUrlKeyReader (invalidUrl , mapper , asyncRunner ))
95
- .isInstanceOf (InvalidConfigurationException .class )
96
- .hasMessageContaining ("Failed to load public keys from invalid URL: " + invalidUrl )
97
- .hasRootCauseInstanceOf (MalformedURLException .class );
98
- verifyNoInteractions (mapper );
101
+ void readKeys_retryDisabled_unreachableUrlFails () throws IOException {
102
+ final UnknownHostException exception = new UnknownHostException ("Unknown host" );
103
+ when (mapper .readValue (any (URL .class ), eq (String [].class ))).thenThrow (exception );
104
+ reader = new ExternalUrlKeyReader (VALID_URL , mapper , Optional .empty ());
105
+
106
+ assertThatThrownBy (reader ::readKeys )
107
+ .isInstanceOf (CompletionException .class )
108
+ .hasCauseInstanceOf (InvalidConfigurationException .class )
109
+ .hasRootCause (exception );
110
+ verifyReadCall ();
111
+ }
112
+
113
+ @ Test
114
+ void readKeys_retryEnabled_validUrlReturnsValidKeys () throws IOException {
115
+ initialise (expectedKeys , true );
116
+
117
+ assertThat (reader .readKeys ()).contains (publicKey1 , publicKey2 );
118
+ verifyReadCall ();
119
+ }
120
+
121
+ @ Test
122
+ void readKeys_retryEnabled_validUrlReturnsEmptyKeys () throws IOException {
123
+ initialise (new String [] {}, true );
124
+
125
+ assertThat (reader .readKeys ()).isEmpty ();
126
+ verifyReadCall ();
127
+ }
128
+
129
+ @ Test
130
+ void readKeys_retryEnabled_validUrlReturnsInvalidKeys () throws IOException {
131
+ initialise (new String [] {"invalid" , "keys" }, true );
132
+
133
+ assertThatThrownBy (() -> reader .readKeys ().collect (Collectors .toSet ()))
134
+ .isInstanceOf (IllegalArgumentException .class )
135
+ .hasMessage ("Invalid odd-length hex binary representation" );
136
+ verifyReadCall ();
99
137
}
100
138
101
139
@ Test
@@ -104,35 +142,47 @@ void readKeysWithRetry_unreachableUrlRetryUntilReachable() throws IOException {
104
142
when (mapper .readValue (any (URL .class ), eq (String [].class )))
105
143
.thenThrow (exception , exception , exception )
106
144
.thenReturn (expectedKeys );
107
- final ExternalUrlKeyReader reader = new ExternalUrlKeyReader (VALID_URL , mapper , asyncRunner );
145
+ reader = new ExternalUrlKeyReader (VALID_URL , mapper , asyncRunner );
108
146
109
- final SafeFuture <String []> keys = reader .getKeysWithRetry ();
147
+ final SafeFuture <String []> keys = reader .getKeysWithRetry (asyncRunner );
110
148
for (int i = 0 ; i < 3 ; i ++) {
111
149
assertThat (keys ).isNotCompleted ();
112
150
timeProvider .advanceTimeBy (DELAY );
113
151
asyncRunner .executeQueuedActions ();
114
152
}
115
153
assertThat (keys ).isCompletedWithValue (expectedKeys );
116
- verify ( mapper , times ( 4 )). readValue ( any ( URL . class ), eq ( String []. class ) );
154
+ verifyReadCalls ( 4 );
117
155
}
118
156
119
157
@ Test
120
158
void readKeysWithRetry_unreachableUrlRetryUntilMaxRetries () throws IOException {
121
159
final IOException exception = new ConnectException ("Connection refused" );
122
160
when (mapper .readValue (any (URL .class ), eq (String [].class ))).thenThrow (exception );
123
- final ExternalUrlKeyReader reader = new ExternalUrlKeyReader (VALID_URL , mapper , asyncRunner );
161
+ reader = new ExternalUrlKeyReader (VALID_URL , mapper , asyncRunner );
124
162
125
- final SafeFuture <String []> keys = reader .getKeysWithRetry ();
163
+ final SafeFuture <String []> keys = reader .getKeysWithRetry (asyncRunner );
126
164
for (int i = 0 ; i < MAX_RETRIES ; i ++) {
127
165
assertThat (keys ).isNotCompleted ();
128
166
timeProvider .advanceTimeBy (DELAY );
129
167
asyncRunner .executeQueuedActions ();
130
168
}
131
169
assertThat (keys ).isCompletedExceptionally ();
132
- assertThatThrownBy (keys ::join )
133
- .isInstanceOf (CompletionException .class )
134
- .hasCauseInstanceOf (InvalidConfigurationException .class )
135
- .hasRootCause (exception );
136
- verify (mapper , times (MAX_RETRIES + 1 )).readValue (any (URL .class ), eq (String [].class ));
170
+ assertThatThrownBy (keys ::join ).isInstanceOf (CompletionException .class ).hasRootCause (exception );
171
+ verifyReadCalls (MAX_RETRIES + 1 );
172
+ }
173
+
174
+ private void initialise (final String [] keys , final boolean retryEnabled ) throws IOException {
175
+ when (mapper .readValue (any (URL .class ), eq (String [].class ))).thenReturn (keys );
176
+ reader =
177
+ new ExternalUrlKeyReader (
178
+ VALID_URL , mapper , Optional .ofNullable (retryEnabled ? asyncRunner : null ));
179
+ }
180
+
181
+ private void verifyReadCall () throws IOException {
182
+ verifyReadCalls (1 );
183
+ }
184
+
185
+ private void verifyReadCalls (final int times ) throws IOException {
186
+ verify (mapper , times (times )).readValue (any (URL .class ), eq (String [].class ));
137
187
}
138
188
}
0 commit comments