@mp-consulting/homebridge-daikin-cloud 1.3.6 → 1.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +39 -1
- package/README.md +3 -1
- package/dist/src/accessories/air-conditioning-accessory.d.ts +2 -2
- package/dist/src/accessories/air-conditioning-accessory.d.ts.map +1 -1
- package/dist/src/accessories/air-conditioning-accessory.js.map +1 -1
- package/dist/src/accessories/altherma-accessory.d.ts +2 -2
- package/dist/src/accessories/altherma-accessory.d.ts.map +1 -1
- package/dist/src/accessories/altherma-accessory.js.map +1 -1
- package/dist/src/accessories/base-accessory.d.ts +5 -5
- package/dist/src/accessories/base-accessory.d.ts.map +1 -1
- package/dist/src/accessories/base-accessory.js +7 -4
- package/dist/src/accessories/base-accessory.js.map +1 -1
- package/dist/src/api/daikin-api.d.ts +25 -25
- package/dist/src/api/daikin-api.d.ts.map +1 -1
- package/dist/src/api/daikin-api.js +41 -31
- package/dist/src/api/daikin-api.js.map +1 -1
- package/dist/src/api/daikin-cloud.repository.d.ts.map +1 -1
- package/dist/src/api/daikin-cloud.repository.js.map +1 -1
- package/dist/src/api/daikin-controller.d.ts +41 -42
- package/dist/src/api/daikin-controller.d.ts.map +1 -1
- package/dist/src/api/daikin-controller.js +39 -39
- package/dist/src/api/daikin-controller.js.map +1 -1
- package/dist/src/api/daikin-device.d.ts +33 -31
- package/dist/src/api/daikin-device.d.ts.map +1 -1
- package/dist/src/api/daikin-device.js +40 -29
- package/dist/src/api/daikin-device.js.map +1 -1
- package/dist/src/api/daikin-mobile-oauth.d.ts +16 -16
- package/dist/src/api/daikin-mobile-oauth.d.ts.map +1 -1
- package/dist/src/api/daikin-mobile-oauth.js +32 -22
- package/dist/src/api/daikin-mobile-oauth.js.map +1 -1
- package/dist/src/api/daikin-oauth.d.ts +29 -29
- package/dist/src/api/daikin-oauth.d.ts.map +1 -1
- package/dist/src/api/daikin-oauth.js +45 -35
- package/dist/src/api/daikin-oauth.js.map +1 -1
- package/dist/src/api/daikin-schemas.d.ts +4 -4
- package/dist/src/api/daikin-schemas.js +3 -3
- package/dist/src/api/daikin-schemas.js.map +1 -1
- package/dist/src/api/daikin-types.js.map +1 -1
- package/dist/src/api/daikin-websocket.d.ts +31 -32
- package/dist/src/api/daikin-websocket.d.ts.map +1 -1
- package/dist/src/api/daikin-websocket.js +30 -30
- package/dist/src/api/daikin-websocket.js.map +1 -1
- package/dist/src/api/index.d.ts +1 -1
- package/dist/src/api/index.d.ts.map +1 -1
- package/dist/src/api/index.js +2 -1
- package/dist/src/api/index.js.map +1 -1
- package/dist/src/api/token-storage.d.ts +1 -1
- package/dist/src/api/token-storage.d.ts.map +1 -1
- package/dist/src/api/token-storage.js +20 -11
- package/dist/src/api/token-storage.js.map +1 -1
- package/dist/src/config/config-manager.d.ts +33 -33
- package/dist/src/config/config-manager.d.ts.map +1 -1
- package/dist/src/config/config-manager.js +33 -33
- package/dist/src/config/config-manager.js.map +1 -1
- package/dist/src/constants/api.constants.js.map +1 -1
- package/dist/src/device/accessory-factory.d.ts +10 -10
- package/dist/src/device/accessory-factory.d.ts.map +1 -1
- package/dist/src/device/accessory-factory.js +6 -6
- package/dist/src/device/accessory-factory.js.map +1 -1
- package/dist/src/device/capability-detector.d.ts +8 -8
- package/dist/src/device/capability-detector.d.ts.map +1 -1
- package/dist/src/device/capability-detector.js +6 -6
- package/dist/src/device/capability-detector.js.map +1 -1
- package/dist/src/device/capability-docs.d.ts +1 -1
- package/dist/src/device/capability-docs.d.ts.map +1 -1
- package/dist/src/device/capability-docs.js +1 -2
- package/dist/src/device/capability-docs.js.map +1 -1
- package/dist/src/device/profiles/device-profile.d.ts +1 -1
- package/dist/src/device/profiles/device-profile.d.ts.map +1 -1
- package/dist/src/device/profiles/device-profile.js +4 -4
- package/dist/src/device/profiles/device-profile.js.map +1 -1
- package/dist/src/features/base-feature.d.ts +2 -2
- package/dist/src/features/base-feature.d.ts.map +1 -1
- package/dist/src/features/base-feature.js +2 -3
- package/dist/src/features/base-feature.js.map +1 -1
- package/dist/src/features/feature-manager.d.ts +8 -8
- package/dist/src/features/feature-manager.d.ts.map +1 -1
- package/dist/src/features/feature-manager.js +5 -5
- package/dist/src/features/feature-manager.js.map +1 -1
- package/dist/src/features/modes/dry-operation-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/dry-operation-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/dry-operation-mode.feature.js.map +1 -1
- package/dist/src/features/modes/econo-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/econo-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/econo-mode.feature.js.map +1 -1
- package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/fan-only-operation-mode.feature.js.map +1 -1
- package/dist/src/features/modes/indoor-silent-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/indoor-silent-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/indoor-silent-mode.feature.js.map +1 -1
- package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/outdoor-silent-mode.feature.js.map +1 -1
- package/dist/src/features/modes/powerful-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/powerful-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/powerful-mode.feature.js.map +1 -1
- package/dist/src/features/modes/streamer-mode.feature.d.ts +1 -1
- package/dist/src/features/modes/streamer-mode.feature.d.ts.map +1 -1
- package/dist/src/features/modes/streamer-mode.feature.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/platform.d.ts +6 -5
- package/dist/src/platform.d.ts.map +1 -1
- package/dist/src/platform.js +2 -2
- package/dist/src/platform.js.map +1 -1
- package/dist/src/services/climate-control.service.d.ts +8 -2
- package/dist/src/services/climate-control.service.d.ts.map +1 -1
- package/dist/src/services/climate-control.service.js +53 -59
- package/dist/src/services/climate-control.service.js.map +1 -1
- package/dist/src/services/hot-water-tank.service.d.ts +6 -2
- package/dist/src/services/hot-water-tank.service.d.ts.map +1 -1
- package/dist/src/services/hot-water-tank.service.js +31 -34
- package/dist/src/services/hot-water-tank.service.js.map +1 -1
- package/dist/src/types/daikin-enums.js +12 -12
- package/dist/src/types/daikin-enums.js.map +1 -1
- package/dist/src/types/device-capabilities.d.ts +1 -1
- package/dist/src/types/device-capabilities.d.ts.map +1 -1
- package/dist/src/utils/log-context.d.ts +23 -23
- package/dist/src/utils/log-context.d.ts.map +1 -1
- package/dist/src/utils/log-context.js +28 -28
- package/dist/src/utils/log-context.js.map +1 -1
- package/dist/src/utils/strings.d.ts.map +1 -1
- package/dist/src/utils/strings.js.map +1 -1
- package/dist/src/utils/update-mapper.d.ts +16 -16
- package/dist/src/utils/update-mapper.d.ts.map +1 -1
- package/dist/src/utils/update-mapper.js +14 -14
- package/dist/src/utils/update-mapper.js.map +1 -1
- package/homebridge-ui/public/script.js +956 -897
- package/homebridge-ui/server.js +739 -695
- package/package.json +27 -24
- package/.claude/settings.json +0 -3
- package/.claude/settings.local.json +0 -24
- package/CHANGELOG.md +0 -114
- package/CLAUDE.md +0 -269
- package/config.md +0 -2
- package/docs/ARCHITECTURE.md +0 -645
- package/docs/IMPLEMENTATION_GUIDE.md +0 -899
- package/docs/IMPROVEMENTS_SUMMARY.md +0 -415
- package/docs/NEXT_STEPS.md +0 -368
- package/docs/Screenshot 2024-07-04 at 18.41.28.png +0 -0
- package/docs/TROUBLESHOOTING.md +0 -475
- package/docs/api-response-for-BRP069A8x.json +0 -520
- package/docs/api-response-for-BRP069C4x-2.json +0 -881
- package/docs/api-response-for-BRP069C4x.json +0 -916
- package/docs/api-response-for-altherma.json +0 -759
- package/docs/api-response-for-altherma2.json +0 -2735
- package/docs/api-response-with-multiple-devices-incl-heatpump.json +0 -2544
- package/docs/cr-insance-altherma-id-0.json +0 -834
- package/docs/mock-air-to-air-dx23.json +0 -759
- package/docs/mock-air-to-air-dx4.json +0 -1134
- package/docs/mock-airpurifier-with-humidifier.json +0 -732
- package/docs/mock-airpurifier.json +0 -450
- package/docs/mock-altherma-air-to-water-lan.json +0 -845
- package/docs/mock-altherma-air-to-water-wlan.json +0 -845
- package/docs/mock-d2cnd-gas-boiler.json +0 -649
- package/docs/setpointmode-vs-controlmode-vs-setpoints-vs-sensorydata.txt +0 -6
- package/images/fan-speed.jpeg +0 -0
- package/images/homekit-controls.jpeg +0 -0
- package/images/homekit-settings.jpeg +0 -0
- package/images/swing-mode.png +0 -0
- package/jest.config.ts +0 -21
- package/test/fixtures/altherma-crSense-2.ts +0 -834
- package/test/fixtures/altherma-fraction.ts +0 -718
- package/test/fixtures/altherma-heat-pump-2.ts +0 -479
- package/test/fixtures/altherma-heat-pump.ts +0 -757
- package/test/fixtures/altherma-miladcerkic-off.ts +0 -524
- package/test/fixtures/altherma-miladcerkic.ts +0 -524
- package/test/fixtures/altherma-v1ckoeln.ts +0 -644
- package/test/fixtures/altherma-with-embedded-id-zero.ts +0 -834
- package/test/fixtures/dx23-airco-2.ts +0 -343
- package/test/fixtures/dx23-airco.ts +0 -518
- package/test/fixtures/dx4-airco.ts +0 -914
- package/test/fixtures/unknown-jan.ts +0 -488
- package/test/fixtures/unknown-kitchen-guests.ts +0 -488
- package/test/hbConfig/.daikin-mobile-tokenset +0 -8
- package/test/hbConfig/.uix-dashboard.json +0 -1
- package/test/hbConfig/.uix-secrets +0 -1
- package/test/hbConfig/accessories/.cachedAccessories.bak +0 -1
- package/test/hbConfig/accessories/cachedAccessories +0 -1
- package/test/hbConfig/accessories/uiAccessoriesLayout.json +0 -1
- package/test/hbConfig/auth.json +0 -10
- package/test/hbConfig/backups/config-backups/config.json.1767953686461 +0 -25
- package/test/hbConfig/backups/config-backups/config.json.1767953695236 +0 -29
- package/test/hbConfig/backups/config-backups/config.json.1767953814763 +0 -29
- package/test/hbConfig/backups/config-backups/config.json.1767953823101 +0 -29
- package/test/hbConfig/backups/config-backups/config.json.1767954822835 +0 -29
- package/test/hbConfig/backups/config-backups/config.json.1767954859218 +0 -29
- package/test/hbConfig/backups/config-backups/config.json.1767960145503 +0 -33
- package/test/hbConfig/backups/config-backups/config.json.1767960168068 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767960170333 +0 -46
- package/test/hbConfig/backups/config-backups/config.json.1767960172731 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767960179323 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767960182114 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767960189302 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767960195194 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767960197301 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767960199151 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767960199667 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767960329839 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767960334503 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767960336208 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767960338537 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767963223953 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767963241753 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767963252785 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767963463944 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767963834475 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767963838474 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767963843066 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767965217715 +0 -44
- package/test/hbConfig/backups/config-backups/config.json.1767965419624 +0 -25
- package/test/hbConfig/backups/config-backups/config.json.1767965870934 +0 -32
- package/test/hbConfig/backups/config-backups/config.json.1767977675045 +0 -32
- package/test/hbConfig/backups/config-backups/config.json.1767977677222 +0 -33
- package/test/hbConfig/backups/config-backups/config.json.1767977710226 +0 -33
- package/test/hbConfig/backups/config-backups/config.json.1767977741397 +0 -33
- package/test/hbConfig/backups/config-backups/config.json.1767977977093 +0 -35
- package/test/hbConfig/backups/config-backups/config.json.1767977981773 +0 -35
- package/test/hbConfig/backups/config-backups/config.json.1767977986514 +0 -35
- package/test/hbConfig/backups/config-backups/config.json.1767977991174 +0 -35
- package/test/hbConfig/backups/config-backups/config.json.1767979424487 +0 -35
- package/test/hbConfig/backups/config-backups/config.json.1767979424987 +0 -35
- package/test/hbConfig/backups/config-backups/config.json.1767979432646 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767979433150 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767979436933 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767979437438 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767979441676 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767979442180 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767979466735 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767979903636 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767979904135 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767979906606 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767979907108 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767988702341 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767988702837 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767988713159 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767988713664 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767988918139 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767988918639 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767988921120 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767988921624 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767988930307 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767988935070 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767988935574 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767989710262 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767989710760 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767989729668 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767990295225 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767990479921 +0 -47
- package/test/hbConfig/backups/config-backups/config.json.1767990481702 +0 -49
- package/test/hbConfig/backups/instance-backups/homebridge-backup-1E4A432551BA.1768010187391.tar.gz +0 -0
- package/test/hbConfig/backups/instance-backups/homebridge-backup-1E4A432551BA.1768096587387.tar.gz +0 -0
- package/test/hbConfig/backups/instance-backups/homebridge-backup-1E4A432551BA.1768182987404.tar.gz +0 -0
- package/test/hbConfig/config.json +0 -47
- package/test/hbConfig/daikin-cloud-certs/server.crt +0 -22
- package/test/hbConfig/daikin-cloud-certs/server.key +0 -28
- package/test/hbConfig/persist/AccessoryInfo.1E4A432551BA.json +0 -1
- package/test/hbConfig/persist/IdentifierCache.1E4A432551BA.json +0 -1
- package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14758 +0 -1
- package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14759 +0 -1
- package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14760 +0 -1
- package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14761 +0 -1
- package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14762 +0 -1
- package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14764 +0 -1
- package/test/helpers/test-isolation.ts +0 -228
- package/test/integration/air-conditioning.test.ts +0 -396
- package/test/integration/altherma.test.ts +0 -279
- package/test/integration/platform.test.ts +0 -118
- package/test/mobile-tokens.json +0 -8
- package/test/mocks/index.ts +0 -27
- package/test/test-gigya-auth.js +0 -443
- package/test/test-mobile-oauth.js +0 -175
- package/test/test-websocket-mobile.js +0 -123
- package/test/test-websocket.js +0 -116
- package/test/unit/api/__snapshots__/daikinCloud.test.ts.snap +0 -1320
- package/test/unit/api/daikin-api.test.ts +0 -442
- package/test/unit/api/daikin-cloud-repository.test.ts +0 -107
- package/test/unit/api/daikin-oauth.test.ts +0 -214
- package/test/unit/api/daikinCloud.test.ts +0 -12
- package/test/unit/api/token-storage.test.ts +0 -90
- package/test/unit/config/config-manager.test.ts +0 -271
- package/test/unit/device/daikin-device.test.ts +0 -73
- package/test/unit/services/hot-water-tank.service.test.ts +0 -123
- package/test/unit/utils/log-context.test.ts +0 -271
- package/test/unit/utils/update-mapper.test.ts +0 -404
|
@@ -1,442 +0,0 @@
|
|
|
1
|
-
import {DaikinApi, RateLimitedError, ApiTimeoutError} from '../../../src/api/daikin-api';
|
|
2
|
-
import {OAuthProvider, TokenSet} from '../../../src/api/daikin-types';
|
|
3
|
-
import {MAX_RETRY_ATTEMPTS} from '../../../src/constants';
|
|
4
|
-
import * as https from 'node:https';
|
|
5
|
-
|
|
6
|
-
jest.mock('node:https');
|
|
7
|
-
|
|
8
|
-
describe('DaikinApi', () => {
|
|
9
|
-
let mockOAuth: jest.Mocked<OAuthProvider>;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
jest.clearAllMocks();
|
|
13
|
-
jest.useFakeTimers();
|
|
14
|
-
mockOAuth = {
|
|
15
|
-
getAccessToken: jest.fn().mockResolvedValue('valid-token'),
|
|
16
|
-
isAuthenticated: jest.fn().mockReturnValue(true),
|
|
17
|
-
refreshToken: jest.fn().mockResolvedValue({
|
|
18
|
-
access_token: 'new-token',
|
|
19
|
-
refresh_token: 'new-refresh-token',
|
|
20
|
-
token_type: 'Bearer',
|
|
21
|
-
expires_in: 3600,
|
|
22
|
-
} as TokenSet),
|
|
23
|
-
};
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
afterEach(() => {
|
|
27
|
-
jest.useRealTimers();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// Helper to run async operations with fake timers
|
|
31
|
-
async function runWithTimers<T>(promise: Promise<T>): Promise<T> {
|
|
32
|
-
const result = promise;
|
|
33
|
-
// Run timers until all pending timers are exhausted
|
|
34
|
-
await jest.runAllTimersAsync();
|
|
35
|
-
return result;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function mockHttpsRequest(statusCode: number, body: string, headers: Record<string, string> = {}) {
|
|
39
|
-
const mockResponse: any = {
|
|
40
|
-
statusCode,
|
|
41
|
-
headers,
|
|
42
|
-
on: jest.fn((event, callback) => {
|
|
43
|
-
if (event === 'data') {
|
|
44
|
-
callback(body);
|
|
45
|
-
}
|
|
46
|
-
if (event === 'end') {
|
|
47
|
-
callback();
|
|
48
|
-
}
|
|
49
|
-
return mockResponse;
|
|
50
|
-
}),
|
|
51
|
-
};
|
|
52
|
-
const mockRequest = {
|
|
53
|
-
on: jest.fn().mockReturnThis(),
|
|
54
|
-
write: jest.fn(),
|
|
55
|
-
end: jest.fn(),
|
|
56
|
-
};
|
|
57
|
-
(https.request as jest.Mock).mockImplementation((options, callback) => {
|
|
58
|
-
callback(mockResponse);
|
|
59
|
-
return mockRequest;
|
|
60
|
-
});
|
|
61
|
-
return {mockRequest, mockResponse};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
describe('getDevices', () => {
|
|
65
|
-
it('should return devices on successful request', async () => {
|
|
66
|
-
const devices = [{id: 'device-1', managementPoints: []}];
|
|
67
|
-
mockHttpsRequest(200, JSON.stringify(devices));
|
|
68
|
-
|
|
69
|
-
const api = new DaikinApi(mockOAuth);
|
|
70
|
-
const result = await api.getDevices();
|
|
71
|
-
|
|
72
|
-
expect(result).toEqual(devices);
|
|
73
|
-
expect(mockOAuth.getAccessToken).toHaveBeenCalled();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should refresh token and retry on 401 Unauthorized with exponential backoff', async () => {
|
|
77
|
-
const devices = [{id: 'device-1', managementPoints: []}];
|
|
78
|
-
let callCount = 0;
|
|
79
|
-
|
|
80
|
-
const mockRequest = {
|
|
81
|
-
on: jest.fn().mockReturnThis(),
|
|
82
|
-
write: jest.fn(),
|
|
83
|
-
end: jest.fn(),
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
(https.request as jest.Mock).mockImplementation((options, callback) => {
|
|
87
|
-
callCount++;
|
|
88
|
-
const statusCode = callCount === 1 ? 401 : 200;
|
|
89
|
-
const body = callCount === 1 ? 'Unauthorized' : JSON.stringify(devices);
|
|
90
|
-
|
|
91
|
-
const mockResponse: any = {
|
|
92
|
-
statusCode,
|
|
93
|
-
headers: {},
|
|
94
|
-
on: jest.fn((event, cb) => {
|
|
95
|
-
if (event === 'data') {
|
|
96
|
-
cb(body);
|
|
97
|
-
}
|
|
98
|
-
if (event === 'end') {
|
|
99
|
-
cb();
|
|
100
|
-
}
|
|
101
|
-
return mockResponse;
|
|
102
|
-
}),
|
|
103
|
-
};
|
|
104
|
-
callback(mockResponse);
|
|
105
|
-
return mockRequest;
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// After refresh, return a new token
|
|
109
|
-
mockOAuth.getAccessToken
|
|
110
|
-
.mockResolvedValueOnce('expired-token')
|
|
111
|
-
.mockResolvedValueOnce('new-token');
|
|
112
|
-
|
|
113
|
-
const api = new DaikinApi(mockOAuth);
|
|
114
|
-
const result = await runWithTimers(api.getDevices());
|
|
115
|
-
|
|
116
|
-
expect(result).toEqual(devices);
|
|
117
|
-
expect(mockOAuth.refreshToken).toHaveBeenCalledTimes(1);
|
|
118
|
-
expect(https.request).toHaveBeenCalledTimes(2);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('should throw error if refresh fails on 401', async () => {
|
|
122
|
-
mockHttpsRequest(401, 'Unauthorized');
|
|
123
|
-
mockOAuth.refreshToken.mockRejectedValue(new Error('Refresh failed'));
|
|
124
|
-
|
|
125
|
-
const api = new DaikinApi(mockOAuth);
|
|
126
|
-
|
|
127
|
-
await expect(api.getDevices()).rejects.toThrow(
|
|
128
|
-
'Unauthorized (401): Token refresh failed. Please re-authenticate.',
|
|
129
|
-
);
|
|
130
|
-
expect(mockOAuth.refreshToken).toHaveBeenCalledTimes(1);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('should throw error if retry after refresh still returns 401', async () => {
|
|
134
|
-
jest.useRealTimers(); // Use real timers for this test
|
|
135
|
-
// Always return 401
|
|
136
|
-
mockHttpsRequest(401, 'Unauthorized');
|
|
137
|
-
|
|
138
|
-
const api = new DaikinApi(mockOAuth);
|
|
139
|
-
|
|
140
|
-
// Temporarily override sleep to be instant for testing
|
|
141
|
-
const originalSleep = (api as unknown as { sleep: (ms: number) => Promise<void> }).sleep;
|
|
142
|
-
(api as unknown as { sleep: (ms: number) => Promise<void> }).sleep = () => Promise.resolve();
|
|
143
|
-
|
|
144
|
-
await expect(api.getDevices()).rejects.toThrow(
|
|
145
|
-
'Unauthorized (401): Token expired or invalid',
|
|
146
|
-
);
|
|
147
|
-
// Should retry MAX_RETRY_ATTEMPTS times
|
|
148
|
-
expect(mockOAuth.refreshToken).toHaveBeenCalledTimes(MAX_RETRY_ATTEMPTS);
|
|
149
|
-
// Initial request + MAX_RETRY_ATTEMPTS retries
|
|
150
|
-
expect(https.request).toHaveBeenCalledTimes(MAX_RETRY_ATTEMPTS + 1);
|
|
151
|
-
|
|
152
|
-
// Restore original sleep
|
|
153
|
-
(api as unknown as { sleep: (ms: number) => Promise<void> }).sleep = originalSleep;
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('should not retry more than MAX_RETRY_ATTEMPTS times on 401', async () => {
|
|
157
|
-
jest.useRealTimers(); // Use real timers for this test
|
|
158
|
-
// Always return 401
|
|
159
|
-
mockHttpsRequest(401, 'Unauthorized');
|
|
160
|
-
|
|
161
|
-
const api = new DaikinApi(mockOAuth);
|
|
162
|
-
|
|
163
|
-
// Temporarily override sleep to be instant for testing
|
|
164
|
-
(api as unknown as { sleep: (ms: number) => Promise<void> }).sleep = () => Promise.resolve();
|
|
165
|
-
|
|
166
|
-
await expect(api.getDevices()).rejects.toThrow('Unauthorized');
|
|
167
|
-
// Should only refresh MAX_RETRY_ATTEMPTS times
|
|
168
|
-
expect(mockOAuth.refreshToken).toHaveBeenCalledTimes(MAX_RETRY_ATTEMPTS);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('should deduplicate concurrent token refresh requests', async () => {
|
|
172
|
-
jest.useRealTimers();
|
|
173
|
-
|
|
174
|
-
// Create a slow refresh that we can control
|
|
175
|
-
let resolveRefresh!: () => void;
|
|
176
|
-
const refreshPromise = new Promise<void>((resolve) => {
|
|
177
|
-
resolveRefresh = resolve;
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
let callCount = 0;
|
|
181
|
-
mockOAuth.refreshToken.mockImplementation(async () => {
|
|
182
|
-
callCount++;
|
|
183
|
-
await refreshPromise;
|
|
184
|
-
return {
|
|
185
|
-
access_token: 'new-token',
|
|
186
|
-
refresh_token: 'new-refresh',
|
|
187
|
-
token_type: 'Bearer',
|
|
188
|
-
expires_in: 3600,
|
|
189
|
-
} as TokenSet;
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const devices = [{id: 'device-1', managementPoints: []}];
|
|
193
|
-
let httpCallCount = 0;
|
|
194
|
-
const mockRequest = {
|
|
195
|
-
on: jest.fn().mockReturnThis(),
|
|
196
|
-
write: jest.fn(),
|
|
197
|
-
end: jest.fn(),
|
|
198
|
-
setTimeout: jest.fn(),
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
(https.request as jest.Mock).mockImplementation((options, callback) => {
|
|
202
|
-
httpCallCount++;
|
|
203
|
-
// First two return 401, then 200
|
|
204
|
-
const statusCode = httpCallCount <= 2 ? 401 : 200;
|
|
205
|
-
const body = statusCode === 200 ? JSON.stringify(devices) : 'Unauthorized';
|
|
206
|
-
const mockResponse: any = {
|
|
207
|
-
statusCode,
|
|
208
|
-
headers: {},
|
|
209
|
-
on: jest.fn((event, cb) => {
|
|
210
|
-
if (event === 'data') {
|
|
211
|
-
cb(body);
|
|
212
|
-
}
|
|
213
|
-
if (event === 'end') {
|
|
214
|
-
cb();
|
|
215
|
-
}
|
|
216
|
-
return mockResponse;
|
|
217
|
-
}),
|
|
218
|
-
};
|
|
219
|
-
callback(mockResponse);
|
|
220
|
-
return mockRequest;
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
const api = new DaikinApi(mockOAuth);
|
|
224
|
-
(api as unknown as { sleep: (ms: number) => Promise<void> }).sleep = () => Promise.resolve();
|
|
225
|
-
|
|
226
|
-
mockOAuth.getAccessToken
|
|
227
|
-
.mockResolvedValueOnce('expired-token')
|
|
228
|
-
.mockResolvedValueOnce('expired-token')
|
|
229
|
-
.mockResolvedValue('new-token');
|
|
230
|
-
|
|
231
|
-
// Fire two concurrent requests that will both get 401
|
|
232
|
-
const p1 = api.getDevices();
|
|
233
|
-
const p2 = api.getDevices();
|
|
234
|
-
|
|
235
|
-
// Let the refresh complete
|
|
236
|
-
resolveRefresh();
|
|
237
|
-
|
|
238
|
-
await Promise.all([p1, p2]);
|
|
239
|
-
|
|
240
|
-
// Should only have refreshed once despite two concurrent 401s
|
|
241
|
-
expect(callCount).toBe(1);
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
describe('rate limiting', () => {
|
|
246
|
-
it('should throw RateLimitedError on 429', async () => {
|
|
247
|
-
mockHttpsRequest(429, 'Too Many Requests', {'retry-after': '60'});
|
|
248
|
-
|
|
249
|
-
const api = new DaikinApi(mockOAuth);
|
|
250
|
-
|
|
251
|
-
await expect(api.getDevices()).rejects.toThrow(RateLimitedError);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('should block subsequent requests after rate limit', async () => {
|
|
255
|
-
mockHttpsRequest(429, 'Too Many Requests', {'retry-after': '60'});
|
|
256
|
-
|
|
257
|
-
const api = new DaikinApi(mockOAuth);
|
|
258
|
-
|
|
259
|
-
await expect(api.getDevices()).rejects.toThrow(RateLimitedError);
|
|
260
|
-
expect(api.isRateLimited()).toBe(true);
|
|
261
|
-
|
|
262
|
-
// Reset mock to return 200, but should still be blocked
|
|
263
|
-
mockHttpsRequest(200, '[]');
|
|
264
|
-
|
|
265
|
-
await expect(api.getDevices()).rejects.toThrow(
|
|
266
|
-
'API request blocked due to rate limit',
|
|
267
|
-
);
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
describe('gateway errors', () => {
|
|
272
|
-
it('should retry on 504 Gateway Timeout and succeed', async () => {
|
|
273
|
-
const devices = [{id: 'device-1', managementPoints: []}];
|
|
274
|
-
let callCount = 0;
|
|
275
|
-
|
|
276
|
-
const mockRequest = {
|
|
277
|
-
on: jest.fn().mockReturnThis(),
|
|
278
|
-
write: jest.fn(),
|
|
279
|
-
end: jest.fn(),
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
(https.request as jest.Mock).mockImplementation((options, callback) => {
|
|
283
|
-
callCount++;
|
|
284
|
-
// First call returns 504, second returns 200
|
|
285
|
-
const statusCode = callCount === 1 ? 504 : 200;
|
|
286
|
-
const body = callCount === 1 ? 'Gateway Timeout' : JSON.stringify(devices);
|
|
287
|
-
|
|
288
|
-
const mockResponse: any = {
|
|
289
|
-
statusCode,
|
|
290
|
-
headers: {},
|
|
291
|
-
on: jest.fn((event, cb) => {
|
|
292
|
-
if (event === 'data') {
|
|
293
|
-
cb(body);
|
|
294
|
-
}
|
|
295
|
-
if (event === 'end') {
|
|
296
|
-
cb();
|
|
297
|
-
}
|
|
298
|
-
return mockResponse;
|
|
299
|
-
}),
|
|
300
|
-
};
|
|
301
|
-
callback(mockResponse);
|
|
302
|
-
return mockRequest;
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
const api = new DaikinApi(mockOAuth);
|
|
306
|
-
const result = await runWithTimers(api.getDevices());
|
|
307
|
-
|
|
308
|
-
expect(result).toEqual(devices);
|
|
309
|
-
expect(https.request).toHaveBeenCalledTimes(2);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
it('should retry on 502 Bad Gateway and succeed', async () => {
|
|
313
|
-
const devices = [{id: 'device-1', managementPoints: []}];
|
|
314
|
-
let callCount = 0;
|
|
315
|
-
|
|
316
|
-
const mockRequest = {
|
|
317
|
-
on: jest.fn().mockReturnThis(),
|
|
318
|
-
write: jest.fn(),
|
|
319
|
-
end: jest.fn(),
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
(https.request as jest.Mock).mockImplementation((options, callback) => {
|
|
323
|
-
callCount++;
|
|
324
|
-
const statusCode = callCount === 1 ? 502 : 200;
|
|
325
|
-
const body = callCount === 1 ? 'Bad Gateway' : JSON.stringify(devices);
|
|
326
|
-
|
|
327
|
-
const mockResponse: any = {
|
|
328
|
-
statusCode,
|
|
329
|
-
headers: {},
|
|
330
|
-
on: jest.fn((event, cb) => {
|
|
331
|
-
if (event === 'data') {
|
|
332
|
-
cb(body);
|
|
333
|
-
}
|
|
334
|
-
if (event === 'end') {
|
|
335
|
-
cb();
|
|
336
|
-
}
|
|
337
|
-
return mockResponse;
|
|
338
|
-
}),
|
|
339
|
-
};
|
|
340
|
-
callback(mockResponse);
|
|
341
|
-
return mockRequest;
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
const api = new DaikinApi(mockOAuth);
|
|
345
|
-
const result = await runWithTimers(api.getDevices());
|
|
346
|
-
|
|
347
|
-
expect(result).toEqual(devices);
|
|
348
|
-
expect(https.request).toHaveBeenCalledTimes(2);
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('should retry on 503 Service Unavailable and succeed', async () => {
|
|
352
|
-
const devices = [{id: 'device-1', managementPoints: []}];
|
|
353
|
-
let callCount = 0;
|
|
354
|
-
|
|
355
|
-
const mockRequest = {
|
|
356
|
-
on: jest.fn().mockReturnThis(),
|
|
357
|
-
write: jest.fn(),
|
|
358
|
-
end: jest.fn(),
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
(https.request as jest.Mock).mockImplementation((options, callback) => {
|
|
362
|
-
callCount++;
|
|
363
|
-
const statusCode = callCount === 1 ? 503 : 200;
|
|
364
|
-
const body = callCount === 1 ? 'Service Unavailable' : JSON.stringify(devices);
|
|
365
|
-
|
|
366
|
-
const mockResponse: any = {
|
|
367
|
-
statusCode,
|
|
368
|
-
headers: {},
|
|
369
|
-
on: jest.fn((event, cb) => {
|
|
370
|
-
if (event === 'data') {
|
|
371
|
-
cb(body);
|
|
372
|
-
}
|
|
373
|
-
if (event === 'end') {
|
|
374
|
-
cb();
|
|
375
|
-
}
|
|
376
|
-
return mockResponse;
|
|
377
|
-
}),
|
|
378
|
-
};
|
|
379
|
-
callback(mockResponse);
|
|
380
|
-
return mockRequest;
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
const api = new DaikinApi(mockOAuth);
|
|
384
|
-
const result = await runWithTimers(api.getDevices());
|
|
385
|
-
|
|
386
|
-
expect(result).toEqual(devices);
|
|
387
|
-
expect(https.request).toHaveBeenCalledTimes(2);
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
it('should throw ApiTimeoutError after exhausting retries on 504', async () => {
|
|
391
|
-
// Always return 504
|
|
392
|
-
mockHttpsRequest(504, 'Gateway Timeout');
|
|
393
|
-
|
|
394
|
-
const api = new DaikinApi(mockOAuth);
|
|
395
|
-
|
|
396
|
-
// Temporarily override sleep to be instant for testing
|
|
397
|
-
(api as unknown as { sleep: (ms: number) => Promise<void> }).sleep = () => Promise.resolve();
|
|
398
|
-
|
|
399
|
-
await expect(api.getDevices()).rejects.toThrow(ApiTimeoutError);
|
|
400
|
-
// Initial request + MAX_RETRY_ATTEMPTS retries
|
|
401
|
-
expect(https.request).toHaveBeenCalledTimes(MAX_RETRY_ATTEMPTS + 1);
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
it('should include status code and attempts in ApiTimeoutError', async () => {
|
|
405
|
-
mockHttpsRequest(504, 'Gateway Timeout');
|
|
406
|
-
|
|
407
|
-
const api = new DaikinApi(mockOAuth);
|
|
408
|
-
(api as unknown as { sleep: (ms: number) => Promise<void> }).sleep = () => Promise.resolve();
|
|
409
|
-
|
|
410
|
-
const error = await api.getDevices().catch((e) => e);
|
|
411
|
-
expect(error).toBeInstanceOf(ApiTimeoutError);
|
|
412
|
-
expect(error.statusCode).toBe(504);
|
|
413
|
-
expect(error.attemptsMade).toBe(MAX_RETRY_ATTEMPTS + 1);
|
|
414
|
-
expect(error.message).toContain('Gateway Timeout');
|
|
415
|
-
expect(error.message).toContain('504');
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it('should throw ApiTimeoutError with correct name for 502', async () => {
|
|
419
|
-
mockHttpsRequest(502, 'Bad Gateway');
|
|
420
|
-
|
|
421
|
-
const api = new DaikinApi(mockOAuth);
|
|
422
|
-
(api as unknown as { sleep: (ms: number) => Promise<void> }).sleep = () => Promise.resolve();
|
|
423
|
-
|
|
424
|
-
const error = await api.getDevices().catch((e) => e);
|
|
425
|
-
expect(error).toBeInstanceOf(ApiTimeoutError);
|
|
426
|
-
expect(error.statusCode).toBe(502);
|
|
427
|
-
expect(error.message).toContain('Bad Gateway');
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
it('should throw ApiTimeoutError with correct name for 503', async () => {
|
|
431
|
-
mockHttpsRequest(503, 'Service Unavailable');
|
|
432
|
-
|
|
433
|
-
const api = new DaikinApi(mockOAuth);
|
|
434
|
-
(api as unknown as { sleep: (ms: number) => Promise<void> }).sleep = () => Promise.resolve();
|
|
435
|
-
|
|
436
|
-
const error = await api.getDevices().catch((e) => e);
|
|
437
|
-
expect(error).toBeInstanceOf(ApiTimeoutError);
|
|
438
|
-
expect(error.statusCode).toBe(503);
|
|
439
|
-
expect(error.message).toContain('Service Unavailable');
|
|
440
|
-
});
|
|
441
|
-
});
|
|
442
|
-
});
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import {DaikinCloudRepo} from '../../../src/api/daikin-cloud.repository';
|
|
2
|
-
|
|
3
|
-
describe('DaikinCloudRepo', () => {
|
|
4
|
-
describe('maskSensitiveCloudDeviceData', () => {
|
|
5
|
-
it('should not mutate the original data', () => {
|
|
6
|
-
const original = {
|
|
7
|
-
id: 'device-1',
|
|
8
|
-
managementPoints: [
|
|
9
|
-
{
|
|
10
|
-
embeddedId: 'gateway',
|
|
11
|
-
ipAddress: {value: '192.168.1.100'},
|
|
12
|
-
macAddress: {value: 'AA:BB:CC:DD:EE:FF'},
|
|
13
|
-
serialNumber: {value: 'SN12345'},
|
|
14
|
-
},
|
|
15
|
-
],
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
DaikinCloudRepo.maskSensitiveCloudDeviceData(original);
|
|
19
|
-
|
|
20
|
-
// Original should NOT be mutated
|
|
21
|
-
expect(original.managementPoints[0].ipAddress.value).toBe('192.168.1.100');
|
|
22
|
-
expect(original.managementPoints[0].macAddress.value).toBe('AA:BB:CC:DD:EE:FF');
|
|
23
|
-
expect(original.managementPoints[0].serialNumber.value).toBe('SN12345');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should return masked data in the cloned output', () => {
|
|
27
|
-
const original = {
|
|
28
|
-
id: 'device-1',
|
|
29
|
-
managementPoints: [
|
|
30
|
-
{
|
|
31
|
-
embeddedId: 'gateway',
|
|
32
|
-
ipAddress: {value: '192.168.1.100'},
|
|
33
|
-
macAddress: {value: 'AA:BB:CC:DD:EE:FF'},
|
|
34
|
-
ssid: {value: 'MyNetwork'},
|
|
35
|
-
serialNumber: {value: 'SN12345'},
|
|
36
|
-
wifiConnectionSSID: {value: 'MyNetwork'},
|
|
37
|
-
consumptionData: {value: 'some data'},
|
|
38
|
-
schedule: {value: 'some schedule'},
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const masked = DaikinCloudRepo.maskSensitiveCloudDeviceData(original);
|
|
44
|
-
|
|
45
|
-
expect(masked.managementPoints[0].ipAddress.value).toBe('REDACTED');
|
|
46
|
-
expect(masked.managementPoints[0].macAddress.value).toBe('REDACTED');
|
|
47
|
-
expect(masked.managementPoints[0].ssid.value).toBe('REDACTED');
|
|
48
|
-
expect(masked.managementPoints[0].serialNumber.value).toBe('REDACTED');
|
|
49
|
-
expect(masked.managementPoints[0].wifiConnectionSSID.value).toBe('REDACTED');
|
|
50
|
-
expect(masked.managementPoints[0].consumptionData).toBe('REDACTED');
|
|
51
|
-
expect(masked.managementPoints[0].schedule).toBe('REDACTED');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should preserve non-sensitive data', () => {
|
|
55
|
-
const original = {
|
|
56
|
-
id: 'device-1',
|
|
57
|
-
deviceModel: 'DX23',
|
|
58
|
-
managementPoints: [
|
|
59
|
-
{
|
|
60
|
-
embeddedId: 'climateControl',
|
|
61
|
-
onOffMode: {value: 'on'},
|
|
62
|
-
operationMode: {value: 'cooling'},
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const masked = DaikinCloudRepo.maskSensitiveCloudDeviceData(original);
|
|
68
|
-
|
|
69
|
-
expect(masked.id).toBe('device-1');
|
|
70
|
-
expect(masked.deviceModel).toBe('DX23');
|
|
71
|
-
expect(masked.managementPoints[0].embeddedId).toBe('climateControl');
|
|
72
|
-
expect(masked.managementPoints[0].onOffMode.value).toBe('on');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should handle data without managementPoints', () => {
|
|
76
|
-
const original = {id: 'device-1', deviceModel: 'Test'};
|
|
77
|
-
|
|
78
|
-
const masked = DaikinCloudRepo.maskSensitiveCloudDeviceData(original);
|
|
79
|
-
|
|
80
|
-
expect(masked.id).toBe('device-1');
|
|
81
|
-
expect(masked.managementPoints).toBeUndefined();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should handle empty managementPoints array', () => {
|
|
85
|
-
const original = {id: 'device-1', managementPoints: []};
|
|
86
|
-
|
|
87
|
-
const masked = DaikinCloudRepo.maskSensitiveCloudDeviceData(original);
|
|
88
|
-
|
|
89
|
-
expect(masked.managementPoints).toHaveLength(0);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should handle managementPoints without sensitive fields', () => {
|
|
93
|
-
const original = {
|
|
94
|
-
managementPoints: [
|
|
95
|
-
{
|
|
96
|
-
embeddedId: 'climateControl',
|
|
97
|
-
onOffMode: {value: 'on'},
|
|
98
|
-
},
|
|
99
|
-
],
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const masked = DaikinCloudRepo.maskSensitiveCloudDeviceData(original);
|
|
103
|
-
|
|
104
|
-
expect(masked.managementPoints[0].onOffMode.value).toBe('on');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
});
|