|
| 1 | +package javasdk; |
| 2 | + |
| 3 | +import org.junit.jupiter.api.Test; |
| 4 | + |
| 5 | +import static org.junit.jupiter.api.Assertions.fail; |
| 6 | + |
| 7 | +public class HookSpecTests { |
| 8 | + @Specification(spec="hooks", number="1.3", text="flag key, flag type, default value properties MUST be immutable. If the language does not support immutability, the hook MUST NOT modify these properties.") |
| 9 | + @Test void immutableValues() { |
| 10 | + try { |
| 11 | + HookContext.class.getMethod("setFlagKey"); |
| 12 | + fail("Shouldn't be able to find this method"); |
| 13 | + } catch (NoSuchMethodException e) { |
| 14 | + // expected |
| 15 | + } |
| 16 | + |
| 17 | + try { |
| 18 | + HookContext.class.getMethod("setType"); |
| 19 | + fail("Shouldn't be able to find this method"); |
| 20 | + } catch (NoSuchMethodException e) { |
| 21 | + // expected |
| 22 | + } |
| 23 | + |
| 24 | + try { |
| 25 | + HookContext.class.getMethod("setDefaultValue"); |
| 26 | + fail("Shouldn't be able to find this method"); |
| 27 | + } catch (NoSuchMethodException e) { |
| 28 | + // expected |
| 29 | + } |
| 30 | + } |
| 31 | + |
| 32 | + @Specification(spec="hooks", number="1.1", text="Hook context MUST provide: the flag key, flag type, evaluation context, and the default value.") |
| 33 | + @Test void nullish_properties_on_hookcontext() { |
| 34 | + // missing ctx |
| 35 | + try { |
| 36 | + HookContext.<Integer>builder() |
| 37 | + .flagKey("key") |
| 38 | + .type(FlagValueType.INTEGER) |
| 39 | + .defaultValue(1) |
| 40 | + .build(); |
| 41 | + fail("Missing context shouldn't be valid"); |
| 42 | + } catch (NullPointerException e) { |
| 43 | + // expected |
| 44 | + } |
| 45 | + |
| 46 | + // missing type |
| 47 | + try { |
| 48 | + HookContext.<Integer>builder() |
| 49 | + .flagKey("key") |
| 50 | + .ctx(new EvaluationContext()) |
| 51 | + .defaultValue(1) |
| 52 | + .build(); |
| 53 | + fail("Missing type shouldn't be valid"); |
| 54 | + } catch (NullPointerException e) { |
| 55 | + // expected |
| 56 | + } |
| 57 | + |
| 58 | + // missing key |
| 59 | + try { |
| 60 | + HookContext.<Integer>builder() |
| 61 | + .type(FlagValueType.INTEGER) |
| 62 | + .ctx(new EvaluationContext()) |
| 63 | + .defaultValue(1) |
| 64 | + .build(); |
| 65 | + fail("Missing key shouldn't be valid"); |
| 66 | + } catch (NullPointerException e) { |
| 67 | + // expected |
| 68 | + } |
| 69 | + |
| 70 | + // missing default value |
| 71 | + try { |
| 72 | + HookContext.<Integer>builder() |
| 73 | + .flagKey("key") |
| 74 | + .type(FlagValueType.INTEGER) |
| 75 | + .ctx(new EvaluationContext()) |
| 76 | + .build(); |
| 77 | + fail("Missing default value shouldn't be valid"); |
| 78 | + } catch (NullPointerException e) { |
| 79 | + // expected |
| 80 | + } |
| 81 | + |
| 82 | + // normal |
| 83 | + try { |
| 84 | + HookContext.<Integer>builder() |
| 85 | + .flagKey("key") |
| 86 | + .type(FlagValueType.INTEGER) |
| 87 | + .ctx(new EvaluationContext()) |
| 88 | + .defaultValue(1) |
| 89 | + .build(); |
| 90 | + } catch (NullPointerException e) { |
| 91 | + fail("NPE after we provided all relevant info"); |
| 92 | + } |
| 93 | + |
| 94 | + } |
| 95 | + |
| 96 | + @Specification(spec="hooks", number="1.2", text="Hook context SHOULD provide: provider (instance) and client (instance)") |
| 97 | + @Test void optional_properties() { |
| 98 | + // don't specify |
| 99 | + HookContext.<Integer>builder() |
| 100 | + .flagKey("key") |
| 101 | + .type(FlagValueType.INTEGER) |
| 102 | + .ctx(new EvaluationContext()) |
| 103 | + .defaultValue(1) |
| 104 | + .build(); |
| 105 | + |
| 106 | + // add optional provider |
| 107 | + HookContext.<Integer>builder() |
| 108 | + .flagKey("key") |
| 109 | + .type(FlagValueType.INTEGER) |
| 110 | + .ctx(new EvaluationContext()) |
| 111 | + .provider(new NoOpProvider()) |
| 112 | + .defaultValue(1) |
| 113 | + .build(); |
| 114 | + |
| 115 | + // add optional client |
| 116 | + HookContext.<Integer>builder() |
| 117 | + .flagKey("key") |
| 118 | + .type(FlagValueType.INTEGER) |
| 119 | + .ctx(new EvaluationContext()) |
| 120 | + .defaultValue(1) |
| 121 | + .client(OpenFeatureAPI.getInstance().getClient()) |
| 122 | + .build(); |
| 123 | + } |
| 124 | + |
| 125 | + @Specification(spec="hooks", number="1.4", text="The evaluation context MUST be mutable only within the before hook.") |
| 126 | + @Specification(spec="hooks", number="2.1", text="HookHints MUST be a map of objects.") |
| 127 | + @Specification(spec="hooks", number="2.2", text="Condition: HookHints MUST be immutable.") |
| 128 | + @Specification(spec="hooks", number="3.1", text="Hooks MUST specify at least one stage.") |
| 129 | + @Specification(spec="hooks", number="3.2", text="The before stage MUST run before flag evaluation occurs. It accepts a hook context (required) and HookHints (optional) as parameters and returns either a HookContext or nothing.") |
| 130 | + @Specification(spec="hooks", number="3.3", text="The after stage MUST run after flag evaluation occurs. It accepts a hook context (required), flag evaluation details (required) and HookHints (optional). It has no return value.") |
| 131 | + @Specification(spec="hooks", number="3.4", text="The error hook MUST run when errors are encountered in the before stage, the after stage or during flag evaluation. It accepts hook context (required), exception for what went wrong (required), and HookHints (optional). It has no return value.") |
| 132 | + @Specification(spec="hooks", number="3.5", text="The finally hook MUST run after the before, after, and error stages. It accepts a hook context (required) and HookHints (optional). There is no return value.") |
| 133 | + @Specification(spec="hooks", number="3.6", text="Condition: If finally is a reserved word in the language, finallyAfter SHOULD be used.") |
| 134 | + @Specification(spec="hooks", number="4.1", text="The API, Client and invocation MUST have a method for registering hooks which accepts flag evaluation options") |
| 135 | + @Specification(spec="hooks", number="4.2", text="Hooks MUST be evaluated in the following order:" + |
| 136 | + "before: API, Client, Invocation" + |
| 137 | + "after: Invocation, Client, API" + |
| 138 | + "error (if applicable): Invocation, Client, API" + |
| 139 | + "finally: Invocation, Client, API") |
| 140 | + @Specification(spec="hooks", number="4.3", text=" If an error occurs in the finally hook, it MUST NOT trigger the error hook.") |
| 141 | + @Specification(spec="hooks", number="4.4", text="If an error occurs in the before or after hooks, the error hooks MUST be invoked.") |
| 142 | + @Specification(spec="hooks", number="4.5", 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.") |
| 143 | + @Specification(spec="hooks", number="4.6", text="If an error is encountered in the error stage, it MUST NOT be returned to the user.") |
| 144 | + @Specification(spec="hooks", number="5.1", text="Flag evalution options MUST contain a list of hooks to evaluate.") |
| 145 | + @Specification(spec="hooks", number="5.2", text="Flag evaluation options MAY contain HookHints, a map of data to be provided to hook invocations.") |
| 146 | + @Specification(spec="hooks", number="5.3", text="HookHints MUST be passed to each hook through a parameter. It is merged into the object in the precedence order API -> Client -> Invocation (last wins).") |
| 147 | + @Specification(spec="hooks", number="5.4", text="The hook MUST NOT alter the HookHints object.") |
| 148 | + @Specification(spec="hooks", number="6.1", text="HookHints MUST passed between each hook.") |
| 149 | + void todo() {} |
| 150 | + |
| 151 | + |
| 152 | +} |
0 commit comments