2525import com .github .tomakehurst .wiremock .junit5 .WireMockExtension ;
2626import java .io .File ;
2727import java .lang .reflect .Field ;
28+ import java .util .ArrayList ;
29+ import java .util .List ;
2830import java .util .concurrent .CompletableFuture ;
31+ import java .util .concurrent .ExecutionException ;
2932import java .util .stream .Stream ;
3033import org .apache .commons .io .FileUtils ;
3134import org .junit .jupiter .api .AfterAll ;
@@ -51,6 +54,8 @@ public class EppoClientTest {
5154 private AssignmentLogger mockAssignmentLogger ;
5255 private BanditLogger mockBanditLogger ;
5356
57+ private static final byte [] EMPTY_CONFIG = "{\" flags\" :{}}" .getBytes ();
58+
5459 @ BeforeAll
5560 public static void initMockServer () {
5661 mockServer = new WireMockServer (TEST_PORT );
@@ -271,6 +276,47 @@ public void testGetConfiguration() {
271276 assertEquals (VariationType .NUMERIC , configuration .getFlagType ("numeric_flag" ));
272277 }
273278
279+ @ Test
280+ public void testConfigurationChangeListener () throws ExecutionException , InterruptedException {
281+ List <Configuration > received = new ArrayList <>();
282+
283+ // Set up a changing response from the "server"
284+ EppoHttpClient mockHttpClient = mock (EppoHttpClient .class );
285+
286+ // Mock sync get to return empty
287+ when (mockHttpClient .get (anyString ())).thenReturn (EMPTY_CONFIG );
288+
289+ // Mock async get to return empty
290+ when (mockHttpClient .get (anyString ())).thenReturn (EMPTY_CONFIG );
291+
292+ setBaseClientHttpClientOverrideField (mockHttpClient );
293+
294+ EppoClient .Builder clientBuilder =
295+ EppoClient .builder (DUMMY_FLAG_API_KEY )
296+ .forceReinitialize (true )
297+ .onConfigurationChange (received ::add )
298+ .isGracefulMode (false );
299+
300+ // Initialize and no exception should be thrown.
301+ EppoClient eppoClient = clientBuilder .buildAndInit ();
302+
303+ verify (mockHttpClient , times (1 )).get (anyString ());
304+ assertEquals (1 , received .size ());
305+
306+ // Now, return the boolean flag config so that the config has changed.
307+ when (mockHttpClient .get (anyString ())).thenReturn (BOOL_FLAG_CONFIG );
308+
309+ // Trigger a reload of the client
310+ eppoClient .loadConfiguration ();
311+
312+ assertEquals (2 , received .size ());
313+
314+ // Reload the client again; the config hasn't changed, but Java doesn't check eTag (yet)
315+ eppoClient .loadConfiguration ();
316+
317+ assertEquals (3 , received .size ());
318+ }
319+
274320 public static void mockHttpError () {
275321 // Create a mock instance of EppoHttpClient
276322 EppoHttpClient mockHttpClient = mock (EppoHttpClient .class );
@@ -353,4 +399,40 @@ public static void setBaseClientHttpClientOverrideField(EppoHttpClient httpClien
353399 throw new RuntimeException (e );
354400 }
355401 }
402+
403+ private static final byte [] BOOL_FLAG_CONFIG =
404+ ("{\n "
405+ + " \" createdAt\" : \" 2024-04-17T19:40:53.716Z\" ,\n "
406+ + " \" format\" : \" SERVER\" ,\n "
407+ + " \" environment\" : {\n "
408+ + " \" name\" : \" Test\" \n "
409+ + " },\n "
410+ + " \" flags\" : {\n "
411+ + " \" 9a2025738dde19ff44cd30b9d2967000\" : {\n "
412+ + " \" key\" : \" 9a2025738dde19ff44cd30b9d2967000\" ,\n "
413+ + " \" enabled\" : true,\n "
414+ + " \" variationType\" : \" BOOLEAN\" ,\n "
415+ + " \" variations\" : {\n "
416+ + " \" b24=\" : {\n "
417+ + " \" key\" : \" b24=\" ,\n "
418+ + " \" value\" : \" dHJ1ZQ==\" \n "
419+ + " }\n "
420+ + " },\n "
421+ + " \" allocations\" : [\n "
422+ + " {\n "
423+ + " \" key\" : \" b24=\" ,\n "
424+ + " \" doLog\" : true,\n "
425+ + " \" splits\" : [\n "
426+ + " {\n "
427+ + " \" variationKey\" : \" b24=\" ,\n "
428+ + " \" shards\" : []\n "
429+ + " }\n "
430+ + " ]\n "
431+ + " }\n "
432+ + " ],\n "
433+ + " \" totalShards\" : 10000\n "
434+ + " }\n "
435+ + " }\n "
436+ + "}" )
437+ .getBytes ();
356438}
0 commit comments