Download client/src/lib/logs.test.ts from automindy/fokemal: direct link, hf CLI and curl.
- Browser
- Download file 5.92 kB
-
https://huggingface.co/spaces/automindy/fokemal/resolve/main/client/src/lib/logs.test.ts
- Command line
-
hf download hf://spaces/automindy/fokemal/client/src/lib/logs.test.ts
-
curl -L -o logs.test.ts https://huggingface.co/spaces/automindy/fokemal/resolve/main/client/src/lib/logs.test.ts
5.92 kB
| import { describe, expect, it } from 'vitest' | |
| import { | |
| advanceCursor, | |
| buildLogsQuery, | |
| clampMessage, | |
| collectProviders, | |
| formatLogTime, | |
| isLongMessage, | |
| levelsCsv, | |
| mergeEntries, | |
| toggleLevel, | |
| type LogEntry, | |
| type LogLevel, | |
| } from './logs' | |
| function entry(id: number, extra: Partial<LogEntry> = {}): LogEntry { | |
| return { | |
| id, | |
| ts: '2024-01-02T03:04:05.678Z', | |
| level: 'info', | |
| message: `message ${id}`, | |
| ...extra, | |
| } | |
| } | |
| describe('buildLogsQuery', () => { | |
| it('always sends the selected levels as a canonically ordered CSV', () => { | |
| expect(buildLogsQuery({ levels: ['error', 'debug', 'warn'] })) | |
| .toBe('/api/logs?levels=debug%2Cwarn%2Cerror&limit=200') | |
| }) | |
| it('omits blank search and the "all" provider sentinel', () => { | |
| expect(buildLogsQuery({ levels: ['warn'], q: ' ', provider: 'all' })) | |
| .toBe('/api/logs?levels=warn&limit=200') | |
| }) | |
| it('sends trimmed search, provider and cursor when present', () => { | |
| const url = buildLogsQuery({ | |
| levels: ['warn', 'error'], | |
| q: ' rate limit ', | |
| provider: 'groq', | |
| sinceId: 123, | |
| limit: 200, | |
| }) | |
| expect(url).toBe('/api/logs?levels=warn%2Cerror&q=rate+limit&provider=groq&sinceId=123&limit=200') | |
| }) | |
| it('treats sinceId 0 as a real cursor but null as the first fetch', () => { | |
| expect(buildLogsQuery({ levels: ['info'], sinceId: 0 })).toContain('sinceId=0') | |
| expect(buildLogsQuery({ levels: ['info'], sinceId: null })).not.toContain('sinceId') | |
| }) | |
| it('collapses duplicate levels', () => { | |
| expect(levelsCsv(['warn', 'warn', 'debug'])).toBe('debug,warn') | |
| }) | |
| }) | |
| describe('mergeEntries', () => { | |
| it('appends new entries in arrival order', () => { | |
| const merged = mergeEntries([entry(1), entry(2)], [entry(3), entry(4)]) | |
| expect(merged.map(e => e.id)).toEqual([1, 2, 3, 4]) | |
| }) | |
| it('never duplicates an id already in the buffer', () => { | |
| const merged = mergeEntries([entry(1), entry(2)], [entry(2), entry(3)]) | |
| expect(merged.map(e => e.id)).toEqual([1, 2, 3]) | |
| }) | |
| it('de-duplicates within a single incoming page', () => { | |
| const merged = mergeEntries([], [entry(1), entry(1), entry(2)]) | |
| expect(merged.map(e => e.id)).toEqual([1, 2]) | |
| }) | |
| it('returns the same array when nothing new arrived', () => { | |
| const buffer = [entry(1), entry(2)] | |
| expect(mergeEntries(buffer, [])).toBe(buffer) | |
| expect(mergeEntries(buffer, [entry(2)])).toBe(buffer) | |
| }) | |
| it('evicts the oldest entries once the cap is passed', () => { | |
| const buffer = [entry(1), entry(2), entry(3)] | |
| const merged = mergeEntries(buffer, [entry(4), entry(5)], 3) | |
| expect(merged.map(e => e.id)).toEqual([3, 4, 5]) | |
| }) | |
| it('caps a single oversized page too', () => { | |
| const page = Array.from({ length: 10 }, (_, i) => entry(i + 1)) | |
| expect(mergeEntries([], page, 4).map(e => e.id)).toEqual([7, 8, 9, 10]) | |
| }) | |
| }) | |
| describe('advanceCursor', () => { | |
| it('takes the server nextId when entries arrived', () => { | |
| expect(advanceCursor(10, { entries: [entry(11), entry(12)], nextId: 12 })).toBe(12) | |
| }) | |
| it('still advances when the page came back empty', () => { | |
| // Entries the query filtered out still bumped the ring's highest id; not | |
| // taking nextId here would re-ask for them on every poll, forever. | |
| expect(advanceCursor(10, { entries: [], nextId: 42 })).toBe(42) | |
| }) | |
| it('accepts nextId 0 on an empty ring', () => { | |
| expect(advanceCursor(null, { entries: [], nextId: 0 })).toBe(0) | |
| }) | |
| it('falls back to the highest seen id when nextId is unusable', () => { | |
| const broken = { entries: [entry(7), entry(9)], nextId: undefined as unknown as number } | |
| expect(advanceCursor(3, broken)).toBe(9) | |
| expect(advanceCursor(20, { entries: [], nextId: NaN })).toBe(20) | |
| }) | |
| }) | |
| describe('toggleLevel', () => { | |
| it('adds a level back in canonical order, not at the end', () => { | |
| expect(toggleLevel(['warn', 'error'], 'debug')).toEqual(['debug', 'warn', 'error']) | |
| }) | |
| it('removes a selected level', () => { | |
| expect(toggleLevel(['info', 'warn', 'error'], 'warn')).toEqual(['info', 'error']) | |
| }) | |
| it('can empty the selection', () => { | |
| expect(toggleLevel(['error'], 'error')).toEqual([]) | |
| }) | |
| it('round-trips', () => { | |
| const start: LogLevel[] = ['info', 'warn', 'error'] | |
| expect(toggleLevel(toggleLevel(start, 'debug'), 'debug')).toEqual(start) | |
| }) | |
| }) | |
| describe('collectProviders', () => { | |
| it('accumulates and sorts, ignoring entries without a provider', () => { | |
| const providers = collectProviders(['groq'], [ | |
| entry(1, { provider: 'cerebras' }), | |
| entry(2), | |
| entry(3, { provider: 'groq' }), | |
| ]) | |
| expect(providers).toEqual(['cerebras', 'groq']) | |
| }) | |
| it('returns the same array when no new provider showed up', () => { | |
| const known = ['groq'] | |
| expect(collectProviders(known, [entry(1, { provider: 'groq' })])).toBe(known) | |
| expect(collectProviders(known, [])).toBe(known) | |
| }) | |
| }) | |
| describe('formatLogTime', () => { | |
| it('formats local wall-clock time as HH:MM:SS.mmm', () => { | |
| // Built in local time, so the expectation holds in any timezone. | |
| const ts = new Date(2024, 0, 2, 3, 4, 5, 678).toISOString() | |
| expect(formatLogTime(ts)).toBe('03:04:05.678') | |
| }) | |
| it('zero-pads the milliseconds', () => { | |
| const ts = new Date(2024, 0, 2, 23, 59, 59, 7).toISOString() | |
| expect(formatLogTime(ts)).toBe('23:59:59.007') | |
| }) | |
| it('degrades to a placeholder of the same width on a bad timestamp', () => { | |
| expect(formatLogTime('not a date')).toBe('--:--:--.---') | |
| }) | |
| }) | |
| describe('message clamping', () => { | |
| it('leaves short messages alone', () => { | |
| expect(isLongMessage('short', 10)).toBe(false) | |
| expect(clampMessage('short', 10)).toBe('short') | |
| }) | |
| it('clamps long messages with an ellipsis', () => { | |
| const long = 'x'.repeat(50) | |
| expect(isLongMessage(long, 10)).toBe(true) | |
| expect(clampMessage(long, 10)).toBe('xxxxxxxxxx…') | |
| }) | |
| }) | |