Skip to content

Commit 61c2a85

Browse files
sezal98sezalchugAniruddh25
authored
[Health]: Test Case for Rest and GraphQL Requests to DAB during health check (#2667)
## Why make this change? Resolves #2662 ## What is this change? 1. Created the HttpUtilities with HttpClient already instantiated and basic details filled from Startup.cs 2. Using this httpClient, call the REST and GraphQL requests with relative URI 3. Created a function in DatabaseObject to access mapping dict as it is an abstract class which was not allowed to be mocked. 4. Test cases for both REST and GraphQL 5. Rest Test case: Mock the client to return a 200 response when we receive a GET request with specific URI. Validate the error message from HttpUtilities should be null. 6. GraphQL Test case: Mock the client to return a 200 response when we get a POST request with /graphql URI and content payload. Mock the Dabase Object with columns in the entity map to only return the db object when a certain entity is called for. ## How was this tested? - [ ] Integration Tests - [x] Unit Tests ## Sample Request(s) Requests are running as expected. Test cases added for Rest and Graphql --------- Co-authored-by: sezalchug <[email protected]> Co-authored-by: Aniruddh Munde <[email protected]>
1 parent 19e322f commit 61c2a85

File tree

10 files changed

+635
-379
lines changed

10 files changed

+635
-379
lines changed

src/Config/DatabasePrimitives/DatabaseObject.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public override int GetHashCode()
5555
/// <summary>
5656
/// Get the underlying SourceDefinition based on database object source type
5757
/// </summary>
58-
public SourceDefinition SourceDefinition
58+
public virtual SourceDefinition SourceDefinition
5959
{
6060
get
6161
{

src/Core/Services/MetadataProviders/ISqlMetadataProvider.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ bool VerifyForeignKeyExistsInDB(
121121
/// </summary>
122122
/// <param name="entityName">The entity whose mapping we lookup.</param>
123123
/// <param name="field">The field used for the lookup in the mapping.</param>
124-
/// <param name="name"/>Out parameter in which we will save backing column name.<param>
124+
/// <param name="name"/>Out parameter in which we will save backing column name.</param>
125125
/// <returns>True if exists, false otherwise.</returns>
126126
/// <throws>KeyNotFoundException if entity name not found.</throws>
127127
bool TryGetBackingColumn(string entityName, string field, [NotNullWhen(true)] out string? name);
@@ -132,6 +132,16 @@ bool VerifyForeignKeyExistsInDB(
132132
/// <returns></returns>
133133
DatabaseType GetDatabaseType();
134134

135+
/// <summary>
136+
/// Method to access the dictionary since DatabaseObject is abstract class
137+
/// </summary>
138+
/// <param name="key">Key.</param>
139+
/// <returns>DatabaseObject.</returns>
140+
public virtual DatabaseObject GetDatabaseObjectByKey(string key)
141+
{
142+
return EntityToDatabaseObject[key];
143+
}
144+
135145
IQueryBuilder GetQueryBuilder();
136146

137147
/// <summary>

src/Service.Tests/Configuration/ConfigurationTests.cs

Lines changed: 5 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
using System.Web;
2121
using Azure.DataApiBuilder.Auth;
2222
using Azure.DataApiBuilder.Config;
23-
using Azure.DataApiBuilder.Config.HealthCheck;
2423
using Azure.DataApiBuilder.Config.ObjectModel;
2524
using Azure.DataApiBuilder.Core;
2625
using Azure.DataApiBuilder.Core.AuthenticationHelpers;
@@ -4018,221 +4017,7 @@ public async Task HealthEndpoint_ValidateContents()
40184017
Dictionary<string, JsonElement> responseProperties = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(responseBody);
40194018
Assert.AreEqual(expected: HttpStatusCode.OK, actual: response.StatusCode, message: "Received unexpected HTTP code from health check endpoint.");
40204019

4021-
ValidateBasicDetailsHealthCheckResponse(responseProperties);
4022-
}
4023-
4024-
/// <summary>
4025-
/// Simulates a GET request to DAB's comprehensive health check endpoint ('/health') and validates the contents of the response.
4026-
/// The expected format of the response is the comprehensive health check response.
4027-
/// This test is currently flakey, failing intermittently in our pipeline, and is therefore ignored.
4028-
/// </summary>
4029-
[Ignore]
4030-
[TestMethod]
4031-
[TestCategory(TestCategory.MSSQL)]
4032-
[DataRow(true, true, true, true, true, true, true, DisplayName = "Validate Health Report all enabled.")]
4033-
[DataRow(false, true, true, true, true, true, true, DisplayName = "Validate when Comprehensive Health Report is disabled")]
4034-
[DataRow(true, true, true, false, true, true, true, DisplayName = "Validate Health Report when data-source health is disabled")]
4035-
[DataRow(true, true, true, true, false, true, true, DisplayName = "Validate Health Report when entity health is disabled")]
4036-
[DataRow(true, false, true, true, true, true, true, DisplayName = "Validate Health Report when global rest health is disabled")]
4037-
[DataRow(true, true, true, true, true, false, true, DisplayName = "Validate Health Report when entity rest health is disabled")]
4038-
[DataRow(true, true, false, true, true, true, true, DisplayName = "Validate Health Report when global graphql health is disabled")]
4039-
[DataRow(true, true, true, true, true, true, false, DisplayName = "Validate Health Report when entity graphql health is disabled")]
4040-
public async Task ComprehensiveHealthEndpoint_ValidateContents(bool enableGlobalHealth, bool enableGlobalRest, bool enableGlobalGraphql, bool enableDatasourceHealth, bool enableEntityHealth, bool enableEntityRest, bool enableEntityGraphQL)
4041-
{
4042-
// Arrange
4043-
// At least one entity is required in the runtime config for the engine to start.
4044-
// Even though this entity is not under test, it must be supplied enable successful
4045-
// config file creation.
4046-
Entity requiredEntity = new(
4047-
Health: new(enabled: enableEntityHealth),
4048-
Source: new("books", EntitySourceType.Table, null, null),
4049-
Rest: new(Enabled: enableEntityRest),
4050-
GraphQL: new("book", "books", enableEntityGraphQL),
4051-
Permissions: new[] { GetMinimalPermissionConfig(AuthorizationResolver.ROLE_ANONYMOUS) },
4052-
Relationships: null,
4053-
Mappings: null);
4054-
4055-
Dictionary<string, Entity> entityMap = new()
4056-
{
4057-
{ "Book", requiredEntity }
4058-
};
4059-
4060-
CreateCustomConfigFile(entityMap, enableGlobalRest, enableGlobalGraphql, enableGlobalHealth, enableDatasourceHealth, HostMode.Development);
4061-
4062-
string[] args = new[]
4063-
{
4064-
$"--ConfigFileName={CUSTOM_CONFIG_FILENAME}"
4065-
};
4066-
4067-
using (TestServer server = new(Program.CreateWebHostBuilder(args)))
4068-
using (HttpClient client = server.CreateClient())
4069-
{
4070-
HttpRequestMessage healthRequest = new(HttpMethod.Get, "/health");
4071-
HttpResponseMessage response = await client.SendAsync(healthRequest);
4072-
4073-
if (!enableGlobalHealth)
4074-
{
4075-
Assert.AreEqual(expected: HttpStatusCode.NotFound, actual: response.StatusCode, message: "Received unexpected HTTP code from health check endpoint.");
4076-
}
4077-
else
4078-
{
4079-
string responseBody = await response.Content.ReadAsStringAsync();
4080-
Dictionary<string, JsonElement> responseProperties = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(responseBody);
4081-
Assert.AreEqual(expected: HttpStatusCode.OK, actual: response.StatusCode, message: "Received unexpected HTTP code from health check endpoint.");
4082-
4083-
ValidateBasicDetailsHealthCheckResponse(responseProperties);
4084-
ValidateConfigurationDetailsHealthCheckResponse(responseProperties, enableGlobalRest, enableGlobalGraphql);
4085-
ValidateIfAttributePresentInResponse(responseProperties, enableDatasourceHealth, HealthCheckConstants.DATASOURCE);
4086-
ValidateIfAttributePresentInResponse(responseProperties, enableEntityHealth, HealthCheckConstants.ENDPOINT);
4087-
if (enableEntityHealth)
4088-
{
4089-
ValidateEntityRestAndGraphQLResponse(responseProperties, enableEntityRest, enableEntityGraphQL, enableGlobalRest, enableGlobalGraphql);
4090-
}
4091-
}
4092-
}
4093-
}
4094-
4095-
private static void ValidateEntityRestAndGraphQLResponse(
4096-
Dictionary<string, JsonElement> responseProperties,
4097-
bool enableEntityRest,
4098-
bool enableEntityGraphQL,
4099-
bool enableGlobalRest,
4100-
bool enableGlobalGraphQL)
4101-
{
4102-
bool hasRestTag = false, hasGraphQLTag = false;
4103-
if (responseProperties.TryGetValue("checks", out JsonElement checksElement) && checksElement.ValueKind == JsonValueKind.Array)
4104-
{
4105-
checksElement.EnumerateArray().ToList().ForEach(entityCheck =>
4106-
{
4107-
// Check if the 'tags' property exists and is of type array
4108-
if (entityCheck.TryGetProperty("tags", out JsonElement tagsElement) && tagsElement.ValueKind == JsonValueKind.Array)
4109-
{
4110-
hasRestTag = hasRestTag || tagsElement.EnumerateArray().Any(tag => tag.ToString() == HealthCheckConstants.REST);
4111-
hasGraphQLTag = hasGraphQLTag || tagsElement.EnumerateArray().Any(tag => tag.ToString() == HealthCheckConstants.GRAPHQL);
4112-
}
4113-
});
4114-
4115-
if (enableGlobalRest)
4116-
{
4117-
// When both enableEntityRest and hasRestTag match the same value
4118-
Assert.AreEqual(enableEntityRest, hasRestTag);
4119-
}
4120-
else
4121-
{
4122-
Assert.IsFalse(hasRestTag);
4123-
}
4124-
4125-
if (enableGlobalGraphQL)
4126-
{
4127-
// When both enableEntityGraphQL and hasGraphQLTag match the same value
4128-
Assert.AreEqual(enableEntityGraphQL, hasGraphQLTag);
4129-
}
4130-
else
4131-
{
4132-
Assert.IsFalse(hasGraphQLTag);
4133-
}
4134-
}
4135-
}
4136-
4137-
private static void ValidateIfAttributePresentInResponse(
4138-
Dictionary<string, JsonElement> responseProperties,
4139-
bool enableFlag,
4140-
string checkString)
4141-
{
4142-
if (responseProperties.TryGetValue("checks", out JsonElement checksElement) && checksElement.ValueKind == JsonValueKind.Array)
4143-
{
4144-
bool checksTags = checksElement.EnumerateArray().Any(entityCheck =>
4145-
{
4146-
if (entityCheck.TryGetProperty("tags", out JsonElement tagsElement) && tagsElement.ValueKind == JsonValueKind.Array)
4147-
{
4148-
return tagsElement.EnumerateArray().Any(tag => tag.ToString() == checkString);
4149-
}
4150-
4151-
return false;
4152-
});
4153-
4154-
Assert.AreEqual(enableFlag, checksTags);
4155-
}
4156-
else
4157-
{
4158-
Assert.Fail("Checks array is not present in the Comprehensive Health Check Report.");
4159-
}
4160-
}
4161-
4162-
private static void ValidateConfigurationIsNotNull(Dictionary<string, JsonElement> configPropertyValues, string objectKey)
4163-
{
4164-
Assert.IsTrue(configPropertyValues.ContainsKey(objectKey), $"Expected {objectKey} to be present in the configuration object.");
4165-
Assert.IsNotNull(configPropertyValues[objectKey], $"Expected {objectKey} to be non-null.");
4166-
}
4167-
4168-
private static void ValidateConfigurationIsCorrectFlag(Dictionary<string, JsonElement> configElement, string objectKey, bool enableFlag)
4169-
{
4170-
Assert.AreEqual(enableFlag, configElement[objectKey].GetBoolean(), $"Expected {objectKey} to be set to {enableFlag}.");
4171-
}
4172-
4173-
private static void ValidateConfigurationDetailsHealthCheckResponse(Dictionary<string, JsonElement> responseProperties, bool enableGlobalRest, bool enableGlobalGraphQL)
4174-
{
4175-
if (responseProperties.TryGetValue("configuration", out JsonElement configElement) && configElement.ValueKind == JsonValueKind.Object)
4176-
{
4177-
Dictionary<string, JsonElement> configPropertyValues = new();
4178-
4179-
// Enumerate through the configProperty's object properties and add them to the dictionary
4180-
foreach (JsonProperty property in configElement.EnumerateObject().ToList())
4181-
{
4182-
configPropertyValues[property.Name] = property.Value;
4183-
}
4184-
4185-
ValidateConfigurationIsNotNull(configPropertyValues, "rest");
4186-
ValidateConfigurationIsCorrectFlag(configPropertyValues, "rest", enableGlobalRest);
4187-
ValidateConfigurationIsNotNull(configPropertyValues, "graphql");
4188-
ValidateConfigurationIsCorrectFlag(configPropertyValues, "graphql", enableGlobalGraphQL);
4189-
ValidateConfigurationIsNotNull(configPropertyValues, "caching");
4190-
ValidateConfigurationIsNotNull(configPropertyValues, "telemetry");
4191-
ValidateConfigurationIsNotNull(configPropertyValues, "mode");
4192-
}
4193-
else
4194-
{
4195-
Assert.Fail("Missing 'configuration' object in Health Check Response.");
4196-
}
4197-
}
4198-
4199-
private static void ValidateBasicDetailsHealthCheckResponse(Dictionary<string, JsonElement> responseProperties)
4200-
{
4201-
// Validate value of 'status' property in reponse.
4202-
if (responseProperties.TryGetValue(key: "status", out JsonElement statusValue))
4203-
{
4204-
Assert.IsTrue(statusValue.ValueKind == JsonValueKind.String, "Unexpected or missing status value as string.");
4205-
}
4206-
else
4207-
{
4208-
Assert.Fail();
4209-
}
4210-
4211-
// Validate value of 'version' property in response.
4212-
if (responseProperties.TryGetValue(key: BasicHealthCheck.DAB_VERSION_KEY, out JsonElement versionValue))
4213-
{
4214-
Assert.AreEqual(
4215-
expected: ProductInfo.GetProductVersion(),
4216-
actual: versionValue.ToString(),
4217-
message: "Unexpected or missing version value.");
4218-
}
4219-
else
4220-
{
4221-
Assert.Fail();
4222-
}
4223-
4224-
// Validate value of 'app-name' property in response.
4225-
if (responseProperties.TryGetValue(key: BasicHealthCheck.DAB_APPNAME_KEY, out JsonElement appNameValue))
4226-
{
4227-
Assert.AreEqual(
4228-
expected: ProductInfo.GetDataApiBuilderUserAgent(),
4229-
actual: appNameValue.ToString(),
4230-
message: "Unexpected or missing DAB user agent string.");
4231-
}
4232-
else
4233-
{
4234-
Assert.Fail();
4235-
}
4020+
HealthEndpointTests.ValidateBasicDetailsHealthCheckResponse(responseProperties);
42364021
}
42374022

42384023
/// <summary>
@@ -4670,22 +4455,20 @@ public async Task TestNoDepthLimitOnGrahQLInNonHostedMode(int? depthLimit)
46704455
/// </summary>
46714456
/// <param name="entityMap">Collection of entityName -> Entity object.</param>
46724457
/// <param name="enableGlobalRest">flag to enable or disabled REST globally.</param>
4673-
private static void CreateCustomConfigFile(Dictionary<string, Entity> entityMap, bool enableGlobalRest = true, bool enableGlobalGraphql = true, bool enableGlobalHealth = true, bool enableDatasourceHealth = true, HostMode hostMode = HostMode.Production)
4458+
private static void CreateCustomConfigFile(Dictionary<string, Entity> entityMap, bool enableGlobalRest = true)
46744459
{
46754460
DataSource dataSource = new(
46764461
DatabaseType.MSSQL,
46774462
GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL),
4678-
Options: null,
4679-
Health: new(enableDatasourceHealth));
4680-
HostOptions hostOptions = new(Mode: hostMode, Cors: null, Authentication: new() { Provider = nameof(EasyAuthType.StaticWebApps) });
4463+
Options: null);
4464+
HostOptions hostOptions = new(Cors: null, Authentication: new() { Provider = nameof(EasyAuthType.StaticWebApps) });
46814465

46824466
RuntimeConfig runtimeConfig = new(
46834467
Schema: string.Empty,
46844468
DataSource: dataSource,
46854469
Runtime: new(
4686-
Health: new(enabled: enableGlobalHealth),
46874470
Rest: new(Enabled: enableGlobalRest),
4688-
GraphQL: new(Enabled: enableGlobalGraphql),
4471+
GraphQL: new(Enabled: true),
46894472
Host: hostOptions
46904473
),
46914474
Entities: new(entityMap));

0 commit comments

Comments
 (0)