diff --git a/package.json b/package.json index dde3e46d13..7a947c7bf5 100644 --- a/package.json +++ b/package.json @@ -151,8 +151,8 @@ "devDependencies": { "@apidevtools/swagger-parser": "10.1.0", "@babel/core": "7.23.0", - "@swc/core": "1.3.92", "@biomejs/biome": "1.2.2", + "@swc/core": "1.3.92", "@swc/jest": "0.2.29", "@types/bcryptjs": "2.4.4", "@types/cors": "2.8.14", diff --git a/src/lib/features/feature-toggle/deep-diff.ts b/src/lib/features/feature-toggle/deep-diff.ts new file mode 100644 index 0000000000..f49414a644 --- /dev/null +++ b/src/lib/features/feature-toggle/deep-diff.ts @@ -0,0 +1,66 @@ +interface Difference { + index: (string | number)[]; + reason: string; + valueA: any; + valueB: any; +} + +export function deepDiff(arr1: any[], arr2: any[]): Difference[] | null { + const diff: Difference[] = []; + + function compare(a: any, b: any, parentIndex: (string | number)[]): void { + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + diff.push({ + index: parentIndex, + reason: 'Different lengths', + valueA: a, + valueB: b, + }); + } else { + for (let i = 0; i < a.length; i++) { + compare(a[i], b[i], parentIndex.concat(i)); + } + } + } else if ( + typeof a === 'object' && + a !== null && + typeof b === 'object' && + b !== null + ) { + const keysA = Object.keys(a); + const keysB = Object.keys(b); + + if (!arraysEqual(keysA, keysB)) { + diff.push({ + index: parentIndex, + reason: 'Different keys', + valueA: a, + valueB: b, + }); + } else { + for (const key of keysA) { + compare(a[key], b[key], parentIndex.concat(key)); + } + } + } else if (a !== b) { + diff.push({ + index: parentIndex, + reason: 'Different values', + valueA: a, + valueB: b, + }); + } + } + + function arraysEqual(a: any[], b: any[]): boolean { + return ( + a.length === b.length && + a.sort().every((val, index) => val === b.sort()[index]) + ); + } + + compare(arr1, arr2, []); + + return diff.length > 0 ? diff : null; +} diff --git a/src/lib/features/feature-toggle/feature-toggle-service.ts b/src/lib/features/feature-toggle/feature-toggle-service.ts index a3fb77fee1..5ef25b2cb0 100644 --- a/src/lib/features/feature-toggle/feature-toggle-service.ts +++ b/src/lib/features/feature-toggle/feature-toggle-service.ts @@ -103,6 +103,7 @@ import { IDependentFeaturesReadModel } from '../dependent-features/dependent-fea import EventService from '../../services/event-service'; import { DependentFeaturesService } from '../dependent-features/dependent-features-service'; import isEqual from 'lodash.isequal'; +import { deepDiff } from './deep-diff'; interface IFeatureContext { featureName: string; @@ -1067,12 +1068,17 @@ class FeatureToggleService { ); if (!equal) { + const difference = deepDiff( + featuresFromClientStore, + featuresFromFeatureToggleStore, + ); this.logger.warn( - 'features from client-feature-toggle-store is not equal to features from feature-toggle-store', + 'getPlaygroundFeatures: features from client-feature-toggle-store is not equal to features from feature-toggle-store', + difference, ); } - const features = this.flagResolver.isEnabled('useLastSeenRefactor') + const features = this.flagResolver.isEnabled('separateAdminClientApi') ? featuresFromFeatureToggleStore : featuresFromClientStore; @@ -1113,12 +1119,17 @@ class FeatureToggleService { ); if (!equal) { + const difference = deepDiff( + featuresFromClientStore, + featuresFromFeatureToggleStore, + ); this.logger.warn( - 'features from client-feature-toggle-store is not equal to features from feature-toggle-store diff', + 'getFeatureToggles: features from client-feature-toggle-store is not equal to features from feature-toggle-store diff', + difference, ); } - const features = this.flagResolver.isEnabled('useLastSeenRefactor') + const features = this.flagResolver.isEnabled('separateAdminClientApi') ? featuresFromFeatureToggleStore : featuresFromClientStore; diff --git a/src/lib/features/feature-toggle/tests/deep-diff.test.ts b/src/lib/features/feature-toggle/tests/deep-diff.test.ts new file mode 100644 index 0000000000..8c53f57661 --- /dev/null +++ b/src/lib/features/feature-toggle/tests/deep-diff.test.ts @@ -0,0 +1,61 @@ +import { deepDiff } from '../deep-diff'; // Import the deepDiff function + +describe('deepDiff', () => { + it('should return null for equal arrays', () => { + const arr1 = [1, 2, 3]; + const arr2 = [1, 2, 3]; + expect(deepDiff(arr1, arr2)).toBe(null); + }); + + it('should find differences in arrays with different lengths', () => { + const arr1 = [1, 2, 3]; + const arr2 = [1, 2, 3, 4]; + expect(deepDiff(arr1, arr2)).toEqual([ + { + index: [], + reason: 'Different lengths', + valueA: arr1, + valueB: arr2, + }, + ]); + }); + + it('should find differences in arrays with different values', () => { + const arr1 = [1, 2, 3]; + const arr2 = [1, 4, 3]; + expect(deepDiff(arr1, arr2)).toEqual([ + { + index: [1], + reason: 'Different values', + valueA: 2, + valueB: 4, + }, + ]); + }); + + it('should find differences in arrays with different keys in objects', () => { + const arr1 = [{ a: 1 }, { b: 2 }]; + const arr2 = [{ a: 1 }, { c: 2 }]; + expect(deepDiff(arr1, arr2)).toEqual([ + { + index: [1], + reason: 'Different keys', + valueA: { b: 2 }, + valueB: { c: 2 }, + }, + ]); + }); + + it('should handle nested differences in objects', () => { + const arr1 = [{ a: { b: 1 } }, { c: { d: 2 } }]; + const arr2 = [{ a: { b: 1 } }, { c: { d: 3 } }]; + expect(deepDiff(arr1, arr2)).toEqual([ + { + index: [1, 'c', 'd'], + reason: 'Different values', + valueA: 2, + valueB: 3, + }, + ]); + }); +});