11package dev .openfeature .sdk ;
22
3+ import static org .assertj .core .api .Assertions .assertThat ;
34import static org .assertj .core .api .Assertions .assertThatCode ;
45import static org .assertj .core .api .Assertions .fail ;
5- import static org .junit .jupiter .api .Assertions .*;
6+ import static org .junit .jupiter .api .Assertions .assertEquals ;
7+ import static org .junit .jupiter .api .Assertions .assertInstanceOf ;
8+ import static org .junit .jupiter .api .Assertions .assertNotNull ;
9+ import static org .junit .jupiter .api .Assertions .assertTrue ;
610import static org .mockito .ArgumentMatchers .any ;
7- import static org .mockito .Mockito .*;
11+ import static org .mockito .Mockito .doThrow ;
12+ import static org .mockito .Mockito .inOrder ;
13+ import static org .mockito .Mockito .mock ;
14+ import static org .mockito .Mockito .never ;
15+ import static org .mockito .Mockito .times ;
16+ import static org .mockito .Mockito .verify ;
17+ import static org .mockito .Mockito .when ;
818
919import dev .openfeature .sdk .exceptions .FlagNotFoundError ;
1020import dev .openfeature .sdk .fixtures .HookFixtures ;
@@ -187,7 +197,7 @@ void feo_has_hook_list() {
187197 void error_hook_run_during_non_finally_stage () {
188198 final boolean [] error_called = {false };
189199 Hook h = mockBooleanHook ();
190- doThrow (RuntimeException .class ).when (h ).finallyAfter (any (), any ());
200+ doThrow (RuntimeException .class ).when (h ).finallyAfter (any (), any (), any () );
191201
192202 verify (h , times (0 )).error (any (), any (), any ());
193203 }
@@ -219,7 +229,7 @@ void error_hook_must_run_if_resolution_details_returns_an_error_code() {
219229
220230 verify (hook , times (1 )).before (any (), any ());
221231 verify (hook , times (1 )).error (any (), captor .capture (), any ());
222- verify (hook , times (1 )).finallyAfter (any (), any ());
232+ verify (hook , times (1 )).finallyAfter (any (), any (), any () );
223233 verify (hook , never ()).after (any (), any (), any ());
224234
225235 Exception exception = captor .getValue ();
@@ -274,7 +284,10 @@ public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object>
274284 }
275285
276286 @ Override
277- public void finallyAfter (HookContext <Boolean > ctx , Map <String , Object > hints ) {
287+ public void finallyAfter (
288+ HookContext <Boolean > ctx ,
289+ FlagEvaluationDetails <Boolean > details ,
290+ Map <String , Object > hints ) {
278291 evalOrder .add ("provider finally" );
279292 }
280293 });
@@ -300,7 +313,8 @@ public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object>
300313 }
301314
302315 @ Override
303- public void finallyAfter (HookContext <Boolean > ctx , Map <String , Object > hints ) {
316+ public void finallyAfter (
317+ HookContext <Boolean > ctx , FlagEvaluationDetails <Boolean > details , Map <String , Object > hints ) {
304318 evalOrder .add ("api finally" );
305319 }
306320 });
@@ -325,7 +339,8 @@ public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object>
325339 }
326340
327341 @ Override
328- public void finallyAfter (HookContext <Boolean > ctx , Map <String , Object > hints ) {
342+ public void finallyAfter (
343+ HookContext <Boolean > ctx , FlagEvaluationDetails <Boolean > details , Map <String , Object > hints ) {
329344 evalOrder .add ("client finally" );
330345 }
331346 });
@@ -357,7 +372,10 @@ public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object>
357372 }
358373
359374 @ Override
360- public void finallyAfter (HookContext <Boolean > ctx , Map <String , Object > hints ) {
375+ public void finallyAfter (
376+ HookContext <Boolean > ctx ,
377+ FlagEvaluationDetails <Boolean > details ,
378+ Map <String , Object > hints ) {
361379 evalOrder .add ("invocation finally" );
362380 }
363381 })
@@ -462,7 +480,8 @@ public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object>
462480 }
463481
464482 @ Override
465- public void finallyAfter (HookContext <Boolean > ctx , Map <String , Object > hints ) {
483+ public void finallyAfter (
484+ HookContext <Boolean > ctx , FlagEvaluationDetails <Boolean > details , Map <String , Object > hints ) {
466485 assertThatCode (() -> hints .put (hintKey , "changed value" ))
467486 .isInstanceOf (UnsupportedOperationException .class );
468487 }
@@ -509,7 +528,7 @@ void flag_eval_hook_order() {
509528 order .verify (hook ).before (any (), any ());
510529 order .verify (provider ).getBooleanEvaluation (any (), any (), any ());
511530 order .verify (hook ).after (any (), any (), any ());
512- order .verify (hook ).finallyAfter (any (), any ());
531+ order .verify (hook ).finallyAfter (any (), any (), any () );
513532 }
514533
515534 @ Specification (
@@ -550,6 +569,58 @@ void error_hooks__after() {
550569 verify (hook , times (1 )).error (any (), any (), any ());
551570 }
552571
572+ @ Test
573+ void erroneous_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails () {
574+ Hook hook = mockBooleanHook ();
575+ doThrow (RuntimeException .class ).when (hook ).after (any (), any (), any ());
576+ String flagKey = "test-flag-key" ;
577+ Client client = getClient (TestEventsProvider .newInitializedTestEventsProvider ());
578+ client .getBooleanValue (
579+ flagKey ,
580+ true ,
581+ new ImmutableContext (),
582+ FlagEvaluationOptions .builder ().hook (hook ).build ());
583+
584+ ArgumentCaptor <FlagEvaluationDetails <Boolean >> captor = ArgumentCaptor .forClass (FlagEvaluationDetails .class );
585+ verify (hook ).finallyAfter (any (), captor .capture (), any ());
586+
587+ FlagEvaluationDetails <Boolean > evaluationDetails = captor .getValue ();
588+ assertThat (evaluationDetails ).isNotNull ();
589+
590+ assertThat (evaluationDetails .getErrorCode ()).isEqualTo (ErrorCode .GENERAL );
591+ assertThat (evaluationDetails .getReason ()).isEqualTo ("ERROR" );
592+ assertThat (evaluationDetails .getVariant ()).isEqualTo ("Passed in default" );
593+ assertThat (evaluationDetails .getFlagKey ()).isEqualTo (flagKey );
594+ assertThat (evaluationDetails .getFlagMetadata ())
595+ .isEqualTo (ImmutableMetadata .builder ().build ());
596+ assertThat (evaluationDetails .getValue ()).isTrue ();
597+ }
598+
599+ @ Test
600+ void successful_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails () {
601+ Hook hook = mockBooleanHook ();
602+ String flagKey = "test-flag-key" ;
603+ Client client = getClient (TestEventsProvider .newInitializedTestEventsProvider ());
604+ client .getBooleanValue (
605+ flagKey ,
606+ true ,
607+ new ImmutableContext (),
608+ FlagEvaluationOptions .builder ().hook (hook ).build ());
609+
610+ ArgumentCaptor <FlagEvaluationDetails <Boolean >> captor = ArgumentCaptor .forClass (FlagEvaluationDetails .class );
611+ verify (hook ).finallyAfter (any (), captor .capture (), any ());
612+
613+ FlagEvaluationDetails <Boolean > evaluationDetails = captor .getValue ();
614+ assertThat (evaluationDetails ).isNotNull ();
615+ assertThat (evaluationDetails .getErrorCode ()).isNull ();
616+ assertThat (evaluationDetails .getReason ()).isEqualTo ("DEFAULT" );
617+ assertThat (evaluationDetails .getVariant ()).isEqualTo ("Passed in default" );
618+ assertThat (evaluationDetails .getFlagKey ()).isEqualTo (flagKey );
619+ assertThat (evaluationDetails .getFlagMetadata ())
620+ .isEqualTo (ImmutableMetadata .builder ().build ());
621+ assertThat (evaluationDetails .getValue ()).isTrue ();
622+ }
623+
553624 @ Test
554625 void multi_hooks_early_out__before () {
555626 Hook <Boolean > hook = mockBooleanHook ();
@@ -649,7 +720,7 @@ void mergeHappensCorrectly() {
649720 void first_finally_broken () {
650721 Hook hook = mockBooleanHook ();
651722 doThrow (RuntimeException .class ).when (hook ).before (any (), any ());
652- doThrow (RuntimeException .class ).when (hook ).finallyAfter (any (), any ());
723+ doThrow (RuntimeException .class ).when (hook ).finallyAfter (any (), any (), any () );
653724 Hook hook2 = mockBooleanHook ();
654725 InOrder order = inOrder (hook , hook2 );
655726
@@ -661,8 +732,8 @@ void first_finally_broken() {
661732 FlagEvaluationOptions .builder ().hook (hook2 ).hook (hook ).build ());
662733
663734 order .verify (hook ).before (any (), any ());
664- order .verify (hook2 ).finallyAfter (any (), any ());
665- order .verify (hook ).finallyAfter (any (), any ());
735+ order .verify (hook2 ).finallyAfter (any (), any (), any () );
736+ order .verify (hook ).finallyAfter (any (), any (), any () );
666737 }
667738
668739 @ Specification (
@@ -711,7 +782,8 @@ void doesnt_use_finally() {
711782 .as ("Not possible. Finally is a reserved word." )
712783 .isInstanceOf (NoSuchMethodException .class );
713784
714- assertThatCode (() -> Hook .class .getMethod ("finallyAfter" , HookContext .class , Map .class ))
785+ assertThatCode (() ->
786+ Hook .class .getMethod ("finallyAfter" , HookContext .class , FlagEvaluationDetails .class , Map .class ))
715787 .doesNotThrowAnyException ();
716788 }
717789}
0 commit comments