@checkstack/healthcheck-backend 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,236 +0,0 @@
1
- import { describe, it, expect, mock, beforeEach } from "bun:test";
2
- import { HealthCheckService } from "./service";
3
- import { subDays, subHours } from "date-fns";
4
-
5
- describe("HealthCheckService.getAvailabilityStats", () => {
6
- // Mock database
7
- let mockDb: ReturnType<typeof createMockDb>;
8
- let service: HealthCheckService;
9
-
10
- // Store mock data for different queries
11
- let mockHourlyAggregates: Array<{
12
- bucketStart: Date;
13
- runCount: number;
14
- healthyCount: number;
15
- }> = [];
16
- let mockDailyAggregates: Array<{
17
- bucketStart: Date;
18
- runCount: number;
19
- healthyCount: number;
20
- }> = [];
21
- let mockRetentionConfig: { retentionConfig: unknown } | undefined = undefined;
22
-
23
- function createMockDb() {
24
- // Select call order for getAvailabilityStats:
25
- // 1. getRetentionConfig (from systemHealthChecks) - uses .then() pattern
26
- // 2. hourlyAggregates
27
- // 3. dailyAggregates
28
- let selectCallCount = 0;
29
-
30
- const createSelectChain = () => {
31
- const currentCall = selectCallCount++;
32
-
33
- return {
34
- from: mock(() => ({
35
- where: mock(() => {
36
- // Call 0: retentionConfig - uses .then() pattern
37
- if (currentCall === 0) {
38
- const result = mockRetentionConfig ? [mockRetentionConfig] : [];
39
- // Return a promise-like object with .then()
40
- return Promise.resolve(result);
41
- }
42
- // Call 1: hourly aggregates
43
- if (currentCall === 1) return Promise.resolve(mockHourlyAggregates);
44
- // Call 2: daily aggregates
45
- return Promise.resolve(mockDailyAggregates);
46
- }),
47
- })),
48
- };
49
- };
50
-
51
- return {
52
- select: mock(createSelectChain),
53
- };
54
- }
55
-
56
- beforeEach(() => {
57
- // Reset mock data
58
- mockHourlyAggregates = [];
59
- mockDailyAggregates = [];
60
- mockRetentionConfig = undefined;
61
- mockDb = createMockDb();
62
- service = new HealthCheckService(mockDb as never, {} as never, {} as never);
63
- });
64
-
65
- describe("with no data", () => {
66
- it("returns null availability when no aggregates exist", async () => {
67
- const result = await service.getAvailabilityStats({
68
- systemId: "sys-1",
69
- configurationId: "config-1",
70
- });
71
-
72
- expect(result.availability31Days).toBeNull();
73
- expect(result.availability365Days).toBeNull();
74
- expect(result.totalRuns31Days).toBe(0);
75
- expect(result.totalRuns365Days).toBe(0);
76
- });
77
- });
78
-
79
- describe("with hourly aggregates (real-time incremental)", () => {
80
- it("calculates 100% availability when all runs are healthy", async () => {
81
- mockHourlyAggregates = [
82
- {
83
- bucketStart: subHours(new Date(), 2),
84
- runCount: 100,
85
- healthyCount: 100,
86
- },
87
- {
88
- bucketStart: subHours(new Date(), 5),
89
- runCount: 100,
90
- healthyCount: 100,
91
- },
92
- ];
93
-
94
- const result = await service.getAvailabilityStats({
95
- systemId: "sys-1",
96
- configurationId: "config-1",
97
- });
98
-
99
- expect(result.availability31Days).toBe(100);
100
- expect(result.availability365Days).toBe(100);
101
- expect(result.totalRuns31Days).toBe(200);
102
- expect(result.totalRuns365Days).toBe(200);
103
- });
104
-
105
- it("calculates correct availability with mixed results", async () => {
106
- mockHourlyAggregates = [
107
- {
108
- bucketStart: subHours(new Date(), 2),
109
- runCount: 100,
110
- healthyCount: 90,
111
- },
112
- {
113
- bucketStart: subHours(new Date(), 5),
114
- runCount: 100,
115
- healthyCount: 80,
116
- },
117
- ];
118
-
119
- const result = await service.getAvailabilityStats({
120
- systemId: "sys-1",
121
- configurationId: "config-1",
122
- });
123
-
124
- // 170 healthy / 200 total = 85%
125
- expect(result.availability31Days).toBe(85);
126
- expect(result.availability365Days).toBe(85);
127
- });
128
-
129
- it("includes current hour data since aggregates are updated incrementally", async () => {
130
- const currentHourStart = new Date();
131
- currentHourStart.setMinutes(0, 0, 0);
132
-
133
- mockHourlyAggregates = [
134
- { bucketStart: currentHourStart, runCount: 10, healthyCount: 9 },
135
- ];
136
-
137
- const result = await service.getAvailabilityStats({
138
- systemId: "sys-1",
139
- configurationId: "config-1",
140
- });
141
-
142
- expect(result.totalRuns31Days).toBe(10);
143
- expect(result.availability31Days).toBe(90);
144
- });
145
- });
146
-
147
- describe("with combined hourly and daily aggregates", () => {
148
- it("combines hourly and daily data correctly", async () => {
149
- mockHourlyAggregates = [
150
- {
151
- bucketStart: subHours(new Date(), 2),
152
- runCount: 100,
153
- healthyCount: 99,
154
- },
155
- ];
156
-
157
- mockDailyAggregates = [
158
- {
159
- bucketStart: subDays(new Date(), 60),
160
- runCount: 100,
161
- healthyCount: 50,
162
- },
163
- ];
164
-
165
- const result = await service.getAvailabilityStats({
166
- systemId: "sys-1",
167
- configurationId: "config-1",
168
- });
169
-
170
- // 31 days: only hourly (99/100) = 99%
171
- expect(result.availability31Days).toBe(99);
172
- expect(result.totalRuns31Days).toBe(100);
173
-
174
- // 365 days: (99+50)/200 = 74.5%
175
- expect(result.availability365Days).toBe(74.5);
176
- expect(result.totalRuns365Days).toBe(200);
177
- });
178
- });
179
-
180
- describe("99.9% availability calculation", () => {
181
- it("calculates precise availability for SLA tracking", async () => {
182
- mockHourlyAggregates = [
183
- {
184
- bucketStart: subHours(new Date(), 5),
185
- runCount: 1000,
186
- healthyCount: 999,
187
- },
188
- ];
189
-
190
- const result = await service.getAvailabilityStats({
191
- systemId: "sys-1",
192
- configurationId: "config-1",
193
- });
194
-
195
- expect(result.availability31Days).toBe(99.9);
196
- expect(result.availability365Days).toBe(99.9);
197
- });
198
-
199
- it("calculates very high availability correctly", async () => {
200
- mockHourlyAggregates = [
201
- {
202
- bucketStart: subHours(new Date(), 5),
203
- runCount: 10_000,
204
- healthyCount: 9999,
205
- },
206
- ];
207
-
208
- const result = await service.getAvailabilityStats({
209
- systemId: "sys-1",
210
- configurationId: "config-1",
211
- });
212
-
213
- expect(result.availability31Days).toBe(99.99);
214
- });
215
- });
216
-
217
- describe("real-time incremental aggregation behavior", () => {
218
- it("uses hourly aggregates directly without raw run queries", async () => {
219
- mockHourlyAggregates = [
220
- {
221
- bucketStart: subHours(new Date(), 1),
222
- runCount: 50,
223
- healthyCount: 48,
224
- },
225
- ];
226
-
227
- const result = await service.getAvailabilityStats({
228
- systemId: "sys-1",
229
- configurationId: "config-1",
230
- });
231
-
232
- expect(result.availability31Days).toBe(96);
233
- expect(result.totalRuns31Days).toBe(50);
234
- });
235
- });
236
- });