Files
Dragan Spiridonov e82a7240ec feat: implement CC-internals improvements IMP-00 through IMP-10 (Waves 0-4)
11 improvements across 4 priority tiers, all integrated into production
code paths with 416 new tests across 33 test files. Each wave passed
a brutal honesty audit with all findings resolved.

Wave 0-2 (P0/P1): Middleware chain, microcompact, tool concurrency,
retry engine, session durability, prompt cache latch, startup fast
paths, hook security hardening.

Wave 3 (P2): 4-tier context compaction pipeline, plugin architecture
with local/GitHub/npm sources.

Wave 4 (P3): QE Quality Daemon — priority queue, git watcher (with
Linux polling fallback), coverage delta analyzer, CI monitor, test
suggester, nightly consolidation, notification service with SSRF guard,
persistent SQLite-backed memory, CLI commands, MCP startup integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 17:32:50 +00:00

191 lines
6.8 KiB
TypeScript

/**
* Tests for the QE Quality Daemon Notification Service (IMP-10).
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdirSync, rmSync, existsSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { NotificationService } from '../../../src/workers/quality-daemon/notification-service';
describe('NotificationService', () => {
let service: NotificationService;
let testDir: string;
beforeEach(() => {
testDir = join(tmpdir(), `aqe-notif-test-${Date.now()}`);
service = new NotificationService({
notificationsDir: testDir,
maxNotifications: 10,
});
service.initialize();
});
afterEach(() => {
if (existsSync(testDir)) {
rmSync(testDir, { recursive: true, force: true });
}
});
// -------------------------------------------------------------------------
// send
// -------------------------------------------------------------------------
it('sends a notification and writes to file', async () => {
const notif = await service.send({
type: 'gate_failure',
title: 'Quality Gate Failed',
message: 'Score 45 below threshold 70',
severity: 'critical',
});
expect(notif.id).toMatch(/^notif-/);
expect(notif.timestamp).toBeGreaterThan(0);
expect(notif.read).toBe(false);
expect(service.sentCount).toBe(1);
});
it('increments sentCount for each notification', async () => {
await service.send({ type: 'ci_failure', title: 'CI Failed', message: 'test', severity: 'high' });
await service.send({ type: 'coverage_drop', title: 'Coverage Drop', message: 'test', severity: 'medium' });
expect(service.sentCount).toBe(2);
});
// -------------------------------------------------------------------------
// list
// -------------------------------------------------------------------------
it('lists notifications in reverse chronological order', async () => {
await service.send({ type: 'gate_failure', title: 'First', message: 'msg', severity: 'high' });
// Small delay to ensure different timestamps in filenames
await new Promise((r) => setTimeout(r, 10));
await service.send({ type: 'ci_failure', title: 'Second', message: 'msg', severity: 'medium' });
const list = service.list();
expect(list).toHaveLength(2);
expect(list[0].title).toBe('Second');
expect(list[1].title).toBe('First');
});
it('filters by type', async () => {
await service.send({ type: 'gate_failure', title: 'Gate', message: 'msg', severity: 'critical' });
await service.send({ type: 'ci_failure', title: 'CI', message: 'msg', severity: 'high' });
const gateOnly = service.list({ type: 'gate_failure' });
expect(gateOnly).toHaveLength(1);
expect(gateOnly[0].type).toBe('gate_failure');
});
it('limits result count', async () => {
for (let i = 0; i < 5; i++) {
await service.send({ type: 'daemon_health', title: `N${i}`, message: 'msg', severity: 'info' });
}
const limited = service.list({ limit: 3 });
expect(limited).toHaveLength(3);
});
it('returns empty array when no notifications exist', () => {
const emptyDir = join(tmpdir(), `aqe-empty-${Date.now()}`);
const emptySvc = new NotificationService({ notificationsDir: emptyDir });
const list = emptySvc.list();
expect(list).toEqual([]);
});
it('unreadOnly filter returns only unread when mixed states exist', async () => {
const n1 = await service.send({ type: 'gate_failure', title: 'First', message: 'msg', severity: 'high' });
await new Promise((r) => setTimeout(r, 10));
const n2 = await service.send({ type: 'ci_failure', title: 'Second', message: 'msg', severity: 'high' });
await new Promise((r) => setTimeout(r, 10));
const n3 = await service.send({ type: 'coverage_drop', title: 'Third', message: 'msg', severity: 'medium' });
// Mark middle notification as read
service.markRead(n2.id);
const unread = service.list({ unreadOnly: true });
expect(unread).toHaveLength(2);
const unreadIds = unread.map((n) => n.id);
expect(unreadIds).toContain(n1.id);
expect(unreadIds).toContain(n3.id);
expect(unreadIds).not.toContain(n2.id);
// Full list still has all 3
const all = service.list();
expect(all).toHaveLength(3);
});
// -------------------------------------------------------------------------
// markRead
// -------------------------------------------------------------------------
it('marks a notification as read', async () => {
const notif = await service.send({ type: 'gate_failure', title: 'Test', message: 'msg', severity: 'high' });
const result = service.markRead(notif.id);
expect(result).toBe(true);
const list = service.list({ unreadOnly: true });
expect(list).toHaveLength(0);
});
it('returns false for non-existent notification id', () => {
const result = service.markRead('nonexistent');
expect(result).toBe(false);
});
// -------------------------------------------------------------------------
// clear
// -------------------------------------------------------------------------
it('clears all notifications', async () => {
await service.send({ type: 'gate_failure', title: 'A', message: 'msg', severity: 'high' });
await service.send({ type: 'ci_failure', title: 'B', message: 'msg', severity: 'medium' });
const cleared = service.clear();
expect(cleared).toBe(2);
expect(service.list()).toHaveLength(0);
});
// -------------------------------------------------------------------------
// pruning
// -------------------------------------------------------------------------
it('prunes oldest notifications when exceeding max', async () => {
const smallService = new NotificationService({
notificationsDir: testDir,
maxNotifications: 3,
});
smallService.initialize();
for (let i = 0; i < 5; i++) {
await smallService.send({ type: 'daemon_health', title: `N${i}`, message: 'msg', severity: 'info' });
// Ensure distinct timestamps
await new Promise((r) => setTimeout(r, 10));
}
const list = smallService.list({ limit: 100 });
expect(list.length).toBeLessThanOrEqual(3);
});
// -------------------------------------------------------------------------
// SSRF guard
// -------------------------------------------------------------------------
it('blocks webhook when URL validator rejects', async () => {
const guardedService = new NotificationService({
notificationsDir: testDir,
webhookUrl: 'http://169.254.169.254/metadata',
urlValidator: (url) => !url.includes('169.254'),
});
guardedService.initialize();
// Should not throw — webhook is silently blocked
const notif = await guardedService.send({
type: 'gate_failure',
title: 'Test',
message: 'msg',
severity: 'high',
});
expect(notif).toBeDefined();
});
});