mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-28 19:27:41 +00:00
feat: stack glob patterns and route severity levels (#1651)
* feat: add stack glob patterns and route severity levels Operators can filter notification routes and mute rules with anchored * globs, and routes can target info, warning, or error. Matching is fail-closed for unsafe stored patterns; write paths keep partial-PUT semantics and ReDoS caps. * test: split mute and routing chip tests to avoid dialog race * fix: move stack pattern client validator out of PatternChips * fix: bound stack glob matching and atomic pattern chip saves
This commit is contained in:
@@ -70,6 +70,7 @@ function makeRoute(overrides: Record<string, unknown> = {}) {
|
||||
stack_patterns: ['my-app'],
|
||||
label_ids: null as number[] | null,
|
||||
categories: null as string[] | null,
|
||||
levels: null as ('info' | 'warning' | 'error')[] | null,
|
||||
channel_type: 'discord' as const,
|
||||
channel_url: 'https://discord.com/api/webhooks/123/abc',
|
||||
priority: 0,
|
||||
@@ -370,6 +371,53 @@ describe('NotificationService - routing logic', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('routes by severity when levels is set; mismatches fall back to agents', async () => {
|
||||
mockGetEnabledNotificationRoutes.mockReturnValue([
|
||||
makeRoute({ stack_patterns: [], levels: ['error'] }),
|
||||
]);
|
||||
mockGetEnabledAgents.mockReturnValue([makeAgent()]);
|
||||
|
||||
await svc.dispatchAlert('info', 'monitor_alert', 'Info only', { stackName: 'my-app' });
|
||||
expect(mockFetch).not.toHaveBeenCalledWith(
|
||||
'https://discord.com/api/webhooks/123/abc',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'https://hooks.slack.com/services/global',
|
||||
expect.objectContaining({ method: 'POST' }),
|
||||
);
|
||||
|
||||
vi.clearAllMocks();
|
||||
mockGetEnabledNotificationRoutes.mockReturnValue([
|
||||
makeRoute({ stack_patterns: [], levels: ['error'] }),
|
||||
]);
|
||||
mockGetEnabledAgents.mockReturnValue([makeAgent()]);
|
||||
|
||||
await svc.dispatchAlert('error', 'monitor_alert', 'Error route', { stackName: 'my-app' });
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'https://discord.com/api/webhooks/123/abc',
|
||||
expect.objectContaining({ method: 'POST' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('fails closed on invalid stored route patterns without blocking agent fallback', async () => {
|
||||
mockGetEnabledNotificationRoutes.mockReturnValue([
|
||||
makeRoute({ stack_patterns: ['****'] }),
|
||||
]);
|
||||
mockGetEnabledAgents.mockReturnValue([makeAgent()]);
|
||||
|
||||
await svc.dispatchAlert('error', 'monitor_alert', 'Bad pattern', { stackName: 'my-app' });
|
||||
|
||||
expect(mockFetch).not.toHaveBeenCalledWith(
|
||||
'https://discord.com/api/webhooks/123/abc',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'https://hooks.slack.com/services/global',
|
||||
expect.objectContaining({ method: 'POST' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('fires a category-only route when category matches', async () => {
|
||||
mockGetEnabledNotificationRoutes.mockReturnValue([
|
||||
makeRoute({ stack_patterns: [], categories: ['deploy_failure'] }),
|
||||
|
||||
Reference in New Issue
Block a user