From 6da907b83be985c6b899bacfc6795c7307185003 Mon Sep 17 00:00:00 2001 From: Daniel Carrillo Date: Sat, 25 Apr 2026 14:52:13 +0200 Subject: [PATCH] add testing to status-page functions --- .github/workflows/ci.yml | 5 ++- README.md | 9 +++-- package.json | 1 + status-page/src/components/Header.astro | 15 +------ status-page/src/components/MonitorCard.astro | 20 +--------- status-page/src/components/UptimeBars.astro | 8 +--- status-page/src/lib/header.test.ts | 27 +++++++++++++ status-page/src/lib/header.ts | 11 +++++ status-page/src/lib/monitor-card.test.ts | 42 ++++++++++++++++++++ status-page/src/lib/monitor-card.ts | 17 ++++++++ status-page/src/lib/uptime-bars.test.ts | 27 +++++++++++++ status-page/src/lib/uptime-bars.ts | 6 +++ status-page/vitest.config.ts | 9 +++++ 13 files changed, 154 insertions(+), 43 deletions(-) create mode 100644 status-page/src/lib/header.test.ts create mode 100644 status-page/src/lib/header.ts create mode 100644 status-page/src/lib/monitor-card.test.ts create mode 100644 status-page/src/lib/monitor-card.ts create mode 100644 status-page/src/lib/uptime-bars.test.ts create mode 100644 status-page/src/lib/uptime-bars.ts create mode 100644 status-page/vitest.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 240e94b..63bdfc7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,5 +36,8 @@ jobs: - name: Build status page run: npm run build:pages - - name: Run tests + - name: Run worker tests run: npm run test + + - name: Run status-page tests + run: npm run test:pages diff --git a/README.md b/README.md index 93d29d2..1fa9e55 100644 --- a/README.md +++ b/README.md @@ -381,15 +381,18 @@ curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*" ### Testing ```bash -# Fist build the status page +# First build the status page npm run build:pages # Worker tests npm run test +# Status page unit tests +npm run test:pages + # Type checking and linting -npm run check # worker -npm run check:pages # pages (astro check + tsc) +npm run check # worker (typecheck + lint + format) +npm run check:pages # pages (astro check + tsc + lint) ``` ## TODO diff --git a/package.json b/package.json index bf070e8..3b7c6b2 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "check:pages": "npm run typecheck --workspace=status-page && npm run lint:pages && npm run format:pages:check", "lint:pages": "npm run lint --workspace=status-page", "lint:pages:fix": "npm run lint:fix --workspace=status-page", + "test:pages": "npm run test --workspace=status-page", "format:pages": "prettier --write \"src/**/*.ts\" \"status-page/src/**/*.ts\"", "format:pages:check": "prettier --check \"src/**/*.ts\" \"status-page/src/**/*.ts\"" }, diff --git a/status-page/src/components/Header.astro b/status-page/src/components/Header.astro index f193bd3..18a2195 100644 --- a/status-page/src/components/Header.astro +++ b/status-page/src/components/Header.astro @@ -13,20 +13,9 @@ interface Props { }; } +import { formatAbsoluteTime } from '../lib/header.js'; + const { summary, lastUpdated, title, banner } = Astro.props; - -function formatAbsoluteTime(unixTimestamp: number): string { - const date = new Date(unixTimestamp * 1000); - return date.toLocaleString('en-US', { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - hour12: false, - timeZoneName: 'short' - }); -} - const absoluteTime = formatAbsoluteTime(lastUpdated); --- diff --git a/status-page/src/components/MonitorCard.astro b/status-page/src/components/MonitorCard.astro index f349b9e..c2ee774 100644 --- a/status-page/src/components/MonitorCard.astro +++ b/status-page/src/components/MonitorCard.astro @@ -1,6 +1,7 @@ --- import type { ApiMonitorStatus } from '@worker/types'; import UptimeBars from './UptimeBars.astro'; +import { formatLastChecked } from '../lib/monitor-card.js'; interface Props { monitor: ApiMonitorStatus; @@ -9,25 +10,6 @@ interface Props { const { monitor } = Astro.props; const uptimeFormatted = monitor.uptimePercent.toFixed(2); - -function formatLastChecked(timestamp: number | undefined): string { - if (timestamp == null) return 'Never'; - const now = Math.floor(Date.now() / 1000); - const diffSeconds = now - timestamp; - - if (diffSeconds < 60) return ''; - if (diffSeconds < 3600) { - const minutes = Math.floor(diffSeconds / 60); - return `${minutes}m ago`; - } - if (diffSeconds < 86400) { - const hours = Math.floor(diffSeconds / 3600); - return `${hours}h ago`; - } - const days = Math.floor(diffSeconds / 86400); - return `${days}d ago`; -} - const lastCheckedText = formatLastChecked(monitor.lastChecked); const chartData = JSON.stringify({ diff --git a/status-page/src/components/UptimeBars.astro b/status-page/src/components/UptimeBars.astro index 404449e..97b8cf3 100644 --- a/status-page/src/components/UptimeBars.astro +++ b/status-page/src/components/UptimeBars.astro @@ -1,18 +1,12 @@ --- import type { ApiDayStatus } from '@worker/types'; +import { getBarColor } from '../lib/uptime-bars.js'; interface Props { dailyHistory: ApiDayStatus[]; } const { dailyHistory } = Astro.props; - -function getBarColor(uptimePercent: number | undefined): string { - if (uptimePercent == null) return 'no-data'; - if (uptimePercent >= 99.8) return 'up'; - if (uptimePercent >= 95) return 'degraded'; - return 'down'; -} ---
diff --git a/status-page/src/lib/header.test.ts b/status-page/src/lib/header.test.ts new file mode 100644 index 0000000..9ebdc30 --- /dev/null +++ b/status-page/src/lib/header.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect, afterAll } from 'vitest'; +import { formatAbsoluteTime } from './header.js'; + +describe('formatAbsoluteTime', () => { + const origTZ = process.env.TZ; + + afterAll(() => { + process.env.TZ = origTZ; + }); + + it('formats timestamp in UTC', () => { + process.env.TZ = 'UTC'; + // 1704067200 = 2024-01-01 00:00:00 UTC + const result = formatAbsoluteTime(1704067200); + expect(result).toContain('Jan'); + expect(result).toContain('1,'); + expect(result).toContain('00:00'); + }); + + it('returns month abbreviation in English', () => { + process.env.TZ = 'UTC'; + // 1719792000 = 2024-07-01 00:00:00 UTC + const result = formatAbsoluteTime(1719792000); + expect(result).toContain('Jul'); + expect(result).toContain('1,'); + }); +}); diff --git a/status-page/src/lib/header.ts b/status-page/src/lib/header.ts new file mode 100644 index 0000000..963f833 --- /dev/null +++ b/status-page/src/lib/header.ts @@ -0,0 +1,11 @@ +export function formatAbsoluteTime(unixTimestamp: number): string { + const date = new Date(unixTimestamp * 1000); + return date.toLocaleString('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: false, + timeZoneName: 'short', + }); +} diff --git a/status-page/src/lib/monitor-card.test.ts b/status-page/src/lib/monitor-card.test.ts new file mode 100644 index 0000000..c9df698 --- /dev/null +++ b/status-page/src/lib/monitor-card.test.ts @@ -0,0 +1,42 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { formatLastChecked } from './monitor-card.js'; + +describe('formatLastChecked', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2024-06-15T12:00:00Z')); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('returns "Never" for null/undefined', () => { + expect(formatLastChecked(undefined)).toBe('Never'); + expect(formatLastChecked(null as unknown as undefined)).toBe('Never'); + }); + + it('returns empty string for < 60 seconds', () => { + const now = Math.floor(Date.now() / 1000); + expect(formatLastChecked(now - 30)).toBe(''); + expect(formatLastChecked(now - 0)).toBe(''); + }); + + it('returns minutes ago', () => { + const now = Math.floor(Date.now() / 1000); + expect(formatLastChecked(now - 120)).toBe('2m ago'); + expect(formatLastChecked(now - 3540)).toBe('59m ago'); + }); + + it('returns hours ago', () => { + const now = Math.floor(Date.now() / 1000); + expect(formatLastChecked(now - 3600)).toBe('1h ago'); + expect(formatLastChecked(now - 82800)).toBe('23h ago'); + }); + + it('returns days ago', () => { + const now = Math.floor(Date.now() / 1000); + expect(formatLastChecked(now - 86400)).toBe('1d ago'); + expect(formatLastChecked(now - 172800)).toBe('2d ago'); + }); +}); diff --git a/status-page/src/lib/monitor-card.ts b/status-page/src/lib/monitor-card.ts new file mode 100644 index 0000000..3885883 --- /dev/null +++ b/status-page/src/lib/monitor-card.ts @@ -0,0 +1,17 @@ +export function formatLastChecked(timestamp: number | undefined): string { + if (timestamp == null) return 'Never'; + const now = Math.floor(Date.now() / 1000); + const diffSeconds = now - timestamp; + + if (diffSeconds < 60) return ''; + if (diffSeconds < 3600) { + const minutes = Math.floor(diffSeconds / 60); + return `${minutes}m ago`; + } + if (diffSeconds < 86400) { + const hours = Math.floor(diffSeconds / 3600); + return `${hours}h ago`; + } + const days = Math.floor(diffSeconds / 86400); + return `${days}d ago`; +} diff --git a/status-page/src/lib/uptime-bars.test.ts b/status-page/src/lib/uptime-bars.test.ts new file mode 100644 index 0000000..dcd9f98 --- /dev/null +++ b/status-page/src/lib/uptime-bars.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect } from 'vitest'; +import { getBarColor } from './uptime-bars.js'; + +describe('getBarColor', () => { + it('returns "no-data" for null/undefined', () => { + expect(getBarColor(undefined)).toBe('no-data'); + expect(getBarColor(null as unknown as undefined)).toBe('no-data'); + }); + + it('returns "up" for >= 99.8', () => { + expect(getBarColor(100)).toBe('up'); + expect(getBarColor(99.8)).toBe('up'); + expect(getBarColor(99.81)).toBe('up'); + }); + + it('returns "degraded" for >= 95 and < 99.8', () => { + expect(getBarColor(99.7)).toBe('degraded'); + expect(getBarColor(97.5)).toBe('degraded'); + expect(getBarColor(95)).toBe('degraded'); + }); + + it('returns "down" for < 95', () => { + expect(getBarColor(94.9)).toBe('down'); + expect(getBarColor(50)).toBe('down'); + expect(getBarColor(0)).toBe('down'); + }); +}); diff --git a/status-page/src/lib/uptime-bars.ts b/status-page/src/lib/uptime-bars.ts new file mode 100644 index 0000000..918e34d --- /dev/null +++ b/status-page/src/lib/uptime-bars.ts @@ -0,0 +1,6 @@ +export function getBarColor(uptimePercent: number | undefined): string { + if (uptimePercent == null) return 'no-data'; + if (uptimePercent >= 99.8) return 'up'; + if (uptimePercent >= 95) return 'degraded'; + return 'down'; +} diff --git a/status-page/vitest.config.ts b/status-page/vitest.config.ts new file mode 100644 index 0000000..7dd1325 --- /dev/null +++ b/status-page/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/**/*.test.ts'], + }, +});