mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-05 05:05:08 +02:00
a104471272
- cli-sync.ts: push-transcript command, show sessions with formatSessionTable(), upgrade cmdSetup() to interactively create .gstack-sync.json if missing - bin/gstack-sync: add push-transcript case and help text - test/lib-llm-summarize.test.ts: 10 tests with mocked fetch (429 retry, 5xx backoff, malformed response, no API key, cache) - test/lib-transcript-sync.test.ts: 22 tests for parsing, grouping, session file extraction, marker management, slug resolution - test/lib-sync-show.test.ts: 4 tests for formatSessionTable Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
169 lines
5.1 KiB
TypeScript
169 lines
5.1 KiB
TypeScript
/**
|
|
* Tests for sync show formatting functions (pure, no network).
|
|
*/
|
|
|
|
import { describe, test, expect } from 'bun:test';
|
|
import { formatTeamSummary, formatEvalTable, formatShipTable, formatSessionTable, formatRelativeTime } from '../lib/cli-sync';
|
|
|
|
describe('formatRelativeTime', () => {
|
|
test('returns "just now" for recent timestamps', () => {
|
|
expect(formatRelativeTime(new Date().toISOString())).toBe('just now');
|
|
});
|
|
|
|
test('returns minutes for recent past', () => {
|
|
const fiveMinAgo = new Date(Date.now() - 5 * 60_000).toISOString();
|
|
expect(formatRelativeTime(fiveMinAgo)).toBe('5m ago');
|
|
});
|
|
|
|
test('returns hours for older past', () => {
|
|
const threeHoursAgo = new Date(Date.now() - 3 * 3_600_000).toISOString();
|
|
expect(formatRelativeTime(threeHoursAgo)).toBe('3h ago');
|
|
});
|
|
|
|
test('returns days for old past', () => {
|
|
const twoDaysAgo = new Date(Date.now() - 2 * 86_400_000).toISOString();
|
|
expect(formatRelativeTime(twoDaysAgo)).toBe('2d ago');
|
|
});
|
|
});
|
|
|
|
describe('formatTeamSummary', () => {
|
|
test('formats summary with data', () => {
|
|
const output = formatTeamSummary({
|
|
teamSlug: 'test-team',
|
|
evalRuns: [
|
|
{ timestamp: new Date().toISOString(), user_id: 'u1', tests: [{ detection_rate: 4 }] },
|
|
{ timestamp: new Date().toISOString(), user_id: 'u2', tests: [{ detection_rate: 5 }] },
|
|
],
|
|
shipLogs: [
|
|
{ created_at: new Date().toISOString() },
|
|
],
|
|
retroSnapshots: [
|
|
{ date: '2026-03-15', streak_days: 47 },
|
|
],
|
|
queueSize: 0,
|
|
cacheLastPull: new Date().toISOString(),
|
|
});
|
|
|
|
expect(output).toContain('test-team');
|
|
expect(output).toContain('2 runs');
|
|
expect(output).toContain('2 contributors');
|
|
expect(output).toContain('1 PRs');
|
|
expect(output).toContain('4.5'); // avg detection
|
|
expect(output).toContain('streak: 47d');
|
|
expect(output).toContain('0 items');
|
|
});
|
|
|
|
test('handles empty data gracefully', () => {
|
|
const output = formatTeamSummary({
|
|
teamSlug: 'empty-team',
|
|
evalRuns: [],
|
|
shipLogs: [],
|
|
retroSnapshots: [],
|
|
queueSize: 3,
|
|
cacheLastPull: null,
|
|
});
|
|
|
|
expect(output).toContain('empty-team');
|
|
expect(output).toContain('0 runs');
|
|
expect(output).toContain('0 PRs');
|
|
expect(output).toContain('3 items');
|
|
expect(output).toContain('never');
|
|
});
|
|
});
|
|
|
|
describe('formatEvalTable', () => {
|
|
test('formats eval runs as table', () => {
|
|
const output = formatEvalTable([
|
|
{ timestamp: '2026-03-15T12:00:00Z', branch: 'main', passed: 10, total_tests: 10, total_cost_usd: 2.50, tier: 'e2e' },
|
|
]);
|
|
|
|
expect(output).toContain('Recent Eval Runs');
|
|
expect(output).toContain('2026-03-15');
|
|
expect(output).toContain('main');
|
|
expect(output).toContain('10/10');
|
|
expect(output).toContain('$2.50');
|
|
expect(output).toContain('e2e');
|
|
});
|
|
|
|
test('returns message for empty data', () => {
|
|
expect(formatEvalTable([])).toContain('No eval runs yet');
|
|
});
|
|
});
|
|
|
|
describe('formatShipTable', () => {
|
|
test('formats ship logs as table', () => {
|
|
const output = formatShipTable([
|
|
{ created_at: '2026-03-15T12:00:00Z', version: '0.3.10', branch: 'feature/sync', pr_url: 'https://github.com/org/repo/pull/1' },
|
|
]);
|
|
|
|
expect(output).toContain('Recent Ship Logs');
|
|
expect(output).toContain('0.3.10');
|
|
expect(output).toContain('feature/sync');
|
|
expect(output).toContain('github.com');
|
|
});
|
|
|
|
test('returns message for empty data', () => {
|
|
expect(formatShipTable([])).toContain('No ship logs yet');
|
|
});
|
|
});
|
|
|
|
describe('formatSessionTable', () => {
|
|
test('formats sessions with enriched data', () => {
|
|
const output = formatSessionTable([
|
|
{
|
|
started_at: '2026-03-15T10:00:00Z',
|
|
ended_at: '2026-03-15T10:15:00Z',
|
|
repo_slug: 'garrytan/gstack',
|
|
summary: 'Fixed login page CSS and added tests',
|
|
total_turns: 8,
|
|
tools_used: ['Edit', 'Bash', 'Read'],
|
|
},
|
|
]);
|
|
|
|
expect(output).toContain('Recent Sessions');
|
|
expect(output).toContain('2026-03-15');
|
|
expect(output).toContain('garrytan/gstack');
|
|
expect(output).toContain('Fixed login');
|
|
expect(output).toContain('8');
|
|
expect(output).toContain('15m');
|
|
expect(output).toContain('Edit');
|
|
});
|
|
|
|
test('handles sessions without enrichment', () => {
|
|
const output = formatSessionTable([
|
|
{
|
|
started_at: '2026-03-15T10:00:00Z',
|
|
ended_at: '2026-03-15T10:00:30Z',
|
|
repo_slug: 'myproject',
|
|
summary: null,
|
|
total_turns: 2,
|
|
tools_used: null,
|
|
},
|
|
]);
|
|
|
|
expect(output).toContain('Recent Sessions');
|
|
expect(output).toContain('myproject');
|
|
// null summary shows as '—'
|
|
expect(output).toContain('—');
|
|
});
|
|
|
|
test('returns message for empty data', () => {
|
|
expect(formatSessionTable([])).toContain('No sessions yet');
|
|
});
|
|
|
|
test('formats duration correctly', () => {
|
|
const output = formatSessionTable([
|
|
{
|
|
started_at: '2026-03-15T10:00:00Z',
|
|
ended_at: '2026-03-15T11:30:00Z',
|
|
repo_slug: 'repo',
|
|
summary: 'Long session',
|
|
total_turns: 50,
|
|
tools_used: ['Bash'],
|
|
},
|
|
]);
|
|
|
|
expect(output).toContain('1h30m');
|
|
});
|
|
});
|