Skip to content
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
52b53ef
Add missing test
RubenCerna2079 Oct 14, 2025
de28682
Add MCP tests
RubenCerna2079 Oct 27, 2025
80e6b2e
Fix formatting error
RubenCerna2079 Oct 27, 2025
8fae2b8
Add missing header
RubenCerna2079 Oct 27, 2025
ccab7f1
Add messages to allow for easier identification of failures
RubenCerna2079 Oct 28, 2025
edd0943
Add request header in hydration section
RubenCerna2079 Oct 28, 2025
5db169f
Fix error messages
RubenCerna2079 Oct 28, 2025
adc89d2
Add status code to repeat in case post fails after hydration
RubenCerna2079 Oct 28, 2025
39647da
Add delay in hydration
RubenCerna2079 Oct 28, 2025
676d70c
Changes based on comments
RubenCerna2079 Nov 13, 2025
98a18af
Merge branch 'main' into dev/rubencerna/mcp-fix-tests
RubenCerna2079 Nov 13, 2025
1700f97
Changes to retry logic based on comments
RubenCerna2079 Nov 17, 2025
4db0eab
Change dab-config file to add mcp property
RubenCerna2079 Nov 18, 2025
64312a9
Add better retry mechanism
RubenCerna2079 Dec 5, 2025
f17f179
Remove hydration section of test
RubenCerna2079 Dec 11, 2025
d4a816f
Remove changes from config file
RubenCerna2079 Dec 11, 2025
7614d26
Add exponential wait time
RubenCerna2079 Dec 11, 2025
2b36641
Merge branch 'main' into dev/rubencerna/mcp-fix-tests
RubenCerna2079 Dec 11, 2025
2b6711a
Changes based on comments
RubenCerna2079 Dec 13, 2025
bf33772
Merge remote-tracking branch 'origin/dev/rubencerna/mcp-fix-tests' in…
RubenCerna2079 Dec 13, 2025
bec0a74
Changes based on comments
RubenCerna2079 Dec 17, 2025
ae38237
Merge branch 'main' into dev/rubencerna/mcp-fix-tests
RubenCerna2079 Dec 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 119 additions & 43 deletions src/Service.Tests/Configuration/ConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
///
Expand Down Expand Up @@ -1128,7 +1128,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.
Expand Down Expand Up @@ -2538,26 +2538,37 @@ public async Task TestRuntimeBaseRouteInNextLinkForPaginatedRestResponse()
/// <param name="expectedStatusCodeForGraphQL">Expected HTTP status code code for the GraphQL request</param>
[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 - 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 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 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")]
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());

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

object payload = new { query };

HttpRequestMessage graphQLRequest = new(HttpMethod.Post, "/graphql")
// GraphQL request
HttpRequestMessage graphQLRequest = new(HttpMethod.Post, configuration.Runtime.GraphQL.Path)
{
Content = JsonContent.Create(payload)
};

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.");

HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/Book");
// REST request
HttpRequestMessage restRequest = new(HttpMethod.Get, $"{configuration.Runtime.Rest.Path}/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
HttpStatusCode mcpResponseCode = await GetMcpResponse(client, configuration.Runtime.Mcp);
Assert.AreEqual(expectedStatusCodeForMcp, mcpResponseCode, "The MCP response is different from the expected result.");
}

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

HttpResponseMessage postResult =
await client.PostAsync(configurationEndpoint, content);
Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode);

HttpStatusCode restResponseCode = await GetRestResponsePostConfigHydration(client);

Assert.AreEqual(expected: expectedStatusCodeForREST, actual: restResponseCode);
HttpResponseMessage postResult = await client.PostAsync(configurationEndpoint, content);
Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode, "The hydration post-response is different from the expected result.");

HttpStatusCode graphqlResponseCode = await GetGraphQLResponsePostConfigHydration(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.");

Assert.AreEqual(expected: expectedStatusCodeForGraphQL, actual: graphqlResponseCode);
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: 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 GetMcpResponse(client, configuration.Runtime.Mcp);
// Assert.AreEqual(expected: expectedStatusCodeForMcp, actual: mcpResponseCode, "The MCP hydration post-response is different from the expected result.");
}
}

