|
| 1 | +import { StepDefinitions } from 'jest-cucumber'; |
| 2 | +import { |
| 3 | + EvaluationContext, |
| 4 | + EvaluationDetails, |
| 5 | + FlagValue, |
| 6 | + JsonObject, |
| 7 | + OpenFeature, |
| 8 | + ProviderEvents, |
| 9 | + StandardResolutionReasons, |
| 10 | +} from '@openfeature/web-sdk'; |
| 11 | +import { E2E_CLIENT_NAME } from '../constants'; |
| 12 | + |
| 13 | +export const flagStepDefinitions: StepDefinitions = ({ given, and, when, then }) => { |
| 14 | + let flagKey: string; |
| 15 | + let value: FlagValue; |
| 16 | + let context: EvaluationContext = {}; |
| 17 | + let details: EvaluationDetails<FlagValue>; |
| 18 | + let fallback: FlagValue; |
| 19 | + let flagsChanged: string[]; |
| 20 | + |
| 21 | + const client = OpenFeature.getClient(E2E_CLIENT_NAME); |
| 22 | + |
| 23 | + beforeAll((done) => { |
| 24 | + client.addHandler(ProviderEvents.Ready, () => { |
| 25 | + done(); |
| 26 | + }); |
| 27 | + }); |
| 28 | + |
| 29 | + beforeEach(() => { |
| 30 | + context = {}; |
| 31 | + }); |
| 32 | + |
| 33 | + given('a provider is registered', () => undefined); |
| 34 | + given('a flagd provider is set', () => undefined); |
| 35 | + |
| 36 | + when( |
| 37 | + /^a boolean flag with key "(.*)" is evaluated with default value "(.*)"$/, |
| 38 | + async (key: string, defaultValue: string) => { |
| 39 | + flagKey = key; |
| 40 | + fallback = defaultValue; |
| 41 | + value = await client.getBooleanValue(key, defaultValue === 'true'); |
| 42 | + }, |
| 43 | + ); |
| 44 | + |
| 45 | + then(/^the resolved boolean value should be "(.*)"$/, (expectedValue: string) => { |
| 46 | + expect(value).toEqual(expectedValue === 'true'); |
| 47 | + }); |
| 48 | + |
| 49 | + when( |
| 50 | + /^a string flag with key "(.*)" is evaluated with default value "(.*)"$/, |
| 51 | + async (key: string, defaultValue: string) => { |
| 52 | + flagKey = key; |
| 53 | + fallback = defaultValue; |
| 54 | + value = await client.getStringValue(key, defaultValue); |
| 55 | + }, |
| 56 | + ); |
| 57 | + |
| 58 | + then(/^the resolved string value should be "(.*)"$/, (expectedValue: string) => { |
| 59 | + expect(value).toEqual(expectedValue); |
| 60 | + }); |
| 61 | + |
| 62 | + when( |
| 63 | + /^an integer flag with key "(.*)" is evaluated with default value (\d+)$/, |
| 64 | + async (key: string, defaultValue: string) => { |
| 65 | + flagKey = key; |
| 66 | + fallback = Number(defaultValue); |
| 67 | + value = await client.getNumberValue(key, Number.parseInt(defaultValue)); |
| 68 | + }, |
| 69 | + ); |
| 70 | + |
| 71 | + then(/^the resolved integer value should be (\d+)$/, (expectedValue: string) => { |
| 72 | + expect(value).toEqual(Number.parseInt(expectedValue)); |
| 73 | + }); |
| 74 | + |
| 75 | + when( |
| 76 | + /^a float flag with key "(.*)" is evaluated with default value (\d+\.?\d*)$/, |
| 77 | + async (key: string, defaultValue: string) => { |
| 78 | + flagKey = key; |
| 79 | + fallback = Number(defaultValue); |
| 80 | + value = await client.getNumberValue(key, Number.parseFloat(defaultValue)); |
| 81 | + }, |
| 82 | + ); |
| 83 | + |
| 84 | + then(/^the resolved float value should be (\d+\.?\d*)$/, (expectedValue: string) => { |
| 85 | + expect(value).toEqual(Number.parseFloat(expectedValue)); |
| 86 | + }); |
| 87 | + |
| 88 | + when(/^an object flag with key "(.*)" is evaluated with a null default value$/, async (key: string) => { |
| 89 | + const defaultValue = {}; |
| 90 | + flagKey = key; |
| 91 | + fallback = ''; |
| 92 | + value = await client.getObjectValue(key, defaultValue); |
| 93 | + }); |
| 94 | + |
| 95 | + then( |
| 96 | + /^the resolved object value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/, |
| 97 | + (field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => { |
| 98 | + const jsonObject = value as JsonObject; |
| 99 | + expect(jsonObject[field1]).toEqual(boolValue === 'true'); |
| 100 | + expect(jsonObject[field2]).toEqual(stringValue); |
| 101 | + expect(jsonObject[field3]).toEqual(Number.parseInt(intValue)); |
| 102 | + }, |
| 103 | + ); |
| 104 | + |
| 105 | + when( |
| 106 | + /^a boolean flag with key "(.*)" is evaluated with details and default value "(.*)"$/, |
| 107 | + async (key: string, defaultValue: string) => { |
| 108 | + flagKey = key; |
| 109 | + fallback = defaultValue; |
| 110 | + details = await client.getBooleanDetails(key, defaultValue === 'true'); |
| 111 | + }, |
| 112 | + ); |
| 113 | + |
| 114 | + then( |
| 115 | + /^the resolved boolean details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/, |
| 116 | + (expectedValue: string, expectedVariant: string, expectedReason: string) => { |
| 117 | + expect(details).toBeDefined(); |
| 118 | + expect(details.value).toEqual(expectedValue === 'true'); |
| 119 | + expect(details.variant).toEqual(expectedVariant); |
| 120 | + expect(details.reason).toEqual(expectedReason); |
| 121 | + }, |
| 122 | + ); |
| 123 | + |
| 124 | + when( |
| 125 | + /^a string flag with key "(.*)" is evaluated with details and default value "(.*)"$/, |
| 126 | + async (key: string, defaultValue: string) => { |
| 127 | + flagKey = key; |
| 128 | + fallback = defaultValue; |
| 129 | + details = await client.getStringDetails(key, defaultValue); |
| 130 | + }, |
| 131 | + ); |
| 132 | + |
| 133 | + then( |
| 134 | + /^the resolved string details value should be "(.*)", the variant should be "(.*)", and the reason should be "(.*)"$/, |
| 135 | + (expectedValue: string, expectedVariant: string, expectedReason: string) => { |
| 136 | + expect(details).toBeDefined(); |
| 137 | + expect(details.value).toEqual(expectedValue); |
| 138 | + expect(details.variant).toEqual(expectedVariant); |
| 139 | + expect(details.reason).toEqual(expectedReason); |
| 140 | + }, |
| 141 | + ); |
| 142 | + |
| 143 | + when( |
| 144 | + /^an integer flag with key "(.*)" is evaluated with details and default value (\d+)$/, |
| 145 | + async (key: string, defaultValue: string) => { |
| 146 | + flagKey = key; |
| 147 | + fallback = defaultValue; |
| 148 | + details = await client.getNumberDetails(key, Number.parseInt(defaultValue)); |
| 149 | + }, |
| 150 | + ); |
| 151 | + |
| 152 | + then( |
| 153 | + /^the resolved integer details value should be (\d+), the variant should be "(.*)", and the reason should be "(.*)"$/, |
| 154 | + (expectedValue: string, expectedVariant: string, expectedReason: string) => { |
| 155 | + expect(details).toBeDefined(); |
| 156 | + expect(details.value).toEqual(Number.parseInt(expectedValue)); |
| 157 | + expect(details.variant).toEqual(expectedVariant); |
| 158 | + expect(details.reason).toEqual(expectedReason); |
| 159 | + }, |
| 160 | + ); |
| 161 | + |
| 162 | + when( |
| 163 | + /^a float flag with key "(.*)" is evaluated with details and default value (\d+\.?\d*)$/, |
| 164 | + async (key: string, defaultValue: string) => { |
| 165 | + flagKey = key; |
| 166 | + fallback = defaultValue; |
| 167 | + details = await client.getNumberDetails(key, Number.parseFloat(defaultValue)); |
| 168 | + }, |
| 169 | + ); |
| 170 | + |
| 171 | + then( |
| 172 | + /^the resolved float details value should be (\d+\.?\d*), the variant should be "(.*)", and the reason should be "(.*)"$/, |
| 173 | + (expectedValue: string, expectedVariant: string, expectedReason: string) => { |
| 174 | + expect(details).toBeDefined(); |
| 175 | + expect(details.value).toEqual(Number.parseFloat(expectedValue)); |
| 176 | + expect(details.variant).toEqual(expectedVariant); |
| 177 | + expect(details.reason).toEqual(expectedReason); |
| 178 | + }, |
| 179 | + ); |
| 180 | + |
| 181 | + when(/^an object flag with key "(.*)" is evaluated with details and a null default value$/, async (key: string) => { |
| 182 | + flagKey = key; |
| 183 | + fallback = {}; |
| 184 | + details = await client.getObjectDetails(key, {}); |
| 185 | + }); |
| 186 | + |
| 187 | + then( |
| 188 | + /^the resolved object details value should be contain fields "(.*)", "(.*)", and "(.*)", with values "(.*)", "(.*)" and (\d+), respectively$/, |
| 189 | + (field1: string, field2: string, field3: string, boolValue: string, stringValue: string, intValue: string) => { |
| 190 | + expect(details).toBeDefined(); |
| 191 | + const jsonObject = details.value as JsonObject; |
| 192 | + |
| 193 | + expect(jsonObject[field1]).toEqual(boolValue === 'true'); |
| 194 | + expect(jsonObject[field2]).toEqual(stringValue); |
| 195 | + expect(jsonObject[field3]).toEqual(Number.parseInt(intValue)); |
| 196 | + }, |
| 197 | + ); |
| 198 | + |
| 199 | + and( |
| 200 | + /^the variant should be "(.*)", and the reason should be "(.*)"$/, |
| 201 | + (expectedVariant: string, expectedReason: string) => { |
| 202 | + expect(details).toBeDefined(); |
| 203 | + expect(details.variant).toEqual(expectedVariant); |
| 204 | + expect(details.reason).toEqual(expectedReason); |
| 205 | + }, |
| 206 | + ); |
| 207 | + |
| 208 | + then(/^the resolved string response should be "(.*)"$/, (expectedValue: string) => { |
| 209 | + expect(value).toEqual(expectedValue); |
| 210 | + }); |
| 211 | + |
| 212 | + when( |
| 213 | + /^a non-existent string flag with key "(.*)" is evaluated with details and a default value "(.*)"$/, |
| 214 | + async (key: string, defaultValue: string) => { |
| 215 | + flagKey = key; |
| 216 | + fallback = defaultValue; |
| 217 | + details = await client.getStringDetails(flagKey, defaultValue); |
| 218 | + }, |
| 219 | + ); |
| 220 | + |
| 221 | + then(/^the default string value should be returned$/, () => { |
| 222 | + expect(details).toBeDefined(); |
| 223 | + expect(details.value).toEqual(fallback); |
| 224 | + }); |
| 225 | + |
| 226 | + and( |
| 227 | + /^the reason should indicate an error and the error code should indicate a missing flag with "(.*)"$/, |
| 228 | + (errorCode: string) => { |
| 229 | + expect(details).toBeDefined(); |
| 230 | + expect(details.reason).toEqual(StandardResolutionReasons.ERROR); |
| 231 | + expect(details.errorCode).toEqual(errorCode); |
| 232 | + }, |
| 233 | + ); |
| 234 | + |
| 235 | + when( |
| 236 | + /^a string flag with key "(.*)" is evaluated as an integer, with details and a default value (\d+)$/, |
| 237 | + async (key: string, defaultValue: string) => { |
| 238 | + flagKey = key; |
| 239 | + fallback = Number.parseInt(defaultValue); |
| 240 | + details = await client.getNumberDetails(flagKey, Number.parseInt(defaultValue)); |
| 241 | + }, |
| 242 | + ); |
| 243 | + |
| 244 | + then(/^the default integer value should be returned$/, () => { |
| 245 | + expect(details).toBeDefined(); |
| 246 | + expect(details.value).toEqual(fallback); |
| 247 | + }); |
| 248 | + |
| 249 | + and( |
| 250 | + /^the reason should indicate an error and the error code should indicate a type mismatch with "(.*)"$/, |
| 251 | + (errorCode: string) => { |
| 252 | + expect(details).toBeDefined(); |
| 253 | + expect(details.reason).toEqual(StandardResolutionReasons.ERROR); |
| 254 | + expect(details.errorCode).toEqual(errorCode); |
| 255 | + }, |
| 256 | + ); |
| 257 | + |
| 258 | + let ran: Promise<boolean>; |
| 259 | + when('a PROVIDER_READY handler is added', () => { |
| 260 | + ran = new Promise<boolean>((resolve) => { |
| 261 | + client.addHandler(ProviderEvents.Ready, async () => { |
| 262 | + resolve(true); |
| 263 | + }); |
| 264 | + }); |
| 265 | + }); |
| 266 | + then('the PROVIDER_READY handler must run', () => { |
| 267 | + expect(ran).toBeTruthy(); |
| 268 | + }); |
| 269 | + |
| 270 | + when('a PROVIDER_CONFIGURATION_CHANGED handler is added', () => { |
| 271 | + ran = new Promise<boolean>((resolve) => { |
| 272 | + client.addHandler(ProviderEvents.ConfigurationChanged, async (details) => { |
| 273 | + // file writes are not atomic, so we get a few events in quick succession from the testbed |
| 274 | + // some will not contain changes, this tolerates that; at least 1 should have our change |
| 275 | + if (details?.flagsChanged?.length) { |
| 276 | + flagsChanged = details?.flagsChanged; |
| 277 | + |
| 278 | + resolve(true); |
| 279 | + } |
| 280 | + }); |
| 281 | + }); |
| 282 | + }); |
| 283 | + |
| 284 | + and(/^a flag with key "(.*)" is modified$/, async () => { |
| 285 | + // this happens every 1s in the associated container, so wait 3s |
| 286 | + await new Promise((resolve) => setTimeout(resolve, 3000)); |
| 287 | + }); |
| 288 | + |
| 289 | + then('the PROVIDER_CONFIGURATION_CHANGED handler must run', () => { |
| 290 | + expect(ran).toBeTruthy(); |
| 291 | + }); |
| 292 | + |
| 293 | + and(/^the event details must indicate "(.*)" was altered$/, (flagName) => { |
| 294 | + expect(flagsChanged).toContain(flagName); |
| 295 | + }); |
| 296 | + |
| 297 | + when( |
| 298 | + /^a zero-value boolean flag with key "(.*)" is evaluated with default value "(.*)"$/, |
| 299 | + (key, defaultVal: string) => { |
| 300 | + flagKey = key; |
| 301 | + fallback = defaultVal === 'true'; |
| 302 | + }, |
| 303 | + ); |
| 304 | + |
| 305 | + then(/^the resolved boolean zero-value should be "(.*)"$/, async (expectedVal: string) => { |
| 306 | + const expectedValue = expectedVal === 'true'; |
| 307 | + const value = await client.getBooleanValue(flagKey, fallback as boolean); |
| 308 | + expect(value).toEqual(expectedValue); |
| 309 | + }); |
| 310 | + |
| 311 | + when(/^a zero-value string flag with key "(.*)" is evaluated with default value "(.*)"$/, (key, defaultVal) => { |
| 312 | + flagKey = key; |
| 313 | + fallback = defaultVal; |
| 314 | + }); |
| 315 | + |
| 316 | + then('the resolved string zero-value should be ""', async () => { |
| 317 | + const value = await client.getStringValue(flagKey, fallback as string); |
| 318 | + expect(value).toEqual(''); |
| 319 | + }); |
| 320 | + |
| 321 | + when(/^a zero-value integer flag with key "(.*)" is evaluated with default value (\d+)$/, (key, defaultVal) => { |
| 322 | + flagKey = key; |
| 323 | + fallback = defaultVal; |
| 324 | + }); |
| 325 | + |
| 326 | + then(/^the resolved integer zero-value should be (\d+)$/, async (expectedValueString) => { |
| 327 | + const expectedValue = Number.parseInt(expectedValueString); |
| 328 | + const value = await client.getNumberValue(flagKey, fallback as number); |
| 329 | + expect(value).toEqual(expectedValue); |
| 330 | + }); |
| 331 | + |
| 332 | + when( |
| 333 | + /^a zero-value float flag with key "(.*)" is evaluated with default value (\d+\.\d+)$/, |
| 334 | + (key, defaultValueString) => { |
| 335 | + flagKey = key; |
| 336 | + fallback = Number.parseFloat(defaultValueString); |
| 337 | + }, |
| 338 | + ); |
| 339 | + |
| 340 | + then(/^the resolved float zero-value should be (\d+\.\d+)$/, async (expectedValueString) => { |
| 341 | + const expectedValue = Number.parseFloat(expectedValueString); |
| 342 | + const value = await client.getNumberValue(flagKey, fallback as number); |
| 343 | + expect(value).toEqual(expectedValue); |
| 344 | + }); |
| 345 | + |
| 346 | + then(/^the returned reason should be "(.*)"$/, (expectedReason) => { |
| 347 | + expect(details.reason).toEqual(expectedReason); |
| 348 | + }); |
| 349 | +}; |
0 commit comments