Skip to content
Open
Changes from all commits
Commits
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
106 changes: 89 additions & 17 deletions src/Service.Tests/Configuration/ConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2528,26 +2528,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 enabled globally")]
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The display name incorrectly states 'GraphQL and MCP enabled globally' when the test parameters show both are disabled (false, false). Should be 'GraphQL and MCP disabled globally'.

Suggested change
[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")]

Copilot uses AI. Check for mistakes.
[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")]
Copy link

Copilot AI Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The display name incorrectly states 'GraphQL and MCP enabled globally' when the test parameters show both are disabled (false, false). Should be 'GraphQL and MCP disabled globally'.

Suggested change
[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")]

Copilot uses AI. Check for mistakes.
[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 @@ -2570,17 +2581,35 @@ public async Task TestGlobalFlagToEnableRestAndGraphQLForHostedAndNonHostedEnvir

object payload = new { query };

// GraphQL request
HttpRequestMessage graphQLRequest = new(HttpMethod.Post, "/graphql")
{
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.");

// 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add a try-wait condition (within a limit- 5 seconds?) until he /mcp endpoint is available instead of hard-coded 2 seconds of wait?

object mcpPayload = new
{
jsonrpc = "2.0",
id = 1,
method = "tools/list"
};
HttpRequestMessage mcpRequest = new(HttpMethod.Post, "/mcp")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we get the MCP path from RuntimeConfig or McpRuntimeOptions itself since that is where it will be configured?

{
Content = JsonContent.Create(mcpPayload)
};
mcpRequest.Headers.Add("Accept", "*/*");
HttpResponseMessage mcpResponse = await client.SendAsync(mcpRequest);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why cant we use the same GetMcpResponsePostConfigHydration method here to account for the retry logic?

Assert.AreEqual(expectedStatusCodeForMcp, mcpResponse.StatusCode, "The MCP response is different from the expected result.");
}

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

HttpResponseMessage postResult =
await client.PostAsync(configurationEndpoint, content);
Assert.AreEqual(HttpStatusCode.OK, postResult.StatusCode);
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);

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

Expand Down Expand Up @@ -5330,6 +5358,50 @@ private static async Task<HttpStatusCode> GetGraphQLResponsePostConfigHydration(
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>
private static async Task<HttpStatusCode> 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
await Task.Delay(2000);
object payload = new
{
jsonrpc = "2.0",
id = 1,
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;

if (responseCode == HttpStatusCode.ServiceUnavailable || responseCode == HttpStatusCode.NotFound)
{
retryCount--;
Thread.Sleep(TimeSpan.FromSeconds(RETRY_WAIT_SECONDS));
continue;
Comment on lines +5395 to +5396
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is asynchronous but using Thread.Sleep() you can block the current thread. Instead use Task.Await(). Additionally, please check if there is a deterministic way to wait for /mcp endpoint within a given limit, instead of a hard-coded value which might vary in different envrionments (ADO pipelines) or scenarios.

}

break;
}

return responseCode;
}

/// <summary>
/// Helper method to instantiate RuntimeConfig object needed for multiple create tests.
/// </summary>
Expand Down
Loading