1212
1313import java .util .*;
1414
15- import static org .assertj .core .api .Assertions .assertThatCode ;
1615import static org .assertj .core .api .Assertions .fail ;
16+ import static org .assertj .core .api .Assertions .*;
1717import static org .junit .jupiter .api .Assertions .*;
1818import static org .mockito .ArgumentMatchers .any ;
1919import static org .mockito .Mockito .*;
@@ -115,7 +115,7 @@ void nullish_properties_on_hookcontext() {
115115
116116 }
117117
118- @ Specification (number = "4.1.2" , text = "The hook context SHOULD provide: access to the client metadata and the provider metadata fields." )
118+ @ Specification (number = "4.1.2" , text = "The ` hook context` SHOULD provide access to the ` client metadata` and the ` provider metadata` fields." )
119119 @ Test
120120 void optional_properties () {
121121 // don't specify
@@ -170,7 +170,7 @@ void feo_has_hook_list() {
170170 void error_hook_run_during_non_finally_stage () {
171171 final boolean [] error_called = {false };
172172 Hook h = mockBooleanHook ();
173- doThrow (RuntimeException .class ).when (h ).finallyAfter (any (), any ());
173+ doThrow (RuntimeException .class ).when (h ).finallyAfter (any (), any (), any () );
174174
175175 verify (h , times (0 )).error (any (), any (), any ());
176176 }
@@ -201,7 +201,7 @@ void error_hook_must_run_if_resolution_details_returns_an_error_code() {
201201
202202 verify (hook , times (1 )).before (any (), any ());
203203 verify (hook , times (1 )).error (any (), captor .capture (), any ());
204- verify (hook , times (1 )).finallyAfter (any (), any ());
204+ verify (hook , times (1 )).finallyAfter (any (), any (), any () );
205205 verify (hook , never ()).after (any (), any (), any ());
206206
207207 Exception exception = captor .getValue ();
@@ -241,7 +241,7 @@ public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object>
241241 }
242242
243243 @ Override
244- public void finallyAfter (HookContext <Boolean > ctx , Map <String , Object > hints ) {
244+ public void finallyAfter (HookContext <Boolean > ctx , FlagEvaluationDetails < Boolean > details , Map <String , Object > hints ) {
245245 evalOrder .add ("provider finally" );
246246 }
247247 });
@@ -266,7 +266,7 @@ public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object>
266266 }
267267
268268 @ Override
269- public void finallyAfter (HookContext <Boolean > ctx , Map <String , Object > hints ) {
269+ public void finallyAfter (HookContext <Boolean > ctx , FlagEvaluationDetails < Boolean > details , Map <String , Object > hints ) {
270270 evalOrder .add ("api finally" );
271271 }
272272 });
@@ -290,7 +290,7 @@ public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object>
290290 }
291291
292292 @ Override
293- public void finallyAfter (HookContext <Boolean > ctx , Map <String , Object > hints ) {
293+ public void finallyAfter (HookContext <Boolean > ctx , FlagEvaluationDetails < Boolean > details , Map <String , Object > hints ) {
294294 evalOrder .add ("client finally" );
295295 }
296296 });
@@ -315,7 +315,7 @@ public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object>
315315 }
316316
317317 @ Override
318- public void finallyAfter (HookContext <Boolean > ctx , Map <String , Object > hints ) {
318+ public void finallyAfter (HookContext <Boolean > ctx , FlagEvaluationDetails < Boolean > details , Map <String , Object > hints ) {
319319 evalOrder .add ("invocation finally" );
320320 }
321321 })
@@ -344,8 +344,8 @@ void error_stops_before() {
344344 .hook (h2 )
345345 .hook (h )
346346 .build ());
347- verify (h , times (1 )).before (any (), any ());
348- verify (h2 , times (0 )).before (any (), any ());
347+ verify (h , times (1 )).before (any (), any ());
348+ verify (h2 , times (0 )).before (any (), any ());
349349 }
350350
351351 @ Specification (number = "4.4.6" , text = "If an error occurs during the evaluation of before or after hooks, any remaining hooks in the before or after stages MUST NOT be invoked." )
@@ -393,7 +393,7 @@ public void error(HookContext<Boolean> ctx, Exception error, Map<String, Object>
393393 }
394394
395395 @ Override
396- public void finallyAfter (HookContext <Boolean > ctx , Map <String , Object > hints ) {
396+ public void finallyAfter (HookContext <Boolean > ctx , FlagEvaluationDetails < Boolean > details , Map <String , Object > hints ) {
397397 assertThatCode (() -> hints .put (hintKey , "changed value" )).isInstanceOf (UnsupportedOperationException .class );
398398 }
399399 };
@@ -435,7 +435,7 @@ void flag_eval_hook_order() {
435435 order .verify (hook ).before (any (), any ());
436436 order .verify (provider ).getBooleanEvaluation (any (), any (), any ());
437437 order .verify (hook ).after (any (), any (), any ());
438- order .verify (hook ).finallyAfter (any (), any ());
438+ order .verify (hook ).finallyAfter (any (), any (), any () );
439439 }
440440
441441 @ Specification (number = "4.4.5" , text = "If an error occurs in the before or after hooks, the error hooks MUST be invoked." )
@@ -464,6 +464,58 @@ void error_hooks__after() {
464464 verify (hook , times (1 )).error (any (), any (), any ());
465465 }
466466
467+ @ Test
468+ void erroneous_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails () {
469+ Hook hook = mockBooleanHook ();
470+ doThrow (RuntimeException .class ).when (hook ).after (any (), any (), any ());
471+ String flagKey = "test-flag-key" ;
472+ Client client = getClient (TestEventsProvider .newInitializedTestEventsProvider ());
473+ client .getBooleanValue (
474+ flagKey ,
475+ true ,
476+ new ImmutableContext (),
477+ FlagEvaluationOptions .builder ().hook (hook ).build ()
478+ );
479+
480+ ArgumentCaptor <FlagEvaluationDetails <Boolean >> captor = ArgumentCaptor .forClass (FlagEvaluationDetails .class );
481+ verify (hook ).finallyAfter (any (), captor .capture (), any ());
482+
483+ FlagEvaluationDetails <Boolean > evaluationDetails = captor .getValue ();
484+ assertThat (evaluationDetails ).isNotNull ();
485+
486+ assertThat (evaluationDetails .getErrorCode ()).isEqualTo (ErrorCode .GENERAL );
487+ assertThat (evaluationDetails .getReason ()).isEqualTo ("ERROR" );
488+ assertThat (evaluationDetails .getVariant ()).isEqualTo ("Passed in default" );
489+ assertThat (evaluationDetails .getFlagKey ()).isEqualTo (flagKey );
490+ assertThat (evaluationDetails .getFlagMetadata ()).isEqualTo (ImmutableMetadata .builder ().build ());
491+ assertThat (evaluationDetails .getValue ()).isEqualTo (true );
492+ }
493+
494+ @ Test
495+ void successful_flagResolution_setsAppropriateFieldsInFlagEvaluationDetails () {
496+ Hook hook = mockBooleanHook ();
497+ String flagKey = "test-flag-key" ;
498+ Client client = getClient (TestEventsProvider .newInitializedTestEventsProvider ());
499+ client .getBooleanValue (
500+ flagKey ,
501+ true ,
502+ new ImmutableContext (),
503+ FlagEvaluationOptions .builder ().hook (hook ).build ()
504+ );
505+
506+ ArgumentCaptor <FlagEvaluationDetails <Boolean >> captor = ArgumentCaptor .forClass (FlagEvaluationDetails .class );
507+ verify (hook ).finallyAfter (any (), captor .capture (), any ());
508+
509+ FlagEvaluationDetails <Boolean > evaluationDetails = captor .getValue ();
510+ assertThat (evaluationDetails ).isNotNull ();
511+ assertThat (evaluationDetails .getErrorCode ()).isNull ();
512+ assertThat (evaluationDetails .getReason ()).isEqualTo ("DEFAULT" );
513+ assertThat (evaluationDetails .getVariant ()).isEqualTo ("Passed in default" );
514+ assertThat (evaluationDetails .getFlagKey ()).isEqualTo (flagKey );
515+ assertThat (evaluationDetails .getFlagMetadata ()).isEqualTo (ImmutableMetadata .builder ().build ());
516+ assertThat (evaluationDetails .getValue ()).isEqualTo (true );
517+ }
518+
467519 @ Test
468520 void multi_hooks_early_out__before () {
469521 Hook <Boolean > hook = mockBooleanHook ();
@@ -556,7 +608,7 @@ void mergeHappensCorrectly() {
556608 void first_finally_broken () {
557609 Hook hook = mockBooleanHook ();
558610 doThrow (RuntimeException .class ).when (hook ).before (any (), any ());
559- doThrow (RuntimeException .class ).when (hook ).finallyAfter (any (), any ());
611+ doThrow (RuntimeException .class ).when (hook ).finallyAfter (any (), any (), any () );
560612 Hook hook2 = mockBooleanHook ();
561613 InOrder order = inOrder (hook , hook2 );
562614
@@ -568,8 +620,8 @@ void first_finally_broken() {
568620 .build ());
569621
570622 order .verify (hook ).before (any (), any ());
571- order .verify (hook2 ).finallyAfter (any (), any ());
572- order .verify (hook ).finallyAfter (any (), any ());
623+ order .verify (hook2 ).finallyAfter (any (), any (), any () );
624+ order .verify (hook ).finallyAfter (any (), any (), any () );
573625 }
574626
575627 @ Specification (number = "4.4.4" , text = "If an error hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining error hooks." )
@@ -616,8 +668,7 @@ void doesnt_use_finally() {
616668 .as ("Not possible. Finally is a reserved word." )
617669 .isInstanceOf (NoSuchMethodException .class );
618670
619- assertThatCode (() -> Hook .class .getMethod ("finallyAfter" , HookContext .class , Map .class ))
671+ assertThatCode (() -> Hook .class .getMethod ("finallyAfter" , HookContext .class , FlagEvaluationDetails . class , Map .class ))
620672 .doesNotThrowAnyException ();
621673 }
622-
623674}
0 commit comments