@checkstack/healthcheck-backend 1.14.0 → 1.16.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.
@@ -0,0 +1,213 @@
1
+ import { describe, expect, it, mock } from "bun:test";
2
+ import { z } from "zod";
3
+ import { Versioned } from "@checkstack/backend-api";
4
+ import {
5
+ computeAssertionKey,
6
+ healthResultNumber,
7
+ healthResultString,
8
+ type CollectorConfigEntry,
9
+ } from "@checkstack/healthcheck-common";
10
+ import { HealthCheckService } from "./service";
11
+
12
+ /**
13
+ * Satellite ingest evaluates assertions ON THE CORE (satellites never held
14
+ * the assertion semantics — before this, satellite-executed checks silently
15
+ * skipped assertions), then strips ephemeral fields for parity with local
16
+ * runs. These tests drive `ingestSatelliteResult` against a mock db and
17
+ * assert on the run row it persists.
18
+ */
19
+
20
+ const KEY = computeAssertionKey({
21
+ assertion: { field: "statusCode", operator: "equals", value: 200 },
22
+ });
23
+
24
+ // `body` is ephemeral: assertable at evaluation time, never persisted.
25
+ const collectorResultSchema = z.object({
26
+ statusCode: healthResultNumber({
27
+ "x-chart-type": "counter",
28
+ "x-anomaly-enabled": false,
29
+ }),
30
+ body: healthResultString({ "x-ephemeral": true }),
31
+ });
32
+
33
+ function buildService({
34
+ entries,
35
+ inserted,
36
+ }: {
37
+ entries: CollectorConfigEntry[];
38
+ inserted: Record<string, unknown>[];
39
+ }) {
40
+ const tx = {
41
+ insert: mock(() => ({
42
+ values: mock((vals: Record<string, unknown>) => {
43
+ inserted.push(vals);
44
+ return Object.assign(Promise.resolve(), {
45
+ onConflictDoUpdate: mock(() => Promise.resolve()),
46
+ onConflictDoNothing: mock(() => Promise.resolve()),
47
+ });
48
+ }),
49
+ })),
50
+ select: mock(() => ({
51
+ from: mock(() => ({
52
+ where: mock(() =>
53
+ Object.assign(Promise.resolve([]), {
54
+ limit: mock(() => Promise.resolve([])),
55
+ }),
56
+ ),
57
+ })),
58
+ })),
59
+ };
60
+
61
+ const db = {
62
+ select: mock(() => ({
63
+ from: mock(() => ({
64
+ where: mock(() => Promise.resolve([{ collectors: entries }])),
65
+ })),
66
+ })),
67
+ transaction: mock(async (fn: (t: typeof tx) => Promise<void>) => fn(tx)),
68
+ };
69
+
70
+ const collectorRegistry = {
71
+ register: mock(() => {}),
72
+ getCollector: mock(() => ({
73
+ collector: {
74
+ id: "test-collector",
75
+ result: new Versioned({ version: 1, schema: collectorResultSchema }),
76
+ },
77
+ })),
78
+ getCollectors: mock(() => []),
79
+ };
80
+
81
+ return new HealthCheckService(
82
+ db as unknown as ConstructorParameters<typeof HealthCheckService>[0],
83
+ {} as unknown as ConstructorParameters<typeof HealthCheckService>[1],
84
+ collectorRegistry as unknown as ConstructorParameters<
85
+ typeof HealthCheckService
86
+ >[2],
87
+ );
88
+ }
89
+
90
+ function satelliteResult({ statusCode }: { statusCode: number }) {
91
+ return {
92
+ status: "healthy",
93
+ latencyMs: 42,
94
+ message: "Completed in 42ms",
95
+ metadata: {
96
+ collectors: {
97
+ "entry-1": {
98
+ _collectorId: "test-collector",
99
+ statusCode,
100
+ body: '{"status":"ok"}',
101
+ },
102
+ },
103
+ },
104
+ };
105
+ }
106
+
107
+ const entries: CollectorConfigEntry[] = [
108
+ {
109
+ id: "entry-1",
110
+ collectorId: "test-collector",
111
+ config: {},
112
+ assertions: [{ field: "statusCode", operator: "equals", value: 200 }],
113
+ },
114
+ ];
115
+
116
+ async function ingest({
117
+ entries,
118
+ statusCode,
119
+ }: {
120
+ entries: CollectorConfigEntry[];
121
+ statusCode: number;
122
+ }) {
123
+ const inserted: Record<string, unknown>[] = [];
124
+ const service = buildService({ entries, inserted });
125
+ await service.ingestSatelliteResult({
126
+ configId: "config-1",
127
+ systemId: "system-1",
128
+ status: "healthy",
129
+ latencyMs: 42,
130
+ result: satelliteResult({ statusCode }) as never,
131
+ executedAt: "2026-07-03T10:00:00.000Z",
132
+ sourceId: "sat-1",
133
+ sourceLabel: "EU West",
134
+ });
135
+ const runInsert = inserted.find((v) => "status" in v && "result" in v);
136
+ expect(runInsert).toBeDefined();
137
+ return runInsert as Record<string, unknown>;
138
+ }
139
+
140
+ function collectorEntryOf(runInsert: Record<string, unknown>) {
141
+ const result = runInsert.result as {
142
+ metadata: { collectors: Record<string, Record<string, unknown>> };
143
+ };
144
+ return result.metadata.collectors["entry-1"];
145
+ }
146
+
147
+ describe("ingestSatelliteResult - assertion evaluation at ingest", () => {
148
+ it("downgrades a satellite-healthy run whose assertion fails", async () => {
149
+ const runInsert = await ingest({ entries, statusCode: 404 });
150
+ expect(runInsert.status).toBe("unhealthy");
151
+
152
+ const entry = collectorEntryOf(runInsert);
153
+ expect(entry._assertionFailed).toBe("statusCode equals 200");
154
+ expect(entry._assertions).toEqual([
155
+ expect.objectContaining({ key: KEY, passed: false, actual: "404" }),
156
+ ]);
157
+ const message = (runInsert.result as { message: string }).message;
158
+ expect(message).toBe(
159
+ "Check failed: Assertion failed: statusCode equals 200",
160
+ );
161
+ });
162
+
163
+ it("keeps a passing run healthy and stores the passing outcome", async () => {
164
+ const runInsert = await ingest({ entries, statusCode: 200 });
165
+ expect(runInsert.status).toBe("healthy");
166
+
167
+ const entry = collectorEntryOf(runInsert);
168
+ expect(entry._assertionFailed).toBeUndefined();
169
+ expect(entry._assertions).toEqual([
170
+ expect.objectContaining({ key: KEY, passed: true, actual: "200" }),
171
+ ]);
172
+ });
173
+
174
+ it("strips ephemeral fields AFTER assertions ran against them", async () => {
175
+ const withBodyAssertion: CollectorConfigEntry[] = [
176
+ {
177
+ id: "entry-1",
178
+ collectorId: "test-collector",
179
+ config: {},
180
+ assertions: [
181
+ {
182
+ field: "body.$",
183
+ jsonPath: "$.status",
184
+ operator: "equals",
185
+ value: "ok",
186
+ },
187
+ ],
188
+ },
189
+ ];
190
+ const runInsert = await ingest({
191
+ entries: withBodyAssertion,
192
+ statusCode: 200,
193
+ });
194
+
195
+ const entry = collectorEntryOf(runInsert);
196
+ // The JSONPath assertion evaluated against the (ephemeral) body...
197
+ expect(entry._assertions).toEqual([
198
+ expect.objectContaining({ passed: true, actual: "ok" }),
199
+ ]);
200
+ // ...but the body itself never reaches storage.
201
+ expect(entry.body).toBeUndefined();
202
+ expect(entry.statusCode).toBe(200);
203
+ expect(runInsert.status).toBe("healthy");
204
+ });
205
+
206
+ it("tolerates collector entries the config no longer knows", async () => {
207
+ const runInsert = await ingest({ entries: [], statusCode: 500 });
208
+ // No assertions configured: status passes through untouched.
209
+ expect(runInsert.status).toBe("healthy");
210
+ const entry = collectorEntryOf(runInsert);
211
+ expect(entry._assertions).toBeUndefined();
212
+ });
213
+ });