add testing to status-page functions

This commit is contained in:
2026-04-25 14:52:13 +02:00
parent 298feb574e
commit 6da907b83b
13 changed files with 154 additions and 43 deletions
+4 -1
View File
@@ -36,5 +36,8 @@ jobs:
- name: Build status page - name: Build status page
run: npm run build:pages run: npm run build:pages
- name: Run tests - name: Run worker tests
run: npm run test run: npm run test
- name: Run status-page tests
run: npm run test:pages
+6 -3
View File
@@ -381,15 +381,18 @@ curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*"
### Testing ### Testing
```bash ```bash
# Fist build the status page # First build the status page
npm run build:pages npm run build:pages
# Worker tests # Worker tests
npm run test npm run test
# Status page unit tests
npm run test:pages
# Type checking and linting # Type checking and linting
npm run check # worker npm run check # worker (typecheck + lint + format)
npm run check:pages # pages (astro check + tsc) npm run check:pages # pages (astro check + tsc + lint)
``` ```
## TODO ## TODO
+1
View File
@@ -23,6 +23,7 @@
"check:pages": "npm run typecheck --workspace=status-page && npm run lint:pages && npm run format:pages:check", "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": "npm run lint --workspace=status-page",
"lint:pages:fix": "npm run lint:fix --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": "prettier --write \"src/**/*.ts\" \"status-page/src/**/*.ts\"",
"format:pages:check": "prettier --check \"src/**/*.ts\" \"status-page/src/**/*.ts\"" "format:pages:check": "prettier --check \"src/**/*.ts\" \"status-page/src/**/*.ts\""
}, },
+2 -13
View File
@@ -13,20 +13,9 @@ interface Props {
}; };
} }
import { formatAbsoluteTime } from '../lib/header.js';
const { summary, lastUpdated, title, banner } = Astro.props; 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); const absoluteTime = formatAbsoluteTime(lastUpdated);
--- ---
+1 -19
View File
@@ -1,6 +1,7 @@
--- ---
import type { ApiMonitorStatus } from '@worker/types'; import type { ApiMonitorStatus } from '@worker/types';
import UptimeBars from './UptimeBars.astro'; import UptimeBars from './UptimeBars.astro';
import { formatLastChecked } from '../lib/monitor-card.js';
interface Props { interface Props {
monitor: ApiMonitorStatus; monitor: ApiMonitorStatus;
@@ -9,25 +10,6 @@ interface Props {
const { monitor } = Astro.props; const { monitor } = Astro.props;
const uptimeFormatted = monitor.uptimePercent.toFixed(2); 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 lastCheckedText = formatLastChecked(monitor.lastChecked);
const chartData = JSON.stringify({ const chartData = JSON.stringify({
+1 -7
View File
@@ -1,18 +1,12 @@
--- ---
import type { ApiDayStatus } from '@worker/types'; import type { ApiDayStatus } from '@worker/types';
import { getBarColor } from '../lib/uptime-bars.js';
interface Props { interface Props {
dailyHistory: ApiDayStatus[]; dailyHistory: ApiDayStatus[];
} }
const { dailyHistory } = Astro.props; 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';
}
--- ---
<div class="uptime-section"> <div class="uptime-section">
+27
View File
@@ -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,');
});
});
+11
View File
@@ -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',
});
}
+42
View File
@@ -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');
});
});
+17
View File
@@ -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`;
}
+27
View File
@@ -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');
});
});
+6
View File
@@ -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';
}
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['src/**/*.test.ts'],
},
});