fix: Ensure no camera outlives a failed initialization (#2657)

This commit is contained in:
Dermot Duffy
2026-08-04 21:33:46 -07:00
committed by GitHub
parent 80ec92e3b2
commit 9f88aacefe
13 changed files with 646 additions and 123 deletions
+60
View File
@@ -726,6 +726,66 @@ describe('Camera', () => {
expect(eventWatcher.unsubscribe).toHaveBeenCalled();
});
it('should release earlier subscriptions when a later subscription throws', async () => {
const camera = new Camera(
createCameraConfig({
id: 'camera_1',
triggers: {
events: [{ event_type: 'zha_event' }],
},
}),
new GenericCameraManagerEngine(createHASSManager()),
);
const error = new Error('subscribe failed');
const stateWatcher = mock<StateWatcherSubscriptionInterface>();
const eventWatcher = mock<EventWatcherSubscriptionInterface>();
vi.mocked(eventWatcher.subscribe).mockImplementation(() => {
throw error;
});
await expect(
camera.initialize({
hassManager: createHASSManager({ stateWatcher, eventWatcher }),
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
}),
).rejects.toThrow(error);
// The state subscription was registered before the failure, so nothing
// else can release it.
expect(stateWatcher.subscribe).toHaveBeenCalled();
expect(stateWatcher.unsubscribe).toHaveBeenCalled();
});
it('should report the initialization failure when cleanup also fails', async () => {
const camera = new Camera(
createCameraConfig({
id: 'camera_1',
triggers: {
events: [{ event_type: 'zha_event' }],
},
}),
new GenericCameraManagerEngine(createHASSManager()),
);
const error = new Error('subscribe failed');
const stateWatcher = mock<StateWatcherSubscriptionInterface>();
vi.mocked(stateWatcher.unsubscribe).mockRejectedValue(new Error('destroy failed'));
const eventWatcher = mock<EventWatcherSubscriptionInterface>();
vi.mocked(eventWatcher.subscribe).mockImplementation(() => {
throw error;
});
await expect(
camera.initialize({
hassManager: createHASSManager({ stateWatcher, eventWatcher }),
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
}),
).rejects.toThrow(error);
expect(stateWatcher.unsubscribe).toHaveBeenCalled();
});
it('should attach a context-only matcher when only a context filter is set', async () => {
const camera = new Camera(
createCameraConfig({