diff --git a/libs/providers/ofrep-web/src/lib/ofrep-web-provider.ts b/libs/providers/ofrep-web/src/lib/ofrep-web-provider.ts index 1a9637fe5..b9a5eddd4 100644 --- a/libs/providers/ofrep-web/src/lib/ofrep-web-provider.ts +++ b/libs/providers/ofrep-web/src/lib/ofrep-web-provider.ts @@ -299,6 +299,14 @@ export class OFREPWebProvider implements Provider { }; } + if (resolved.value === undefined || resolved.value === null) { + return { + value: defaultValue, + flagMetadata: resolved.flagMetadata, + reason: resolved.reason || StandardResolutionReasons.DEFAULT, + }; + } + if (typeof resolved.value !== type) { return { value: defaultValue, diff --git a/libs/providers/ofrep/src/lib/ofrep-provider.spec.ts b/libs/providers/ofrep/src/lib/ofrep-provider.spec.ts index 3cf933b16..f4f7f8e4b 100644 --- a/libs/providers/ofrep/src/lib/ofrep-provider.spec.ts +++ b/libs/providers/ofrep/src/lib/ofrep-provider.spec.ts @@ -60,6 +60,14 @@ describe('OFREPProvider should', () => { ); }); + it.each([true, false])('returns default if value is null', async (defaultValue) => { + await expect(provider.resolveBooleanEvaluation('my-null-value-flag', defaultValue, {})).resolves.toStrictEqual({ + value: defaultValue, + reason: 'DEFAULT', + flagMetadata: expect.objectContaining(TEST_FLAG_METADATA), + }); + }); + it('throw OFREPApiUnauthorizedError on HTTP 401', async () => { await expect(provider.resolveBooleanEvaluation('my-flag', false, { expectedAuthHeader: 'secret' })).rejects.toThrow( OFREPApiUnauthorizedError, diff --git a/libs/providers/ofrep/src/lib/ofrep-provider.ts b/libs/providers/ofrep/src/lib/ofrep-provider.ts index 53f0d9f83..e62420bc5 100644 --- a/libs/providers/ofrep/src/lib/ofrep-provider.ts +++ b/libs/providers/ofrep/src/lib/ofrep-provider.ts @@ -94,7 +94,11 @@ export class OFREPProvider implements Provider { return handleEvaluationError(result, defaultValue); } - if (typeof result.value.value !== typeof defaultValue) { + if ( + result.value.value !== undefined && + result.value.value !== null && + typeof result.value.value !== typeof defaultValue + ) { return { value: defaultValue, reason: StandardResolutionReasons.ERROR, @@ -104,6 +108,6 @@ export class OFREPProvider implements Provider { }; } - return toResolutionDetails(result.value); + return toResolutionDetails(result.value, defaultValue); } } diff --git a/libs/shared/ofrep-core/src/lib/api/ofrep-api.ts b/libs/shared/ofrep-core/src/lib/api/ofrep-api.ts index d008d88f6..808050b51 100644 --- a/libs/shared/ofrep-core/src/lib/api/ofrep-api.ts +++ b/libs/shared/ofrep-core/src/lib/api/ofrep-api.ts @@ -191,13 +191,22 @@ export function handleEvaluationError( export function toResolutionDetails( result: EvaluationSuccessResponse, + defaultValue: T, ): ResolutionDetails { - return { - value: result.value as T, - variant: result.variant, - reason: result.reason, - flagMetadata: result.metadata && toFlagMetadata(result.metadata), - }; + if (result.value === undefined || result.value === null) { + return { + value: defaultValue, + reason: result.reason || StandardResolutionReasons.DEFAULT, + flagMetadata: result.metadata && toFlagMetadata(result.metadata), + }; + } else { + return { + value: result.value as T, + variant: result.variant, + reason: result.reason, + flagMetadata: result.metadata && toFlagMetadata(result.metadata), + }; + } } export function toFlagMetadata(metadata: object): FlagMetadata { diff --git a/libs/shared/ofrep-core/src/test/handlers.ts b/libs/shared/ofrep-core/src/test/handlers.ts index af8fab070..077241fd8 100644 --- a/libs/shared/ofrep-core/src/test/handlers.ts +++ b/libs/shared/ofrep-core/src/test/handlers.ts @@ -121,14 +121,21 @@ export const handlers = [ } const scopeValue = new URL(info.request.url).searchParams.get('scope'); + const key = info.params.key; + const reason = key.includes('null-value') + ? StandardResolutionReasons.DEFAULT + : requestBody.context?.targetingKey + ? StandardResolutionReasons.TARGETING_MATCH + : StandardResolutionReasons.STATIC; + + const value = key.includes('null-value') ? null : true; + const variant = key.includes('null-value') ? undefined : scopeValue ? 'scoped' : 'default'; return HttpResponse.json({ key: info.params.key, - reason: requestBody.context?.targetingKey - ? StandardResolutionReasons.TARGETING_MATCH - : StandardResolutionReasons.STATIC, - variant: scopeValue ? 'scoped' : 'default', - value: true, + reason, + variant, + value, metadata: TEST_FLAG_METADATA, }); },