diff --git a/src/Service.Tests/Configuration/HealthEndpointTests.cs b/src/Service.Tests/Configuration/HealthEndpointTests.cs index 1eac7416e3..2c86fa9622 100644 --- a/src/Service.Tests/Configuration/HealthEndpointTests.cs +++ b/src/Service.Tests/Configuration/HealthEndpointTests.cs @@ -564,6 +564,94 @@ private static RuntimeConfig CreateRuntimeConfig(Dictionary enti return runtimeConfig; } + /// + /// Verifies that stored procedures are excluded from health check results. + /// Creates a config with both a table entity and a stored procedure entity, + /// then validates that only the table entity appears in the health endpoint response. + /// + [TestMethod] + [TestCategory(TestCategory.MSSQL)] + public async Task HealthEndpoint_ExcludesStoredProcedures() + { + // Create a table entity + Entity tableEntity = new( + Health: new(enabled: true), + Source: new("books", EntitySourceType.Table, null, null), + Fields: null, + Rest: new(Enabled: true), + GraphQL: new("book", "bookLists", true), + Permissions: new[] { ConfigurationTests.GetMinimalPermissionConfig(AuthorizationResolver.ROLE_ANONYMOUS) }, + Relationships: null, + Mappings: null); + + // Create a stored procedure entity + Entity storedProcEntity = new( + Health: new(enabled: true), + Source: new("GetData", EntitySourceType.StoredProcedure, null, null), + Fields: null, + Rest: new(Enabled: true), + GraphQL: new("getData", "getDataList", true), + Permissions: new[] { ConfigurationTests.GetMinimalPermissionConfig(AuthorizationResolver.ROLE_ANONYMOUS) }, + Relationships: null, + Mappings: null); + + Dictionary entityMap = new() + { + { "Book", tableEntity }, + { "GetData", storedProcEntity } + }; + + RuntimeConfig runtimeConfig = CreateRuntimeConfig( + entityMap, + enableGlobalRest: true, + enableGlobalGraphql: true, + enabledGlobalMcp: true, + enableGlobalHealth: true, + enableDatasourceHealth: true, + hostMode: HostMode.Development); + + WriteToCustomConfigFile(runtimeConfig); + + string[] args = new[] + { + $"--ConfigFileName={CUSTOM_CONFIG_FILENAME}" + }; + + using (TestServer server = new(Program.CreateWebHostBuilder(args))) + using (HttpClient client = server.CreateClient()) + { + HttpRequestMessage healthRequest = new(HttpMethod.Get, $"{BASE_DAB_URL}/health"); + HttpResponseMessage response = await client.SendAsync(healthRequest); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "Health endpoint should return OK"); + + string responseBody = await response.Content.ReadAsStringAsync(); + Dictionary responseProperties = JsonSerializer.Deserialize>(responseBody); + + // Get the checks array + Assert.IsTrue(responseProperties.TryGetValue("checks", out JsonElement checksElement), "Response should contain 'checks' property"); + Assert.AreEqual(JsonValueKind.Array, checksElement.ValueKind, "Checks should be an array"); + + // Get all entity names from the health check results + List entityNamesInHealthCheck = new(); + foreach (JsonElement check in checksElement.EnumerateArray()) + { + if (check.TryGetProperty("name", out JsonElement nameElement)) + { + entityNamesInHealthCheck.Add(nameElement.GetString()); + } + } + + // Verify that the table entity (Book) appears in health checks + Assert.IsTrue(entityNamesInHealthCheck.Contains("Book"), + "Table entity 'Book' should be included in health check results"); + + // Verify that the stored procedure entity (GetData) does NOT appear in health checks + Assert.IsFalse(entityNamesInHealthCheck.Contains("GetData"), + "Stored procedure entity 'GetData' should be excluded from health check results"); + } + } + private static void WriteToCustomConfigFile(RuntimeConfig runtimeConfig) { File.WriteAllText( diff --git a/src/Service/HealthCheck/HealthCheckHelper.cs b/src/Service/HealthCheck/HealthCheckHelper.cs index 9b7bbd272c..ab19756195 100644 --- a/src/Service/HealthCheck/HealthCheckHelper.cs +++ b/src/Service/HealthCheck/HealthCheckHelper.cs @@ -199,10 +199,11 @@ private async Task UpdateDataSourceHealthCheckResultsAsync(ComprehensiveHealthCh // Updates the Entity Health Check Results in the response. // Goes through the entities one by one and executes the rest and graphql checks (if enabled). + // Stored procedures are excluded from health checks because they require parameters and are not guaranteed to be deterministic. private async Task UpdateEntityHealthCheckResultsAsync(ComprehensiveHealthCheckReport report, RuntimeConfig runtimeConfig) { List> enabledEntities = runtimeConfig.Entities.Entities - .Where(e => e.Value.IsEntityHealthEnabled) + .Where(e => e.Value.IsEntityHealthEnabled && e.Value.Source.Type != EntitySourceType.StoredProcedure) .ToList(); if (enabledEntities.Count == 0)