Motorul NU trimite o notificare per eveniment. Decide daca merita notificat, cui, ce severitate, daca se agrega, ce canal si cand. - notifications / notification_preferences / notification_processed_events. Ultimul da idempotency: aceeasi regula nu proceseaza acelasi eveniment de doua ori (unique rule_id + event_id). - Reguli in COD, nu in DB cu condition_expression evaluat dinamic. Un evaluator de expresii e o suprafata de atac si un limbaj in plus, fara ca nimeni sa administreze inca reguli per tenant. Contractul ramane acelasi cand vor migra in DB. - Destinatarii se rezolva server-side din memberships, niciodata din payload. Implicit nu notificam actorul despre propria actiune. - Deduplicare pe cheia tenant+recipient+categorie+entitate+versiune regula. - Agregare: 47 de taskuri intr-o ora devin o notificare cu aggregated_count, nu 47 de notificari. Testul verifica invariantul ca fereastra de agregare <= fereastra de dedup, altfel s-ar crea una noua inainte sa se agrege. - Quiet hours cu fereastra care poate traversa miezul noptii. CRITICAL le depaseste DOAR daca politica userului permite, nu implicit. - Inbox: filtre (unread/action_required/critical/intelligence/system), mark-read, acknowledge (opreste escaladarea), dismiss, snooze. - Preferinte per user+tenant cu UPSERT (NULLS NOT DISTINCT pe category, ca randul implicit "toate categoriile" sa fie unic).
40 lines
1.5 KiB
TypeScript
40 lines
1.5 KiB
TypeScript
import { SEVERITY_ORDER, rulesFor, subscribedEventTypes } from './notification-rules';
|
|
|
|
describe('notification rules', () => {
|
|
it('ordoneaza severitatile de la SYSTEM la CRITICAL', () => {
|
|
expect(SEVERITY_ORDER.SYSTEM).toBeLessThan(SEVERITY_ORDER.INFORMATION);
|
|
expect(SEVERITY_ORDER.INFORMATION).toBeLessThan(SEVERITY_ORDER.ACTION_REQUIRED);
|
|
expect(SEVERITY_ORDER.ACTION_REQUIRED).toBeLessThan(SEVERITY_ORDER.CRITICAL);
|
|
});
|
|
|
|
it('nu intoarce reguli pentru evenimente la care nimeni nu s-a abonat', () => {
|
|
expect(rulesFor('inventat.nu_exista')).toEqual([]);
|
|
});
|
|
|
|
it('fiecare regula are o cheie de deduplicare pozitiva', () => {
|
|
for (const eventType of subscribedEventTypes()) {
|
|
for (const rule of rulesFor(eventType)) {
|
|
expect(rule.deduplicationWindowMinutes).toBeGreaterThan(0);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('regulile agregabile au fereastra de agregare mai mica sau egala cu cea de dedup', () => {
|
|
// Altfel s-ar crea o notificare noua inainte sa se poata agrega in cea veche.
|
|
for (const eventType of subscribedEventTypes()) {
|
|
for (const rule of rulesFor(eventType)) {
|
|
if (rule.aggregationWindowMinutes) {
|
|
expect(rule.aggregationWindowMinutes).toBeLessThanOrEqual(
|
|
rule.deduplicationWindowMinutes,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
it('nu notifica actorul despre propria actiune, in afara de evenimentele de sistem', () => {
|
|
for (const rule of rulesFor('task.created')) {
|
|
expect(rule.notifyActor).toBe(false);
|
|
}
|
|
});
|
|
});
|