Browse Source

Fix detection of not defined values in nested objects

hasNotDefinedValuesInObject returned the result of the first nested
object it encountered, so sibling keys after a fully defined nested
object were never checked. A later null or undefined value (or a later
nested object holding one) was therefore missed and the function
incorrectly returned false.

Only return early on a positive hit and keep scanning the remaining
keys otherwise, so the function returns true if any key (nested or not)
is null or undefined and false only after all keys are clean. This
ensures hasError is set correctly in the portfolio details endpoint.
pull/7068/head
Vijay Sai 4 weeks ago
parent
commit
7ee9653b1f
  1. 1
      CHANGELOG.md
  2. 111
      apps/api/src/helper/object.helper.spec.ts
  3. 4
      apps/api/src/helper/object.helper.ts

1
CHANGELOG.md

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Fixed an issue with the localization of the country names - Fixed an issue with the localization of the country names
- Fixed the detection of not defined values in nested objects of the portfolio details endpoint
## 3.12.0 - 2026-06-17 ## 3.12.0 - 2026-06-17

111
apps/api/src/helper/object.helper.spec.ts

@ -1,6 +1,10 @@
import { DEFAULT_REDACTED_PATHS } from '@ghostfolio/common/config'; import { DEFAULT_REDACTED_PATHS } from '@ghostfolio/common/config';
import { query, redactPaths } from './object.helper'; import {
hasNotDefinedValuesInObject,
query,
redactPaths
} from './object.helper';
describe('query', () => { describe('query', () => {
it('should get market price from stock API response', () => { it('should get market price from stock API response', () => {
@ -22,6 +26,111 @@ describe('query', () => {
}); });
}); });
describe('hasNotDefinedValuesInObject', () => {
it('should return false when all values are defined', () => {
const object = {
currency: 'USD',
market: {
previousClose: 273.04,
price: 271.86
},
symbol: 'AAPL'
};
expect(hasNotDefinedValuesInObject(object)).toBe(false);
});
it('should return true when a top-level value is null', () => {
const object = {
currency: 'USD',
marketPrice: null,
symbol: 'AAPL'
};
expect(hasNotDefinedValuesInObject(object)).toBe(true);
});
it('should return true when a top-level value is undefined', () => {
const object = {
currency: 'USD',
marketPrice: undefined,
symbol: 'AAPL'
};
expect(hasNotDefinedValuesInObject(object)).toBe(true);
});
it('should return true when a value nested in an object is null', () => {
const object = {
currency: 'USD',
market: {
previousClose: 273.04,
price: null
},
symbol: 'AAPL'
};
expect(hasNotDefinedValuesInObject(object)).toBe(true);
});
it('should return true when the first nested object is fully defined but a later sibling key is null', () => {
const object = {
market: {
previousClose: 273.04,
price: 271.86
},
marketPrice: null,
symbol: 'AAPL'
};
expect(hasNotDefinedValuesInObject(object)).toBe(true);
});
it('should return true when the first nested object is fully defined but a later sibling key is undefined', () => {
const object = {
market: {
previousClose: 273.04,
price: 271.86
},
marketPrice: undefined,
symbol: 'AAPL'
};
expect(hasNotDefinedValuesInObject(object)).toBe(true);
});
it('should return true when the first nested object is fully defined but a later nested object holds null', () => {
const object = {
developedMarkets: {
UNKNOWN: 0,
weight: 1
},
emergingMarkets: {
UNKNOWN: null,
weight: 0
}
};
expect(hasNotDefinedValuesInObject(object)).toBe(true);
});
it('should return false when a deeply nested object is fully defined and is followed by a defined sibling', () => {
const object = {
holding: {
market: {
region: {
UNKNOWN: 0,
developedMarkets: 1
}
}
},
symbol: 'AAPL'
};
expect(hasNotDefinedValuesInObject(object)).toBe(false);
});
});
describe('redactAttributes', () => { describe('redactAttributes', () => {
it('should redact provided attributes', () => { it('should redact provided attributes', () => {
expect(redactPaths({ object: {}, paths: [] })).toStrictEqual({}); expect(redactPaths({ object: {}, paths: [] })).toStrictEqual({});

4
apps/api/src/helper/object.helper.ts

@ -7,7 +7,9 @@ export function hasNotDefinedValuesInObject(aObject: Object): boolean {
if (aObject[key] === null || aObject[key] === undefined) { if (aObject[key] === null || aObject[key] === undefined) {
return true; return true;
} else if (isObject(aObject[key])) { } else if (isObject(aObject[key])) {
return hasNotDefinedValuesInObject(aObject[key]); if (hasNotDefinedValuesInObject(aObject[key])) {
return true;
}
} }
} }

Loading…
Cancel
Save