mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-31 04:38:11 +00:00
fix(notifications): prevent self-container stack routing (#1242)
* fix(notifications): prevent self-container stack routing * fix(stack-files): stabilize download metrics in CI
This commit is contained in:
@@ -25,6 +25,7 @@ const {
|
||||
mockInspect,
|
||||
mockGetContainer,
|
||||
mockGetDocker,
|
||||
mockIsOwnContainer,
|
||||
} = vi.hoisted(() => ({
|
||||
mockDispatchAlert: vi.fn().mockResolvedValue(undefined),
|
||||
mockBroadcastEvent: vi.fn(),
|
||||
@@ -34,6 +35,7 @@ const {
|
||||
mockInspect: vi.fn().mockResolvedValue({}),
|
||||
mockGetContainer: vi.fn(),
|
||||
mockGetDocker: vi.fn(),
|
||||
mockIsOwnContainer: vi.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
vi.mock('../services/NotificationService', () => ({
|
||||
@@ -57,6 +59,14 @@ vi.mock('../services/NodeRegistry', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../services/SelfIdentityService', () => ({
|
||||
default: {
|
||||
getInstance: () => ({
|
||||
isOwnContainer: mockIsOwnContainer,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
// ── Fake Docker stream helper ──────────────────────────────────────────
|
||||
|
||||
interface FakeStream extends EventEmitter {
|
||||
@@ -88,6 +98,8 @@ beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.useFakeTimers();
|
||||
mockGetGlobalSettings.mockReturnValue({ global_crash: '1' });
|
||||
mockIsOwnContainer.mockReset();
|
||||
mockIsOwnContainer.mockReturnValue(false);
|
||||
|
||||
stream = makeStream();
|
||||
mockGetEvents.mockImplementation(async () => stream);
|
||||
@@ -130,6 +142,61 @@ describe('DockerEventService - die classification', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps stack and container routing for non-self compose crashes', async () => {
|
||||
service = new DockerEventService(1, 'local');
|
||||
await service.start();
|
||||
|
||||
stream.push({
|
||||
Type: 'container',
|
||||
Action: 'die',
|
||||
Actor: {
|
||||
ID: 'app-id',
|
||||
Attributes: {
|
||||
exitCode: '1',
|
||||
name: 'web',
|
||||
'com.docker.compose.project': 'web-stack',
|
||||
},
|
||||
},
|
||||
});
|
||||
await vi.advanceTimersByTimeAsync(600);
|
||||
|
||||
expect(mockDispatchAlert).toHaveBeenCalledWith(
|
||||
'error',
|
||||
'monitor_alert',
|
||||
expect.stringContaining('Container Crash Detected'),
|
||||
expect.objectContaining({ stackName: 'web-stack', containerName: 'web' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('routes self-container crashes as system-only notifications', async () => {
|
||||
mockIsOwnContainer.mockImplementation((idOrName: string) =>
|
||||
idOrName === 'self-id' || idOrName === 'sencho',
|
||||
);
|
||||
service = new DockerEventService(1, 'local');
|
||||
await service.start();
|
||||
|
||||
stream.push({
|
||||
Type: 'container',
|
||||
Action: 'die',
|
||||
Actor: {
|
||||
ID: 'self-id',
|
||||
Attributes: {
|
||||
exitCode: '1',
|
||||
name: 'sencho',
|
||||
'com.docker.compose.project': 'sencho',
|
||||
},
|
||||
},
|
||||
});
|
||||
await vi.advanceTimersByTimeAsync(600);
|
||||
|
||||
expect(mockDispatchAlert).toHaveBeenCalledWith(
|
||||
'error',
|
||||
'monitor_alert',
|
||||
expect.stringContaining('Container Crash Detected'),
|
||||
{ actor: 'system:docker-events' },
|
||||
);
|
||||
});
|
||||
|
||||
it('does not emit when die follows a recent kill (intentional)', async () => {
|
||||
service = new DockerEventService(1, 'local');
|
||||
await service.start();
|
||||
@@ -231,6 +298,33 @@ describe('DockerEventService - die classification', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('routes self-container unhealthy alerts as system-only notifications', async () => {
|
||||
mockIsOwnContainer.mockImplementation((idOrName: string) =>
|
||||
idOrName === 'self-id' || idOrName === 'sencho',
|
||||
);
|
||||
service = new DockerEventService(1, 'local');
|
||||
await service.start();
|
||||
|
||||
stream.push({
|
||||
Type: 'container',
|
||||
Action: 'health_status: unhealthy',
|
||||
Actor: {
|
||||
ID: 'self-id',
|
||||
Attributes: {
|
||||
name: 'sencho',
|
||||
'com.docker.compose.project': 'sencho',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockDispatchAlert).toHaveBeenCalledWith(
|
||||
'error',
|
||||
'monitor_alert',
|
||||
expect.stringContaining('Healthcheck failed'),
|
||||
{ actor: 'system:docker-events' },
|
||||
);
|
||||
});
|
||||
|
||||
it('does not emit when global_crash is disabled', async () => {
|
||||
mockGetGlobalSettings.mockReturnValue({ global_crash: '0' });
|
||||
service = new DockerEventService(1, 'local');
|
||||
@@ -616,6 +710,30 @@ describe('DockerEventService - state-invalidate broadcasts', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it('does not broadcast stack state-invalidate for Sencho self-container events', async () => {
|
||||
mockIsOwnContainer.mockImplementation((idOrName: string) =>
|
||||
idOrName === 'self-id' || idOrName === 'sencho',
|
||||
);
|
||||
service = new DockerEventService(7, 'node-7');
|
||||
await service.start();
|
||||
|
||||
stream.push({
|
||||
Type: 'container',
|
||||
Action: 'start',
|
||||
Actor: {
|
||||
ID: 'self-id',
|
||||
Attributes: {
|
||||
name: 'sencho',
|
||||
'com.docker.compose.project': 'sencho',
|
||||
},
|
||||
},
|
||||
time: 1,
|
||||
});
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
|
||||
expect(mockBroadcastEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('broadcasts state-invalidate on health_status:unhealthy', async () => {
|
||||
service = new DockerEventService(1, 'local');
|
||||
await service.start();
|
||||
|
||||
Reference in New Issue
Block a user