feat(telemetry): TelemetryService.emit with KST rotation (#7 v0.2.3)
This commit is contained in:
74
tests/unit/TelemetryService.test.ts
Normal file
74
tests/unit/TelemetryService.test.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, rmSync, readFileSync, existsSync, readdirSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { TelemetryService } from '@main/services/TelemetryService.js';
|
||||
|
||||
describe('TelemetryService.emit', () => {
|
||||
let dir: string;
|
||||
|
||||
beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'inkling-telem-')); });
|
||||
afterEach(() => { rmSync(dir, { recursive: true, force: true }); });
|
||||
|
||||
it('appends a JSONL line to events-YYYY-MM-DD.jsonl (KST date)', async () => {
|
||||
// 2026-05-01 12:00 UTC → 2026-05-01 21:00 KST
|
||||
const svc = new TelemetryService(dir, () => new Date('2026-05-01T12:00:00Z'));
|
||||
await svc.emit({ kind: 'capture', payload: { noteId: 'n1', rawTextLength: 5, hasMedia: false } });
|
||||
const file = join(dir, 'events-2026-05-01.jsonl');
|
||||
expect(existsSync(file)).toBe(true);
|
||||
const content = readFileSync(file, 'utf8').trim();
|
||||
const parsed = JSON.parse(content);
|
||||
expect(parsed.kind).toBe('capture');
|
||||
expect(parsed.payload.noteId).toBe('n1');
|
||||
expect(typeof parsed.ts).toBe('string');
|
||||
});
|
||||
|
||||
it('uses KST date even when UTC date differs (around midnight)', async () => {
|
||||
// 2026-05-01 23:30 UTC → 2026-05-02 08:30 KST
|
||||
const svc = new TelemetryService(dir, () => new Date('2026-05-01T23:30:00Z'));
|
||||
await svc.emit({ kind: 'capture', payload: { noteId: 'n2', rawTextLength: 1, hasMedia: false } });
|
||||
expect(existsSync(join(dir, 'events-2026-05-02.jsonl'))).toBe(true);
|
||||
});
|
||||
|
||||
it('appends multiple events to same-day file', async () => {
|
||||
const svc = new TelemetryService(dir, () => new Date('2026-05-01T12:00:00Z'));
|
||||
await svc.emit({ kind: 'capture', payload: { noteId: 'n1', rawTextLength: 5, hasMedia: false } });
|
||||
await svc.emit({ kind: 'ai_succeeded', payload: { noteId: 'n1', durationMs: 100, attempts: 0 } });
|
||||
const lines = readFileSync(join(dir, 'events-2026-05-01.jsonl'), 'utf8').trim().split('\n');
|
||||
expect(lines).toHaveLength(2);
|
||||
expect(JSON.parse(lines[0]!).kind).toBe('capture');
|
||||
expect(JSON.parse(lines[1]!).kind).toBe('ai_succeeded');
|
||||
});
|
||||
|
||||
it('creates telemetry dir if absent', async () => {
|
||||
const fresh = join(dir, 'nested', 'telem');
|
||||
const svc = new TelemetryService(fresh, () => new Date('2026-05-01T12:00:00Z'));
|
||||
await svc.emit({ kind: 'capture', payload: { noteId: 'n1', rawTextLength: 1, hasMedia: false } });
|
||||
expect(existsSync(fresh)).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects malformed event (privacy invariant) — does NOT write file', async () => {
|
||||
const svc = new TelemetryService(dir, () => new Date('2026-05-01T12:00:00Z'));
|
||||
await expect(svc.emit({
|
||||
kind: 'capture',
|
||||
payload: { noteId: 'n1', rawTextLength: 1, hasMedia: false, rawText: 'leak' } as never
|
||||
})).rejects.toThrow();
|
||||
// No file should have been created
|
||||
expect(readdirSync(dir).filter((f) => f.startsWith('events-'))).toEqual([]);
|
||||
});
|
||||
|
||||
it('emit is silent (does not throw) when fs write fails — invariant: telemetry never breaks app', async () => {
|
||||
// Pass a non-writable path; emit should swallow the error.
|
||||
const svc = new TelemetryService(
|
||||
'/proc/0/no-such-thing-readonly',
|
||||
() => new Date('2026-05-01T12:00:00Z'),
|
||||
14,
|
||||
{ silent: true }
|
||||
);
|
||||
// Should resolve, not throw
|
||||
await expect(svc.emit({
|
||||
kind: 'capture',
|
||||
payload: { noteId: 'n1', rawTextLength: 1, hasMedia: false }
|
||||
})).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user