Skip to content

Commit 9931218

Browse files
committed
test(checkup): add comprehensive tests for PostgreSQL versions 13-18
Add tests to verify checkup command works correctly across all current PostgreSQL versions (PG13-PG18): - Version parsing tests for PG13, PG17, PG18 (complements existing tests) - Report generator tests for all checks (A002, A003, A004, A007, A013, H001, H002, H004) across all 6 PostgreSQL versions - Version-aware SQL query selection tests to ensure metrics loader returns valid SQL for all PG versions - METRIC_NAMES mapping verification
1 parent 2042d70 commit 9931218

1 file changed

Lines changed: 307 additions & 0 deletions

File tree

cli/test/checkup.test.ts

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { resolve } from "path";
44
// Import from source directly since we're using Bun
55
import * as checkup from "../lib/checkup";
66
import * as api from "../lib/checkup-api";
7+
import * as metricsLoader from "../lib/metrics-loader";
78
import { createMockClient } from "./test-utils";
89

910

@@ -40,6 +41,24 @@ describe("parseVersionNum", () => {
4041
expect(result.minor).toBe("12");
4142
});
4243

44+
test("parses PG 13.16 version number", () => {
45+
const result = checkup.parseVersionNum("130016");
46+
expect(result.major).toBe("13");
47+
expect(result.minor).toBe("16");
48+
});
49+
50+
test("parses PG 17.2 version number", () => {
51+
const result = checkup.parseVersionNum("170002");
52+
expect(result.major).toBe("17");
53+
expect(result.minor).toBe("2");
54+
});
55+
56+
test("parses PG 18.0 version number", () => {
57+
const result = checkup.parseVersionNum("180000");
58+
expect(result.major).toBe("18");
59+
expect(result.minor).toBe("0");
60+
});
61+
4362
test("handles empty string", () => {
4463
const result = checkup.parseVersionNum("");
4564
expect(result.major).toBe("");
@@ -888,4 +907,292 @@ describe("checkup-api", () => {
888907
});
889908
});
890909

