From 52b53ef2d86b0a81f68d6d0452144e975251acb7 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 13 Oct 2025 17:30:49 -0700 Subject: [PATCH 01/18] Add missing test --- .../Configuration/ConfigurationTests.cs | 85 ++++++++++++++++--- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 65f6e6643b..0b236621e3 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -2528,26 +2528,31 @@ public async Task TestRuntimeBaseRouteInNextLinkForPaginatedRestResponse() /// Expected HTTP status code code for the GraphQL request [DataTestMethod] [TestCategory(TestCategory.MSSQL)] - [DataRow(true, true, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Both Rest and GraphQL endpoints enabled globally")] - [DataRow(true, false, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest enabled and GraphQL endpoints disabled globally")] - [DataRow(false, true, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest disabled and GraphQL endpoints enabled globally")] - [DataRow(true, true, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Both Rest and GraphQL endpoints enabled globally")] - [DataRow(true, false, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest enabled and GraphQL endpoints disabled globally")] - [DataRow(false, true, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled and GraphQL endpoints enabled globally")] - public async Task TestGlobalFlagToEnableRestAndGraphQLForHostedAndNonHostedEnvironment( + [DataRow(true, true, true, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Both Rest and GraphQL endpoints enabled globally")] + [DataRow(true, false, false, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest enabled and GraphQL endpoints disabled globally")] + [DataRow(false, true, false, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest disabled and GraphQL endpoints enabled globally")] + [DataRow(true, true, false, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Both Rest and GraphQL endpoints enabled globally")] + [DataRow(true, false, true, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest enabled and GraphQL endpoints disabled globally")] + [DataRow(false, true, true, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled and GraphQL endpoints enabled globally")] + [DataRow(false, false, true, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled and GraphQL endpoints enabled globally")] + [DataRow(false, false, false, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled and GraphQL endpoints enabled globally")] + public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEnvironment( bool isRestEnabled, bool isGraphQLEnabled, + bool isMcpEnabled, HttpStatusCode expectedStatusCodeForREST, HttpStatusCode expectedStatusCodeForGraphQL, + HttpStatusCode expectedStatusCodeForMcp, string configurationEndpoint) { GraphQLRuntimeOptions graphqlOptions = new(Enabled: isGraphQLEnabled); RestRuntimeOptions restRuntimeOptions = new(Enabled: isRestEnabled); + McpRuntimeOptions mcpRuntimeOptions = new(Enabled: isMcpEnabled); DataSource dataSource = new(DatabaseType.MSSQL, GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null); - RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, null); + RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpRuntimeOptions); const string CUSTOM_CONFIG = "custom-config.json"; File.WriteAllText(CUSTOM_CONFIG, configuration.ToJson()); @@ -2570,6 +2575,7 @@ public async Task TestGlobalFlagToEnableRestAndGraphQLForHostedAndNonHostedEnvir object payload = new { query }; + // GraphQL request HttpRequestMessage graphQLRequest = new(HttpMethod.Post, "/graphql") { Content = JsonContent.Create(payload) @@ -2578,9 +2584,24 @@ public async Task TestGlobalFlagToEnableRestAndGraphQLForHostedAndNonHostedEnvir HttpResponseMessage graphQLResponse = await client.SendAsync(graphQLRequest); Assert.AreEqual(expectedStatusCodeForGraphQL, graphQLResponse.StatusCode); + // REST request HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/Book"); HttpResponseMessage restResponse = await client.SendAsync(restRequest); Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode); + + // MCP request + object mcpPayload = new + { + jsonrpc = "2.0", + id = 1, + method = "tools/call" + }; + HttpRequestMessage mcpRequest = new(HttpMethod.Post, "/mcp") + { + Content = JsonContent.Create(mcpPayload) + }; + HttpResponseMessage mcpResponse = await client.SendAsync(mcpRequest); + Assert.AreEqual(expectedStatusCodeForMcp, mcpResponse.StatusCode); } // Hosted Scenario @@ -2590,8 +2611,7 @@ public async Task TestGlobalFlagToEnableRestAndGraphQLForHostedAndNonHostedEnvir { JsonContent content = GetPostStartupConfigParams(MSSQL_ENVIRONMENT, configuration, configurationEndpoint); - HttpResponseMessage postResult = - await client.PostAsync(configurationEndpoint, content); + HttpResponseMessage postResult = await client.PostAsync(configurationEndpoint, content); Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode); HttpStatusCode restResponseCode = await GetRestResponsePostConfigHydration(client); @@ -2602,6 +2622,8 @@ public async Task TestGlobalFlagToEnableRestAndGraphQLForHostedAndNonHostedEnvir Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode); + HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client); + Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode); } } @@ -5330,6 +5352,49 @@ private static async Task GetGraphQLResponsePostConfigHydration( return responseCode; } + /// + /// Executing MCP POST requests against the engine until a non-503 error is received. + /// + /// Client used for request execution. + /// ServiceUnavailable if service is not successfully hydrated with config, + /// else the response code from the MCP request + private static async Task GetMcpResponsePostConfigHydration(HttpClient httpClient) + { + // Retry request RETRY_COUNT times in 1 second increments to allow required services + // time to instantiate and hydrate permissions. + int retryCount = RETRY_COUNT; + HttpStatusCode responseCode = HttpStatusCode.ServiceUnavailable; + while (retryCount > 0) + { + // Minimal MCP request (list tools) – valid JSON-RPC request + object payload = new + { + jsonrpc = "2.0", + id = 1, + method = "tools/call" + }; + + HttpRequestMessage mcpRequest = new(HttpMethod.Post, "/mcp") + { + Content = JsonContent.Create(payload) + }; + + HttpResponseMessage mcpResponse = await httpClient.SendAsync(mcpRequest); + responseCode = mcpResponse.StatusCode; + + if (responseCode == HttpStatusCode.ServiceUnavailable) + { + retryCount--; + Thread.Sleep(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS)); + continue; + } + + break; + } + + return responseCode; + } + /// /// Helper method to instantiate RuntimeConfig object needed for multiple create tests. /// From de28682fcd79e615cb27f06bf8e6778600744af9 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 27 Oct 2025 12:30:21 -0700 Subject: [PATCH 02/18] Add MCP tests --- .../Configuration/ConfigurationTests.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 0b236621e3..aa5418e32f 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -2528,14 +2528,20 @@ public async Task TestRuntimeBaseRouteInNextLinkForPaginatedRestResponse() /// Expected HTTP status code code for the GraphQL request [DataTestMethod] [TestCategory(TestCategory.MSSQL)] - [DataRow(true, true, true, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Both Rest and GraphQL endpoints enabled globally")] - [DataRow(true, false, false, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest enabled and GraphQL endpoints disabled globally")] - [DataRow(false, true, false, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest disabled and GraphQL endpoints enabled globally")] - [DataRow(true, true, false, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Both Rest and GraphQL endpoints enabled globally")] - [DataRow(true, false, true, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest enabled and GraphQL endpoints disabled globally")] - [DataRow(false, true, true, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled and GraphQL endpoints enabled globally")] - [DataRow(false, false, true, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled and GraphQL endpoints enabled globally")] - [DataRow(false, false, false, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled and GraphQL endpoints enabled globally")] + [DataRow(true, true, true, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest, GraphQL, and MCP enabled globally")] + [DataRow(true, true, false, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest and GraphQL enabled, MCP disabled globally")] + [DataRow(true, false, true, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest enabled, GraphQL disabled, and MCP enabled globally")] + [DataRow(true, false, false, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest enabled, GraphQL and MCP enabled globally")] + [DataRow(false, true, true, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest disabled, GraphQL and MCP enabled globally")] + [DataRow(false, true, false, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest disabled, GraphQL enabled, and MCP disabled globally")] + [DataRow(false, false, true, HttpStatusCode.NotFound, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest and GraphQL disabled, MCP enabled globally")] + [DataRow(true, true, true, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest, GraphQL, and MCP enabled globally")] + [DataRow(true, true, false, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest and GraphQL enabled, MCP disabled globally")] + [DataRow(true, false, true, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest enabled, GraphQL disabled, and MCP enabled globally")] + [DataRow(true, false, false, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest enabled, GraphQL and MCP enabled globally")] + [DataRow(false, true, true, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled, GraphQL and MCP enabled globally")] + [DataRow(false, true, false, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled, GraphQL enabled, and MCP disabled globally")] + [DataRow(false, false, true, HttpStatusCode.NotFound, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest and GraphQL disabled, MCP enabled globally")] public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEnvironment( bool isRestEnabled, bool isGraphQLEnabled, @@ -2589,7 +2595,8 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn HttpResponseMessage restResponse = await client.SendAsync(restRequest); Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode); - // MCP request + // MCP request + await Task.Delay(2000); object mcpPayload = new { jsonrpc = "2.0", From 80e6b2e7a8863628c2421b9b585e2904218d733a Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 27 Oct 2025 13:21:16 -0700 Subject: [PATCH 03/18] Fix formatting error --- src/Service.Tests/Configuration/ConfigurationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index aa5418e32f..bf191eccc4 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -2596,7 +2596,7 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode); // MCP request - await Task.Delay(2000); + await Task.Delay(2000); object mcpPayload = new { jsonrpc = "2.0", From 8fae2b88b5dedbe71123780180f0d1a1209fd032 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 27 Oct 2025 15:20:05 -0700 Subject: [PATCH 04/18] Add missing header --- src/Service.Tests/Configuration/ConfigurationTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index bf191eccc4..4e17d76400 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -2596,17 +2596,17 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode); // MCP request - await Task.Delay(2000); object mcpPayload = new { jsonrpc = "2.0", id = 1, - method = "tools/call" + method = "tools/list" }; HttpRequestMessage mcpRequest = new(HttpMethod.Post, "/mcp") { Content = JsonContent.Create(mcpPayload) }; + mcpRequest.Headers.Add("Accept", "*/*"); HttpResponseMessage mcpResponse = await client.SendAsync(mcpRequest); Assert.AreEqual(expectedStatusCodeForMcp, mcpResponse.StatusCode); } From ccab7f12747d722330483850fb0a91114a176b14 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 27 Oct 2025 22:58:16 -0700 Subject: [PATCH 05/18] Add messages to allow for easier identification of failures --- .../Configuration/ConfigurationTests.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 4e17d76400..f9295aade5 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -2588,14 +2588,15 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn }; HttpResponseMessage graphQLResponse = await client.SendAsync(graphQLRequest); - Assert.AreEqual(expectedStatusCodeForGraphQL, graphQLResponse.StatusCode); + Assert.AreEqual(expectedStatusCodeForGraphQL, graphQLResponse.StatusCode, "The GraphQL response is different from the expected result."); // REST request HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/Book"); HttpResponseMessage restResponse = await client.SendAsync(restRequest); - Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode); + Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode, "The REST response is different from the expected result."); // MCP request + await Task.Delay(2000); object mcpPayload = new { jsonrpc = "2.0", @@ -2608,7 +2609,7 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn }; mcpRequest.Headers.Add("Accept", "*/*"); HttpResponseMessage mcpResponse = await client.SendAsync(mcpRequest); - Assert.AreEqual(expectedStatusCodeForMcp, mcpResponse.StatusCode); + Assert.AreEqual(expectedStatusCodeForMcp, mcpResponse.StatusCode, "The MCP response is different from the expected result."); } // Hosted Scenario @@ -2619,18 +2620,18 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn JsonContent content = GetPostStartupConfigParams(MSSQL_ENVIRONMENT, configuration, configurationEndpoint); HttpResponseMessage postResult = await client.PostAsync(configurationEndpoint, content); - Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode); + Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode, "The hydration post-response is different from the expected result."); HttpStatusCode restResponseCode = await GetRestResponsePostConfigHydration(client); - Assert.AreEqual(expected: expectedStatusCodeForREST, actual: restResponseCode); + Assert.AreEqual(expected: expectedStatusCodeForREST, actual: restResponseCode, "The REST hydration post-response is different from the expected result."); HttpStatusCode graphqlResponseCode = await GetGraphQLResponsePostConfigHydration(client); - Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode); + Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode, "The GraphQL hydration post-response is different from the expected result."); HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client); - Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode); + Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode, "The MCP hydration post-response is different from the expected result."); } } From edd0943927982f903a21b13c431c34b4bc0371eb Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Tue, 28 Oct 2025 10:07:10 -0700 Subject: [PATCH 06/18] Add request header in hydration section --- .../Configuration/ConfigurationTests.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index f9295aade5..17b6e4f38b 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -2588,15 +2588,14 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn }; HttpResponseMessage graphQLResponse = await client.SendAsync(graphQLRequest); - Assert.AreEqual(expectedStatusCodeForGraphQL, graphQLResponse.StatusCode, "The GraphQL response is different from the expected result."); + Assert.AreEqual(expectedStatusCodeForGraphQL, graphQLResponse.StatusCode); // REST request HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/Book"); HttpResponseMessage restResponse = await client.SendAsync(restRequest); - Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode, "The REST response is different from the expected result."); + Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode); // MCP request - await Task.Delay(2000); object mcpPayload = new { jsonrpc = "2.0", @@ -2609,7 +2608,7 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn }; mcpRequest.Headers.Add("Accept", "*/*"); HttpResponseMessage mcpResponse = await client.SendAsync(mcpRequest); - Assert.AreEqual(expectedStatusCodeForMcp, mcpResponse.StatusCode, "The MCP response is different from the expected result."); + Assert.AreEqual(expectedStatusCodeForMcp, mcpResponse.StatusCode); } // Hosted Scenario @@ -2620,18 +2619,16 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn JsonContent content = GetPostStartupConfigParams(MSSQL_ENVIRONMENT, configuration, configurationEndpoint); HttpResponseMessage postResult = await client.PostAsync(configurationEndpoint, content); - Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode, "The hydration post-response is different from the expected result."); + Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode); HttpStatusCode restResponseCode = await GetRestResponsePostConfigHydration(client); - - Assert.AreEqual(expected: expectedStatusCodeForREST, actual: restResponseCode, "The REST hydration post-response is different from the expected result."); + Assert.AreEqual(expected: expectedStatusCodeForREST, actual: restResponseCode); HttpStatusCode graphqlResponseCode = await GetGraphQLResponsePostConfigHydration(client); - - Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode, "The GraphQL hydration post-response is different from the expected result."); + Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode); HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client); - Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode, "The MCP hydration post-response is different from the expected result."); + Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode); } } @@ -5379,13 +5376,14 @@ private static async Task GetMcpResponsePostConfigHydration(Http { jsonrpc = "2.0", id = 1, - method = "tools/call" + method = "tools/list" }; HttpRequestMessage mcpRequest = new(HttpMethod.Post, "/mcp") { Content = JsonContent.Create(payload) }; + mcpRequest.Headers.Add("Accept", "*/*"); HttpResponseMessage mcpResponse = await httpClient.SendAsync(mcpRequest); responseCode = mcpResponse.StatusCode; From 5db169fb65596ac33d90fb2755a167dd072b0dcf Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Tue, 28 Oct 2025 11:19:43 -0700 Subject: [PATCH 07/18] Fix error messages --- .../Configuration/ConfigurationTests.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 17b6e4f38b..28823d9ccd 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -2588,14 +2588,15 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn }; HttpResponseMessage graphQLResponse = await client.SendAsync(graphQLRequest); - Assert.AreEqual(expectedStatusCodeForGraphQL, graphQLResponse.StatusCode); + Assert.AreEqual(expectedStatusCodeForGraphQL, graphQLResponse.StatusCode, "The GraphQL response is different from the expected result."); // REST request HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/Book"); HttpResponseMessage restResponse = await client.SendAsync(restRequest); - Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode); + Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode, "The REST response is different from the expected result."); // MCP request + await Task.Delay(2000); object mcpPayload = new { jsonrpc = "2.0", @@ -2608,7 +2609,7 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn }; mcpRequest.Headers.Add("Accept", "*/*"); HttpResponseMessage mcpResponse = await client.SendAsync(mcpRequest); - Assert.AreEqual(expectedStatusCodeForMcp, mcpResponse.StatusCode); + Assert.AreEqual(expectedStatusCodeForMcp, mcpResponse.StatusCode, "The MCP response is different from the expected result."); } // Hosted Scenario @@ -2619,16 +2620,18 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn JsonContent content = GetPostStartupConfigParams(MSSQL_ENVIRONMENT, configuration, configurationEndpoint); HttpResponseMessage postResult = await client.PostAsync(configurationEndpoint, content); - Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode); + Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode, "The hydration post-response is different from the expected result."); HttpStatusCode restResponseCode = await GetRestResponsePostConfigHydration(client); - Assert.AreEqual(expected: expectedStatusCodeForREST, actual: restResponseCode); + + Assert.AreEqual(expected: expectedStatusCodeForREST, actual: restResponseCode, "The REST hydration post-response is different from the expected result."); HttpStatusCode graphqlResponseCode = await GetGraphQLResponsePostConfigHydration(client); - Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode); + + Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode, "The GraphQL hydration post-response is different from the expected result."); HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client); - Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode); + Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode, "The MCP hydration post-response is different from the expected result."); } } @@ -5378,7 +5381,6 @@ private static async Task GetMcpResponsePostConfigHydration(Http id = 1, method = "tools/list" }; - HttpRequestMessage mcpRequest = new(HttpMethod.Post, "/mcp") { Content = JsonContent.Create(payload) From adc89d2ec39e6af223594c6fecaa8b0898e681c9 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Tue, 28 Oct 2025 13:26:23 -0700 Subject: [PATCH 08/18] Add status code to repeat in case post fails after hydration --- src/Service.Tests/Configuration/ConfigurationTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 28823d9ccd..787529d75c 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -2623,11 +2623,9 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode, "The hydration post-response is different from the expected result."); HttpStatusCode restResponseCode = await GetRestResponsePostConfigHydration(client); - Assert.AreEqual(expected: expectedStatusCodeForREST, actual: restResponseCode, "The REST hydration post-response is different from the expected result."); HttpStatusCode graphqlResponseCode = await GetGraphQLResponsePostConfigHydration(client); - Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode, "The GraphQL hydration post-response is different from the expected result."); HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client); @@ -5390,7 +5388,7 @@ private static async Task GetMcpResponsePostConfigHydration(Http HttpResponseMessage mcpResponse = await httpClient.SendAsync(mcpRequest); responseCode = mcpResponse.StatusCode; - if (responseCode == HttpStatusCode.ServiceUnavailable) + if (responseCode == HttpStatusCode.ServiceUnavailable || responseCode == HttpStatusCode.NotFound) { retryCount--; Thread.Sleep(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS)); From 39647da3ccb7097460246f5cc5a6f4a4b11d8e02 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Tue, 28 Oct 2025 15:26:04 -0700 Subject: [PATCH 09/18] Add delay in hydration --- src/Service.Tests/Configuration/ConfigurationTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 787529d75c..4a8cdbe92d 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -5372,7 +5372,8 @@ private static async Task GetMcpResponsePostConfigHydration(Http HttpStatusCode responseCode = HttpStatusCode.ServiceUnavailable; while (retryCount > 0) { - // Minimal MCP request (list tools) – valid JSON-RPC request + // Minimal MCP request (list tools) – valid JSON-RPC request + await Task.Delay(2000); object payload = new { jsonrpc = "2.0", From 676d70ce7fe2ebff4a509868148212f14556817c Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 13 Nov 2025 14:36:14 -0800 Subject: [PATCH 10/18] Changes based on comments --- .../Configuration/ConfigurationTests.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 4a8cdbe92d..deb43f3ecc 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Configuration; using System.IdentityModel.Tokens.Jwt; using System.IO; using System.IO.Abstractions; @@ -1128,7 +1129,7 @@ public async Task TestSqlSettingPostStartupConfigurations(string configurationEn await httpClient.GetAsync($"/{OPENAPI_SWAGGER_ENDPOINT}"); Assert.AreEqual(HttpStatusCode.ServiceUnavailable, preConfigOpenApiSwaggerEndpointAvailability.StatusCode); - HttpStatusCode responseCode = await HydratePostStartupConfiguration(httpClient, content, configurationEndpoint); + HttpStatusCode responseCode = await HydratePostStartupConfiguration(httpClient, content, configurationEndpoint, configuration.Runtime.Rest); // When the authorization resolver is properly configured, authorization will have failed // because no auth headers are present. @@ -2531,14 +2532,14 @@ public async Task TestRuntimeBaseRouteInNextLinkForPaginatedRestResponse() [DataRow(true, true, true, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest, GraphQL, and MCP enabled globally")] [DataRow(true, true, false, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest and GraphQL enabled, MCP disabled globally")] [DataRow(true, false, true, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest enabled, GraphQL disabled, and MCP enabled globally")] - [DataRow(true, false, false, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest enabled, GraphQL and MCP enabled globally")] + [DataRow(true, false, false, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest enabled, GraphQL and MCP disabled globally")] [DataRow(false, true, true, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest disabled, GraphQL and MCP enabled globally")] [DataRow(false, true, false, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest disabled, GraphQL enabled, and MCP disabled globally")] [DataRow(false, false, true, HttpStatusCode.NotFound, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest and GraphQL disabled, MCP enabled globally")] [DataRow(true, true, true, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest, GraphQL, and MCP enabled globally")] [DataRow(true, true, false, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest and GraphQL enabled, MCP disabled globally")] [DataRow(true, false, true, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest enabled, GraphQL disabled, and MCP enabled globally")] - [DataRow(true, false, false, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest enabled, GraphQL and MCP enabled globally")] + [DataRow(true, false, false, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest enabled, GraphQL and MCP disabled globally")] [DataRow(false, true, true, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled, GraphQL and MCP enabled globally")] [DataRow(false, true, false, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled, GraphQL enabled, and MCP disabled globally")] [DataRow(false, false, true, HttpStatusCode.NotFound, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest and GraphQL disabled, MCP enabled globally")] @@ -2582,7 +2583,7 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn object payload = new { query }; // GraphQL request - HttpRequestMessage graphQLRequest = new(HttpMethod.Post, "/graphql") + HttpRequestMessage graphQLRequest = new(HttpMethod.Post, configuration.Runtime.GraphQL.Path) { Content = JsonContent.Create(payload) }; @@ -2591,7 +2592,7 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn Assert.AreEqual(expectedStatusCodeForGraphQL, graphQLResponse.StatusCode, "The GraphQL response is different from the expected result."); // REST request - HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/Book"); + HttpRequestMessage restRequest = new(HttpMethod.Get, $"{configuration.Runtime.Rest.Path}/Book"); HttpResponseMessage restResponse = await client.SendAsync(restRequest); Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode, "The REST response is different from the expected result."); @@ -2603,7 +2604,7 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn id = 1, method = "tools/list" }; - HttpRequestMessage mcpRequest = new(HttpMethod.Post, "/mcp") + HttpRequestMessage mcpRequest = new(HttpMethod.Post, configuration.Runtime.Mcp.Path) { Content = JsonContent.Create(mcpPayload) }; @@ -2622,13 +2623,13 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn HttpResponseMessage postResult = await client.PostAsync(configurationEndpoint, content); Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode, "The hydration post-response is different from the expected result."); - HttpStatusCode restResponseCode = await GetRestResponsePostConfigHydration(client); + HttpStatusCode restResponseCode = await GetRestResponsePostConfigHydration(client, configuration.Runtime.Rest); Assert.AreEqual(expected: expectedStatusCodeForREST, actual: restResponseCode, "The REST hydration post-response is different from the expected result."); - HttpStatusCode graphqlResponseCode = await GetGraphQLResponsePostConfigHydration(client); + HttpStatusCode graphqlResponseCode = await GetGraphQLResponsePostConfigHydration(client, configuration.Runtime.GraphQL); Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode, "The GraphQL hydration post-response is different from the expected result."); - HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client); + HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client, configuration.Runtime.Mcp); Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode, "The MCP hydration post-response is different from the expected result."); } } @@ -3674,7 +3675,7 @@ public async Task TestSchemaIntrospectionQuery(bool enableIntrospection, bool ex using (HttpClient client = server.CreateClient()) { JsonContent content = GetPostStartupConfigParams(MSSQL_ENVIRONMENT, configuration, configurationEndpoint); - HttpStatusCode responseCode = await HydratePostStartupConfiguration(client, content, configurationEndpoint); + HttpStatusCode responseCode = await HydratePostStartupConfiguration(client, content, configurationEndpoint, configuration.Runtime.Rest); Assert.AreEqual(expected: HttpStatusCode.OK, actual: responseCode, message: "Configuration hydration failed."); @@ -5271,14 +5272,14 @@ private static JsonContent GetPostStartupConfigParams(string environment, Runtim /// Client used for request execution. /// Post-startup configuration /// ServiceUnavailable if service is not successfully hydrated with config - private static async Task HydratePostStartupConfiguration(HttpClient httpClient, JsonContent content, string configurationEndpoint) + private static async Task HydratePostStartupConfiguration(HttpClient httpClient, JsonContent content, string configurationEndpoint, RestRuntimeOptions rest) { // Hydrate configuration post-startup HttpResponseMessage postResult = await httpClient.PostAsync(configurationEndpoint, content); Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode); - return await GetRestResponsePostConfigHydration(httpClient); + return await GetRestResponsePostConfigHydration(httpClient, rest); } /// @@ -5287,7 +5288,7 @@ private static async Task HydratePostStartupConfiguration(HttpCl /// Client used for request execution. /// ServiceUnavailable if service is not successfully hydrated with config, /// else the response code from the REST request - private static async Task GetRestResponsePostConfigHydration(HttpClient httpClient) + private static async Task GetRestResponsePostConfigHydration(HttpClient httpClient, RestRuntimeOptions rest) { // Retry request RETRY_COUNT times in 1 second increments to allow required services // time to instantiate and hydrate permissions. @@ -5297,7 +5298,7 @@ private static async Task GetRestResponsePostConfigHydration(Htt { // Spot test authorization resolver utilization to ensure configuration is used. HttpResponseMessage postConfigHydrationResult = - await httpClient.GetAsync($"api/{POST_STARTUP_CONFIG_ENTITY}"); + await httpClient.GetAsync($"{rest.Path}/{POST_STARTUP_CONFIG_ENTITY}"); responseCode = postConfigHydrationResult.StatusCode; if (postConfigHydrationResult.StatusCode == HttpStatusCode.ServiceUnavailable) @@ -5319,7 +5320,7 @@ private static async Task GetRestResponsePostConfigHydration(Htt /// Client used for request execution. /// ServiceUnavailable if service is not successfully hydrated with config, /// else the response code from the GRAPHQL request - private static async Task GetGraphQLResponsePostConfigHydration(HttpClient httpClient) + private static async Task GetGraphQLResponsePostConfigHydration(HttpClient httpClient, GraphQLRuntimeOptions graphQL) { // Retry request RETRY_COUNT times in 1 second increments to allow required services // time to instantiate and hydrate permissions. @@ -5337,7 +5338,7 @@ private static async Task GetGraphQLResponsePostConfigHydration( object payload = new { query }; - HttpRequestMessage graphQLRequest = new(HttpMethod.Post, "/graphql") + HttpRequestMessage graphQLRequest = new(HttpMethod.Post, graphQL.Path) { Content = JsonContent.Create(payload) }; @@ -5364,7 +5365,7 @@ private static async Task GetGraphQLResponsePostConfigHydration( /// Client used for request execution. /// ServiceUnavailable if service is not successfully hydrated with config, /// else the response code from the MCP request - private static async Task GetMcpResponsePostConfigHydration(HttpClient httpClient) + private static async Task GetMcpResponsePostConfigHydration(HttpClient httpClient, McpRuntimeOptions mcp) { // Retry request RETRY_COUNT times in 1 second increments to allow required services // time to instantiate and hydrate permissions. @@ -5373,14 +5374,13 @@ private static async Task GetMcpResponsePostConfigHydration(Http while (retryCount > 0) { // Minimal MCP request (list tools) – valid JSON-RPC request - await Task.Delay(2000); object payload = new { jsonrpc = "2.0", id = 1, method = "tools/list" }; - HttpRequestMessage mcpRequest = new(HttpMethod.Post, "/mcp") + HttpRequestMessage mcpRequest = new(HttpMethod.Post, mcp.Path) { Content = JsonContent.Create(payload) }; From 1700f979546edbe92c0d2a0dfb049200001bfe7b Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 17 Nov 2025 12:22:03 -0800 Subject: [PATCH 11/18] Changes to retry logic based on comments --- .../Configuration/ConfigurationTests.cs | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index deb43f3ecc..f143ff2cfb 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.Configuration; using System.IdentityModel.Tokens.Jwt; using System.IO; using System.IO.Abstractions; @@ -2597,20 +2596,8 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode, "The REST response is different from the expected result."); // MCP request - await Task.Delay(2000); - object mcpPayload = new - { - jsonrpc = "2.0", - id = 1, - method = "tools/list" - }; - HttpRequestMessage mcpRequest = new(HttpMethod.Post, configuration.Runtime.Mcp.Path) - { - Content = JsonContent.Create(mcpPayload) - }; - mcpRequest.Headers.Add("Accept", "*/*"); - HttpResponseMessage mcpResponse = await client.SendAsync(mcpRequest); - Assert.AreEqual(expectedStatusCodeForMcp, mcpResponse.StatusCode, "The MCP response is different from the expected result."); + HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client, configuration.Runtime.Mcp); + Assert.AreEqual(expectedStatusCodeForMcp, mcpResponseCode, "The MCP response is different from the expected result."); } // Hosted Scenario @@ -5304,7 +5291,7 @@ private static async Task GetRestResponsePostConfigHydration(Htt if (postConfigHydrationResult.StatusCode == HttpStatusCode.ServiceUnavailable) { retryCount--; - Thread.Sleep(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS)); + await Task.Delay(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS)); continue; } @@ -5349,7 +5336,7 @@ private static async Task GetGraphQLResponsePostConfigHydration( if (responseCode == HttpStatusCode.ServiceUnavailable) { retryCount--; - Thread.Sleep(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS)); + await Task.Delay(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS)); continue; } @@ -5392,7 +5379,7 @@ private static async Task GetMcpResponsePostConfigHydration(Http if (responseCode == HttpStatusCode.ServiceUnavailable || responseCode == HttpStatusCode.NotFound) { retryCount--; - Thread.Sleep(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS)); + await Task.Delay(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS)); continue; } From 4db0eab836939f96bc4ba823f4b2f88e12ffcd9f Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 17 Nov 2025 16:26:38 -0800 Subject: [PATCH 12/18] Change dab-config file to add mcp property --- src/Service/dab-config.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Service/dab-config.json b/src/Service/dab-config.json index 859267100e..0207b01e14 100644 --- a/src/Service/dab-config.json +++ b/src/Service/dab-config.json @@ -17,6 +17,9 @@ "path": "/graphql", "allow-introspection": true }, + "mcp": { + "enabled": true + }, "host": { "authentication": { "provider": "StaticWebApps" From 64312a9f1c33c71c89077e24a23511b191c5a2e3 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Fri, 5 Dec 2025 10:10:07 -0800 Subject: [PATCH 13/18] Add better retry mechanism --- .../Configuration/ConfigurationTests.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index f143ff2cfb..17ff164fce 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -5257,7 +5257,9 @@ private static JsonContent GetPostStartupConfigParams(string environment, Runtim /// by executing HTTP requests against the engine until a non-503 error is received. /// /// Client used for request execution. - /// Post-startup configuration + /// New config file content that will be added to DAB. + /// Endpoint through which content will be sent to DAB." + /// Global settings used at runtime for REST APIs. /// ServiceUnavailable if service is not successfully hydrated with config private static async Task HydratePostStartupConfiguration(HttpClient httpClient, JsonContent content, string configurationEndpoint, RestRuntimeOptions rest) { @@ -5273,15 +5275,16 @@ private static async Task HydratePostStartupConfiguration(HttpCl /// Executing REST requests against the engine until a non-503 error is received. /// /// Client used for request execution. + /// Global settings used at runtime for REST APIs. /// ServiceUnavailable if service is not successfully hydrated with config, /// else the response code from the REST request private static async Task GetRestResponsePostConfigHydration(HttpClient httpClient, RestRuntimeOptions rest) { - // Retry request RETRY_COUNT times in 1 second increments to allow required services + // Retry request RETRY_COUNT times in exponential increments to allow required services // time to instantiate and hydrate permissions. - int retryCount = RETRY_COUNT; + int retryCount = 0; HttpStatusCode responseCode = HttpStatusCode.ServiceUnavailable; - while (retryCount > 0) + while (retryCount < RETRY_COUNT) { // Spot test authorization resolver utilization to ensure configuration is used. HttpResponseMessage postConfigHydrationResult = @@ -5290,8 +5293,8 @@ private static async Task GetRestResponsePostConfigHydration(Htt if (postConfigHydrationResult.StatusCode == HttpStatusCode.ServiceUnavailable) { - retryCount--; - await Task.Delay(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS)); + retryCount++; + await Task.Delay(TimeSpan.FromSeconds(Math.Pow(RETRY_WAIT_SECONDS * 2, retryCount))); continue; } From f17f17964202079919c98c30e93327036dbb4aaf Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Wed, 10 Dec 2025 16:03:45 -0800 Subject: [PATCH 14/18] Remove hydration section of test --- src/Service.Tests/Configuration/ConfigurationTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 17ff164fce..605b966a79 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -2616,8 +2616,10 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn HttpStatusCode graphqlResponseCode = await GetGraphQLResponsePostConfigHydration(client, configuration.Runtime.GraphQL); Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode, "The GraphQL hydration post-response is different from the expected result."); - HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client, configuration.Runtime.Mcp); - Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode, "The MCP hydration post-response is different from the expected result."); + // TODO: Currently DAB is unable to start MCP with the hydration post-response. + // This needs to be fixed before uncommenting the MCP check + // HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client, configuration.Runtime.Mcp); + // Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode, "The MCP hydration post-response is different from the expected result."); } } From d4a816f6d7e844085cefb928706be4a4e8408c8f Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Wed, 10 Dec 2025 16:07:19 -0800 Subject: [PATCH 15/18] Remove changes from config file --- src/Service/dab-config.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Service/dab-config.json b/src/Service/dab-config.json index 0207b01e14..859267100e 100644 --- a/src/Service/dab-config.json +++ b/src/Service/dab-config.json @@ -17,9 +17,6 @@ "path": "/graphql", "allow-introspection": true }, - "mcp": { - "enabled": true - }, "host": { "authentication": { "provider": "StaticWebApps" From 7614d26f93aeb44fc30b7ff9147cf3f8a7cbed51 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Wed, 10 Dec 2025 16:15:43 -0800 Subject: [PATCH 16/18] Add exponential wait time --- .../Configuration/ConfigurationTests.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 605b966a79..6024fdee13 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -74,7 +74,7 @@ public class ConfigurationTests private const string BROWSER_ACCEPT_HEADER = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"; private const int RETRY_COUNT = 5; - private const int RETRY_WAIT_SECONDS = 1; + private const int RETRY_WAIT_SECONDS = 2; /// /// @@ -5296,7 +5296,7 @@ private static async Task GetRestResponsePostConfigHydration(Htt if (postConfigHydrationResult.StatusCode == HttpStatusCode.ServiceUnavailable) { retryCount++; - await Task.Delay(TimeSpan.FromSeconds(Math.Pow(RETRY_WAIT_SECONDS * 2, retryCount))); + await Task.Delay(TimeSpan.FromSeconds(Math.Pow(RETRY_WAIT_SECONDS, retryCount))); continue; } @@ -5314,11 +5314,11 @@ private static async Task GetRestResponsePostConfigHydration(Htt /// else the response code from the GRAPHQL request private static async Task GetGraphQLResponsePostConfigHydration(HttpClient httpClient, GraphQLRuntimeOptions graphQL) { - // Retry request RETRY_COUNT times in 1 second increments to allow required services + // Retry request RETRY_COUNT times in exponential increments to allow required services // time to instantiate and hydrate permissions. - int retryCount = RETRY_COUNT; + int retryCount = 0; HttpStatusCode responseCode = HttpStatusCode.ServiceUnavailable; - while (retryCount > 0) + while (retryCount < RETRY_COUNT) { string query = @"{ book_by_pk(id: 1) { @@ -5340,8 +5340,8 @@ private static async Task GetGraphQLResponsePostConfigHydration( if (responseCode == HttpStatusCode.ServiceUnavailable) { - retryCount--; - await Task.Delay(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS)); + retryCount++; + await Task.Delay(TimeSpan.FromSeconds(Math.Pow(RETRY_WAIT_SECONDS, retryCount))); continue; } @@ -5359,11 +5359,11 @@ private static async Task GetGraphQLResponsePostConfigHydration( /// else the response code from the MCP request private static async Task GetMcpResponsePostConfigHydration(HttpClient httpClient, McpRuntimeOptions mcp) { - // Retry request RETRY_COUNT times in 1 second increments to allow required services - // time to instantiate and hydrate permissions. - int retryCount = RETRY_COUNT; + // Retry request RETRY_COUNT times in exponential increments to allow required services + // time to instantiate and hydrate permissions. + int retryCount = 0; HttpStatusCode responseCode = HttpStatusCode.ServiceUnavailable; - while (retryCount > 0) + while (retryCount < RETRY_COUNT) { // Minimal MCP request (list tools) – valid JSON-RPC request object payload = new @@ -5383,8 +5383,8 @@ private static async Task GetMcpResponsePostConfigHydration(Http if (responseCode == HttpStatusCode.ServiceUnavailable || responseCode == HttpStatusCode.NotFound) { - retryCount--; - await Task.Delay(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS)); + retryCount++; + await Task.Delay(TimeSpan.FromSeconds(Math.Pow(RETRY_WAIT_SECONDS, retryCount))); continue; } From 2b6711ad872debbfffce4d821cd2ef52cde0fd31 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Sat, 13 Dec 2025 08:27:59 -0800 Subject: [PATCH 17/18] Changes based on comments --- src/Service.Tests/Configuration/ConfigurationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 6024fdee13..0f6f467a9a 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -2616,7 +2616,7 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn HttpStatusCode graphqlResponseCode = await GetGraphQLResponsePostConfigHydration(client, configuration.Runtime.GraphQL); Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode, "The GraphQL hydration post-response is different from the expected result."); - // TODO: Currently DAB is unable to start MCP with the hydration post-response. + // TODO: Issue #3012 - Currently DAB is unable to start MCP with the hydration post-response. // This needs to be fixed before uncommenting the MCP check // HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client, configuration.Runtime.Mcp); // Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode, "The MCP hydration post-response is different from the expected result."); From bec0a74beffdccfbdcfa5f2af05fbe947bdf811a Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Wed, 17 Dec 2025 15:58:27 -0800 Subject: [PATCH 18/18] Changes based on comments --- .../Configuration/ConfigurationTests.cs | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 3f6384ef34..9728c27ee7 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -2606,7 +2606,7 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode, "The REST response is different from the expected result."); // MCP request - HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client, configuration.Runtime.Mcp); + HttpStatusCode mcpResponseCode = await GetMcpResponse(client, configuration.Runtime.Mcp); Assert.AreEqual(expectedStatusCodeForMcp, mcpResponseCode, "The MCP response is different from the expected result."); } @@ -2628,7 +2628,7 @@ public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEn // TODO: Issue #3012 - Currently DAB is unable to start MCP with the hydration post-response. // This needs to be fixed before uncommenting the MCP check - // HttpStatusCode mcpResponseCode = await GetMcpResponsePostConfigHydration(client, configuration.Runtime.Mcp); + // HttpStatusCode mcpResponseCode = await GetMcpResponse(client, configuration.Runtime.Mcp); // Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode, "The MCP hydration post-response is different from the expected result."); } } @@ -5297,8 +5297,12 @@ private static async Task HydratePostStartupConfiguration(HttpCl /// else the response code from the REST request private static async Task GetRestResponsePostConfigHydration(HttpClient httpClient, RestRuntimeOptions rest) { - // Retry request RETRY_COUNT times in exponential increments to allow required services - // time to instantiate and hydrate permissions. + // Retry request RETRY_COUNT times in exponential increments to allow + // required services time to instantiate and hydrate permissions because + // the DAB services may take an unpredictable amount of time to become ready. + // + // The service might still fail due to the service not being available yet, + // but it is highly unlikely to be the case. int retryCount = 0; HttpStatusCode responseCode = HttpStatusCode.ServiceUnavailable; while (retryCount < RETRY_COUNT) @@ -5329,8 +5333,12 @@ private static async Task GetRestResponsePostConfigHydration(Htt /// else the response code from the GRAPHQL request private static async Task GetGraphQLResponsePostConfigHydration(HttpClient httpClient, GraphQLRuntimeOptions graphQL) { - // Retry request RETRY_COUNT times in exponential increments to allow required services - // time to instantiate and hydrate permissions. + // Retry request RETRY_COUNT times in exponential increments to allow + // required services time to instantiate and hydrate permissions because + // the DAB services may take an unpredictable amount of time to become ready. + // + // The service might still fail due to the service not being available yet, + // but it is highly unlikely to be the case. int retryCount = 0; HttpStatusCode responseCode = HttpStatusCode.ServiceUnavailable; while (retryCount < RETRY_COUNT) @@ -5372,10 +5380,14 @@ private static async Task GetGraphQLResponsePostConfigHydration( /// Client used for request execution. /// ServiceUnavailable if service is not successfully hydrated with config, /// else the response code from the MCP request - private static async Task GetMcpResponsePostConfigHydration(HttpClient httpClient, McpRuntimeOptions mcp) + public static async Task GetMcpResponse(HttpClient httpClient, McpRuntimeOptions mcp) { - // Retry request RETRY_COUNT times in exponential increments to allow required services - // time to instantiate and hydrate permissions. + // Retry request RETRY_COUNT times in exponential increments to allow + // required services time to instantiate and hydrate permissions because + // the DAB services may take an unpredictable amount of time to become ready. + // + // The service might still fail due to the service not being available yet, + // but it is highly unlikely to be the case. int retryCount = 0; HttpStatusCode responseCode = HttpStatusCode.ServiceUnavailable; while (retryCount < RETRY_COUNT)