Expand Down Expand Up @@ -3661,7 +3679,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.");

Expand Down Expand Up @@ -5256,41 +5274,48 @@ private static JsonContent GetPostStartupConfigParams(string environment, Runtim
/// by executing HTTP requests against the engine until a non-503 error is received.
/// </summary>
/// <param name="httpClient">Client used for request execution.</param>
/// <param name="config">Post-startup configuration</param>
/// <param name="content">New config file content that will be added to DAB.</param>
/// <param name="configurationEndpoint">Endpoint through which content will be sent to DAB."</param>
/// <param name="rest">Global settings used at runtime for REST APIs.</param>
/// <returns>ServiceUnavailable if service is not successfully hydrated with config</returns>
private static async Task<HttpStatusCode> HydratePostStartupConfiguration(HttpClient httpClient, JsonContent content, string configurationEndpoint)
private static async Task<HttpStatusCode> 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);
}

/// <summary>
/// Executing REST requests against the engine until a non-503 error is received.
/// </summary>
/// <param name="httpClient">Client used for request execution.</param>
/// <param name="rest">Global settings used at runtime for REST APIs.</param>
/// <returns>ServiceUnavailable if service is not successfully hydrated with config,
/// else the response code from the REST request</returns>
private static async Task<HttpStatusCode> GetRestResponsePostConfigHydration(HttpClient httpClient)
private static async Task<HttpStatusCode> GetRestResponsePostConfigHydration(HttpClient httpClient, RestRuntimeOptions rest)
{
// 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 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 > 0)
while (retryCount < RETRY_COUNT)
{
// 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)
{
retryCount--;
Thread.Sleep(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS));
retryCount++;
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(RETRY_WAIT_SECONDS, retryCount)));
continue;
}

Expand All @@ -5306,13 +5331,17 @@ private static async Task<HttpStatusCode> GetRestResponsePostConfigHydration(Htt
/// <param name="httpClient">Client used for request execution.</param>
/// <returns>ServiceUnavailable if service is not successfully hydrated with config,
/// else the response code from the GRAPHQL request</returns>
private static async Task<HttpStatusCode> GetGraphQLResponsePostConfigHydration(HttpClient httpClient)
private static async Task<HttpStatusCode> GetGraphQLResponsePostConfigHydration(HttpClient httpClient, GraphQLRuntimeOptions graphQL)
{
// 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 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 > 0)
while (retryCount < RETRY_COUNT)
{
string query = @"{
book_by_pk(id: 1) {
Expand All @@ -5324,7 +5353,7 @@ private static async Task<HttpStatusCode> GetGraphQLResponsePostConfigHydration(

object payload = new { query };

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

if (responseCode == HttpStatusCode.ServiceUnavailable)
{
retryCount--;
Thread.Sleep(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS));
retryCount++;
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(RETRY_WAIT_SECONDS, retryCount)));
continue;
}

break;
}

return responseCode;
}

/// <summary>
/// Executing MCP POST requests against the engine until a non-503 error is received.
/// </summary>
/// <param name="httpClient">Client used for request execution.</param>
/// <returns>ServiceUnavailable if service is not successfully hydrated with config,
/// else the response code from the MCP request</returns>
public static async Task<HttpStatusCode> GetMcpResponse(HttpClient httpClient, McpRuntimeOptions mcp)
{
// 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)
{
// Minimal MCP request (list tools) – valid JSON-RPC request
object payload = new
{
jsonrpc = "2.0",
id = 1,
method = "tools/list"
};
HttpRequestMessage mcpRequest = new(HttpMethod.Post, mcp.Path)
{
Content = JsonContent.Create(payload)
};
mcpRequest.Headers.Add("Accept", "*/*");

HttpResponseMessage mcpResponse = await httpClient.SendAsync(mcpRequest);
responseCode = mcpResponse.StatusCode;

if (responseCode == HttpStatusCode.ServiceUnavailable || responseCode == HttpStatusCode.NotFound)
{
retryCount++;
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(RETRY_WAIT_SECONDS, retryCount)));
continue;
}

Expand Down
Loading