sla.test.ts73 lines · main
1import { describe, expect, test } from 'bun:test';
2
3import { creditPercentForBreach, previousMonthBounds, SLA_TIERS } from './sla.js';
4
5describe('SLA_TIERS', () => {
6 test('free tier has no SLA — no auto-credit ever fires', () => {
7 expect(SLA_TIERS.free).toBeNull();
8 });
9
10 test('pro tier targets 99.5% uptime', () => {
11 expect(SLA_TIERS.pro?.targetUptime).toBe(0.995);
12 });
13
14 test('team tier targets 99.9% uptime — more than 2x stricter than pro', () => {
15 expect(SLA_TIERS.team?.targetUptime).toBe(0.999);
16 const proDowntime = 1 - SLA_TIERS.pro!.targetUptime;
17 const teamDowntime = 1 - SLA_TIERS.team!.targetUptime;
18 expect(proDowntime / teamDowntime).toBeCloseTo(5, 0); // pro allows 5x the downtime
19 });
20
21 test('downtime budgets are sane for a 30-day month', () => {
22 const monthSeconds = 30 * 86400;
23 // Pro: 0.5% of a month ≈ 3h 36m
24 expect(SLA_TIERS.pro!.maxMonthlyDowntimeSeconds).toBeGreaterThan(12_000);
25 expect(SLA_TIERS.pro!.maxMonthlyDowntimeSeconds).toBeLessThan(monthSeconds * 0.01);
26 // Team: 0.1% of a month ≈ 43m
27 expect(SLA_TIERS.team!.maxMonthlyDowntimeSeconds).toBeGreaterThan(2_000);
28 expect(SLA_TIERS.team!.maxMonthlyDowntimeSeconds).toBeLessThan(3_000);
29 });
30});
31
32describe('creditPercentForBreach', () => {
33 test('no breach → no credit', () => {
34 expect(creditPercentForBreach(0.995, 0.999)).toBe(0);
35 expect(creditPercentForBreach(0.995, 0.995)).toBe(0);
36 });
37
38 test('small breach (< 0.5% over the threshold) → 10% credit', () => {
39 expect(creditPercentForBreach(0.995, 0.993)).toBe(0.1);
40 expect(creditPercentForBreach(0.995, 0.991)).toBe(0.1);
41 });
42
43 test('moderate breach (0.5%–1%) → 25% credit', () => {
44 expect(creditPercentForBreach(0.995, 0.989)).toBe(0.25);
45 expect(creditPercentForBreach(0.995, 0.986)).toBe(0.25);
46 });
47
48 test('substantial breach (1%–5%) → 50% credit', () => {
49 expect(creditPercentForBreach(0.995, 0.98)).toBe(0.5);
50 expect(creditPercentForBreach(0.995, 0.95)).toBe(0.5);
51 });
52
53 test('major outage (> 5% breach) → 100% credit (full month free)', () => {
54 expect(creditPercentForBreach(0.995, 0.94)).toBe(1);
55 expect(creditPercentForBreach(0.999, 0.85)).toBe(1);
56 });
57});
58
59describe('previousMonthBounds', () => {
60 test('mid-month March → all of February', () => {
61 const now = new Date('2026-03-15T10:00:00Z');
62 const { start, end } = previousMonthBounds(now);
63 expect(start.toISOString()).toBe('2026-02-01T00:00:00.000Z');
64 expect(end.toISOString()).toBe('2026-03-01T00:00:00.000Z');
65 });
66
67 test('first of January → all of December last year', () => {
68 const now = new Date('2026-01-01T00:00:00Z');
69 const { start, end } = previousMonthBounds(now);
70 expect(start.toISOString()).toBe('2025-12-01T00:00:00.000Z');
71 expect(end.toISOString()).toBe('2026-01-01T00:00:00.000Z');
72 });
73});