postgresai 0.14.0-dev.39 → 0.14.0-dev.41
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.
- package/bin/postgres-ai.ts +0 -287
- package/dist/bin/postgres-ai.js +0 -263
- package/dist/bin/postgres-ai.js.map +1 -1
- package/dist/lib/config.d.ts +0 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +0 -2
- package/dist/lib/config.js.map +1 -1
- package/dist/package.json +1 -1
- package/lib/config.ts +0 -3
- package/package.json +1 -1
- package/dist/lib/checkup-api.d.ts +0 -33
- package/dist/lib/checkup-api.d.ts.map +0 -1
- package/dist/lib/checkup-api.js +0 -187
- package/dist/lib/checkup-api.js.map +0 -1
- package/dist/lib/checkup.d.ts +0 -153
- package/dist/lib/checkup.d.ts.map +0 -1
- package/dist/lib/checkup.js +0 -536
- package/dist/lib/checkup.js.map +0 -1
- package/lib/checkup-api.ts +0 -177
- package/lib/checkup.ts +0 -622
- package/reports/A002.json +0 -23
- package/reports/A003.json +0 -3343
- package/reports/A004.json +0 -134
- package/reports/A007.json +0 -683
- package/reports/A013.json +0 -23
- package/test/checkup.test.cjs +0 -645
package/reports/A013.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "0.0.0-dev.0",
|
|
3
|
-
"build_ts": "2025-12-25T10:09:54.142Z",
|
|
4
|
-
"checkId": "A013",
|
|
5
|
-
"checkTitle": "Postgres minor version",
|
|
6
|
-
"timestamptz": "2025-12-25T10:11:14.520Z",
|
|
7
|
-
"nodes": {
|
|
8
|
-
"primary": "node-01",
|
|
9
|
-
"standbys": []
|
|
10
|
-
},
|
|
11
|
-
"results": {
|
|
12
|
-
"node-01": {
|
|
13
|
-
"data": {
|
|
14
|
-
"version": {
|
|
15
|
-
"version": "17.6 (Ubuntu 17.6-2.pgdg22.04+1)",
|
|
16
|
-
"server_version_num": "170006",
|
|
17
|
-
"server_major_ver": "17",
|
|
18
|
-
"server_minor_ver": "6"
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
package/test/checkup.test.cjs
DELETED
|
@@ -1,645 +0,0 @@
|
|
|
1
|
-
const test = require("node:test");
|
|
2
|
-
const assert = require("node:assert/strict");
|
|
3
|
-
|
|
4
|
-
// These tests intentionally import the compiled JS output.
|
|
5
|
-
// Run via: npm --prefix cli test
|
|
6
|
-
const checkup = require("../dist/lib/checkup.js");
|
|
7
|
-
|
|
8
|
-
function runCli(args, env = {}) {
|
|
9
|
-
const { spawnSync } = require("node:child_process");
|
|
10
|
-
const path = require("node:path");
|
|
11
|
-
const node = process.execPath;
|
|
12
|
-
const cliPath = path.resolve(__dirname, "..", "dist", "bin", "postgres-ai.js");
|
|
13
|
-
return spawnSync(node, [cliPath, ...args], {
|
|
14
|
-
encoding: "utf8",
|
|
15
|
-
env: { ...process.env, ...env },
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Unit tests for parseVersionNum
|
|
20
|
-
test("parseVersionNum parses PG 16.3 version number", () => {
|
|
21
|
-
const result = checkup.parseVersionNum("160003");
|
|
22
|
-
assert.equal(result.major, "16");
|
|
23
|
-
assert.equal(result.minor, "3");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("parseVersionNum parses PG 15.7 version number", () => {
|
|
27
|
-
const result = checkup.parseVersionNum("150007");
|
|
28
|
-
assert.equal(result.major, "15");
|
|
29
|
-
assert.equal(result.minor, "7");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("parseVersionNum parses PG 14.12 version number", () => {
|
|
33
|
-
const result = checkup.parseVersionNum("140012");
|
|
34
|
-
assert.equal(result.major, "14");
|
|
35
|
-
assert.equal(result.minor, "12");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("parseVersionNum handles empty string", () => {
|
|
39
|
-
const result = checkup.parseVersionNum("");
|
|
40
|
-
assert.equal(result.major, "");
|
|
41
|
-
assert.equal(result.minor, "");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("parseVersionNum handles null/undefined", () => {
|
|
45
|
-
const result = checkup.parseVersionNum(null);
|
|
46
|
-
assert.equal(result.major, "");
|
|
47
|
-
assert.equal(result.minor, "");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("parseVersionNum handles short string", () => {
|
|
51
|
-
const result = checkup.parseVersionNum("123");
|
|
52
|
-
assert.equal(result.major, "");
|
|
53
|
-
assert.equal(result.minor, "");
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Unit tests for createBaseReport
|
|
57
|
-
test("createBaseReport creates correct structure", () => {
|
|
58
|
-
const report = checkup.createBaseReport("A002", "Postgres major version", "test-node");
|
|
59
|
-
|
|
60
|
-
assert.equal(report.checkId, "A002");
|
|
61
|
-
assert.equal(report.checkTitle, "Postgres major version");
|
|
62
|
-
assert.ok(typeof report.version === "string" && report.version.length > 0);
|
|
63
|
-
assert.ok(typeof report.build_ts === "string" && report.build_ts.length > 0);
|
|
64
|
-
assert.equal(report.nodes.primary, "test-node");
|
|
65
|
-
assert.deepEqual(report.nodes.standbys, []);
|
|
66
|
-
assert.deepEqual(report.results, {});
|
|
67
|
-
assert.ok(typeof report.timestamptz === "string");
|
|
68
|
-
// Verify timestamp is ISO format
|
|
69
|
-
assert.ok(new Date(report.timestamptz).toISOString() === report.timestamptz);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("createBaseReport uses provided node name", () => {
|
|
73
|
-
const report = checkup.createBaseReport("A003", "Postgres settings", "my-custom-node");
|
|
74
|
-
assert.equal(report.nodes.primary, "my-custom-node");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// Tests for CHECK_INFO
|
|
78
|
-
test("CHECK_INFO contains A002", () => {
|
|
79
|
-
assert.ok("A002" in checkup.CHECK_INFO);
|
|
80
|
-
assert.equal(checkup.CHECK_INFO.A002, "Postgres major version");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("CHECK_INFO contains A003", () => {
|
|
84
|
-
assert.ok("A003" in checkup.CHECK_INFO);
|
|
85
|
-
assert.equal(checkup.CHECK_INFO.A003, "Postgres settings");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("CHECK_INFO contains A013", () => {
|
|
89
|
-
assert.ok("A013" in checkup.CHECK_INFO);
|
|
90
|
-
assert.equal(checkup.CHECK_INFO.A013, "Postgres minor version");
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("CHECK_INFO contains A004", () => {
|
|
94
|
-
assert.ok("A004" in checkup.CHECK_INFO);
|
|
95
|
-
assert.equal(checkup.CHECK_INFO.A004, "Cluster information");
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("CHECK_INFO contains A007", () => {
|
|
99
|
-
assert.ok("A007" in checkup.CHECK_INFO);
|
|
100
|
-
assert.equal(checkup.CHECK_INFO.A007, "Altered settings");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Tests for REPORT_GENERATORS
|
|
104
|
-
test("REPORT_GENERATORS has generator for A002", () => {
|
|
105
|
-
assert.ok("A002" in checkup.REPORT_GENERATORS);
|
|
106
|
-
assert.equal(typeof checkup.REPORT_GENERATORS.A002, "function");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("REPORT_GENERATORS has generator for A003", () => {
|
|
110
|
-
assert.ok("A003" in checkup.REPORT_GENERATORS);
|
|
111
|
-
assert.equal(typeof checkup.REPORT_GENERATORS.A003, "function");
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("REPORT_GENERATORS has generator for A013", () => {
|
|
115
|
-
assert.ok("A013" in checkup.REPORT_GENERATORS);
|
|
116
|
-
assert.equal(typeof checkup.REPORT_GENERATORS.A013, "function");
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("REPORT_GENERATORS has generator for A004", () => {
|
|
120
|
-
assert.ok("A004" in checkup.REPORT_GENERATORS);
|
|
121
|
-
assert.equal(typeof checkup.REPORT_GENERATORS.A004, "function");
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("REPORT_GENERATORS has generator for A007", () => {
|
|
125
|
-
assert.ok("A007" in checkup.REPORT_GENERATORS);
|
|
126
|
-
assert.equal(typeof checkup.REPORT_GENERATORS.A007, "function");
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("REPORT_GENERATORS and CHECK_INFO have same keys", () => {
|
|
130
|
-
const generatorKeys = Object.keys(checkup.REPORT_GENERATORS).sort();
|
|
131
|
-
const infoKeys = Object.keys(checkup.CHECK_INFO).sort();
|
|
132
|
-
assert.deepEqual(generatorKeys, infoKeys);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Tests for METRICS_SQL
|
|
136
|
-
test("METRICS_SQL.settings queries pg_settings", () => {
|
|
137
|
-
assert.ok(checkup.METRICS_SQL.settings.includes("pg_settings"));
|
|
138
|
-
assert.ok(checkup.METRICS_SQL.settings.includes("name"));
|
|
139
|
-
assert.ok(checkup.METRICS_SQL.settings.includes("setting"));
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("METRICS_SQL.version queries server_version fields", () => {
|
|
143
|
-
assert.ok(checkup.METRICS_SQL.version.includes("server_version"));
|
|
144
|
-
assert.ok(checkup.METRICS_SQL.version.includes("server_version_num"));
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("METRICS_SQL.alteredSettings filters non-default settings", () => {
|
|
148
|
-
assert.ok(checkup.METRICS_SQL.alteredSettings.includes("pg_settings"));
|
|
149
|
-
assert.ok(checkup.METRICS_SQL.alteredSettings.includes("source <> 'default'"));
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("METRICS_SQL.databaseSizes queries pg_database", () => {
|
|
153
|
-
assert.ok(checkup.METRICS_SQL.databaseSizes.includes("pg_database"));
|
|
154
|
-
assert.ok(checkup.METRICS_SQL.databaseSizes.includes("pg_database_size"));
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("METRICS_SQL.clusterStats queries pg_stat_database", () => {
|
|
158
|
-
assert.ok(checkup.METRICS_SQL.clusterStats.includes("pg_stat_database"));
|
|
159
|
-
assert.ok(checkup.METRICS_SQL.clusterStats.includes("xact_commit"));
|
|
160
|
-
assert.ok(checkup.METRICS_SQL.clusterStats.includes("deadlocks"));
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("METRICS_SQL.connectionStates queries pg_stat_activity", () => {
|
|
164
|
-
assert.ok(checkup.METRICS_SQL.connectionStates.includes("pg_stat_activity"));
|
|
165
|
-
assert.ok(checkup.METRICS_SQL.connectionStates.includes("state"));
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// Tests for formatBytes
|
|
169
|
-
test("formatBytes formats zero bytes", () => {
|
|
170
|
-
assert.equal(checkup.formatBytes(0), "0 B");
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test("formatBytes formats bytes", () => {
|
|
174
|
-
assert.equal(checkup.formatBytes(500), "500.00 B");
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test("formatBytes formats kilobytes", () => {
|
|
178
|
-
assert.equal(checkup.formatBytes(1024), "1.00 kB");
|
|
179
|
-
assert.equal(checkup.formatBytes(1536), "1.50 kB");
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test("formatBytes formats megabytes", () => {
|
|
183
|
-
assert.equal(checkup.formatBytes(1048576), "1.00 MB");
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("formatBytes formats gigabytes", () => {
|
|
187
|
-
assert.equal(checkup.formatBytes(1073741824), "1.00 GB");
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// Mock client tests for report generators
|
|
191
|
-
function createMockClient(versionRows, settingsRows, options = {}) {
|
|
192
|
-
const {
|
|
193
|
-
alteredSettingsRows = [],
|
|
194
|
-
databaseSizesRows = [],
|
|
195
|
-
clusterStatsRows = [],
|
|
196
|
-
connectionStatesRows = [],
|
|
197
|
-
uptimeRows = [],
|
|
198
|
-
} = options;
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
query: async (sql) => {
|
|
202
|
-
// Version query (used by many reports)
|
|
203
|
-
if (sql.includes("server_version") && sql.includes("server_version_num") && !sql.includes("ORDER BY")) {
|
|
204
|
-
return { rows: versionRows };
|
|
205
|
-
}
|
|
206
|
-
// Full settings query (A003) - check this BEFORE altered settings
|
|
207
|
-
// because full settings has "ORDER BY" and "CASE WHEN source <> 'default'"
|
|
208
|
-
// while altered settings has "WHERE source <> 'default'"
|
|
209
|
-
if (sql.includes("pg_settings") && sql.includes("ORDER BY") && sql.includes("is_default")) {
|
|
210
|
-
return { rows: settingsRows };
|
|
211
|
-
}
|
|
212
|
-
// Altered settings query (A007) - has "WHERE source <> 'default'" (not in a CASE)
|
|
213
|
-
if (sql.includes("pg_settings") && sql.includes("WHERE source <> 'default'")) {
|
|
214
|
-
return { rows: alteredSettingsRows };
|
|
215
|
-
}
|
|
216
|
-
// Database sizes (A004)
|
|
217
|
-
if (sql.includes("pg_database") && sql.includes("pg_database_size")) {
|
|
218
|
-
return { rows: databaseSizesRows };
|
|
219
|
-
}
|
|
220
|
-
// Cluster stats (A004)
|
|
221
|
-
if (sql.includes("pg_stat_database") && sql.includes("xact_commit")) {
|
|
222
|
-
return { rows: clusterStatsRows };
|
|
223
|
-
}
|
|
224
|
-
// Connection states (A004)
|
|
225
|
-
if (sql.includes("pg_stat_activity") && sql.includes("state")) {
|
|
226
|
-
return { rows: connectionStatesRows };
|
|
227
|
-
}
|
|
228
|
-
// Uptime info (A004)
|
|
229
|
-
if (sql.includes("pg_postmaster_start_time")) {
|
|
230
|
-
return { rows: uptimeRows };
|
|
231
|
-
}
|
|
232
|
-
throw new Error(`Unexpected query: ${sql}`);
|
|
233
|
-
},
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
test("getPostgresVersion extracts version info from mock client", async () => {
|
|
238
|
-
const mockClient = createMockClient([
|
|
239
|
-
{ name: "server_version", setting: "16.3" },
|
|
240
|
-
{ name: "server_version_num", setting: "160003" },
|
|
241
|
-
], []);
|
|
242
|
-
|
|
243
|
-
const version = await checkup.getPostgresVersion(mockClient);
|
|
244
|
-
assert.equal(version.version, "16.3");
|
|
245
|
-
assert.equal(version.server_version_num, "160003");
|
|
246
|
-
assert.equal(version.server_major_ver, "16");
|
|
247
|
-
assert.equal(version.server_minor_ver, "3");
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
test("getSettings transforms rows to keyed object", async () => {
|
|
251
|
-
const mockClient = createMockClient([], [
|
|
252
|
-
{
|
|
253
|
-
name: "shared_buffers",
|
|
254
|
-
setting: "16384",
|
|
255
|
-
unit: "8kB",
|
|
256
|
-
category: "Resource Usage / Memory",
|
|
257
|
-
context: "postmaster",
|
|
258
|
-
vartype: "integer",
|
|
259
|
-
pretty_value: "128 MB",
|
|
260
|
-
},
|
|
261
|
-
{
|
|
262
|
-
name: "work_mem",
|
|
263
|
-
setting: "4096",
|
|
264
|
-
unit: "kB",
|
|
265
|
-
category: "Resource Usage / Memory",
|
|
266
|
-
context: "user",
|
|
267
|
-
vartype: "integer",
|
|
268
|
-
pretty_value: "4 MB",
|
|
269
|
-
},
|
|
270
|
-
]);
|
|
271
|
-
|
|
272
|
-
const settings = await checkup.getSettings(mockClient);
|
|
273
|
-
assert.ok("shared_buffers" in settings);
|
|
274
|
-
assert.ok("work_mem" in settings);
|
|
275
|
-
assert.equal(settings.shared_buffers.setting, "16384");
|
|
276
|
-
assert.equal(settings.shared_buffers.unit, "8kB");
|
|
277
|
-
assert.equal(settings.work_mem.pretty_value, "4 MB");
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test("generateA002 creates report with version data", async () => {
|
|
281
|
-
const mockClient = createMockClient([
|
|
282
|
-
{ name: "server_version", setting: "16.3" },
|
|
283
|
-
{ name: "server_version_num", setting: "160003" },
|
|
284
|
-
], []);
|
|
285
|
-
|
|
286
|
-
const report = await checkup.generateA002(mockClient, "test-node");
|
|
287
|
-
assert.equal(report.checkId, "A002");
|
|
288
|
-
assert.equal(report.checkTitle, "Postgres major version");
|
|
289
|
-
assert.equal(report.nodes.primary, "test-node");
|
|
290
|
-
assert.ok("test-node" in report.results);
|
|
291
|
-
assert.ok("version" in report.results["test-node"].data);
|
|
292
|
-
assert.equal(report.results["test-node"].data.version.version, "16.3");
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
test("generateA003 creates report with settings and version", async () => {
|
|
296
|
-
const mockClient = createMockClient(
|
|
297
|
-
[
|
|
298
|
-
{ name: "server_version", setting: "16.3" },
|
|
299
|
-
{ name: "server_version_num", setting: "160003" },
|
|
300
|
-
],
|
|
301
|
-
[
|
|
302
|
-
{
|
|
303
|
-
name: "shared_buffers",
|
|
304
|
-
setting: "16384",
|
|
305
|
-
unit: "8kB",
|
|
306
|
-
category: "Resource Usage / Memory",
|
|
307
|
-
context: "postmaster",
|
|
308
|
-
vartype: "integer",
|
|
309
|
-
pretty_value: "128 MB",
|
|
310
|
-
},
|
|
311
|
-
]
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
const report = await checkup.generateA003(mockClient, "test-node");
|
|
315
|
-
assert.equal(report.checkId, "A003");
|
|
316
|
-
assert.equal(report.checkTitle, "Postgres settings");
|
|
317
|
-
assert.ok("test-node" in report.results);
|
|
318
|
-
assert.ok("shared_buffers" in report.results["test-node"].data);
|
|
319
|
-
assert.ok(report.results["test-node"].postgres_version);
|
|
320
|
-
assert.equal(report.results["test-node"].postgres_version.version, "16.3");
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test("generateA013 creates report with minor version data", async () => {
|
|
324
|
-
const mockClient = createMockClient([
|
|
325
|
-
{ name: "server_version", setting: "16.3" },
|
|
326
|
-
{ name: "server_version_num", setting: "160003" },
|
|
327
|
-
], []);
|
|
328
|
-
|
|
329
|
-
const report = await checkup.generateA013(mockClient, "test-node");
|
|
330
|
-
assert.equal(report.checkId, "A013");
|
|
331
|
-
assert.equal(report.checkTitle, "Postgres minor version");
|
|
332
|
-
assert.equal(report.nodes.primary, "test-node");
|
|
333
|
-
assert.ok("test-node" in report.results);
|
|
334
|
-
assert.ok("version" in report.results["test-node"].data);
|
|
335
|
-
assert.equal(report.results["test-node"].data.version.server_minor_ver, "3");
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
test("generateAllReports returns reports for all checks", async () => {
|
|
339
|
-
const mockClient = createMockClient(
|
|
340
|
-
[
|
|
341
|
-
{ name: "server_version", setting: "16.3" },
|
|
342
|
-
{ name: "server_version_num", setting: "160003" },
|
|
343
|
-
],
|
|
344
|
-
[
|
|
345
|
-
{
|
|
346
|
-
name: "shared_buffers",
|
|
347
|
-
setting: "16384",
|
|
348
|
-
unit: "8kB",
|
|
349
|
-
category: "Resource Usage / Memory",
|
|
350
|
-
context: "postmaster",
|
|
351
|
-
vartype: "integer",
|
|
352
|
-
pretty_value: "128 MB",
|
|
353
|
-
},
|
|
354
|
-
],
|
|
355
|
-
{
|
|
356
|
-
alteredSettingsRows: [
|
|
357
|
-
{ name: "shared_buffers", setting: "16384", unit: "8kB", category: "Resource Usage / Memory", pretty_value: "128 MB" },
|
|
358
|
-
],
|
|
359
|
-
databaseSizesRows: [{ datname: "postgres", size_bytes: "1073741824" }],
|
|
360
|
-
clusterStatsRows: [{ total_connections: 5, total_commits: 100, total_rollbacks: 1, blocks_read: 1000, blocks_hit: 9000, tuples_returned: 500, tuples_fetched: 400, tuples_inserted: 50, tuples_updated: 30, tuples_deleted: 10, total_deadlocks: 0, temp_files_created: 0, temp_bytes_written: 0 }],
|
|
361
|
-
connectionStatesRows: [{ state: "active", count: 2 }, { state: "idle", count: 3 }],
|
|
362
|
-
uptimeRows: [{ start_time: new Date("2024-01-01T00:00:00Z"), uptime: "10 days" }],
|
|
363
|
-
}
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
const reports = await checkup.generateAllReports(mockClient, "test-node");
|
|
367
|
-
assert.ok("A002" in reports);
|
|
368
|
-
assert.ok("A003" in reports);
|
|
369
|
-
assert.ok("A004" in reports);
|
|
370
|
-
assert.ok("A007" in reports);
|
|
371
|
-
assert.ok("A013" in reports);
|
|
372
|
-
assert.equal(reports.A002.checkId, "A002");
|
|
373
|
-
assert.equal(reports.A003.checkId, "A003");
|
|
374
|
-
assert.equal(reports.A004.checkId, "A004");
|
|
375
|
-
assert.equal(reports.A007.checkId, "A007");
|
|
376
|
-
assert.equal(reports.A013.checkId, "A013");
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
// Tests for A007 (Altered settings)
|
|
380
|
-
test("getAlteredSettings returns non-default settings", async () => {
|
|
381
|
-
const mockClient = createMockClient([], [], {
|
|
382
|
-
alteredSettingsRows: [
|
|
383
|
-
{ name: "shared_buffers", setting: "256MB", unit: "", category: "Resource Usage / Memory", pretty_value: "256 MB" },
|
|
384
|
-
{ name: "work_mem", setting: "64MB", unit: "", category: "Resource Usage / Memory", pretty_value: "64 MB" },
|
|
385
|
-
],
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
const settings = await checkup.getAlteredSettings(mockClient);
|
|
389
|
-
assert.ok("shared_buffers" in settings);
|
|
390
|
-
assert.ok("work_mem" in settings);
|
|
391
|
-
assert.equal(settings.shared_buffers.value, "256MB");
|
|
392
|
-
assert.equal(settings.work_mem.pretty_value, "64 MB");
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
test("generateA007 creates report with altered settings", async () => {
|
|
396
|
-
const mockClient = createMockClient(
|
|
397
|
-
[
|
|
398
|
-
{ name: "server_version", setting: "16.3" },
|
|
399
|
-
{ name: "server_version_num", setting: "160003" },
|
|
400
|
-
],
|
|
401
|
-
[],
|
|
402
|
-
{
|
|
403
|
-
alteredSettingsRows: [
|
|
404
|
-
{ name: "max_connections", setting: "200", unit: "", category: "Connections and Authentication", pretty_value: "200" },
|
|
405
|
-
],
|
|
406
|
-
}
|
|
407
|
-
);
|
|
408
|
-
|
|
409
|
-
const report = await checkup.generateA007(mockClient, "test-node");
|
|
410
|
-
assert.equal(report.checkId, "A007");
|
|
411
|
-
assert.equal(report.checkTitle, "Altered settings");
|
|
412
|
-
assert.equal(report.nodes.primary, "test-node");
|
|
413
|
-
assert.ok("test-node" in report.results);
|
|
414
|
-
assert.ok("max_connections" in report.results["test-node"].data);
|
|
415
|
-
assert.equal(report.results["test-node"].data.max_connections.value, "200");
|
|
416
|
-
assert.ok(report.results["test-node"].postgres_version);
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
// Tests for A004 (Cluster information)
|
|
420
|
-
test("getDatabaseSizes returns database sizes", async () => {
|
|
421
|
-
const mockClient = createMockClient([], [], {
|
|
422
|
-
databaseSizesRows: [
|
|
423
|
-
{ datname: "postgres", size_bytes: "1073741824" },
|
|
424
|
-
{ datname: "mydb", size_bytes: "536870912" },
|
|
425
|
-
],
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
const sizes = await checkup.getDatabaseSizes(mockClient);
|
|
429
|
-
assert.ok("postgres" in sizes);
|
|
430
|
-
assert.ok("mydb" in sizes);
|
|
431
|
-
assert.equal(sizes.postgres, 1073741824);
|
|
432
|
-
assert.equal(sizes.mydb, 536870912);
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
test("getClusterInfo returns cluster metrics", async () => {
|
|
436
|
-
const mockClient = createMockClient([], [], {
|
|
437
|
-
clusterStatsRows: [{
|
|
438
|
-
total_connections: 10,
|
|
439
|
-
total_commits: 1000,
|
|
440
|
-
total_rollbacks: 5,
|
|
441
|
-
blocks_read: 500,
|
|
442
|
-
blocks_hit: 9500,
|
|
443
|
-
tuples_returned: 5000,
|
|
444
|
-
tuples_fetched: 4000,
|
|
445
|
-
tuples_inserted: 100,
|
|
446
|
-
tuples_updated: 50,
|
|
447
|
-
tuples_deleted: 25,
|
|
448
|
-
total_deadlocks: 0,
|
|
449
|
-
temp_files_created: 2,
|
|
450
|
-
temp_bytes_written: 1048576,
|
|
451
|
-
}],
|
|
452
|
-
connectionStatesRows: [
|
|
453
|
-
{ state: "active", count: 3 },
|
|
454
|
-
{ state: "idle", count: 7 },
|
|
455
|
-
],
|
|
456
|
-
uptimeRows: [{
|
|
457
|
-
start_time: new Date("2024-01-01T00:00:00Z"),
|
|
458
|
-
uptime: "30 days",
|
|
459
|
-
}],
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
const info = await checkup.getClusterInfo(mockClient);
|
|
463
|
-
assert.ok("total_connections" in info);
|
|
464
|
-
assert.ok("cache_hit_ratio" in info);
|
|
465
|
-
assert.ok("connections_active" in info);
|
|
466
|
-
assert.ok("connections_idle" in info);
|
|
467
|
-
assert.ok("start_time" in info);
|
|
468
|
-
assert.equal(info.total_connections.value, "10");
|
|
469
|
-
assert.equal(info.cache_hit_ratio.value, "95.00");
|
|
470
|
-
assert.equal(info.connections_active.value, "3");
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
test("generateA004 creates report with cluster info and database sizes", async () => {
|
|
474
|
-
const mockClient = createMockClient(
|
|
475
|
-
[
|
|
476
|
-
{ name: "server_version", setting: "16.3" },
|
|
477
|
-
{ name: "server_version_num", setting: "160003" },
|
|
478
|
-
],
|
|
479
|
-
[],
|
|
480
|
-
{
|
|
481
|
-
databaseSizesRows: [
|
|
482
|
-
{ datname: "postgres", size_bytes: "1073741824" },
|
|
483
|
-
],
|
|
484
|
-
clusterStatsRows: [{
|
|
485
|
-
total_connections: 5,
|
|
486
|
-
total_commits: 100,
|
|
487
|
-
total_rollbacks: 1,
|
|
488
|
-
blocks_read: 100,
|
|
489
|
-
blocks_hit: 900,
|
|
490
|
-
tuples_returned: 500,
|
|
491
|
-
tuples_fetched: 400,
|
|
492
|
-
tuples_inserted: 50,
|
|
493
|
-
tuples_updated: 30,
|
|
494
|
-
tuples_deleted: 10,
|
|
495
|
-
total_deadlocks: 0,
|
|
496
|
-
temp_files_created: 0,
|
|
497
|
-
temp_bytes_written: 0,
|
|
498
|
-
}],
|
|
499
|
-
connectionStatesRows: [{ state: "active", count: 2 }],
|
|
500
|
-
uptimeRows: [{ start_time: new Date("2024-01-01T00:00:00Z"), uptime: "10 days" }],
|
|
501
|
-
}
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
const report = await checkup.generateA004(mockClient, "test-node");
|
|
505
|
-
assert.equal(report.checkId, "A004");
|
|
506
|
-
assert.equal(report.checkTitle, "Cluster information");
|
|
507
|
-
assert.equal(report.nodes.primary, "test-node");
|
|
508
|
-
assert.ok("test-node" in report.results);
|
|
509
|
-
|
|
510
|
-
const data = report.results["test-node"].data;
|
|
511
|
-
assert.ok("general_info" in data);
|
|
512
|
-
assert.ok("database_sizes" in data);
|
|
513
|
-
assert.ok("total_connections" in data.general_info);
|
|
514
|
-
assert.ok("postgres" in data.database_sizes);
|
|
515
|
-
assert.equal(data.database_sizes.postgres, 1073741824);
|
|
516
|
-
assert.ok(report.results["test-node"].postgres_version);
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
// CLI tests
|
|
520
|
-
test("cli: checkup command exists and shows help", () => {
|
|
521
|
-
const r = runCli(["checkup", "--help"]);
|
|
522
|
-
assert.equal(r.status, 0, r.stderr || r.stdout);
|
|
523
|
-
assert.match(r.stdout, /express mode/i);
|
|
524
|
-
assert.match(r.stdout, /--check-id/);
|
|
525
|
-
assert.match(r.stdout, /--node-name/);
|
|
526
|
-
assert.match(r.stdout, /--output/);
|
|
527
|
-
assert.match(r.stdout, /--upload/);
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
test("cli: checkup --help shows available check IDs", () => {
|
|
531
|
-
const r = runCli(["checkup", "--help"]);
|
|
532
|
-
assert.equal(r.status, 0, r.stderr || r.stdout);
|
|
533
|
-
assert.match(r.stdout, /A002/);
|
|
534
|
-
assert.match(r.stdout, /A003/);
|
|
535
|
-
assert.match(r.stdout, /A004/);
|
|
536
|
-
assert.match(r.stdout, /A007/);
|
|
537
|
-
assert.match(r.stdout, /A013/);
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
test("cli: checkup without connection shows help", () => {
|
|
541
|
-
const r = runCli(["checkup"]);
|
|
542
|
-
assert.notEqual(r.status, 0);
|
|
543
|
-
// Should show full help (options + examples), like `checkup --help`
|
|
544
|
-
assert.match(r.stdout, /generate health check reports/i);
|
|
545
|
-
assert.match(r.stdout, /--check-id/);
|
|
546
|
-
assert.match(r.stdout, /available checks/i);
|
|
547
|
-
assert.match(r.stdout, /A002/);
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
test("cli: set-default-project writes config defaultProject", () => {
|
|
551
|
-
const fs = require("node:fs");
|
|
552
|
-
const path = require("node:path");
|
|
553
|
-
|
|
554
|
-
const tmpRoot = path.resolve(__dirname, ".tmp-config");
|
|
555
|
-
const xdgHome = path.join(tmpRoot, "xdg");
|
|
556
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
557
|
-
fs.mkdirSync(xdgHome, { recursive: true });
|
|
558
|
-
|
|
559
|
-
const r = runCli(["set-default-project", "cli_project"], { XDG_CONFIG_HOME: xdgHome });
|
|
560
|
-
assert.equal(r.status, 0, r.stderr || r.stdout);
|
|
561
|
-
|
|
562
|
-
const cfgPath = path.join(xdgHome, "postgresai", "config.json");
|
|
563
|
-
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
564
|
-
assert.equal(cfg.defaultProject, "cli_project");
|
|
565
|
-
|
|
566
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
test("cli: checkup --output validates/creates output dir before connecting", () => {
|
|
570
|
-
const fs = require("node:fs");
|
|
571
|
-
const path = require("node:path");
|
|
572
|
-
|
|
573
|
-
const tmpRoot = path.resolve(__dirname, ".tmp-output");
|
|
574
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
575
|
-
fs.mkdirSync(tmpRoot, { recursive: true });
|
|
576
|
-
|
|
577
|
-
// Make parent dir non-writable and attempt to create a child output dir inside it.
|
|
578
|
-
const locked = path.join(tmpRoot, "locked");
|
|
579
|
-
fs.mkdirSync(locked, { recursive: true });
|
|
580
|
-
fs.chmodSync(locked, 0o555);
|
|
581
|
-
|
|
582
|
-
const out = path.join(locked, "reports");
|
|
583
|
-
const r = runCli(["checkup", "postgresql://user:pass@127.0.0.1:1/db", "--output", out]);
|
|
584
|
-
assert.notEqual(r.status, 0);
|
|
585
|
-
assert.match(r.stderr, /failed to create output directory/i);
|
|
586
|
-
|
|
587
|
-
// Cleanup: restore perms so we can delete it.
|
|
588
|
-
fs.chmodSync(locked, 0o755);
|
|
589
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
test("cli: checkup --upload requires API key (fast-fail)", () => {
|
|
593
|
-
const r = runCli(["checkup", "postgresql://user:pass@127.0.0.1:1/db", "--upload", "--project", "p"], {
|
|
594
|
-
PGAI_API_KEY: "",
|
|
595
|
-
XDG_CONFIG_HOME: "/nonexistent",
|
|
596
|
-
});
|
|
597
|
-
assert.notEqual(r.status, 0);
|
|
598
|
-
assert.match(r.stderr, /api key is required/i);
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
test("cli: checkup --upload uses defaultProject when --project omitted", () => {
|
|
602
|
-
const fs = require("node:fs");
|
|
603
|
-
const path = require("node:path");
|
|
604
|
-
|
|
605
|
-
const tmpRoot = path.resolve(__dirname, ".tmp-config2");
|
|
606
|
-
const xdgHome = path.join(tmpRoot, "xdg");
|
|
607
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
608
|
-
fs.mkdirSync(path.join(xdgHome, "postgresai"), { recursive: true });
|
|
609
|
-
|
|
610
|
-
// Provide API key + defaultProject so preflight passes without --project.
|
|
611
|
-
fs.writeFileSync(
|
|
612
|
-
path.join(xdgHome, "postgresai", "config.json"),
|
|
613
|
-
JSON.stringify({ apiKey: "dummy", defaultProject: "p" }, null, 2) + "\n"
|
|
614
|
-
);
|
|
615
|
-
|
|
616
|
-
// It will fail later on connection (port 1) — that's fine; we only assert it didn't
|
|
617
|
-
// fail due to missing project/API key.
|
|
618
|
-
const r = runCli(["checkup", "postgresql://user:pass@127.0.0.1:1/db", "--upload"], { XDG_CONFIG_HOME: xdgHome });
|
|
619
|
-
assert.notEqual(r.status, 0);
|
|
620
|
-
assert.doesNotMatch(r.stderr, /--project is required/i);
|
|
621
|
-
assert.doesNotMatch(r.stderr, /api key is required/i);
|
|
622
|
-
|
|
623
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
test("formatRpcErrorForDisplay formats details/hint nicely", () => {
|
|
627
|
-
const api = require("../dist/lib/checkup-api.js");
|
|
628
|
-
const err = new api.RpcError({
|
|
629
|
-
rpcName: "checkup_report_file_post",
|
|
630
|
-
statusCode: 402,
|
|
631
|
-
payloadText: JSON.stringify({
|
|
632
|
-
hint: "Start an express checkup subscription for the organization or contact support.",
|
|
633
|
-
details: "Checkup report uploads require an active checkup subscription",
|
|
634
|
-
}),
|
|
635
|
-
payloadJson: {
|
|
636
|
-
hint: "Start an express checkup subscription for the organization or contact support.",
|
|
637
|
-
details: "Checkup report uploads require an active checkup subscription.",
|
|
638
|
-
},
|
|
639
|
-
});
|
|
640
|
-
const lines = api.formatRpcErrorForDisplay(err);
|
|
641
|
-
const text = lines.join("\n");
|
|
642
|
-
assert.match(text, /RPC checkup_report_file_post failed: HTTP 402/);
|
|
643
|
-
assert.match(text, /Details:/);
|
|
644
|
-
assert.match(text, /Hint:/);
|
|
645
|
-
});
|