homebridge-securitysystem 0.0.1-matter
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/.gitattributes +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +46 -0
- package/.github/ISSUE_TEMPLATE/feature-request.yml +17 -0
- package/.github/ISSUE_TEMPLATE/questions-answers.yml +40 -0
- package/.github/demo.jpg +0 -0
- package/.github/dependabot.yml +19 -0
- package/.github/release.yml +36 -0
- package/.github/workflows/build-package.yml +49 -0
- package/.github/workflows/bump-version.yml +59 -0
- package/.github/workflows/dependency-review.yml +26 -0
- package/.github/workflows/publish.yml +155 -0
- package/.github/workflows/stale-activity.yml +27 -0
- package/AGENTS.md +247 -0
- package/CHANGELOG.md +572 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +5 -0
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/SECURITY.md +12 -0
- package/config.schema.json +934 -0
- package/dist/conditions/already-triggered-condition.d.ts +10 -0
- package/dist/conditions/already-triggered-condition.js +22 -0
- package/dist/conditions/already-triggered-condition.js.map +1 -0
- package/dist/conditions/arming-in-progress-condition.d.ts +10 -0
- package/dist/conditions/arming-in-progress-condition.js +21 -0
- package/dist/conditions/arming-in-progress-condition.js.map +1 -0
- package/dist/conditions/arming-lock-condition.d.ts +10 -0
- package/dist/conditions/arming-lock-condition.js +43 -0
- package/dist/conditions/arming-lock-condition.js.map +1 -0
- package/dist/conditions/condition.d.ts +22 -0
- package/dist/conditions/condition.js +19 -0
- package/dist/conditions/condition.js.map +1 -0
- package/dist/conditions/double-knock-condition.d.ts +24 -0
- package/dist/conditions/double-knock-condition.js +73 -0
- package/dist/conditions/double-knock-condition.js.map +1 -0
- package/dist/conditions/not-armed-condition.d.ts +10 -0
- package/dist/conditions/not-armed-condition.js +26 -0
- package/dist/conditions/not-armed-condition.js.map +1 -0
- package/dist/conditions/trigger-already-running-condition.d.ts +10 -0
- package/dist/conditions/trigger-already-running-condition.js +21 -0
- package/dist/conditions/trigger-already-running-condition.js.map +1 -0
- package/dist/constants/default-constant.d.ts +24 -0
- package/dist/constants/default-constant.js +27 -0
- package/dist/constants/default-constant.js.map +1 -0
- package/dist/constants/homekit-constant.d.ts +2 -0
- package/dist/constants/homekit-constant.js +3 -0
- package/dist/constants/homekit-constant.js.map +1 -0
- package/dist/constants/matter-constant.d.ts +21 -0
- package/dist/constants/matter-constant.js +61 -0
- package/dist/constants/matter-constant.js.map +1 -0
- package/dist/handlers/sensor-handler.d.ts +24 -0
- package/dist/handlers/sensor-handler.js +63 -0
- package/dist/handlers/sensor-handler.js.map +1 -0
- package/dist/handlers/state-handler.d.ts +49 -0
- package/dist/handlers/state-handler.js +204 -0
- package/dist/handlers/state-handler.js.map +1 -0
- package/dist/handlers/switch-handler.d.ts +28 -0
- package/dist/handlers/switch-handler.js +95 -0
- package/dist/handlers/switch-handler.js.map +1 -0
- package/dist/handlers/trip-handler.d.ts +44 -0
- package/dist/handlers/trip-handler.js +156 -0
- package/dist/handlers/trip-handler.js.map +1 -0
- package/dist/homekit/homekit-registrar.d.ts +12 -0
- package/dist/homekit/homekit-registrar.js +23 -0
- package/dist/homekit/homekit-registrar.js.map +1 -0
- package/dist/homekit/service-factory.d.ts +12 -0
- package/dist/homekit/service-factory.js +24 -0
- package/dist/homekit/service-factory.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/condition-context-interface.d.ts +14 -0
- package/dist/interfaces/condition-context-interface.js +2 -0
- package/dist/interfaces/condition-context-interface.js.map +1 -0
- package/dist/interfaces/hap-types-interface.d.ts +9 -0
- package/dist/interfaces/hap-types-interface.js +2 -0
- package/dist/interfaces/hap-types-interface.js.map +1 -0
- package/dist/interfaces/mqtt-status-payload-interface.d.ts +6 -0
- package/dist/interfaces/mqtt-status-payload-interface.js +2 -0
- package/dist/interfaces/mqtt-status-payload-interface.js.map +1 -0
- package/dist/interfaces/options-interface.d.ts +87 -0
- package/dist/interfaces/options-interface.js +2 -0
- package/dist/interfaces/options-interface.js.map +1 -0
- package/dist/interfaces/persisted-state-interface.d.ts +6 -0
- package/dist/interfaces/persisted-state-interface.js +2 -0
- package/dist/interfaces/persisted-state-interface.js.map +1 -0
- package/dist/interfaces/service-registry-interface.d.ts +10 -0
- package/dist/interfaces/service-registry-interface.js +2 -0
- package/dist/interfaces/service-registry-interface.js.map +1 -0
- package/dist/interfaces/system-state-interface.d.ts +22 -0
- package/dist/interfaces/system-state-interface.js +2 -0
- package/dist/interfaces/system-state-interface.js.map +1 -0
- package/dist/schemas/arming-lock-schema.d.ts +10 -0
- package/dist/schemas/arming-lock-schema.js +12 -0
- package/dist/schemas/arming-lock-schema.js.map +1 -0
- package/dist/schemas/error-schema.d.ts +4 -0
- package/dist/schemas/error-schema.js +7 -0
- package/dist/schemas/error-schema.js.map +1 -0
- package/dist/schemas/mode-request-schema.d.ts +10 -0
- package/dist/schemas/mode-request-schema.js +18 -0
- package/dist/schemas/mode-request-schema.js.map +1 -0
- package/dist/schemas/status-response-schema.d.ts +7 -0
- package/dist/schemas/status-response-schema.js +10 -0
- package/dist/schemas/status-response-schema.js.map +1 -0
- package/dist/schemas/trip-mode-request-schema.d.ts +9 -0
- package/dist/schemas/trip-mode-request-schema.js +24 -0
- package/dist/schemas/trip-mode-request-schema.js.map +1 -0
- package/dist/security-system-platform.d.ts +19 -0
- package/dist/security-system-platform.js +56 -0
- package/dist/security-system-platform.js.map +1 -0
- package/dist/security-system.d.ts +28 -0
- package/dist/security-system.js +169 -0
- package/dist/security-system.js.map +1 -0
- package/dist/services/command-service.d.ts +16 -0
- package/dist/services/command-service.js +65 -0
- package/dist/services/command-service.js.map +1 -0
- package/dist/services/configuration-service.d.ts +24 -0
- package/dist/services/configuration-service.js +189 -0
- package/dist/services/configuration-service.js.map +1 -0
- package/dist/services/event-bus-service.d.ts +12 -0
- package/dist/services/event-bus-service.js +17 -0
- package/dist/services/event-bus-service.js.map +1 -0
- package/dist/services/matter-service.d.ts +69 -0
- package/dist/services/matter-service.js +363 -0
- package/dist/services/matter-service.js.map +1 -0
- package/dist/services/mqtt-service.d.ts +16 -0
- package/dist/services/mqtt-service.js +97 -0
- package/dist/services/mqtt-service.js.map +1 -0
- package/dist/services/server-service.d.ts +28 -0
- package/dist/services/server-service.js +275 -0
- package/dist/services/server-service.js.map +1 -0
- package/dist/services/storage-service.d.ts +14 -0
- package/dist/services/storage-service.js +70 -0
- package/dist/services/storage-service.js.map +1 -0
- package/dist/services/webhook-service.d.ts +16 -0
- package/dist/services/webhook-service.js +68 -0
- package/dist/services/webhook-service.js.map +1 -0
- package/dist/tests/conditions.test.d.ts +1 -0
- package/dist/tests/conditions.test.js +175 -0
- package/dist/tests/conditions.test.js.map +1 -0
- package/dist/tests/matter-service.test.d.ts +1 -0
- package/dist/tests/matter-service.test.js +475 -0
- package/dist/tests/matter-service.test.js.map +1 -0
- package/dist/tests/mqtt-service.test.d.ts +1 -0
- package/dist/tests/mqtt-service.test.js +168 -0
- package/dist/tests/mqtt-service.test.js.map +1 -0
- package/dist/tests/sensor-handler.test.d.ts +1 -0
- package/dist/tests/sensor-handler.test.js +87 -0
- package/dist/tests/sensor-handler.test.js.map +1 -0
- package/dist/tests/server-service.test.d.ts +1 -0
- package/dist/tests/server-service.test.js +188 -0
- package/dist/tests/server-service.test.js.map +1 -0
- package/dist/tests/state-handler.test.d.ts +1 -0
- package/dist/tests/state-handler.test.js +276 -0
- package/dist/tests/state-handler.test.js.map +1 -0
- package/dist/tests/switch-handler.test.d.ts +1 -0
- package/dist/tests/switch-handler.test.js +186 -0
- package/dist/tests/switch-handler.test.js.map +1 -0
- package/dist/tests/trip-handler.test.d.ts +1 -0
- package/dist/tests/trip-handler.test.js +179 -0
- package/dist/tests/trip-handler.test.js.map +1 -0
- package/dist/timers/timer-manager.d.ts +31 -0
- package/dist/timers/timer-manager.js +118 -0
- package/dist/timers/timer-manager.js.map +1 -0
- package/dist/types/event-payload-map-type.d.ts +15 -0
- package/dist/types/event-payload-map-type.js +2 -0
- package/dist/types/event-payload-map-type.js.map +1 -0
- package/dist/types/event-type.d.ts +44 -0
- package/dist/types/event-type.js +20 -0
- package/dist/types/event-type.js.map +1 -0
- package/dist/types/matter-accessory-key-type.d.ts +7 -0
- package/dist/types/matter-accessory-key-type.js +2 -0
- package/dist/types/matter-accessory-key-type.js.map +1 -0
- package/dist/types/mode-type.d.ts +4 -0
- package/dist/types/mode-type.js +2 -0
- package/dist/types/mode-type.js.map +1 -0
- package/dist/types/origin-type.d.ts +9 -0
- package/dist/types/origin-type.js +11 -0
- package/dist/types/origin-type.js.map +1 -0
- package/dist/types/security-state-type.d.ts +11 -0
- package/dist/types/security-state-type.js +13 -0
- package/dist/types/security-state-type.js.map +1 -0
- package/dist/types/sensor-kind-type.d.ts +2 -0
- package/dist/types/sensor-kind-type.js +2 -0
- package/dist/types/sensor-kind-type.js.map +1 -0
- package/dist/types/service-result-type.d.ts +4 -0
- package/dist/types/service-result-type.js +2 -0
- package/dist/types/service-result-type.js.map +1 -0
- package/dist/utils/arming-util.d.ts +19 -0
- package/dist/utils/arming-util.js +24 -0
- package/dist/utils/arming-util.js.map +1 -0
- package/dist/utils/state-util.d.ts +8 -0
- package/dist/utils/state-util.js +29 -0
- package/dist/utils/state-util.js.map +1 -0
- package/docs/MQTT.md +50 -0
- package/package.json +64 -0
- package/src/@types/homebridge-lib.d.ts +11 -0
- package/src/conditions/already-triggered-condition.ts +24 -0
- package/src/conditions/arming-in-progress-condition.ts +23 -0
- package/src/conditions/arming-lock-condition.ts +50 -0
- package/src/conditions/condition.ts +31 -0
- package/src/conditions/double-knock-condition.ts +84 -0
- package/src/conditions/not-armed-condition.ts +30 -0
- package/src/conditions/trigger-already-running-condition.ts +23 -0
- package/src/constants/default-constant.ts +28 -0
- package/src/constants/homekit-constant.ts +2 -0
- package/src/constants/matter-constant.ts +69 -0
- package/src/handlers/sensor-handler.ts +78 -0
- package/src/handlers/state-handler.ts +257 -0
- package/src/handlers/switch-handler.ts +111 -0
- package/src/handlers/trip-handler.ts +193 -0
- package/src/homekit/homekit-registrar.ts +28 -0
- package/src/homekit/service-factory.ts +35 -0
- package/src/index.ts +9 -0
- package/src/interfaces/condition-context-interface.ts +15 -0
- package/src/interfaces/hap-types-interface.ts +11 -0
- package/src/interfaces/mqtt-status-payload-interface.ts +6 -0
- package/src/interfaces/options-interface.ts +116 -0
- package/src/interfaces/persisted-state-interface.ts +7 -0
- package/src/interfaces/service-registry-interface.ts +11 -0
- package/src/interfaces/system-state-interface.ts +25 -0
- package/src/schemas/arming-lock-schema.ts +12 -0
- package/src/schemas/error-schema.ts +7 -0
- package/src/schemas/mode-request-schema.ts +18 -0
- package/src/schemas/status-response-schema.ts +10 -0
- package/src/schemas/trip-mode-request-schema.ts +24 -0
- package/src/security-system-platform.ts +68 -0
- package/src/security-system.ts +199 -0
- package/src/services/command-service.ts +76 -0
- package/src/services/configuration-service.ts +224 -0
- package/src/services/event-bus-service.ts +21 -0
- package/src/services/matter-service.ts +446 -0
- package/src/services/mqtt-service.ts +123 -0
- package/src/services/server-service.ts +327 -0
- package/src/services/storage-service.ts +80 -0
- package/src/services/webhook-service.ts +77 -0
- package/src/tests/conditions.test.ts +212 -0
- package/src/tests/matter-service.test.ts +610 -0
- package/src/tests/mqtt-service.test.ts +231 -0
- package/src/tests/sensor-handler.test.ts +116 -0
- package/src/tests/server-service.test.ts +238 -0
- package/src/tests/state-handler.test.ts +371 -0
- package/src/tests/switch-handler.test.ts +234 -0
- package/src/tests/trip-handler.test.ts +244 -0
- package/src/timers/timer-manager.ts +141 -0
- package/src/types/event-payload-map-type.ts +25 -0
- package/src/types/event-type.ts +61 -0
- package/src/types/matter-accessory-key-type.ts +33 -0
- package/src/types/mode-type.ts +4 -0
- package/src/types/origin-type.ts +9 -0
- package/src/types/security-state-type.ts +11 -0
- package/src/types/sensor-kind-type.ts +2 -0
- package/src/types/service-result-type.ts +4 -0
- package/src/utils/arming-util.ts +44 -0
- package/src/utils/state-util.ts +32 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { SecurityState } from '../types/security-state-type.js';
|
|
3
|
+
import { OriginType } from '../types/origin-type.js';
|
|
4
|
+
import type { SystemState } from '../interfaces/system-state-interface.js';
|
|
5
|
+
import type { SecuritySystemOptions } from '../interfaces/options-interface.js';
|
|
6
|
+
import type { ServiceRegistry } from '../interfaces/service-registry-interface.js';
|
|
7
|
+
import type { StorageService } from '../services/storage-service.js';
|
|
8
|
+
import { EventType } from '../types/event-type.js';
|
|
9
|
+
|
|
10
|
+
// ── Minimal mocks ─────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
function makeMockChar(initialValue: unknown = false) {
|
|
13
|
+
const char = { value: initialValue, updateValue: vi.fn(), setProps: vi.fn() };
|
|
14
|
+
char.updateValue.mockImplementation((v: unknown) => {
|
|
15
|
+
char.value = v;
|
|
16
|
+
});
|
|
17
|
+
return char;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function makeMockService(charValue: unknown = false) {
|
|
21
|
+
const char = makeMockChar(charValue);
|
|
22
|
+
return {
|
|
23
|
+
getCharacteristic: vi.fn().mockReturnValue(char),
|
|
24
|
+
setCharacteristic: vi.fn().mockReturnThis(),
|
|
25
|
+
updateCharacteristic: vi.fn(),
|
|
26
|
+
addCharacteristic: vi.fn(),
|
|
27
|
+
addOptionalCharacteristic: vi.fn(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function makeServices(): ServiceRegistry {
|
|
32
|
+
const s = {} as Record<string, ReturnType<typeof makeMockService>>;
|
|
33
|
+
const keys = [
|
|
34
|
+
'mainService', 'accessoryInfoService', 'tripSwitchService', 'tripHomeSwitchService',
|
|
35
|
+
'tripAwaySwitchService', 'tripNightSwitchService', 'tripOverrideSwitchService',
|
|
36
|
+
'armingLockSwitchService', 'armingLockHomeSwitchService', 'armingLockAwaySwitchService',
|
|
37
|
+
'armingLockNightSwitchService', 'modeHomeSwitchService', 'modeAwaySwitchService',
|
|
38
|
+
'modeNightSwitchService', 'modeOffSwitchService', 'modeAwayExtendedSwitchService',
|
|
39
|
+
'modePauseSwitchService', 'armingMotionSensorService',
|
|
40
|
+
'trippedMotionSensorService', 'triggeredResetMotionSensorService',
|
|
41
|
+
];
|
|
42
|
+
for (const k of keys) {
|
|
43
|
+
s[k] = makeMockService();
|
|
44
|
+
}
|
|
45
|
+
return s as unknown as ServiceRegistry;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function makeState(overrides: Partial<SystemState> = {}): SystemState {
|
|
49
|
+
return {
|
|
50
|
+
currentState: SecurityState.OFF,
|
|
51
|
+
targetState: SecurityState.OFF,
|
|
52
|
+
defaultState: SecurityState.OFF,
|
|
53
|
+
availableTargetStates: [SecurityState.HOME, SecurityState.AWAY, SecurityState.NIGHT, SecurityState.OFF],
|
|
54
|
+
isArming: false,
|
|
55
|
+
isTripping: false,
|
|
56
|
+
isKnocked: false,
|
|
57
|
+
serverAuthenticationAttempts: 0,
|
|
58
|
+
pausedCurrentState: null,
|
|
59
|
+
armingLocks: { global: false, home: false, away: false, night: false },
|
|
60
|
+
modeAwayExtended: false,
|
|
61
|
+
...overrides,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function makeOptions(overrides: Partial<SecuritySystemOptions> = {}): SecuritySystemOptions {
|
|
66
|
+
return {
|
|
67
|
+
armSeconds: 0,
|
|
68
|
+
triggerSeconds: 0,
|
|
69
|
+
resetMinutes: 10,
|
|
70
|
+
testMode: false,
|
|
71
|
+
proxyMode: false,
|
|
72
|
+
saveState: false,
|
|
73
|
+
armingLockSwitch: false,
|
|
74
|
+
armingLockSwitches: false,
|
|
75
|
+
disabledModes: [],
|
|
76
|
+
homeArmSeconds: null,
|
|
77
|
+
awayArmSeconds: null,
|
|
78
|
+
nightArmSeconds: null,
|
|
79
|
+
homeTriggerSeconds: null,
|
|
80
|
+
awayTriggerSeconds: null,
|
|
81
|
+
nightTriggerSeconds: null,
|
|
82
|
+
trippedMotionSensor: false,
|
|
83
|
+
trippedMotionSensorSeconds: 5,
|
|
84
|
+
resetOffFlow: false,
|
|
85
|
+
...overrides,
|
|
86
|
+
} as unknown as SecuritySystemOptions;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function makeMockLog() {
|
|
90
|
+
return { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function makeStorage() {
|
|
94
|
+
return { save: vi.fn(), load: vi.fn(), init: vi.fn() } as unknown as StorageService;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function makeTimers() {
|
|
98
|
+
return {
|
|
99
|
+
setArmTimer: vi.fn(), clearArmTimer: vi.fn(),
|
|
100
|
+
setTriggerTimer: vi.fn(), clearTriggerTimer: vi.fn(), isTriggerRunning: vi.fn().mockReturnValue(false),
|
|
101
|
+
setPauseTimer: vi.fn(), clearPauseTimer: vi.fn(),
|
|
102
|
+
setDoubleKnockTimer: vi.fn(), clearDoubleKnockTimer: vi.fn(),
|
|
103
|
+
setResetTimer: vi.fn(), clearResetTimer: vi.fn(),
|
|
104
|
+
setTrippedInterval: vi.fn(), clearTrippedInterval: vi.fn(),
|
|
105
|
+
clearAll: vi.fn(),
|
|
106
|
+
} as any;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function makeMockSensor() {
|
|
110
|
+
return {
|
|
111
|
+
resetArmingMotionSensor: vi.fn(),
|
|
112
|
+
setTrippedMotionSensor: vi.fn(),
|
|
113
|
+
resetTrippedMotionSensor: vi.fn(),
|
|
114
|
+
pulseResetMotionSensor: vi.fn(),
|
|
115
|
+
updateArmingMotionSensor: vi.fn(),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── StateHandler tests ────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
describe('StateHandler.getArmingSeconds', async () => {
|
|
122
|
+
const { StateHandler } = await import('../handlers/state-handler.js');
|
|
123
|
+
|
|
124
|
+
let stateHandler: InstanceType<typeof StateHandler>;
|
|
125
|
+
let state: SystemState;
|
|
126
|
+
|
|
127
|
+
beforeEach(async () => {
|
|
128
|
+
const { EventBusService } = await import('../services/event-bus-service.js');
|
|
129
|
+
|
|
130
|
+
state = makeState();
|
|
131
|
+
const services = makeServices();
|
|
132
|
+
const options = makeOptions();
|
|
133
|
+
const log = makeMockLog();
|
|
134
|
+
const bus = new EventBusService();
|
|
135
|
+
|
|
136
|
+
const sensor = makeMockSensor() as any;
|
|
137
|
+
stateHandler = new StateHandler(services, state, options, {} as any, log as any, bus, makeStorage(), makeTimers(), sensor);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('returns 0 when current state is TRIGGERED', () => {
|
|
141
|
+
state.currentState = SecurityState.TRIGGERED;
|
|
142
|
+
expect(stateHandler.getArmingSeconds(SecurityState.HOME)).toBe(0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('returns 0 when target state is OFF (disarm)', () => {
|
|
146
|
+
state.currentState = SecurityState.HOME;
|
|
147
|
+
expect(stateHandler.getArmingSeconds(SecurityState.OFF)).toBe(0);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('returns global armSeconds when no mode-specific value', () => {
|
|
151
|
+
state.currentState = SecurityState.HOME;
|
|
152
|
+
expect(stateHandler.getArmingSeconds(SecurityState.AWAY)).toBe(0);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// ── StateHandler.updateTargetState ───────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
describe('StateHandler.updateTargetState', async () => {
|
|
159
|
+
const { StateHandler } = await import('../handlers/state-handler.js');
|
|
160
|
+
const { EventBusService } = await import('../services/event-bus-service.js');
|
|
161
|
+
|
|
162
|
+
it('returns success (no-op) when currentState already matches the requested mode', async () => {
|
|
163
|
+
const state = makeState({ currentState: SecurityState.HOME, targetState: SecurityState.HOME });
|
|
164
|
+
const log = makeMockLog();
|
|
165
|
+
const bus = new EventBusService();
|
|
166
|
+
const sensor = makeMockSensor() as any;
|
|
167
|
+
const timers = makeTimers();
|
|
168
|
+
const handler = new StateHandler(makeServices(), state, makeOptions(), {} as any, log as any, bus, makeStorage(), timers, sensor);
|
|
169
|
+
|
|
170
|
+
const result = handler.updateTargetState(SecurityState.HOME, OriginType.INTERNAL, 0);
|
|
171
|
+
expect(result.success).toBe(true);
|
|
172
|
+
expect(timers.clearArmTimer).not.toHaveBeenCalled();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('cancels arm timer and applies state immediately on same-target re-call while arming', async () => {
|
|
176
|
+
const state = makeState({ currentState: SecurityState.OFF, targetState: SecurityState.HOME, isArming: true });
|
|
177
|
+
const log = makeMockLog();
|
|
178
|
+
const bus = new EventBusService();
|
|
179
|
+
const sensor = makeMockSensor() as any;
|
|
180
|
+
const timers = makeTimers();
|
|
181
|
+
const handler = new StateHandler(makeServices(), state, makeOptions(), {} as any, log as any, bus, makeStorage(), timers, sensor);
|
|
182
|
+
|
|
183
|
+
const result = handler.updateTargetState(SecurityState.HOME, OriginType.EXTERNAL, 0);
|
|
184
|
+
expect(result.success).toBe(true);
|
|
185
|
+
expect(timers.clearArmTimer).toHaveBeenCalled();
|
|
186
|
+
expect(state.isArming).toBe(false);
|
|
187
|
+
expect(state.currentState).toBe(SecurityState.HOME);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('applies state immediately on same-target re-call when not arming', async () => {
|
|
191
|
+
const state = makeState({ currentState: SecurityState.OFF, targetState: SecurityState.HOME, isArming: false });
|
|
192
|
+
const log = makeMockLog();
|
|
193
|
+
const bus = new EventBusService();
|
|
194
|
+
const sensor = makeMockSensor() as any;
|
|
195
|
+
const timers = makeTimers();
|
|
196
|
+
const handler = new StateHandler(makeServices(), state, makeOptions(), {} as any, log as any, bus, makeStorage(), timers, sensor);
|
|
197
|
+
|
|
198
|
+
const result = handler.updateTargetState(SecurityState.HOME, OriginType.EXTERNAL, 0);
|
|
199
|
+
expect(result.success).toBe(true);
|
|
200
|
+
expect(timers.clearArmTimer).not.toHaveBeenCalled();
|
|
201
|
+
expect(state.currentState).toBe(SecurityState.HOME);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('transitions to a new armed mode', async () => {
|
|
205
|
+
const state = makeState({ currentState: SecurityState.OFF, targetState: SecurityState.OFF });
|
|
206
|
+
const log = makeMockLog();
|
|
207
|
+
const bus = new EventBusService();
|
|
208
|
+
const sensor = makeMockSensor() as any;
|
|
209
|
+
const handler = new StateHandler(makeServices(), state, makeOptions(), {} as any, log as any, bus, makeStorage(), makeTimers(), sensor);
|
|
210
|
+
|
|
211
|
+
const result = handler.updateTargetState(SecurityState.HOME, OriginType.REGULAR_SWITCH, 0);
|
|
212
|
+
expect(result.success).toBe(true);
|
|
213
|
+
expect(state.targetState).toBe(SecurityState.HOME);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// ── Regression: RESET_TRIP_SWITCHES on TRIGGERED ───────────────────────────────
|
|
218
|
+
// Issue 905: Trip Switch was being reset to OFF when the alarm triggered,
|
|
219
|
+
// hiding the active sensor from the user.
|
|
220
|
+
|
|
221
|
+
describe('StateHandler.setCurrentState - RESET_TRIP_SWITCHES', async () => {
|
|
222
|
+
const { StateHandler } = await import('../handlers/state-handler.js');
|
|
223
|
+
const { EventBusService } = await import('../services/event-bus-service.js');
|
|
224
|
+
|
|
225
|
+
it('does not emit RESET_TRIP_SWITCHES when entering TRIGGERED', () => {
|
|
226
|
+
const state = makeState({ currentState: SecurityState.NIGHT });
|
|
227
|
+
const bus = new EventBusService();
|
|
228
|
+
const spy = vi.fn();
|
|
229
|
+
bus.on(EventType.RESET_TRIP_SWITCHES, spy);
|
|
230
|
+
|
|
231
|
+
const handler = new StateHandler(
|
|
232
|
+
makeServices(), state, makeOptions(), {} as any,
|
|
233
|
+
makeMockLog() as any, bus, makeStorage(),
|
|
234
|
+
makeTimers(), makeMockSensor() as any,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
handler.setCurrentState(SecurityState.TRIGGERED, OriginType.EXTERNAL);
|
|
238
|
+
|
|
239
|
+
expect(spy).not.toHaveBeenCalled();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('still emits RESET_TRIP_SWITCHES when entering a non-TRIGGERED state', () => {
|
|
243
|
+
const state = makeState({ currentState: SecurityState.TRIGGERED });
|
|
244
|
+
const bus = new EventBusService();
|
|
245
|
+
const spy = vi.fn();
|
|
246
|
+
bus.on(EventType.RESET_TRIP_SWITCHES, spy);
|
|
247
|
+
|
|
248
|
+
const handler = new StateHandler(
|
|
249
|
+
makeServices(), state, makeOptions(), {} as any,
|
|
250
|
+
makeMockLog() as any, bus, makeStorage(),
|
|
251
|
+
makeTimers(), makeMockSensor() as any,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
handler.setCurrentState(SecurityState.NIGHT, OriginType.EXTERNAL);
|
|
255
|
+
|
|
256
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ── Regression: isTripping reset on target state change ───────────────────────
|
|
261
|
+
// Issue 905: isTripping was not being reset when the user changed the target
|
|
262
|
+
// mode, causing stale trip state after OFF disarm + re-arm.
|
|
263
|
+
|
|
264
|
+
describe('StateHandler.updateTargetState - isTripping reset', async () => {
|
|
265
|
+
const { StateHandler } = await import('../handlers/state-handler.js');
|
|
266
|
+
const { EventBusService } = await import('../services/event-bus-service.js');
|
|
267
|
+
|
|
268
|
+
it('resets isTripping to false when target state changes to a different mode', () => {
|
|
269
|
+
const state = makeState({ currentState: SecurityState.HOME, isTripping: true });
|
|
270
|
+
const handler = new StateHandler(
|
|
271
|
+
makeServices(), state, makeOptions(), {} as any,
|
|
272
|
+
makeMockLog() as any, new EventBusService(), makeStorage(),
|
|
273
|
+
makeTimers(), makeMockSensor() as any,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
handler.updateTargetState(SecurityState.AWAY, OriginType.INTERNAL, 0);
|
|
277
|
+
|
|
278
|
+
expect(state.isTripping).toBe(false);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('resets isTripping to false when disarming from TRIGGERED to OFF', () => {
|
|
282
|
+
const state = makeState({
|
|
283
|
+
currentState: SecurityState.TRIGGERED,
|
|
284
|
+
targetState: SecurityState.NIGHT,
|
|
285
|
+
isTripping: true,
|
|
286
|
+
});
|
|
287
|
+
const handler = new StateHandler(
|
|
288
|
+
makeServices(), state, makeOptions(), {} as any,
|
|
289
|
+
makeMockLog() as any, new EventBusService(), makeStorage(),
|
|
290
|
+
makeTimers(), makeMockSensor() as any,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
handler.updateTargetState(SecurityState.OFF, OriginType.INTERNAL, 0);
|
|
294
|
+
|
|
295
|
+
expect(state.isTripping).toBe(false);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// ── Arming locks read from SystemState (switches are Matter-only) ─────────────
|
|
300
|
+
|
|
301
|
+
describe('StateHandler.isArmingLocked', async () => {
|
|
302
|
+
const { StateHandler } = await import('../handlers/state-handler.js');
|
|
303
|
+
const { EventBusService } = await import('../services/event-bus-service.js');
|
|
304
|
+
|
|
305
|
+
function makeHandler(state: SystemState) {
|
|
306
|
+
return new StateHandler(
|
|
307
|
+
makeServices(), state, makeOptions({ armingLockSwitch: true }), {} as any,
|
|
308
|
+
makeMockLog() as any, new EventBusService(), makeStorage(),
|
|
309
|
+
makeTimers(), makeMockSensor() as any,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
it('blocks when the global arming lock is active', () => {
|
|
314
|
+
const state = makeState({ armingLocks: { global: true, home: false, away: false, night: false } });
|
|
315
|
+
expect(makeHandler(state).isArmingLocked(SecurityState.HOME)).toBe(true);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('blocks when the matching mode lock is active', () => {
|
|
319
|
+
const state = makeState({ armingLocks: { global: false, home: false, away: true, night: false } });
|
|
320
|
+
expect(makeHandler(state).isArmingLocked(SecurityState.AWAY)).toBe(true);
|
|
321
|
+
expect(makeHandler(state).isArmingLocked(SecurityState.HOME)).toBe(false);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('never blocks disarming', () => {
|
|
325
|
+
const state = makeState({ armingLocks: { global: true, home: false, away: false, night: false } });
|
|
326
|
+
expect(makeHandler(state).isArmingLocked(SecurityState.OFF)).toBe(false);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// ── Away-extended flag is cleared on any target change ───────────────────────
|
|
331
|
+
|
|
332
|
+
describe('StateHandler.updateTargetState - modeAwayExtended reset', async () => {
|
|
333
|
+
const { StateHandler } = await import('../handlers/state-handler.js');
|
|
334
|
+
const { EventBusService } = await import('../services/event-bus-service.js');
|
|
335
|
+
|
|
336
|
+
it('clears modeAwayExtended when the target changes', () => {
|
|
337
|
+
const state = makeState({ currentState: SecurityState.OFF, modeAwayExtended: true });
|
|
338
|
+
const handler = new StateHandler(
|
|
339
|
+
makeServices(), state, makeOptions(), {} as any,
|
|
340
|
+
makeMockLog() as any, new EventBusService(), makeStorage(),
|
|
341
|
+
makeTimers(), makeMockSensor() as any,
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
handler.updateTargetState(SecurityState.HOME, OriginType.INTERNAL, 0);
|
|
345
|
+
|
|
346
|
+
expect(state.modeAwayExtended).toBe(false);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// ── Tripped sensor reset on TRIGGERED ────────────────────────────────────────
|
|
351
|
+
|
|
352
|
+
describe('StateHandler.setCurrentState - tripped sensor reset', async () => {
|
|
353
|
+
const { StateHandler } = await import('../handlers/state-handler.js');
|
|
354
|
+
const { EventBusService } = await import('../services/event-bus-service.js');
|
|
355
|
+
|
|
356
|
+
it('resets tripped motion sensor when entering TRIGGERED', () => {
|
|
357
|
+
const state = makeState({ currentState: SecurityState.NIGHT });
|
|
358
|
+
const bus = new EventBusService();
|
|
359
|
+
const sensor = makeMockSensor() as any;
|
|
360
|
+
|
|
361
|
+
const handler = new StateHandler(
|
|
362
|
+
makeServices(), state, makeOptions(), {} as any,
|
|
363
|
+
makeMockLog() as any, bus, makeStorage(),
|
|
364
|
+
makeTimers(), sensor,
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
handler.setCurrentState(SecurityState.TRIGGERED, OriginType.EXTERNAL);
|
|
368
|
+
|
|
369
|
+
expect(sensor.resetTrippedMotionSensor).toHaveBeenCalled();
|
|
370
|
+
});
|
|
371
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { SecurityState } from '../types/security-state-type.js';
|
|
3
|
+
import { OriginType } from '../types/origin-type.js';
|
|
4
|
+
import type { SystemState } from '../interfaces/system-state-interface.js';
|
|
5
|
+
import type { SecuritySystemOptions } from '../interfaces/options-interface.js';
|
|
6
|
+
import { SwitchHandler } from '../handlers/switch-handler.js';
|
|
7
|
+
|
|
8
|
+
// ── Minimal mocks ─────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
function makeState(overrides: Partial<SystemState> = {}): SystemState {
|
|
11
|
+
return {
|
|
12
|
+
currentState: SecurityState.OFF,
|
|
13
|
+
targetState: SecurityState.OFF,
|
|
14
|
+
defaultState: SecurityState.OFF,
|
|
15
|
+
availableTargetStates: [SecurityState.HOME, SecurityState.AWAY, SecurityState.NIGHT, SecurityState.OFF],
|
|
16
|
+
isArming: false,
|
|
17
|
+
isTripping: false,
|
|
18
|
+
isKnocked: false,
|
|
19
|
+
serverAuthenticationAttempts: 0,
|
|
20
|
+
pausedCurrentState: null,
|
|
21
|
+
armingLocks: { global: false, home: false, away: false, night: false },
|
|
22
|
+
modeAwayExtended: false,
|
|
23
|
+
...overrides,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function makeOptions(overrides: Partial<SecuritySystemOptions> = {}): SecuritySystemOptions {
|
|
28
|
+
return {
|
|
29
|
+
armSeconds: 0,
|
|
30
|
+
triggerSeconds: 0,
|
|
31
|
+
resetMinutes: 10,
|
|
32
|
+
testMode: false,
|
|
33
|
+
proxyMode: false,
|
|
34
|
+
saveState: false,
|
|
35
|
+
overrideOff: false,
|
|
36
|
+
doubleKnock: false,
|
|
37
|
+
doubleKnockSeconds: 90,
|
|
38
|
+
doubleKnockModes: [],
|
|
39
|
+
homeTriggerSeconds: null,
|
|
40
|
+
awayTriggerSeconds: null,
|
|
41
|
+
nightTriggerSeconds: null,
|
|
42
|
+
homeDoubleKnockSeconds: null,
|
|
43
|
+
awayDoubleKnockSeconds: null,
|
|
44
|
+
nightDoubleKnockSeconds: null,
|
|
45
|
+
modeAwayExtendedSwitchTriggerSeconds: null,
|
|
46
|
+
armingLockSwitch: false,
|
|
47
|
+
armingLockSwitches: false,
|
|
48
|
+
disabledModes: [],
|
|
49
|
+
homeArmSeconds: null,
|
|
50
|
+
awayArmSeconds: null,
|
|
51
|
+
nightArmSeconds: null,
|
|
52
|
+
trippedMotionSensor: false,
|
|
53
|
+
trippedMotionSensorSeconds: 5,
|
|
54
|
+
resetOffFlow: false,
|
|
55
|
+
...overrides,
|
|
56
|
+
} as unknown as SecuritySystemOptions;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function makeMockLog() {
|
|
60
|
+
return { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function makeMockStateHandler() {
|
|
64
|
+
return {
|
|
65
|
+
updateTargetState: vi.fn().mockReturnValue({ success: true }),
|
|
66
|
+
getArmingSeconds: vi.fn().mockReturnValue(0),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function makeMockTimers() {
|
|
71
|
+
return {
|
|
72
|
+
setArmTimer: vi.fn(), clearArmTimer: vi.fn(),
|
|
73
|
+
setTriggerTimer: vi.fn(), clearTriggerTimer: vi.fn(), isTriggerRunning: vi.fn().mockReturnValue(false),
|
|
74
|
+
setPauseTimer: vi.fn(), clearPauseTimer: vi.fn(),
|
|
75
|
+
setDoubleKnockTimer: vi.fn(), clearDoubleKnockTimer: vi.fn(),
|
|
76
|
+
setResetTimer: vi.fn(), clearResetTimer: vi.fn(),
|
|
77
|
+
setTrippedInterval: vi.fn(), clearTrippedInterval: vi.fn(),
|
|
78
|
+
clearAll: vi.fn(),
|
|
79
|
+
} as any;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function makeHandler(state: SystemState, options = makeOptions()) {
|
|
83
|
+
const stateHandler = makeMockStateHandler();
|
|
84
|
+
const log = makeMockLog();
|
|
85
|
+
const timers = makeMockTimers();
|
|
86
|
+
const handler = new SwitchHandler(state, options, log as any, timers, stateHandler as any);
|
|
87
|
+
return { handler, stateHandler, log, timers };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── SwitchHandler tests ───────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
describe('SwitchHandler', () => {
|
|
93
|
+
it('setModeSwitch arms via updateTargetState with the arming delay', () => {
|
|
94
|
+
const state = makeState();
|
|
95
|
+
const { handler, stateHandler } = makeHandler(state);
|
|
96
|
+
stateHandler.getArmingSeconds.mockReturnValue(42);
|
|
97
|
+
|
|
98
|
+
const result = handler.setModeSwitch(SecurityState.HOME, true);
|
|
99
|
+
|
|
100
|
+
expect(stateHandler.updateTargetState).toHaveBeenCalledWith(SecurityState.HOME, OriginType.INTERNAL, 42);
|
|
101
|
+
expect(result.success).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('setModeSwitch reports how updateTargetState refused the change', () => {
|
|
105
|
+
const state = makeState();
|
|
106
|
+
const { handler, stateHandler } = makeHandler(state);
|
|
107
|
+
stateHandler.updateTargetState.mockReturnValue({ success: false, reason: 'arming is blocked by an arming lock switch' });
|
|
108
|
+
|
|
109
|
+
const result = handler.setModeSwitch(SecurityState.HOME, true);
|
|
110
|
+
|
|
111
|
+
expect(result.success).toBe(false);
|
|
112
|
+
expect(result.reason).toBe('arming is blocked by an arming lock switch');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('setModeSwitch rejects turning a mode switch off', () => {
|
|
116
|
+
const state = makeState();
|
|
117
|
+
const { handler, stateHandler } = makeHandler(state);
|
|
118
|
+
|
|
119
|
+
const result = handler.setModeSwitch(SecurityState.HOME, false);
|
|
120
|
+
|
|
121
|
+
expect(result.success).toBe(false);
|
|
122
|
+
expect(stateHandler.updateTargetState).not.toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('setModeOffSwitch disarms and propagates refusals', () => {
|
|
126
|
+
const state = makeState();
|
|
127
|
+
const { handler, stateHandler } = makeHandler(state);
|
|
128
|
+
|
|
129
|
+
handler.setModeOffSwitch(true);
|
|
130
|
+
expect(stateHandler.updateTargetState).toHaveBeenCalledWith(SecurityState.OFF, OriginType.INTERNAL, 0);
|
|
131
|
+
|
|
132
|
+
stateHandler.updateTargetState.mockReturnValue({ success: false, reason: 'target mode is disabled' });
|
|
133
|
+
const refused = handler.setModeOffSwitch(true);
|
|
134
|
+
expect(refused).toEqual({ success: false, reason: 'target mode is disabled' });
|
|
135
|
+
|
|
136
|
+
const rejected = handler.setModeOffSwitch(false);
|
|
137
|
+
expect(rejected.success).toBe(false);
|
|
138
|
+
// Only the two successful ON commands reach updateTargetState.
|
|
139
|
+
expect(stateHandler.updateTargetState).toHaveBeenCalledTimes(2);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('setModeAwayExtendedSwitch arms away and records the extended flag', () => {
|
|
143
|
+
const state = makeState();
|
|
144
|
+
const { handler, stateHandler } = makeHandler(state);
|
|
145
|
+
|
|
146
|
+
const result = handler.setModeAwayExtendedSwitch(true);
|
|
147
|
+
|
|
148
|
+
expect(stateHandler.updateTargetState).toHaveBeenCalledWith(SecurityState.AWAY, OriginType.INTERNAL, 0);
|
|
149
|
+
expect(state.modeAwayExtended).toBe(true);
|
|
150
|
+
expect(result.success).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('setModeAwayExtendedSwitch does not set the extended flag when arming was refused', () => {
|
|
154
|
+
const state = makeState();
|
|
155
|
+
const { handler, stateHandler } = makeHandler(state);
|
|
156
|
+
stateHandler.updateTargetState.mockReturnValue({ success: false, reason: 'arming is blocked by an arming lock switch' });
|
|
157
|
+
|
|
158
|
+
const result = handler.setModeAwayExtendedSwitch(true);
|
|
159
|
+
|
|
160
|
+
expect(result.success).toBe(false);
|
|
161
|
+
expect(state.modeAwayExtended).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('setModeAwayExtendedSwitch rejects turning off', () => {
|
|
165
|
+
const state = makeState();
|
|
166
|
+
const { handler } = makeHandler(state);
|
|
167
|
+
|
|
168
|
+
expect(handler.setModeAwayExtendedSwitch(false).success).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('setModePauseSwitch rejects while triggered', () => {
|
|
172
|
+
const state = makeState({ currentState: SecurityState.TRIGGERED });
|
|
173
|
+
const { handler, timers } = makeHandler(state);
|
|
174
|
+
|
|
175
|
+
const result = handler.setModePauseSwitch(true);
|
|
176
|
+
|
|
177
|
+
expect(result.success).toBe(false);
|
|
178
|
+
expect(timers.setPauseTimer).not.toHaveBeenCalled();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('setModePauseSwitch rejects while disarmed', () => {
|
|
182
|
+
const state = makeState({ currentState: SecurityState.OFF });
|
|
183
|
+
const { handler, timers } = makeHandler(state);
|
|
184
|
+
|
|
185
|
+
const result = handler.setModePauseSwitch(true);
|
|
186
|
+
|
|
187
|
+
expect(result.success).toBe(false);
|
|
188
|
+
expect(timers.setPauseTimer).not.toHaveBeenCalled();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('setModePauseSwitch pauses and schedules the resume timer', () => {
|
|
192
|
+
const state = makeState({ currentState: SecurityState.HOME });
|
|
193
|
+
const { handler, stateHandler, timers } = makeHandler(state, makeOptions({ pauseMinutes: 5 }));
|
|
194
|
+
|
|
195
|
+
const result = handler.setModePauseSwitch(true);
|
|
196
|
+
|
|
197
|
+
expect(state.pausedCurrentState).toBe(SecurityState.HOME);
|
|
198
|
+
expect(stateHandler.updateTargetState).toHaveBeenCalledWith(SecurityState.OFF, OriginType.INTERNAL, 0);
|
|
199
|
+
expect(timers.setPauseTimer).toHaveBeenCalledWith(5 * 60 * 1000, expect.any(Function));
|
|
200
|
+
expect(result.success).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('setModePauseSwitch cancel resumes the paused mode', () => {
|
|
204
|
+
const state = makeState({ currentState: SecurityState.OFF, pausedCurrentState: SecurityState.AWAY });
|
|
205
|
+
const { handler, stateHandler, timers } = makeHandler(state);
|
|
206
|
+
|
|
207
|
+
const result = handler.setModePauseSwitch(false);
|
|
208
|
+
|
|
209
|
+
expect(timers.clearPauseTimer).toHaveBeenCalled();
|
|
210
|
+
expect(stateHandler.updateTargetState).toHaveBeenCalledWith(SecurityState.AWAY, OriginType.INTERNAL, 0);
|
|
211
|
+
expect(result.success).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('updateArmingLock stores the lock on state and returns success', () => {
|
|
215
|
+
const state = makeState();
|
|
216
|
+
const { handler } = makeHandler(state);
|
|
217
|
+
|
|
218
|
+
expect(handler.updateArmingLock('global', true).success).toBe(true);
|
|
219
|
+
expect(state.armingLocks.global).toBe(true);
|
|
220
|
+
|
|
221
|
+
expect(handler.updateArmingLock('away', false).success).toBe(true);
|
|
222
|
+
expect(state.armingLocks.away).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('updateArmingLock rejects unknown modes', () => {
|
|
226
|
+
const state = makeState();
|
|
227
|
+
const { handler } = makeHandler(state);
|
|
228
|
+
|
|
229
|
+
const result = handler.updateArmingLock('garage', true);
|
|
230
|
+
|
|
231
|
+
expect(result.success).toBe(false);
|
|
232
|
+
expect(result.reason).toBe('unknown arming lock mode: garage');
|
|
233
|
+
});
|
|
234
|
+
});
|