From 5ad35124ec4a5fbc5cd4930b9e825428718250ab Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 16 Mar 2026 14:21:53 -0300 Subject: [PATCH 1/2] test: add tests for TraitConfig format in local evaluation Add tests verifying that segment matching works correctly when traits are passed using the TraitConfig format ({ value, transient }) in local evaluation mode. These tests expose a bug where TraitConfig objects are passed to the engine without unwrapping the actual value, causing segment rules to never match. Tests added: - getIdentityFlags with plain traits matches segment (baseline) - getIdentityFlags with TraitConfig format matches segment - getIdentityFlags with mixed trait formats matches segment - getIdentitySegments with TraitConfig format matches segment Co-Authored-By: Claude Opus 4.6 --- tests/sdk/flagsmith-identity-flags.test.ts | 70 ++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tests/sdk/flagsmith-identity-flags.test.ts b/tests/sdk/flagsmith-identity-flags.test.ts index 86ceba0..355ab57 100644 --- a/tests/sdk/flagsmith-identity-flags.test.ts +++ b/tests/sdk/flagsmith-identity-flags.test.ts @@ -209,6 +209,76 @@ test('test_identity_with_transient_traits', async () => { expect(identityFlags[0].featureName).toBe('some_feature'); }); +test('getIdentityFlags local evaluation with plain traits matches segment', async () => { + const identifier = 'identifier'; + // Plain trait format: age=30 should match segment rule "age LESS_THAN 40" + const traits = { age: 30 }; + + const flg = flagsmith({ + environmentKey: 'ser.key', + enableLocalEvaluation: true + }); + + const flags = await flg.getIdentityFlags(identifier, traits); + + // Should get segment override value, not the default + expect(flags.getFeatureValue('some_feature')).toBe('segment_override'); + expect(flags.isFeatureEnabled('some_feature')).toBe(false); +}); + +test('getIdentityFlags local evaluation with TraitConfig format matches segment', async () => { + const identifier = 'identifier'; + // TraitConfig format: same trait value wrapped with transient metadata + const traits = { age: { value: 30, transient: true } }; + + const flg = flagsmith({ + environmentKey: 'ser.key', + enableLocalEvaluation: true + }); + + const flags = await flg.getIdentityFlags(identifier, traits); + + // Should get segment override value — same result as plain trait format + expect(flags.getFeatureValue('some_feature')).toBe('segment_override'); + expect(flags.isFeatureEnabled('some_feature')).toBe(false); +}); + +test('getIdentityFlags local evaluation with mixed trait formats matches segment', async () => { + const identifier = 'identifier'; + // Mix of plain and TraitConfig formats + const traits = { + age: { value: 30, transient: true }, + some_other_trait: 'plain_value' + }; + + const flg = flagsmith({ + environmentKey: 'ser.key', + enableLocalEvaluation: true + }); + + const flags = await flg.getIdentityFlags(identifier, traits); + + // Should get segment override value + expect(flags.getFeatureValue('some_feature')).toBe('segment_override'); + expect(flags.isFeatureEnabled('some_feature')).toBe(false); +}); + +test('getIdentitySegments with TraitConfig format matches segment', async () => { + const identifier = 'identifier'; + // TraitConfig format should work for getIdentitySegments too + const traits = { age: { value: 30, transient: true } }; + + const flg = flagsmith({ + environmentKey: 'ser.key', + enableLocalEvaluation: true + }); + + const segments = await flg.getIdentitySegments(identifier, traits); + + expect(segments).toHaveLength(1); + expect(segments[0].name).toBe('regular_segment'); +}); + test('getIdentityFlags fails if API call failed and no default flag handler was provided', async () => { const flg = flagsmith({ fetch: badFetch From 15d1894d692ad23e19e1833d3ea0985c80b8a237 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 16 Mar 2026 14:23:20 -0300 Subject: [PATCH 2/2] fix: unwrap TraitConfig values in local evaluation before segment matching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TraitConfig objects ({ value, transient }) were passed directly to the evaluation engine without extracting the actual value. This caused segment condition comparisons to fail (e.g. 'cedars' == { value: 'cedars', transient: true } → false), so segment overrides were never applied. The remote evaluation path already handled this correctly via isTraitConfig() in generateIdentitiesData. This fix applies the same unwrapping in getIdentityFlagsFromDocument and getIdentitySegments. Fixes #238 Co-Authored-By: Claude Opus 4.6 --- sdk/index.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sdk/index.ts b/sdk/index.ts index 6618ed8..a8f2b83 100644 --- a/sdk/index.ts +++ b/sdk/index.ts @@ -8,7 +8,13 @@ import { FlagsmithAPIError, FlagsmithClientError } from './errors.js'; import { DefaultFlag, Flags } from './models.js'; import { EnvironmentDataPollingManager } from './polling_manager.js'; -import { Deferred, generateIdentitiesData, getUserAgent, retryFetch } from './utils.js'; +import { + Deferred, + generateIdentitiesData, + getUserAgent, + isTraitConfig, + retryFetch +} from './utils.js'; import { SegmentModel, EnvironmentModel, @@ -275,7 +281,7 @@ export class Flagsmith { identifier, Object.keys(traits || {}).map(key => ({ key, - value: traits?.[key] + value: isTraitConfig(traits?.[key]) ? traits![key].value : traits?.[key] })) ); @@ -474,7 +480,7 @@ export class Flagsmith { identifier, Object.keys(traits).map(key => ({ key, - value: traits[key] + value: isTraitConfig(traits[key]) ? traits[key].value : traits[key] })) );