Skip to content

Commit 0985d72

Browse files
authored
Merge branch 'main' into copilot/fix-health-check-stored-procedures
2 parents 9cf6772 + 0b43e3b commit 0985d72

1 file changed

Lines changed: 119 additions & 43 deletions

File tree

src/Service.Tests/Configuration/ConfigurationTests.cs

Lines changed: 119 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public class ConfigurationTests
7474
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";
7575

7676
private const int RETRY_COUNT = 5;
77-
private const int RETRY_WAIT_SECONDS = 1;
77+
private const int RETRY_WAIT_SECONDS = 2;
7878

7979
/// <summary>
8080
///
@@ -1128,7 +1128,7 @@ public async Task TestSqlSettingPostStartupConfigurations(string configurationEn
11281128
await httpClient.GetAsync($"/{OPENAPI_SWAGGER_ENDPOINT}");
11291129
Assert.AreEqual(HttpStatusCode.ServiceUnavailable, preConfigOpenApiSwaggerEndpointAvailability.StatusCode);
11301130

1131-
HttpStatusCode responseCode = await HydratePostStartupConfiguration(httpClient, content, configurationEndpoint);
1131+
HttpStatusCode responseCode = await HydratePostStartupConfiguration(httpClient, content, configurationEndpoint, configuration.Runtime.Rest);
11321132

11331133
// When the authorization resolver is properly configured, authorization will have failed
11341134
// because no auth headers are present.
@@ -2538,26 +2538,37 @@ public async Task TestRuntimeBaseRouteInNextLinkForPaginatedRestResponse()
25382538
/// <param name="expectedStatusCodeForGraphQL">Expected HTTP status code code for the GraphQL request</param>
25392539
[DataTestMethod]
25402540
[TestCategory(TestCategory.MSSQL)]
2541-
[DataRow(true, true, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Both Rest and GraphQL endpoints enabled globally")]
2542-
[DataRow(true, false, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest enabled and GraphQL endpoints disabled globally")]
2543-
[DataRow(false, true, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest disabled and GraphQL endpoints enabled globally")]
2544-
[DataRow(true, true, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Both Rest and GraphQL endpoints enabled globally")]
2545-
[DataRow(true, false, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest enabled and GraphQL endpoints disabled globally")]
2546-
[DataRow(false, true, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled and GraphQL endpoints enabled globally")]
2547-
public async Task TestGlobalFlagToEnableRestAndGraphQLForHostedAndNonHostedEnvironment(
2541+
[DataRow(true, true, true, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest, GraphQL, and MCP enabled globally")]
2542+
[DataRow(true, true, false, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest and GraphQL enabled, MCP disabled globally")]
2543+
[DataRow(true, false, true, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest enabled, GraphQL disabled, and MCP enabled globally")]
2544+
[DataRow(true, false, false, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest enabled, GraphQL and MCP disabled globally")]
2545+
[DataRow(false, true, true, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest disabled, GraphQL and MCP enabled globally")]
2546+
[DataRow(false, true, false, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest disabled, GraphQL enabled, and MCP disabled globally")]
2547+
[DataRow(false, false, true, HttpStatusCode.NotFound, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT, DisplayName = "V1 - Rest and GraphQL disabled, MCP enabled globally")]
2548+
[DataRow(true, true, true, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest, GraphQL, and MCP enabled globally")]
2549+
[DataRow(true, true, false, HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest and GraphQL enabled, MCP disabled globally")]
2550+
[DataRow(true, false, true, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest enabled, GraphQL disabled, and MCP enabled globally")]
2551+
[DataRow(true, false, false, HttpStatusCode.OK, HttpStatusCode.NotFound, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest enabled, GraphQL and MCP disabled globally")]
2552+
[DataRow(false, true, true, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled, GraphQL and MCP enabled globally")]
2553+
[DataRow(false, true, false, HttpStatusCode.NotFound, HttpStatusCode.OK, HttpStatusCode.NotFound, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest disabled, GraphQL enabled, and MCP disabled globally")]
2554+
[DataRow(false, false, true, HttpStatusCode.NotFound, HttpStatusCode.NotFound, HttpStatusCode.OK, CONFIGURATION_ENDPOINT_V2, DisplayName = "V2 - Rest and GraphQL disabled, MCP enabled globally")]
2555+
public async Task TestGlobalFlagToEnableRestGraphQLAndMcpForHostedAndNonHostedEnvironment(
25482556
bool isRestEnabled,
25492557
bool isGraphQLEnabled,
2558+
bool isMcpEnabled,
25502559
HttpStatusCode expectedStatusCodeForREST,
25512560
HttpStatusCode expectedStatusCodeForGraphQL,
2561+
HttpStatusCode expectedStatusCodeForMcp,
25522562
string configurationEndpoint)
25532563
{
25542564
GraphQLRuntimeOptions graphqlOptions = new(Enabled: isGraphQLEnabled);
25552565
RestRuntimeOptions restRuntimeOptions = new(Enabled: isRestEnabled);
2566+
McpRuntimeOptions mcpRuntimeOptions = new(Enabled: isMcpEnabled);
25562567

25572568
DataSource dataSource = new(DatabaseType.MSSQL,
25582569
GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);
25592570

2560-
RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, null);
2571+
RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions, restRuntimeOptions, mcpRuntimeOptions);
25612572
const string CUSTOM_CONFIG = "custom-config.json";
25622573
File.WriteAllText(CUSTOM_CONFIG, configuration.ToJson());
25632574

@@ -2580,17 +2591,23 @@ public async Task TestGlobalFlagToEnableRestAndGraphQLForHostedAndNonHostedEnvir
25802591

25812592
object payload = new { query };
25822593

2583-
HttpRequestMessage graphQLRequest = new(HttpMethod.Post, "/graphql")
2594+
// GraphQL request
2595+
HttpRequestMessage graphQLRequest = new(HttpMethod.Post, configuration.Runtime.GraphQL.Path)
25842596
{
25852597
Content = JsonContent.Create(payload)
25862598
};
25872599

25882600
HttpResponseMessage graphQLResponse = await client.SendAsync(graphQLRequest);
2589-
Assert.AreEqual(expectedStatusCodeForGraphQL, graphQLResponse.StatusCode);
2601+
Assert.AreEqual(expectedStatusCodeForGraphQL, graphQLResponse.StatusCode, "The GraphQL response is different from the expected result.");
25902602

2591-
HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/Book");
2603+
// REST request
2604+
HttpRequestMessage restRequest = new(HttpMethod.Get, $"{configuration.Runtime.Rest.Path}/Book");
25922605
HttpResponseMessage restResponse = await client.SendAsync(restRequest);
2593-
Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode);
2606+
Assert.AreEqual(expectedStatusCodeForREST, restResponse.StatusCode, "The REST response is different from the expected result.");
2607+
2608+
// MCP request
2609+
HttpStatusCode mcpResponseCode = await GetMcpResponse(client, configuration.Runtime.Mcp);
2610+
Assert.AreEqual(expectedStatusCodeForMcp, mcpResponseCode, "The MCP response is different from the expected result.");
25942611
}
25952612

25962613
// Hosted Scenario
@@ -2600,18 +2617,19 @@ public async Task TestGlobalFlagToEnableRestAndGraphQLForHostedAndNonHostedEnvir
26002617
{
26012618
JsonContent content = GetPostStartupConfigParams(MSSQL_ENVIRONMENT, configuration, configurationEndpoint);
26022619

2603-
HttpResponseMessage postResult =
2604-
await client.PostAsync(configurationEndpoint, content);
2605-
Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode);
2606-
2607-
HttpStatusCode restResponseCode = await GetRestResponsePostConfigHydration(client);
2608-
2609-
Assert.AreEqual(expected: expectedStatusCodeForREST, actual: restResponseCode);
2620+
HttpResponseMessage postResult = await client.PostAsync(configurationEndpoint, content);
2621+
Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode, "The hydration post-response is different from the expected result.");
26102622

2611-
HttpStatusCode graphqlResponseCode = await GetGraphQLResponsePostConfigHydration(client);
2623+
HttpStatusCode restResponseCode = await GetRestResponsePostConfigHydration(client, configuration.Runtime.Rest);
2624+
Assert.AreEqual(expected: expectedStatusCodeForREST, actual: restResponseCode, "The REST hydration post-response is different from the expected result.");
26122625

2613-
Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode);
2626+
HttpStatusCode graphqlResponseCode = await GetGraphQLResponsePostConfigHydration(client, configuration.Runtime.GraphQL);
2627+
Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode, "The GraphQL hydration post-response is different from the expected result.");
26142628

2629+
// TODO: Issue #3012 - Currently DAB is unable to start MCP with the hydration post-response.
2630+
// This needs to be fixed before uncommenting the MCP check
2631+
// HttpStatusCode mcpResponseCode = await GetMcpResponse(client, configuration.Runtime.Mcp);
2632+
// Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode, "The MCP hydration post-response is different from the expected result.");
26152633
}
26162634
}
26172635

@@ -3661,7 +3679,7 @@ public async Task TestSchemaIntrospectionQuery(bool enableIntrospection, bool ex
36613679
using (HttpClient client = server.CreateClient())
36623680
{
36633681
JsonContent content = GetPostStartupConfigParams(MSSQL_ENVIRONMENT, configuration, configurationEndpoint);
3664-
HttpStatusCode responseCode = await HydratePostStartupConfiguration(client, content, configurationEndpoint);
3682+
HttpStatusCode responseCode = await HydratePostStartupConfiguration(client, content, configurationEndpoint, configuration.Runtime.Rest);
36653683

36663684
Assert.AreEqual(expected: HttpStatusCode.OK, actual: responseCode, message: "Configuration hydration failed.");
36673685

@@ -5256,41 +5274,48 @@ private static JsonContent GetPostStartupConfigParams(string environment, Runtim
52565274
/// by executing HTTP requests against the engine until a non-503 error is received.
52575275
/// </summary>
52585276
/// <param name="httpClient">Client used for request execution.</param>
5259-
/// <param name="config">Post-startup configuration</param>
5277+
/// <param name="content">New config file content that will be added to DAB.</param>
5278+
/// <param name="configurationEndpoint">Endpoint through which content will be sent to DAB."</param>
5279+
/// <param name="rest">Global settings used at runtime for REST APIs.</param>
52605280
/// <returns>ServiceUnavailable if service is not successfully hydrated with config</returns>
5261-
private static async Task<HttpStatusCode> HydratePostStartupConfiguration(HttpClient httpClient, JsonContent content, string configurationEndpoint)
5281+
private static async Task<HttpStatusCode> HydratePostStartupConfiguration(HttpClient httpClient, JsonContent content, string configurationEndpoint, RestRuntimeOptions rest)
52625282
{
52635283
// Hydrate configuration post-startup
52645284
HttpResponseMessage postResult =
52655285
await httpClient.PostAsync(configurationEndpoint, content);
52665286
Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode);
52675287

5268-
return await GetRestResponsePostConfigHydration(httpClient);
5288+
return await GetRestResponsePostConfigHydration(httpClient, rest);
52695289
}
52705290

52715291
/// <summary>
52725292
/// Executing REST requests against the engine until a non-503 error is received.
52735293
/// </summary>
52745294
/// <param name="httpClient">Client used for request execution.</param>
5295+
/// <param name="rest">Global settings used at runtime for REST APIs.</param>
52755296
/// <returns>ServiceUnavailable if service is not successfully hydrated with config,
52765297
/// else the response code from the REST request</returns>
5277-
private static async Task<HttpStatusCode> GetRestResponsePostConfigHydration(HttpClient httpClient)
5298+
private static async Task<HttpStatusCode> GetRestResponsePostConfigHydration(HttpClient httpClient, RestRuntimeOptions rest)
52785299
{
5279-
// Retry request RETRY_COUNT times in 1 second increments to allow required services
5280-
// time to instantiate and hydrate permissions.
5281-
int retryCount = RETRY_COUNT;
5300+
// Retry request RETRY_COUNT times in exponential increments to allow
5301+
// required services time to instantiate and hydrate permissions because
5302+
// the DAB services may take an unpredictable amount of time to become ready.
5303+
//
5304+
// The service might still fail due to the service not being available yet,
5305+
// but it is highly unlikely to be the case.
5306+
int retryCount = 0;
52825307
HttpStatusCode responseCode = HttpStatusCode.ServiceUnavailable;
5283-
while (retryCount > 0)
5308+
while (retryCount < RETRY_COUNT)
52845309
{
52855310
// Spot test authorization resolver utilization to ensure configuration is used.
52865311
HttpResponseMessage postConfigHydrationResult =
5287-
await httpClient.GetAsync($"api/{POST_STARTUP_CONFIG_ENTITY}");
5312+
await httpClient.GetAsync($"{rest.Path}/{POST_STARTUP_CONFIG_ENTITY}");
52885313
responseCode = postConfigHydrationResult.StatusCode;
52895314

52905315
if (postConfigHydrationResult.StatusCode == HttpStatusCode.ServiceUnavailable)
52915316
{
5292-
retryCount--;
5293-
Thread.Sleep(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS));
5317+
retryCount++;
5318+
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(RETRY_WAIT_SECONDS, retryCount)));
52945319
continue;
52955320
}
52965321

@@ -5306,13 +5331,17 @@ private static async Task<HttpStatusCode> GetRestResponsePostConfigHydration(Htt
53065331
/// <param name="httpClient">Client used for request execution.</param>
53075332
/// <returns>ServiceUnavailable if service is not successfully hydrated with config,
53085333
/// else the response code from the GRAPHQL request</returns>
5309-
private static async Task<HttpStatusCode> GetGraphQLResponsePostConfigHydration(HttpClient httpClient)
5334+
private static async Task<HttpStatusCode> GetGraphQLResponsePostConfigHydration(HttpClient httpClient, GraphQLRuntimeOptions graphQL)
53105335
{
5311-
// Retry request RETRY_COUNT times in 1 second increments to allow required services
5312-
// time to instantiate and hydrate permissions.
5313-
int retryCount = RETRY_COUNT;
5336+
// Retry request RETRY_COUNT times in exponential increments to allow
5337+
// required services time to instantiate and hydrate permissions because
5338+
// the DAB services may take an unpredictable amount of time to become ready.
5339+
//
5340+
// The service might still fail due to the service not being available yet,
5341+
// but it is highly unlikely to be the case.
5342+
int retryCount = 0;
53145343
HttpStatusCode responseCode = HttpStatusCode.ServiceUnavailable;
5315-
while (retryCount > 0)
5344+
while (retryCount < RETRY_COUNT)
53165345
{
53175346
string query = @"{
53185347
book_by_pk(id: 1) {
@@ -5324,7 +5353,7 @@ private static async Task<HttpStatusCode> GetGraphQLResponsePostConfigHydration(
53245353

53255354
object payload = new { query };
53265355

5327-
HttpRequestMessage graphQLRequest = new(HttpMethod.Post, "/graphql")
5356+
HttpRequestMessage graphQLRequest = new(HttpMethod.Post, graphQL.Path)
53285357
{
53295358
Content = JsonContent.Create(payload)
53305359
};
@@ -5334,8 +5363,55 @@ private static async Task<HttpStatusCode> GetGraphQLResponsePostConfigHydration(
53345363

53355364
if (responseCode == HttpStatusCode.ServiceUnavailable)
53365365
{
5337-
retryCount--;
5338-
Thread.Sleep(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS));
5366+
retryCount++;
5367+
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(RETRY_WAIT_SECONDS, retryCount)));
5368+
continue;
5369+
}
5370+
5371+
break;
5372+
}
5373+
5374+
return responseCode;
5375+
}
5376+
5377+
/// <summary>
5378+
/// Executing MCP POST requests against the engine until a non-503 error is received.
5379+
/// </summary>
5380+
/// <param name="httpClient">Client used for request execution.</param>
5381+
/// <returns>ServiceUnavailable if service is not successfully hydrated with config,
5382+
/// else the response code from the MCP request</returns>
5383+
public static async Task<HttpStatusCode> GetMcpResponse(HttpClient httpClient, McpRuntimeOptions mcp)
5384+
{
5385+
// Retry request RETRY_COUNT times in exponential increments to allow
5386+
// required services time to instantiate and hydrate permissions because
5387+
// the DAB services may take an unpredictable amount of time to become ready.
5388+
//
5389+
// The service might still fail due to the service not being available yet,
5390+
// but it is highly unlikely to be the case.
5391+
int retryCount = 0;
5392+
HttpStatusCode responseCode = HttpStatusCode.ServiceUnavailable;
5393+
while (retryCount < RETRY_COUNT)
5394+
{
5395+
// Minimal MCP request (list tools) – valid JSON-RPC request
5396+
object payload = new
5397+
{
5398+
jsonrpc = "2.0",
5399+
id = 1,
5400+
method = "tools/list"
5401+
};
5402+
HttpRequestMessage mcpRequest = new(HttpMethod.Post, mcp.Path)
5403+
{
5404+
Content = JsonContent.Create(payload)
5405+
};
5406+
mcpRequest.Headers.Add("Accept", "*/*");
5407+
5408+
HttpResponseMessage mcpResponse = await httpClient.SendAsync(mcpRequest);
5409+
responseCode = mcpResponse.StatusCode;
5410+
5411+
if (responseCode == HttpStatusCode.ServiceUnavailable || responseCode == HttpStatusCode.NotFound)
5412+
{
5413+
retryCount++;
5414+
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(RETRY_WAIT_SECONDS, retryCount)));
53395415
continue;
53405416
}
53415417

0 commit comments

Comments
 (0)