From a034436769026e4418faa06cecac816132e43e3e Mon Sep 17 00:00:00 2001 From: leonace924 Date: Tue, 6 Jan 2026 01:54:38 -0500 Subject: [PATCH] fix: apply both updates to mssql server monitor and mssql test --- server/monitor-types/mssql.js | 95 +++-- test/backend-test/monitors/test-mssql.js | 460 ++++++++++++----------- 2 files changed, 298 insertions(+), 257 deletions(-) diff --git a/server/monitor-types/mssql.js b/server/monitor-types/mssql.js index 40385351f..c87714b8f 100644 --- a/server/monitor-types/mssql.js +++ b/server/monitor-types/mssql.js @@ -21,53 +21,88 @@ class MssqlMonitorType extends MonitorType { * @inheritdoc */ async check(monitor, heartbeat, _server) { - let startTime = dayjs().valueOf(); - let query = monitor.databaseQuery; // No query provided by user, use SELECT 1 if (!query || (typeof query === "string" && query.trim() === "")) { query = "SELECT 1"; } - let result; - try { - result = await this.mssqlQuery( - monitor.databaseConnectionString, - query - ); - } catch (error) { - log.error("sqlserver", "Database query failed:", error.message); - throw new Error( - `Database connection/query failed: ${error.message}` - ); - } finally { - heartbeat.ping = dayjs().valueOf() - startTime; - } - const conditions = ConditionExpressionGroup.fromMonitor(monitor); - const handleConditions = (data) => - conditions ? evaluateExpressionGroup(conditions, data) : true; + const hasConditions = conditions && conditions.length > 0; - // Since result is now a single value, pass it directly to conditions - const conditionsResult = handleConditions({ result: String(result) }); + const startTime = dayjs().valueOf(); + try { + if (hasConditions) { + // When conditions are enabled, expect a single value result + const result = await this.mssqlQuerySingleValue( + monitor.databaseConnectionString, + query + ); + heartbeat.ping = dayjs().valueOf() - startTime; - if (!conditionsResult) { - throw new Error( - `Query result did not meet the specified conditions (${result})` - ); + const conditionsResult = evaluateExpressionGroup(conditions, { result: String(result) }); + + if (!conditionsResult) { + throw new Error(`Query result (${result}) did not meet the specified conditions`); + } + + heartbeat.status = UP; + heartbeat.msg = "Query did meet specified conditions"; + } else { + // Backwards compatible: just check connection and return row count + const result = await this.mssqlQuery( + monitor.databaseConnectionString, + query + ); + heartbeat.ping = dayjs().valueOf() - startTime; + heartbeat.status = UP; + heartbeat.msg = result; + } + } catch (error) { + heartbeat.ping = dayjs().valueOf() - startTime; + throw new Error(`Database connection/query failed: ${error.message}`); } - - heartbeat.msg = ""; - heartbeat.status = UP; } /** - * Run a query on MSSQL server + * Run a query on MSSQL server (backwards compatible - returns row count) + * @param {string} connectionString The database connection string + * @param {string} query The query to validate the database with + * @returns {Promise} Row count message + */ + async mssqlQuery(connectionString, query) { + let pool; + try { + pool = new mssql.ConnectionPool(connectionString); + await pool.connect(); + const result = await pool.request().query(query); + + if (result.recordset) { + return "Rows: " + result.recordset.length; + } else { + return "No Error, but the result is not an array. Type: " + typeof result.recordset; + } + } catch (err) { + log.debug( + "sqlserver", + "Error caught in the query execution.", + err.message + ); + throw err; + } finally { + if (pool) { + await pool.close(); + } + } + } + + /** + * Run a query on MSSQL server expecting a single value result * @param {string} connectionString The database connection string * @param {string} query The query to validate the database with * @returns {Promise} Single value from the first column of the first row */ - async mssqlQuery(connectionString, query) { + async mssqlQuerySingleValue(connectionString, query) { let pool; try { pool = new mssql.ConnectionPool(connectionString); diff --git a/test/backend-test/monitors/test-mssql.js b/test/backend-test/monitors/test-mssql.js index f265bcdff..e9289aa7b 100644 --- a/test/backend-test/monitors/test-mssql.js +++ b/test/backend-test/monitors/test-mssql.js @@ -6,20 +6,25 @@ const { UP, PENDING } = require("../../../src/util"); /** * Helper function to create and start a MSSQL container - * @returns {Promise} The started MSSQL container + * @returns {Promise<{container: MSSQLServerContainer, connectionString: string}>} The started container and connection string */ async function createAndStartMSSQLContainer() { - return await new MSSQLServerContainer( + const container = await new MSSQLServerContainer( "mcr.microsoft.com/mssql/server:2022-latest" ) .acceptLicense() // The default timeout of 30 seconds might not be enough for the container to start .withStartupTimeout(60000) .start(); + + return { + container, + connectionString: container.getConnectionUri(false) + }; } describe( - "MSSQL Single Node", + "MSSQL Monitor", { skip: !!process.env.CI && @@ -27,56 +32,11 @@ describe( }, () => { test("check() sets status to UP when MSSQL server is reachable", async () => { - let mssqlContainer; - - try { - mssqlContainer = await createAndStartMSSQLContainer(); - - const mssqlMonitor = new MssqlMonitorType(); - const monitor = { - databaseConnectionString: - mssqlContainer.getConnectionUri(false), - conditions: "[]", - }; - - const heartbeat = { - msg: "", - status: PENDING, - }; - - await mssqlMonitor.check(monitor, heartbeat, {}); - assert.strictEqual( - heartbeat.status, - UP, - `Expected status ${UP} but got ${heartbeat.status}` - ); - } catch (error) { - console.error("Test failed with error:", error.message); - console.error("Error stack:", error.stack); - if (mssqlContainer) { - console.error("Container ID:", mssqlContainer.getId()); - console.error( - "Container logs:", - await mssqlContainer.logs() - ); - } - throw error; - } finally { - if (mssqlContainer) { - console.log("Stopping MSSQL container..."); - await mssqlContainer.stop(); - } - } - }); - - test("check() sets status to UP when custom query returns single value", async () => { - const mssqlContainer = await createAndStartMSSQLContainer(); + const { container, connectionString } = await createAndStartMSSQLContainer(); const mssqlMonitor = new MssqlMonitorType(); const monitor = { - databaseConnectionString: - mssqlContainer.getConnectionUri(false), - databaseQuery: "SELECT 42", + databaseConnectionString: connectionString, conditions: "[]", }; @@ -93,183 +53,7 @@ describe( `Expected status ${UP} but got ${heartbeat.status}` ); } finally { - await mssqlContainer.stop(); - } - }); - - test("check() sets status to UP when custom query result meets condition", async () => { - const mssqlContainer = await createAndStartMSSQLContainer(); - - const mssqlMonitor = new MssqlMonitorType(); - const monitor = { - databaseConnectionString: - mssqlContainer.getConnectionUri(false), - databaseQuery: "SELECT 42 as value", - conditions: JSON.stringify([ - { - type: "expression", - andOr: "and", - variable: "result", - operator: "equals", - value: "42", - }, - ]), - }; - - const heartbeat = { - msg: "", - status: PENDING, - }; - - try { - await mssqlMonitor.check(monitor, heartbeat, {}); - assert.strictEqual( - heartbeat.status, - UP, - `Expected status ${UP} but got ${heartbeat.status}` - ); - } finally { - await mssqlContainer.stop(); - } - }); - - test("check() rejects when custom query result does not meet condition", async () => { - const mssqlContainer = await createAndStartMSSQLContainer(); - - const mssqlMonitor = new MssqlMonitorType(); - const monitor = { - databaseConnectionString: - mssqlContainer.getConnectionUri(false), - databaseQuery: "SELECT 99 as value", - conditions: JSON.stringify([ - { - type: "expression", - andOr: "and", - variable: "result", - operator: "equals", - value: "42", - }, - ]), - }; - - const heartbeat = { - msg: "", - status: PENDING, - }; - - try { - await assert.rejects( - mssqlMonitor.check(monitor, heartbeat, {}), - new Error( - "Query result did not meet the specified conditions (99)" - ) - ); - assert.strictEqual( - heartbeat.status, - PENDING, - `Expected status should not be ${heartbeat.status}` - ); - } finally { - await mssqlContainer.stop(); - } - }); - - test("check() rejects when query returns no results", async () => { - const mssqlContainer = await createAndStartMSSQLContainer(); - - const mssqlMonitor = new MssqlMonitorType(); - const monitor = { - databaseConnectionString: - mssqlContainer.getConnectionUri(false), - databaseQuery: "SELECT 1 WHERE 1 = 0", - conditions: "[]", - }; - - const heartbeat = { - msg: "", - status: PENDING, - }; - - try { - await assert.rejects( - mssqlMonitor.check(monitor, heartbeat, {}), - new Error( - "Database connection/query failed: Query returned no results" - ) - ); - assert.strictEqual( - heartbeat.status, - PENDING, - `Expected status should not be ${heartbeat.status}` - ); - } finally { - await mssqlContainer.stop(); - } - }); - - test("check() rejects when query returns multiple rows", async () => { - const mssqlContainer = await createAndStartMSSQLContainer(); - - const mssqlMonitor = new MssqlMonitorType(); - const monitor = { - databaseConnectionString: - mssqlContainer.getConnectionUri(false), - databaseQuery: "SELECT 1 UNION ALL SELECT 2", - conditions: "[]", - }; - - const heartbeat = { - msg: "", - status: PENDING, - }; - - try { - await assert.rejects( - mssqlMonitor.check(monitor, heartbeat, {}), - new Error( - "Database connection/query failed: Multiple values were found, expected only one value" - ) - ); - assert.strictEqual( - heartbeat.status, - PENDING, - `Expected status should not be ${heartbeat.status}` - ); - } finally { - await mssqlContainer.stop(); - } - }); - - test("check() rejects when query returns multiple columns", async () => { - const mssqlContainer = await createAndStartMSSQLContainer(); - - const mssqlMonitor = new MssqlMonitorType(); - const monitor = { - databaseConnectionString: - mssqlContainer.getConnectionUri(false), - databaseQuery: "SELECT 1 AS col1, 2 AS col2", - conditions: "[]", - }; - - const heartbeat = { - msg: "", - status: PENDING, - }; - - try { - await assert.rejects( - mssqlMonitor.check(monitor, heartbeat, {}), - new Error( - "Database connection/query failed: Multiple columns were found, expected only one value" - ) - ); - assert.strictEqual( - heartbeat.status, - PENDING, - `Expected status should not be ${heartbeat.status}` - ); - } finally { - await mssqlContainer.stop(); + await container.stop(); } }); @@ -298,5 +82,227 @@ describe( `Expected status should not be ${heartbeat.status}` ); }); + + test("check() sets status to UP when custom query returns single value", async () => { + const { container, connectionString } = await createAndStartMSSQLContainer(); + + const mssqlMonitor = new MssqlMonitorType(); + const monitor = { + databaseConnectionString: connectionString, + databaseQuery: "SELECT 42", + conditions: "[]", + }; + + const heartbeat = { + msg: "", + status: PENDING, + }; + + try { + await mssqlMonitor.check(monitor, heartbeat, {}); + assert.strictEqual( + heartbeat.status, + UP, + `Expected status ${UP} but got ${heartbeat.status}` + ); + } finally { + await container.stop(); + } + }); + + test("check() sets status to UP when custom query result meets condition", async () => { + const { container, connectionString } = await createAndStartMSSQLContainer(); + + const mssqlMonitor = new MssqlMonitorType(); + const monitor = { + databaseConnectionString: connectionString, + databaseQuery: "SELECT 42 AS value", + conditions: JSON.stringify([ + { + type: "expression", + andOr: "and", + variable: "result", + operator: "equals", + value: "42", + }, + ]), + }; + + const heartbeat = { + msg: "", + status: PENDING, + }; + + try { + await mssqlMonitor.check(monitor, heartbeat, {}); + assert.strictEqual( + heartbeat.status, + UP, + `Expected status ${UP} but got ${heartbeat.status}` + ); + } finally { + await container.stop(); + } + }); + + test("check() rejects when custom query result does not meet condition", async () => { + const { container, connectionString } = await createAndStartMSSQLContainer(); + + const mssqlMonitor = new MssqlMonitorType(); + const monitor = { + databaseConnectionString: connectionString, + databaseQuery: "SELECT 99 AS value", + conditions: JSON.stringify([ + { + type: "expression", + andOr: "and", + variable: "result", + operator: "equals", + value: "42", + }, + ]), + }; + + const heartbeat = { + msg: "", + status: PENDING, + }; + + try { + await assert.rejects( + mssqlMonitor.check(monitor, heartbeat, {}), + new Error( + "Query result (99) did not meet the specified conditions" + ) + ); + assert.strictEqual( + heartbeat.status, + PENDING, + `Expected status should not be ${heartbeat.status}` + ); + } finally { + await container.stop(); + } + }); + + test("check() rejects when query returns no results with conditions", async () => { + const { container, connectionString } = await createAndStartMSSQLContainer(); + + const mssqlMonitor = new MssqlMonitorType(); + const monitor = { + databaseConnectionString: connectionString, + databaseQuery: "SELECT 1 WHERE 1 = 0", + conditions: JSON.stringify([ + { + type: "expression", + andOr: "and", + variable: "result", + operator: "equals", + value: "1", + }, + ]), + }; + + const heartbeat = { + msg: "", + status: PENDING, + }; + + try { + await assert.rejects( + mssqlMonitor.check(monitor, heartbeat, {}), + new Error( + "Database connection/query failed: Query returned no results" + ) + ); + assert.strictEqual( + heartbeat.status, + PENDING, + `Expected status should not be ${heartbeat.status}` + ); + } finally { + await container.stop(); + } + }); + + test("check() rejects when query returns multiple rows with conditions", async () => { + const { container, connectionString } = await createAndStartMSSQLContainer(); + + const mssqlMonitor = new MssqlMonitorType(); + const monitor = { + databaseConnectionString: connectionString, + databaseQuery: "SELECT 1 UNION ALL SELECT 2", + conditions: JSON.stringify([ + { + type: "expression", + andOr: "and", + variable: "result", + operator: "equals", + value: "1", + }, + ]), + }; + + const heartbeat = { + msg: "", + status: PENDING, + }; + + try { + await assert.rejects( + mssqlMonitor.check(monitor, heartbeat, {}), + new Error( + "Database connection/query failed: Multiple values were found, expected only one value" + ) + ); + assert.strictEqual( + heartbeat.status, + PENDING, + `Expected status should not be ${heartbeat.status}` + ); + } finally { + await container.stop(); + } + }); + + test("check() rejects when query returns multiple columns with conditions", async () => { + const { container, connectionString } = await createAndStartMSSQLContainer(); + + const mssqlMonitor = new MssqlMonitorType(); + const monitor = { + databaseConnectionString: connectionString, + databaseQuery: "SELECT 1 AS col1, 2 AS col2", + conditions: JSON.stringify([ + { + type: "expression", + andOr: "and", + variable: "result", + operator: "equals", + value: "1", + }, + ]), + }; + + const heartbeat = { + msg: "", + status: PENDING, + }; + + try { + await assert.rejects( + mssqlMonitor.check(monitor, heartbeat, {}), + new Error( + "Database connection/query failed: Multiple columns were found, expected only one value" + ) + ); + assert.strictEqual( + heartbeat.status, + PENDING, + `Expected status should not be ${heartbeat.status}` + ); + } finally { + await container.stop(); + } + }); } );