104 lines
4.8 KiB
TypeScript
104 lines
4.8 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { aggregateStats } from '@main/services/telemetryStats.js';
|
|
import type { TelemetryEvent } from '@main/services/telemetryEvents.js';
|
|
|
|
const e = (ts: string, kind: TelemetryEvent['kind'], payload: TelemetryEvent['payload']): TelemetryEvent =>
|
|
({ ts, kind, payload } as TelemetryEvent);
|
|
|
|
describe('aggregateStats', () => {
|
|
it('produces empty stats for empty input', () => {
|
|
const r = aggregateStats([], new Date('2026-05-08T00:00:00Z'));
|
|
expect(r.eventCount).toBe(0);
|
|
expect(r.md).toContain('총 이벤트: 0');
|
|
});
|
|
|
|
it('counts events per KST day per kind', () => {
|
|
const events: TelemetryEvent[] = [
|
|
e('2026-05-01T12:00:00Z', 'capture', { noteId: 'n1', rawTextLength: 5, hasMedia: false }),
|
|
e('2026-05-01T12:01:00Z', 'capture', { noteId: 'n2', rawTextLength: 3, hasMedia: true }),
|
|
e('2026-05-01T12:02:00Z', 'ai_succeeded', { noteId: 'n1', durationMs: 1000, attempts: 0 }),
|
|
e('2026-05-02T00:00:00Z', 'ai_failed', { noteId: 'n2', reason: 'unreachable', attempts: 3 })
|
|
];
|
|
const r = aggregateStats(events, new Date('2026-05-08T00:00:00Z'));
|
|
expect(r.eventCount).toBe(4);
|
|
expect(r.md).toContain('| 2026-05-01 | 2 | 1 | 0 |');
|
|
expect(r.md).toContain('| 2026-05-02 | 0 | 0 | 1 |');
|
|
});
|
|
|
|
it('computes AI 성공률', () => {
|
|
const events: TelemetryEvent[] = [
|
|
e('2026-05-01T00:00:00Z', 'ai_succeeded', { noteId: 'n1', durationMs: 1, attempts: 0 }),
|
|
e('2026-05-01T00:00:01Z', 'ai_succeeded', { noteId: 'n2', durationMs: 1, attempts: 0 }),
|
|
e('2026-05-01T00:00:02Z', 'ai_succeeded', { noteId: 'n3', durationMs: 1, attempts: 0 }),
|
|
e('2026-05-01T00:00:03Z', 'ai_failed', { noteId: 'n4', reason: 'other', attempts: 1 })
|
|
];
|
|
const r = aggregateStats(events, new Date('2026-05-02T00:00:00Z'));
|
|
expect(r.md).toContain('AI 성공률: 75.0%');
|
|
expect(r.md).toContain('3/4');
|
|
});
|
|
|
|
it('AI 성공률 N/A when no AI events', () => {
|
|
const events: TelemetryEvent[] = [
|
|
e('2026-05-01T00:00:00Z', 'capture', { noteId: 'n1', rawTextLength: 1, hasMedia: false })
|
|
];
|
|
const r = aggregateStats(events, new Date('2026-05-02T00:00:00Z'));
|
|
expect(r.md).toContain('AI 성공률: N/A');
|
|
});
|
|
|
|
it('computes 평균 ai_succeeded durationMs', () => {
|
|
const events: TelemetryEvent[] = [
|
|
e('2026-05-01T00:00:00Z', 'ai_succeeded', { noteId: 'n1', durationMs: 1000, attempts: 0 }),
|
|
e('2026-05-01T00:00:01Z', 'ai_succeeded', { noteId: 'n2', durationMs: 2000, attempts: 0 }),
|
|
e('2026-05-01T00:00:02Z', 'ai_succeeded', { noteId: 'n3', durationMs: 3000, attempts: 0 })
|
|
];
|
|
const r = aggregateStats(events, new Date('2026-05-02T00:00:00Z'));
|
|
expect(r.md).toContain('평균 ai_succeeded durationMs: 2000');
|
|
});
|
|
|
|
it('buckets near-midnight UTC events on the correct KST day (regression: not naive UTC)', () => {
|
|
// 2026-05-01T15:30:00Z → 2026-05-02 00:30 KST → KST day 2026-05-02
|
|
// Naive UTC slice(0,10) would put this on 2026-05-01 — this test catches that regression.
|
|
const events: TelemetryEvent[] = [
|
|
e('2026-05-01T15:30:00Z', 'capture', { noteId: 'n1', rawTextLength: 1, hasMedia: false })
|
|
];
|
|
const r = aggregateStats(events, new Date('2026-05-08T00:00:00Z'));
|
|
expect(r.md).toContain('| 2026-05-02 | 1 | 0 | 0 |');
|
|
expect(r.md).not.toContain('| 2026-05-01 |');
|
|
});
|
|
});
|
|
|
|
describe('aggregateStats — trash family (v0.2.3 #4)', () => {
|
|
it('counts trash/restore/permanent_delete/empty_trash per day', () => {
|
|
const events: TelemetryEvent[] = [
|
|
e('2026-05-01T00:00:00Z', 'trash', { noteId: 'n1' }),
|
|
e('2026-05-01T01:00:00Z', 'trash', { noteId: 'n2' }),
|
|
e('2026-05-01T02:00:00Z', 'restore', { noteId: 'n1' }),
|
|
e('2026-05-01T03:00:00Z', 'permanent_delete', { noteId: 'n3' }),
|
|
e('2026-05-01T04:00:00Z', 'empty_trash', { count: 5 })
|
|
];
|
|
const r = aggregateStats(events, new Date('2026-05-08T00:00:00Z'));
|
|
expect(r.eventCount).toBe(5);
|
|
expect(r.md).toContain('| 2026-05-01 | 0 | 0 | 0 | 2 | 1 | 1 | 1 |');
|
|
});
|
|
|
|
it('computes restore/trash ratio', () => {
|
|
const events: TelemetryEvent[] = [
|
|
e('2026-05-01T00:00:00Z', 'trash', { noteId: 'a' }),
|
|
e('2026-05-01T00:00:01Z', 'trash', { noteId: 'b' }),
|
|
e('2026-05-01T00:00:02Z', 'trash', { noteId: 'c' }),
|
|
e('2026-05-01T00:00:03Z', 'trash', { noteId: 'd' }),
|
|
e('2026-05-01T00:00:04Z', 'restore', { noteId: 'a' })
|
|
];
|
|
const r = aggregateStats(events, new Date('2026-05-02T00:00:00Z'));
|
|
expect(r.md).toContain('휴지통 회수율: 25.0% (1/4)');
|
|
});
|
|
|
|
it('휴지통 회수율 N/A when no trash events', () => {
|
|
const events: TelemetryEvent[] = [
|
|
e('2026-05-01T00:00:00Z', 'capture', { noteId: 'n1', rawTextLength: 1, hasMedia: false })
|
|
];
|
|
const r = aggregateStats(events, new Date('2026-05-02T00:00:00Z'));
|
|
expect(r.md).toContain('휴지통 회수율: N/A');
|
|
});
|
|
});
|