Skip to content

Commit 4487959

Browse files
committed
Spec annotations in the right place, approximately
1 parent 30c5ac4 commit 4487959

File tree

1 file changed

+35
-15
lines changed

1 file changed

+35
-15
lines changed

lib/src/test/java/dev/openfeature/javasdk/HookSpecTests.java

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ void emptyApiHooks() {
140140
.build();
141141
}
142142

143+
@Specification(number="4.3.2", text="The before stage MUST run before flag resolution occurs. It accepts a hook context (required) and hook hints (optional) as parameters and returns either an evaluation context or nothing.")
143144
@Test void before_runs_ahead_of_evaluation() {
144145
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
145146
api.setProvider(new AlwaysBrokenProvider());
@@ -180,7 +181,12 @@ void emptyApiHooks() {
180181
verify(h, times(0)).error(any(), any(), any());
181182
}
182183

184+
183185
@Specification(number="4.4.1", text="The API, Client and invocation MUST have a method for registering hooks which accepts flag evaluation options")
186+
@Specification(number="4.3.5", text="The after stage MUST run after flag resolution occurs. It accepts a hook context (required), flag evaluation details (required) and hook hints (optional). It has no return value.")
187+
@Specification(number="4.4.2", text="Hooks MUST be evaluated in the following order: - before: API, Client, Invocation - after: Invocation, Client, API - error (if applicable): Invocation, Client, API - finally: Invocation, Client, API")
188+
@Specification(number="4.3.6", text="The error hook MUST run when errors are encountered in the before stage, the after stage or during flag resolution. It accepts hook context (required), exception representing what went wrong (required), and hook hints (optional). It has no return value.")
189+
@Specification(number="4.3.7", text="The finally hook MUST run after the before, after, and error stages. It accepts a hook context (required) and hook hints (optional). There is no return value.")
184190
@Test void hook_eval_order() {
185191
List<String> evalOrder = new ArrayList<String>();
186192
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
@@ -281,6 +287,8 @@ public void finallyAfter(HookContext<Boolean> ctx, ImmutableMap<String, Object>
281287
}
282288

283289

290+
@Specification(number="4.2.1", text="hook hints MUST be a structure supports definition of arbitrary properties, with keys of type string, and values of type boolean | string | number | datetime | structure..")
291+
@Specification(number="4.5.2", text="hook hints MUST be passed to each hook.")
284292
@Test void hook_hints() {
285293
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
286294
api.setProvider(new NoOpProvider());
@@ -316,6 +324,7 @@ public void finallyAfter(HookContext<Boolean> ctx, ImmutableMap<String, Object>
316324
.build());
317325
}
318326

327+
@Specification(number="4.5.1", text="Flag evaluation options MAY contain hook hints, a map of data to be provided to hook invocations.")
319328
@Test void missing_hook_hints() {
320329
FlagEvaluationOptions feo = FlagEvaluationOptions.builder().build();
321330
assertNotNull(feo.getHookHints());
@@ -343,7 +352,8 @@ public void finallyAfter(HookContext<Boolean> ctx, ImmutableMap<String, Object>
343352
order.verify(hook).finallyAfter(any(),any());
344353
}
345354

346-
@Test void error_hooks() {
355+
@Specification(number="4.4.5", text="If an error occurs in the before or after hooks, the error hooks MUST be invoked.")
356+
@Test void error_hooks__before() {
347357
Hook hook = mock(Hook.class);
348358
doThrow(RuntimeException.class).when(hook).before(any(), any());
349359
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
@@ -355,6 +365,19 @@ public void finallyAfter(HookContext<Boolean> ctx, ImmutableMap<String, Object>
355365
verify(hook, times(1)).error(any(), any(), any());
356366
}
357367

368+
@Specification(number="4.4.5", text="If an error occurs in the before or after hooks, the error hooks MUST be invoked.")
369+
@Test void error_hooks__after() {
370+
Hook hook = mock(Hook.class);
371+
doThrow(RuntimeException.class).when(hook).after(any(), any(), any());
372+
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
373+
api.setProvider(new NoOpProvider());
374+
Client client = api.getClient();
375+
client.getBooleanValue("key", false, new EvaluationContext(),
376+
FlagEvaluationOptions.builder().hook(hook).build());
377+
verify(hook, times(1)).after(any(), any(), any());
378+
verify(hook, times(1)).error(any(), any(), any());
379+
}
380+
358381
@Test void multi_hooks_early_out__before() {
359382
Hook hook = mock(Hook.class);
360383
Hook hook2 = mock(Hook.class);
@@ -377,6 +400,8 @@ public void finallyAfter(HookContext<Boolean> ctx, ImmutableMap<String, Object>
377400
verify(hook2, times(1)).error(any(), any(), any());
378401
}
379402

403+
@Specification(number="4.1.4", text="The evaluation context MUST be mutable only within the before hook.")
404+
@Specification(number="4.3.3", text="Any evaluation context returned from a before hook MUST be passed to subsequent before hooks (via HookContext).")
380405
@Test void beforeContextUpdated() {
381406
EvaluationContext ctx = new EvaluationContext();
382407
Hook hook = mock(Hook.class);
@@ -404,6 +429,8 @@ public void finallyAfter(HookContext<Boolean> ctx, ImmutableMap<String, Object>
404429
assertEquals(hc.getCtx(), ctx);
405430

406431
}
432+
433+
@Specification(number="4.3.4", text="When before hooks have finished executing, any resulting evaluation context MUST be merged with the invocation evaluation context with the invocation evaluation context taking precedence in the case of any conflicts.")
407434
@Test void mergeHappensCorrectly() {
408435
EvaluationContext hookCtx = new EvaluationContext();
409436
hookCtx.addStringAttribute("test", "broken");
@@ -437,28 +464,21 @@ public void finallyAfter(HookContext<Boolean> ctx, ImmutableMap<String, Object>
437464

438465

439466
@Specification(number="4.1.2", text="The hook context SHOULD provide: access to the client metadata and the provider metadata fields.")
440-
@Specification(number="4.1.4", text="The evaluation context MUST be mutable only within the before hook.")
441-
@Specification(number="4.2.1", text="hook hints MUST be a structure supports definition of arbitrary properties, with keys of type string, and values of type boolean | string | number | datetime | structure..")
442467
@Specification(number="4.2.2.1", text="Condition: Hook hints MUST be immutable.")
443468
@Specification(number="4.2.2.2", text="Condition: The client metadata field in the hook context MUST be immutable.")
444469
@Specification(number="4.2.2.3", text="Condition: The provider metadata field in the hook context MUST be immutable.")
445-
@Specification(number="4.3.1", text="Hooks MUST specify at least one stage.")
446-
@Specification(number="4.3.2", text="The before stage MUST run before flag resolution occurs. It accepts a hook context (required) and hook hints (optional) as parameters and returns either an evaluation context or nothing.")
447-
@Specification(number="4.3.3", text="Any evaluation context returned from a before hook MUST be passed to subsequent before hooks (via HookContext).")
448-
@Specification(number="4.3.4", text="When before hooks have finished executing, any resulting evaluation context MUST be merged with the invocation evaluation context with the invocation evaluation context taking precedence in the case of any conflicts.")
449-
@Specification(number="4.3.5", text="The after stage MUST run after flag resolution occurs. It accepts a hook context (required), flag evaluation details (required) and hook hints (optional). It has no return value.")
450-
@Specification(number="4.3.6", text="The error hook MUST run when errors are encountered in the before stage, the after stage or during flag resolution. It accepts hook context (required), exception representing what went wrong (required), and hook hints (optional). It has no return value.")
451-
@Specification(number="4.3.7", text="The finally hook MUST run after the before, after, and error stages. It accepts a hook context (required) and hook hints (optional). There is no return value.")
452-
@Specification(number="4.3.8.1", text="Instead of finally, finallyAfter SHOULD be used.")
453-
@Specification(number="4.4.2", text="Hooks MUST be evaluated in the following order: - before: API, Client, Invocation - after: Invocation, Client, API - error (if applicable): Invocation, Client, API - finally: Invocation, Client, API")
470+
471+
454472
@Specification(number="4.4.3", text="If a finally hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining finally hooks.")
455473
@Specification(number="4.4.4", text="If an error hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining error hooks.")
456-
@Specification(number="4.4.5", text="If an error occurs in the before or after hooks, the error hooks MUST be invoked.")
457-
@Specification(number="4.5.1", text="Flag evaluation options MAY contain hook hints, a map of data to be provided to hook invocations.")
458-
@Specification(number="4.5.2", text="hook hints MUST be passed to each hook.")
474+
459475
@Specification(number="4.5.3", text="The hook MUST NOT alter the hook hints structure.")
460476
@Test @Disabled void todo() {}
461477

478+
@Specification(number="4.3.1", text="Hooks MUST specify at least one stage.")
479+
@Test void default_methods_so_impossible() {}
480+
481+
@Specification(number="4.3.8.1", text="Instead of finally, finallyAfter SHOULD be used.")
462482
@SneakyThrows
463483
@Test void doesnt_use_finally() {
464484
try {

0 commit comments

Comments
 (0)