910+
// PostgreSQL version compatibility tests (PG13-PG18)
911+
describe("PostgreSQL version compatibility (PG13-PG18)", () => {
912+
// Helper to create version-specific mock data
913+
const createVersionMockData = (major: number, minor: number) => ({
914+
versionRows: [
915+
{ name: "server_version", setting: `${major}.${minor}` },
916+
{ name: "server_version_num", setting: `${major}${String(minor).padStart(4, "0")}` },
917+
],
918+
settingsRows: [
919+
{
920+
tag_setting_name: "shared_buffers",
921+
tag_setting_value: "16384",
922+
tag_unit: "8kB",
923+
tag_category: "Resource Usage / Memory",
924+
tag_vartype: "integer",
925+
is_default: 0,
926+
setting_normalized: "134217728",
927+
unit_normalized: "bytes",
928+
},
929+
],
930+
databaseSizesRows: [{ datname: "postgres", size_bytes: "1073741824" }],
931+
dbStatsRows: [{
932+
numbackends: 5,
933+
xact_commit: 100,
934+
xact_rollback: 1,
935+
blks_read: 100,
936+
blks_hit: 900,
937+
tup_returned: 500,
938+
tup_fetched: 400,
939+
tup_inserted: 50,
940+
tup_updated: 30,
941+
tup_deleted: 10,
942+
deadlocks: 0,
943+
temp_files: 0,
944+
temp_bytes: 0,
945+
postmaster_uptime_s: 864000,
946+
}],
947+
connectionStatesRows: [{ state: "active", count: 2 }, { state: "idle", count: 3 }],
948+
uptimeRows: [{ start_time: new Date("2024-01-01T00:00:00Z"), uptime: "10 days" }],
949+
invalidIndexesRows: [],
950+
unusedIndexesRows: [],
951+
redundantIndexesRows: [],
952+
});
953+
954+
// Test matrix for all supported PostgreSQL versions
955+
const pgVersions = [
956+
{ major: 13, minor: 16, versionNum: "130016" },
957+
{ major: 14, minor: 12, versionNum: "140012" },
958+
{ major: 15, minor: 7, versionNum: "150007" },
959+
{ major: 16, minor: 3, versionNum: "160003" },
960+
{ major: 17, minor: 2, versionNum: "170002" },
961+
{ major: 18, minor: 0, versionNum: "180000" },
962+
];
963+
964+
describe("getPostgresVersion extracts correct version for each PG version", () => {
965+
for (const { major, minor, versionNum } of pgVersions) {
966+
test(`PG ${major}.${minor}`, async () => {
967+
const mockClient = createMockClient(createVersionMockData(major, minor));
968+
const version = await checkup.getPostgresVersion(mockClient as any);
969+
970+
expect(version.version).toBe(`${major}.${minor}`);
971+
expect(version.server_version_num).toBe(versionNum);
972+
expect(version.server_major_ver).toBe(String(major));
973+
expect(version.server_minor_ver).toBe(String(minor));
974+
});
975+
}
976+
});
977+
978+
describe("generateA002 (major version) works for each PG version", () => {
979+
for (const { major, minor } of pgVersions) {
980+
test(`PG ${major}.${minor}`, async () => {
981+
const mockClient = createMockClient(createVersionMockData(major, minor));
982+
const report = await checkup.generateA002(mockClient as any, "test-node");
983+
984+
expect(report.checkId).toBe("A002");
985+
expect(report.checkTitle).toBe("Postgres major version");
986+
expect(report.results["test-node"].data.version.version).toBe(`${major}.${minor}`);
987+
expect(report.results["test-node"].data.version.server_major_ver).toBe(String(major));
988+
expect(report.results["test-node"].data.version.server_minor_ver).toBe(String(minor));
989+
});
990+
}
991+
});
992+
993+
describe("generateA013 (minor version) works for each PG version", () => {
994+
for (const { major, minor } of pgVersions) {
995+
test(`PG ${major}.${minor}`, async () => {
996+
const mockClient = createMockClient(createVersionMockData(major, minor));
997+
const report = await checkup.generateA013(mockClient as any, "test-node");
998+
999+
expect(report.checkId).toBe("A013");
1000+
expect(report.checkTitle).toBe("Postgres minor version");
1001+
expect(report.results["test-node"].data.version.server_minor_ver).toBe(String(minor));
1002+
expect(report.results["test-node"].data.version.version).toBe(`${major}.${minor}`);
1003+
});
1004+
}
1005+
});
1006+
1007+
describe("generateA003 (settings) works for each PG version", () => {
1008+
for (const { major, minor } of pgVersions) {
1009+
test(`PG ${major}.${minor}`, async () => {
1010+
const mockClient = createMockClient(createVersionMockData(major, minor));
1011+
const report = await checkup.generateA003(mockClient as any, "test-node");
1012+
1013+
expect(report.checkId).toBe("A003");
1014+
expect(report.checkTitle).toBe("Postgres settings");
1015+
expect("shared_buffers" in report.results["test-node"].data).toBe(true);
1016+
expect(report.results["test-node"].postgres_version?.version).toBe(`${major}.${minor}`);
1017+
expect(report.results["test-node"].postgres_version?.server_major_ver).toBe(String(major));
1018+
});
1019+
}
1020+
});
1021+
1022+
describe("generateA007 (altered settings) works for each PG version", () => {
1023+
for (const { major, minor } of pgVersions) {
1024+
test(`PG ${major}.${minor}`, async () => {
1025+
const mockClient = createMockClient(createVersionMockData(major, minor));
1026+
const report = await checkup.generateA007(mockClient as any, "test-node");
1027+
1028+
expect(report.checkId).toBe("A007");
1029+
expect(report.checkTitle).toBe("Altered settings");
1030+
expect("shared_buffers" in report.results["test-node"].data).toBe(true);
1031+
expect(report.results["test-node"].postgres_version?.version).toBe(`${major}.${minor}`);
1032+
});
1033+
}
1034+
});
1035+
1036+
describe("generateA004 (cluster info) works for each PG version", () => {
1037+
for (const { major, minor } of pgVersions) {
1038+
test(`PG ${major}.${minor}`, async () => {
1039+
const mockClient = createMockClient(createVersionMockData(major, minor));
1040+
const report = await checkup.generateA004(mockClient as any, "test-node");
1041+
1042+
expect(report.checkId).toBe("A004");
1043+
expect(report.checkTitle).toBe("Cluster information");
1044+
expect("general_info" in report.results["test-node"].data).toBe(true);
1045+
expect("database_sizes" in report.results["test-node"].data).toBe(true);
1046+
expect(report.results["test-node"].postgres_version?.version).toBe(`${major}.${minor}`);
1047+
});
1048+
}
1049+
});
1050+
1051+
describe("generateH001 (invalid indexes) works for each PG version", () => {
1052+
for (const { major, minor } of pgVersions) {
1053+
test(`PG ${major}.${minor}`, async () => {
1054+
const mockClient = createMockClient(createVersionMockData(major, minor));
1055+
const report = await checkup.generateH001(mockClient as any, "test-node");
1056+
1057+
expect(report.checkId).toBe("H001");
1058+
expect(report.checkTitle).toBe("Invalid indexes");
1059+
expect("test-node" in report.results).toBe(true);
1060+
expect(report.results["test-node"].postgres_version?.version).toBe(`${major}.${minor}`);
1061+
});
1062+
}
1063+
});
1064+
1065+
describe("generateH002 (unused indexes) works for each PG version", () => {
1066+
for (const { major, minor } of pgVersions) {
1067+
test(`PG ${major}.${minor}`, async () => {
1068+
const mockClient = createMockClient(createVersionMockData(major, minor));
1069+
const report = await checkup.generateH002(mockClient as any, "test-node");
1070+
1071+
expect(report.checkId).toBe("H002");
1072+
expect(report.checkTitle).toBe("Unused indexes");
1073+
expect("test-node" in report.results).toBe(true);
1074+
expect(report.results["test-node"].postgres_version?.version).toBe(`${major}.${minor}`);
1075+
});
1076+
}
1077+
});
1078+
1079+
describe("generateH004 (redundant indexes) works for each PG version", () => {
1080+
for (const { major, minor } of pgVersions) {
1081+
test(`PG ${major}.${minor}`, async () => {
1082+
const mockClient = createMockClient(createVersionMockData(major, minor));
1083+
const report = await checkup.generateH004(mockClient as any, "test-node");
1084+
1085+
expect(report.checkId).toBe("H004");
1086+
expect(report.checkTitle).toBe("Redundant indexes");
1087+
expect("test-node" in report.results).toBe(true);
1088+
expect(report.results["test-node"].postgres_version?.version).toBe(`${major}.${minor}`);
1089+
});
1090+
}
1091+
});
1092+
1093+
describe("generateAllReports works for each PG version", () => {
1094+
for (const { major, minor } of pgVersions) {
1095+
test(`PG ${major}.${minor}`, async () => {
1096+
const mockClient = createMockClient(createVersionMockData(major, minor));
1097+
const reports = await checkup.generateAllReports(mockClient as any, "test-node");
1098+
1099+
// Verify all expected checks are generated
1100+
const expectedChecks = ["A002", "A003", "A004", "A007", "A013", "H001", "H002", "H004"];
1101+
for (const checkId of expectedChecks) {
1102+
expect(checkId in reports).toBe(true);
1103+
expect(reports[checkId].checkId).toBe(checkId);
1104+
}
1105+
1106+
// Verify postgres version is correctly set in A002 report (via data.version)
1107+
expect(reports.A002.results["test-node"].data.version.version).toBe(`${major}.${minor}`);
1108+
expect(reports.A002.results["test-node"].data.version.server_major_ver).toBe(String(major));
1109+
1110+
// Verify postgres_version is set in reports that include it (A003, A004, H001, etc.)
1111+
expect(reports.A003.results["test-node"].postgres_version?.version).toBe(`${major}.${minor}`);
1112+
expect(reports.A004.results["test-node"].postgres_version?.version).toBe(`${major}.${minor}`);
1113+
});
1114+
}
1115+
});
1116+
});
1117+
1118+
// Tests for version-aware SQL query selection
1119+
describe("Version-aware SQL query selection (PG13-PG18)", () => {
1120+
const pgVersions = [13, 14, 15, 16, 17, 18];
1121+
1122+
// Core metrics that should be available for all versions
1123+
const coreMetrics = [
1124+
"settings",
1125+
"db_stats",
1126+
"db_size",
1127+
"stats_reset",
1128+
"pg_invalid_indexes",
1129+
"unused_indexes",
1130+
"redundant_indexes",
1131+
];
1132+
1133+
describe("getMetricSql returns SQL for all PG versions", () => {
1134+
for (const pgVersion of pgVersions) {
1135+
for (const metric of coreMetrics) {
1136+
test(`${metric} for PG${pgVersion}`, () => {
1137+
const sql = metricsLoader.getMetricSql(metric, pgVersion);
1138+
expect(typeof sql).toBe("string");
1139+
expect(sql.length).toBeGreaterThan(0);
1140+
// Verify it's actually SQL
1141+
expect(sql.toLowerCase()).toMatch(/select/);
1142+
});
1143+
}
1144+
}
1145+
});
1146+
1147+
describe("getMetricSql selects appropriate version for each PG major version", () => {
1148+
for (const pgVersion of pgVersions) {
1149+
test(`PG${pgVersion} gets compatible SQL version`, () => {
1150+
// Settings metric should return SQL for all supported versions
1151+
const sql = metricsLoader.getMetricSql("settings", pgVersion);
1152+
expect(sql).toBeTruthy();
1153+
// SQL should be valid (not throw an error)
1154+
expect(() => metricsLoader.getMetricSql("settings", pgVersion)).not.toThrow();
1155+
});
1156+
}
1157+
});
1158+
1159+
describe("getMetricDefinition returns metadata for all metrics", () => {
1160+
for (const metric of coreMetrics) {
1161+
test(`${metric} has definition`, () => {
1162+
const definition = metricsLoader.getMetricDefinition(metric);
1163+
expect(definition).toBeTruthy();
1164+
expect(definition?.sqls).toBeTruthy();
1165+
expect(typeof definition?.sqls).toBe("object");
1166+
});
1167+
}
1168+
});
1169+
1170+
test("listMetricNames returns all expected metrics", () => {
1171+
const names = metricsLoader.listMetricNames();
1172+
expect(Array.isArray(names)).toBe(true);
1173+
// Should include core metrics
1174+
for (const metric of coreMetrics) {
1175+
expect(names).toContain(metric);
1176+
}
1177+
});
1178+
1179+
describe("METRIC_NAMES maps check IDs correctly", () => {
1180+
test("H001 maps to pg_invalid_indexes", () => {
1181+
expect(metricsLoader.METRIC_NAMES.H001).toBe("pg_invalid_indexes");
1182+
});
1183+
1184+
test("H002 maps to unused_indexes", () => {
1185+
expect(metricsLoader.METRIC_NAMES.H002).toBe("unused_indexes");
1186+
});
1187+
1188+
test("H004 maps to redundant_indexes", () => {
1189+
expect(metricsLoader.METRIC_NAMES.H004).toBe("redundant_indexes");
1190+
});
1191+
1192+
test("settings metric exists", () => {
1193+
expect(metricsLoader.METRIC_NAMES.settings).toBe("settings");
1194+
});
1195+
});
1196+
});
1197+
8911198

0 commit comments

Comments
 (0)