feat: Add event-based automation triggers (#2537)
This commit is contained in:
committed by
dermotduffy
parent
b701366762
commit
a31816c168
@@ -4,6 +4,7 @@ import { createIssueManager } from '../../../src/card-controller/issues/factory'
|
||||
import { IssueManager } from '../../../src/card-controller/issues/issue-manager';
|
||||
import { ConditionStateManager } from '../../../src/condition-trigger/conditions/state-manager';
|
||||
import { createCardAPI } from '../../test-utils';
|
||||
import { createSubscriptionHealth } from '../test-utils';
|
||||
|
||||
describe('createIssueManager', () => {
|
||||
beforeEach(() => {
|
||||
@@ -15,12 +16,15 @@ describe('createIssueManager', () => {
|
||||
});
|
||||
|
||||
it('should return a IssueManager instance', () => {
|
||||
const manager = createIssueManager(createCardAPI());
|
||||
const manager = createIssueManager(createCardAPI(), createSubscriptionHealth());
|
||||
expect(manager).toBeInstanceOf(IssueManager);
|
||||
});
|
||||
|
||||
it('should register all expected issues', () => {
|
||||
const manager = createIssueManager(createCardAPI()).getStateManager();
|
||||
const manager = createIssueManager(
|
||||
createCardAPI(),
|
||||
createSubscriptionHealth(),
|
||||
).getStateManager();
|
||||
|
||||
expect(manager.getIssueDescriptions()).toHaveLength(0);
|
||||
|
||||
@@ -29,6 +33,7 @@ describe('createIssueManager', () => {
|
||||
'config_upgrade',
|
||||
'config_upgrade_failure',
|
||||
'connection',
|
||||
'event_subscription',
|
||||
'initialization',
|
||||
'legacy_resource',
|
||||
'media_query',
|
||||
@@ -51,7 +56,7 @@ describe('createIssueManager', () => {
|
||||
const api = createCardAPI();
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
const manager = createIssueManager(api);
|
||||
const manager = createIssueManager(api, createSubscriptionHealth());
|
||||
|
||||
manager.trigger('media_query', { error: new Error('x') });
|
||||
manager.trigger('initialization', { error: new Error('x') });
|
||||
@@ -76,7 +81,7 @@ describe('createIssueManager', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const manager = createIssueManager(api);
|
||||
const manager = createIssueManager(api, createSubscriptionHealth());
|
||||
|
||||
// Setting view starts the media_load timer (via the condition state
|
||||
// listener → evaluate → detectDynamic).
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { EventSubscriptionIssue } from '../../../../src/card-controller/issues/issues/event-subscription';
|
||||
import { Issue } from '../../../../src/card-controller/issues/types';
|
||||
import { SubscriptionFailure } from '../../../../src/ha/connection/subscription-health-monitor';
|
||||
import { localize } from '../../../../src/localize/localize';
|
||||
import { createSubscriptionHealth } from '../../test-utils';
|
||||
|
||||
const createSubscriptionFailure = (key: string): SubscriptionFailure<string> => ({
|
||||
key,
|
||||
error: new Error(key),
|
||||
failureCount: 1,
|
||||
});
|
||||
|
||||
describe('EventSubscriptionIssue', () => {
|
||||
it('should register the change callback as a health listener', () => {
|
||||
const health = createSubscriptionHealth();
|
||||
const changeCallback = vi.fn();
|
||||
|
||||
new EventSubscriptionIssue(health, changeCallback);
|
||||
|
||||
expect(health.addListener).toBeCalledWith(changeCallback);
|
||||
});
|
||||
|
||||
it('should have no issue when there are no failures', () => {
|
||||
const health = createSubscriptionHealth();
|
||||
const issue = new EventSubscriptionIssue(health, vi.fn());
|
||||
|
||||
expect(issue.hasIssue()).toBe(false);
|
||||
expect(issue.getIssue()).toBeNull();
|
||||
expect(issue.getNotification()).toBeNull();
|
||||
});
|
||||
|
||||
it('should describe the failing event types sorted, at medium severity', () => {
|
||||
const health = createSubscriptionHealth();
|
||||
health.getFailures.mockReturnValue([
|
||||
createSubscriptionFailure('zebra_event'),
|
||||
createSubscriptionFailure('alpha_event'),
|
||||
]);
|
||||
const issue = new EventSubscriptionIssue(health, vi.fn());
|
||||
|
||||
expect(issue.hasIssue()).toBe(true);
|
||||
|
||||
const description = issue.getIssue();
|
||||
expect(description?.severity).toBe('medium');
|
||||
expect(description?.notification.heading?.text).toBe(
|
||||
localize('issues.event_subscription.heading'),
|
||||
);
|
||||
expect(description?.notification.metadata?.map((detail) => detail.text)).toEqual([
|
||||
'alpha_event',
|
||||
'zebra_event',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should offer a retry control on the notification', () => {
|
||||
const health = createSubscriptionHealth();
|
||||
health.getFailures.mockReturnValue([createSubscriptionFailure('zha_event')]);
|
||||
const issue = new EventSubscriptionIssue(health, vi.fn());
|
||||
|
||||
expect(issue.getNotification()?.controls?.[0].icon).toBe('mdi:refresh');
|
||||
});
|
||||
|
||||
it('should re-drive the failing subscriptions on retry', () => {
|
||||
const health = createSubscriptionHealth();
|
||||
const issue = new EventSubscriptionIssue(health, vi.fn());
|
||||
|
||||
expect(issue.retry()).toBe(true);
|
||||
expect(health.retry).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not opt into IssueManager-scheduled retries', () => {
|
||||
// No `needsRetry()` means the subscription manager stays the sole auto-retry
|
||||
// loop; the IssueManager never schedules this issue.
|
||||
const issue: Issue = new EventSubscriptionIssue(createSubscriptionHealth(), vi.fn());
|
||||
|
||||
expect(issue.needsRetry).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should remove its health listener on destroy', () => {
|
||||
const health = createSubscriptionHealth();
|
||||
const unsubscribe = vi.fn();
|
||||
health.addListener.mockReturnValue(unsubscribe);
|
||||
const issue = new EventSubscriptionIssue(health, vi.fn());
|
||||
|
||||
issue.destroy();
|
||||
|
||||
expect(unsubscribe).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -539,7 +539,7 @@ describe('IssueStateManager', () => {
|
||||
});
|
||||
|
||||
describe('destroy', () => {
|
||||
it('should destroy all issues and clear', () => {
|
||||
it('should clear, reset and destroy all issues', () => {
|
||||
const manager = createManager();
|
||||
manager.destroy();
|
||||
|
||||
@@ -549,6 +549,14 @@ describe('IssueStateManager', () => {
|
||||
expect(mockConfigUpgrade.reset).toBeCalled();
|
||||
expect(mockLegacyResource.reset).toBeCalled();
|
||||
expect(mockMediaLoad.reset).toBeCalled();
|
||||
|
||||
assert(mockConfigUpgrade.destroy);
|
||||
assert(mockLegacyResource.destroy);
|
||||
assert(mockMediaLoad.destroy);
|
||||
expect(mockConfigUpgrade.destroy).toBeCalled();
|
||||
expect(mockLegacyResource.destroy).toBeCalled();
|
||||
expect(mockMediaLoad.destroy).toBeCalled();
|
||||
|
||||
expect(manager.getIssuePresence().size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user