morphit/apps/ops-cli/test/time.test.ts

133 lines
4.4 KiB
TypeScript

/**
* Morphit ops CLI — time helper smokes.
*
* parseDurationSpec and formatDuration are core to every
* subcommand that takes --since or --age, and to the status
* dashboard's age formatting. Bugs here mean every command
* silently misbehaves.
*/
import { describe, it, expect } from 'vitest';
import {
parseDurationSpec,
formatDuration,
relativeTime,
ageSeconds,
utcMidnightToday
} from '../src/lib/time.ts';
describe('parseDurationSpec', () => {
it('parses seconds', () => {
expect(parseDurationSpec('30s')).toBe(30);
expect(parseDurationSpec('0s')).toBe(0);
});
it('parses minutes', () => {
expect(parseDurationSpec('5m')).toBe(300);
});
it('parses hours', () => {
expect(parseDurationSpec('1h')).toBe(3600);
expect(parseDurationSpec('24h')).toBe(86400);
});
it('parses days', () => {
expect(parseDurationSpec('7d')).toBe(7 * 86400);
});
it('accepts whitespace between number and unit', () => {
expect(parseDurationSpec('5 m')).toBe(300);
expect(parseDurationSpec('1 h')).toBe(3600);
});
it('is case-insensitive on the unit', () => {
expect(parseDurationSpec('5M')).toBe(300);
expect(parseDurationSpec('1H')).toBe(3600);
expect(parseDurationSpec('1D')).toBe(86400);
});
it('rejects invalid formats', () => {
expect(parseDurationSpec('')).toBeNull();
expect(parseDurationSpec('5')).toBeNull();
expect(parseDurationSpec('m')).toBeNull();
expect(parseDurationSpec('5x')).toBeNull();
expect(parseDurationSpec('-5m')).toBeNull();
expect(parseDurationSpec('abc')).toBeNull();
expect(parseDurationSpec('5min')).toBeNull(); // unit must be 1 char
});
});
describe('formatDuration', () => {
it('handles zero and negative', () => {
expect(formatDuration(0)).toBe('0s');
expect(formatDuration(-1)).toBe('0s');
});
it('formats sub-minute durations as seconds only', () => {
expect(formatDuration(1)).toBe('1s');
expect(formatDuration(59)).toBe('59s');
});
it('formats minute durations with seconds when nonzero', () => {
expect(formatDuration(60)).toBe('1m');
expect(formatDuration(90)).toBe('1m 30s');
expect(formatDuration(125)).toBe('2m 5s');
});
it('formats hour durations with minutes when nonzero', () => {
expect(formatDuration(3600)).toBe('1h');
expect(formatDuration(3660)).toBe('1h 1m');
expect(formatDuration(7320)).toBe('2h 2m');
});
it('formats day durations with hours when nonzero', () => {
expect(formatDuration(86400)).toBe('1d');
expect(formatDuration(86400 + 3600)).toBe('1d 1h');
expect(formatDuration(86400 * 3 + 3600 * 5)).toBe('3d 5h');
});
it('floors fractional input', () => {
expect(formatDuration(59.9)).toBe('59s');
expect(formatDuration(120.7)).toBe('2m');
});
});
describe('relativeTime', () => {
const now = new Date('2026-04-26T12:00:00Z');
it('reports just now for sub-5s deltas', () => {
expect(relativeTime(new Date('2026-04-26T11:59:58Z'), now)).toBe('just now');
});
it('reports seconds ago', () => {
expect(relativeTime(new Date('2026-04-26T11:59:30Z'), now)).toBe('30s ago');
});
it('reports minutes ago', () => {
expect(relativeTime(new Date('2026-04-26T11:55:00Z'), now)).toBe('5m ago');
});
it('reports hours ago', () => {
expect(relativeTime(new Date('2026-04-26T08:00:00Z'), now)).toBe('4h ago');
});
it('reports days ago', () => {
expect(relativeTime(new Date('2026-04-23T12:00:00Z'), now)).toBe('3d ago');
});
it('reports months ago', () => {
expect(relativeTime(new Date('2026-02-01T00:00:00Z'), now)).toBe('2mo ago');
});
it('handles future dates gracefully', () => {
expect(relativeTime(new Date('2026-04-26T12:01:00Z'), now)).toBe('in the future');
});
});
describe('ageSeconds', () => {
it('returns positive seconds since the given Date', () => {
const now = new Date('2026-04-26T12:00:00Z');
const then = new Date('2026-04-26T11:59:30Z');
expect(ageSeconds(then, now)).toBe(30);
});
it('floors to integer seconds', () => {
const now = new Date('2026-04-26T12:00:00.500Z');
const then = new Date('2026-04-26T11:59:59.000Z');
expect(ageSeconds(then, now)).toBe(1);
});
});
describe('utcMidnightToday', () => {
it('returns UTC 00:00:00 for the date of `now`', () => {
const now = new Date('2026-04-26T15:30:00Z');
const midnight = utcMidnightToday(now);
expect(midnight.toISOString()).toBe('2026-04-26T00:00:00.000Z');
});
it('handles late-evening UTC correctly', () => {
const now = new Date('2026-04-26T23:59:59Z');
const midnight = utcMidnightToday(now);
expect(midnight.toISOString()).toBe('2026-04-26T00:00:00.000Z');
});
});