Skip to content

Commit fac7148

Browse files
aaronburtleseantleonardabhishekkumams
authored
Add End to End test for runtime section in a Hot-Reload scenario (#2439)
## Why make this change? Adds support for End to End tests within the ConfigurationTests class, along with tests for hot-reloading the runtime section of the config. Closes #2438 Closes #2109 ## What is this change? Add a function for generating a file to be used for starting the engine and then for hot-reloading. In other words, this function creates a valid string using arguments to replace portions of the string that it generates to represent the config file, it then writes that string as a file to the current working directory. If this file is what we use to start the engine, calling this function again will overwrite that file and initiate a hot-reload. We use this function then to overwrite the file with new runtime values and hot-reload, demonstrating that hot-reload will work for the runtime section. Once the hot-reload has happened we can validate that the engine is able to correctly process requests. ## How was this tested? Tests create a valid config file, start the engine with that file, assert that requests function correctly, modify that file by saving a new one with the same path triggering a hot-reload, run new requests and assert that they function correctly. Specifically we test that new paths still result in valid results. And that changing the value of enabled from true to false results in the response changing from status OK to status NotFound. ## Sample Request(s) - Example REST and/or GraphQL request to demonstrate modifications - Example of CLI usage to demonstrate modifications --------- Co-authored-by: Sean Leonard <[email protected]> Co-authored-by: Abhishek Kumar <[email protected]> Co-authored-by: Abhishek Kumar <[email protected]>
1 parent 4f22ca8 commit fac7148

File tree

3 files changed

+302
-8
lines changed

3 files changed

+302
-8
lines changed

src/Service.Tests/Configuration/ConfigurationTests.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4592,7 +4592,6 @@ private static async Task<HttpStatusCode> GetGraphQLResponsePostConfigHydration(
45924592
/// <summary>
45934593
/// Helper method to instantiate RuntimeConfig object needed for multiple create tests.
45944594
/// </summary>
4595-
/// <returns></returns>
45964595
public static RuntimeConfig InitialzieRuntimeConfigForMultipleCreateTests(bool isMultipleCreateOperationEnabled)
45974596
{
45984597
// Multiple create operations are enabled.
@@ -4667,7 +4666,6 @@ public static RuntimeConfig InitialzieRuntimeConfigForMultipleCreateTests(bool i
46674666
/// Instantiate minimal runtime config with custom global settings.
46684667
/// </summary>
46694668
/// <param name="dataSource">DataSource to pull connection string required for engine start.</param>
4670-
/// <returns></returns>
46714669
public static RuntimeConfig InitMinimalRuntimeConfig(
46724670
DataSource dataSource,
46734671
GraphQLRuntimeOptions graphqlOptions,
@@ -4818,7 +4816,6 @@ private static void ValidateCosmosDbSetup(TestServer server)
48184816
/// <summary>
48194817
/// Create basic runtime config with given DatabaseType and connectionString with no entity.
48204818
/// </summary>
4821-
/// <returns></returns>
48224819
private static RuntimeConfig CreateBasicRuntimeConfigWithNoEntity(
48234820
DatabaseType dbType = DatabaseType.MSSQL,
48244821
string connectionString = "")
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.IO;
5+
using System.Net;
6+
using System.Net.Http;
7+
using System.Net.Http.Json;
8+
using System.Text.Json;
9+
using System.Threading.Tasks;
10+
using Azure.DataApiBuilder.Config.ObjectModel;
11+
using Azure.DataApiBuilder.Core.Configurations;
12+
using Azure.DataApiBuilder.Service.Tests.SqlTests;
13+
using Microsoft.AspNetCore.TestHost;
14+
using Microsoft.Extensions.DependencyInjection;
15+
using Microsoft.VisualStudio.TestTools.UnitTesting;
16+
17+
namespace Azure.DataApiBuilder.Service.Tests.Configuration.HotReload;
18+
19+
[TestClass]
20+
public class ConfigurationHotReloadTests
21+
{
22+
private const string MSSQL_ENVIRONMENT = TestCategory.MSSQL;
23+
private static TestServer _testServer;
24+
private static HttpClient _testClient;
25+
private static RuntimeConfigProvider _configProvider;
26+
internal const string CONFIG_FILE_NAME = "hot-reload.dab-config.json";
27+
internal const string GQL_QUERY_NAME = "books";
28+
29+
internal const string GQL_QUERY = @"{
30+
books(first: 100) {
31+
items {
32+
id
33+
title
34+
publisher_id
35+
}
36+
}
37+
}";
38+
39+
internal static string _bookDBOContents;
40+
41+
private static void GenerateConfigFile(
42+
DatabaseType databaseType = DatabaseType.MSSQL,
43+
string connectionString = "",
44+
string restPath = "rest",
45+
string restEnabled = "true",
46+
string gQLPath = "/graphQL",
47+
string gQLEnabled = "true",
48+
string entityName = "Book",
49+
string sourceObject = "books",
50+
string gQLEntityEnabled = "true",
51+
string gQLEntitySingular = "book",
52+
string gQLEntityPlural = "books",
53+
string restEntityEnabled = "true",
54+
string entityBackingColumn = "title",
55+
string entityExposedName = "title",
56+
string configFileName = CONFIG_FILE_NAME)
57+
{
58+
File.WriteAllText(configFileName, @"
59+
{
60+
""$schema"": """",
61+
""data-source"": {
62+
""database-type"": """ + databaseType + @""",
63+
""options"": {
64+
""set-session-context"": true
65+
},
66+
""connection-string"": """ + connectionString + @"""
67+
},
68+
""runtime"": {
69+
""rest"": {
70+
""enabled"": " + restEnabled + @",
71+
""path"": ""/" + restPath + @""",
72+
""request-body-strict"": true
73+
},
74+
""graphql"": {
75+
""enabled"": " + gQLEnabled + @",
76+
""path"": """ + gQLPath + @""",
77+
""allow-introspection"": true
78+
},
79+
""host"": {
80+
""cors"": {
81+
""origins"": [
82+
""http://localhost:5000""
83+
],
84+
""allow-credentials"": false
85+
},
86+
""authentication"": {
87+
""provider"": ""StaticWebApps""
88+
},
89+
""mode"": ""development""
90+
}
91+
},
92+
""entities"": {
93+
""" + entityName + @""": {
94+
""source"": {
95+
""object"": """ + sourceObject + @""",
96+
""type"": ""table""
97+
},
98+
""graphql"": {
99+
""enabled"": " + gQLEntityEnabled + @",
100+
""type"": {
101+
""singular"": """ + gQLEntitySingular + @""",
102+
""plural"": """ + gQLEntityPlural + @"""
103+
}
104+
},
105+
""rest"": {
106+
""enabled"": " + restEntityEnabled + @"
107+
},
108+
""permissions"": [
109+
{
110+
""role"": ""anonymous"",
111+
""actions"": [
112+
{
113+
""action"": ""create""
114+
},
115+
{
116+
""action"": ""read""
117+
},
118+
{
119+
""action"": ""update""
120+
},
121+
{
122+
""action"": ""delete""
123+
}
124+
]
125+
},
126+
{
127+
""role"": ""authenticated"",
128+
""actions"": [
129+
{
130+
""action"": ""create""
131+
},
132+
{
133+
""action"": ""read""
134+
},
135+
{
136+
""action"": ""update""
137+
},
138+
{
139+
""action"": ""delete""
140+
}
141+
]
142+
}
143+
],
144+
""mappings"": {
145+
""" + entityBackingColumn + @""": """ + entityExposedName + @"""
146+
}
147+
}
148+
}
149+
}");
150+
}
151+
152+
/// <summary>
153+
/// Initialize the test fixture by creating the initial configuration file and starting
154+
/// the test server with it. Validate that the test server returns OK status when handling
155+
/// valid requests.
156+
/// </summary>
157+
[ClassInitialize]
158+
public static async Task ClassInitializeAsync(TestContext context)
159+
{
160+
// Arrange
161+
GenerateConfigFile(connectionString: $"{ConfigurationTests.GetConnectionStringFromEnvironmentConfig(TestCategory.MSSQL).Replace("\\", "\\\\")}");
162+
_testServer = new(Program.CreateWebHostBuilder(new string[] { "--ConfigFileName", CONFIG_FILE_NAME }));
163+
_testClient = _testServer.CreateClient();
164+
_configProvider = _testServer.Services.GetService<RuntimeConfigProvider>();
165+
166+
string query = GQL_QUERY;
167+
object payload =
168+
new { query };
169+
170+
HttpRequestMessage request = new(HttpMethod.Post, "/graphQL")
171+
{
172+
Content = JsonContent.Create(payload)
173+
};
174+
175+
HttpResponseMessage restResult = await _testClient.GetAsync("/rest/Book");
176+
HttpResponseMessage gQLResult = await _testClient.SendAsync(request);
177+
178+
// Assert rest and graphQL requests return status OK.
179+
Assert.AreEqual(HttpStatusCode.OK, restResult.StatusCode);
180+
Assert.AreEqual(HttpStatusCode.OK, gQLResult.StatusCode);
181+
182+
// Save the contents from request to validate results after hot-reloads.
183+
string restContent = await restResult.Content.ReadAsStringAsync();
184+
using JsonDocument doc = JsonDocument.Parse(restContent);
185+
_bookDBOContents = doc.RootElement.GetProperty("value").ToString();
186+
}
187+
188+
[ClassCleanup]
189+
public static void ClassCleanup()
190+
{
191+
if (File.Exists(CONFIG_FILE_NAME))
192+
{
193+
File.Delete(CONFIG_FILE_NAME);
194+
}
195+
196+
_testServer.Dispose();
197+
_testClient.Dispose();
198+
}
199+
200+
/// <summary>
201+
/// Hot reload the configuration by saving a new file with different rest and graphQL paths.
202+
/// Validate that the response is correct when making a request with the newly hot-reloaded paths.
203+
/// </summary>
204+
[TestCategory(MSSQL_ENVIRONMENT)]
205+
[TestMethod("Hot-reload runtime paths.")]
206+
public async Task HotReloadConfigRuntimePathsEndToEndTest()
207+
{
208+
// Arrange
209+
string restBookContents = $"{{\"value\":{_bookDBOContents}}}";
210+
string restPath = "restApi";
211+
string gQLPath = "/gQLApi";
212+
string query = GQL_QUERY;
213+
object payload =
214+
new { query };
215+
216+
HttpRequestMessage request = new(HttpMethod.Post, "/graphQL")
217+
{
218+
Content = JsonContent.Create(payload)
219+
};
220+
221+
GenerateConfigFile(
222+
connectionString: $"{ConfigurationTests.GetConnectionStringFromEnvironmentConfig(TestCategory.MSSQL).Replace("\\", "\\\\")}",
223+
restPath: restPath,
224+
gQLPath: gQLPath);
225+
System.Threading.Thread.Sleep(2000);
226+
227+
// Act
228+
HttpResponseMessage badPathRestResult = await _testClient.GetAsync($"rest/Book");
229+
HttpResponseMessage badPathGQLResult = await _testClient.SendAsync(request);
230+
231+
HttpResponseMessage result = await _testClient.GetAsync($"{restPath}/Book");
232+
string reloadRestContent = await result.Content.ReadAsStringAsync();
233+
JsonElement reloadGQLContents = await GraphQLRequestExecutor.PostGraphQLRequestAsync(
234+
_testClient,
235+
_configProvider,
236+
GQL_QUERY_NAME,
237+
GQL_QUERY);
238+
239+
// Assert
240+
// Old paths are not found.
241+
Assert.AreEqual(HttpStatusCode.BadRequest, badPathRestResult.StatusCode);
242+
Assert.AreEqual(HttpStatusCode.NotFound, badPathGQLResult.StatusCode);
243+
// Hot reloaded paths return correct response.
244+
Assert.IsTrue(SqlTestHelper.JsonStringsDeepEqual(restBookContents, reloadRestContent));
245+
SqlTestHelper.PerformTestEqualJsonStrings(_bookDBOContents, reloadGQLContents.GetProperty("items").ToString());
246+
}
247+
248+
/// <summary>
249+
/// Hot reload the configuration file by saving a new file with the rest enabled property
250+
/// set to false. Validate that the response from the server is NOT FOUND when making a request after
251+
/// the hot reload.
252+
/// </summary>
253+
[TestCategory(MSSQL_ENVIRONMENT)]
254+
[TestMethod("Hot-reload rest enabled.")]
255+
public async Task HotReloadConfigRuntimeRestEnabledEndToEndTest()
256+
{
257+
// Arrange
258+
string restEnabled = "false";
259+
260+
GenerateConfigFile(
261+
connectionString: $"{ConfigurationTests.GetConnectionStringFromEnvironmentConfig(TestCategory.MSSQL).Replace("\\", "\\\\")}",
262+
restEnabled: restEnabled);
263+
System.Threading.Thread.Sleep(2000);
264+
265+
// Act
266+
HttpResponseMessage restResult = await _testClient.GetAsync($"rest/Book");
267+
268+
// Assert
269+
Assert.AreEqual(HttpStatusCode.NotFound, restResult.StatusCode);
270+
}
271+
272+
/// <summary>
273+
/// Hot reload the configuration file by saving a new file with the graphQL enabled property
274+
/// set to false. Validate that the response from the server is NOT FOUND when making a request after
275+
/// the hot reload.
276+
/// </summary>
277+
[TestCategory(MSSQL_ENVIRONMENT)]
278+
[TestMethod("Hot-reload gql enabled.")]
279+
public async Task HotReloadConfigRuntimeGQLEnabledEndToEndTest()
280+
{
281+
// Arrange
282+
string gQLEnabled = "false";
283+
string query = GQL_QUERY;
284+
object payload =
285+
new { query };
286+
287+
HttpRequestMessage request = new(HttpMethod.Post, "/graphQL")
288+
{
289+
Content = JsonContent.Create(payload)
290+
};
291+
GenerateConfigFile(
292+
connectionString: $"{ConfigurationTests.GetConnectionStringFromEnvironmentConfig(TestCategory.MSSQL).Replace("\\", "\\\\")}",
293+
gQLEnabled: gQLEnabled);
294+
System.Threading.Thread.Sleep(2000);
295+
296+
// Act
297+
HttpResponseMessage gQLResult = await _testClient.SendAsync(request);
298+
299+
// Assert
300+
Assert.AreEqual(HttpStatusCode.NotFound, gQLResult.StatusCode);
301+
}
302+
}

src/Service.Tests/Unittests/ConfigFileWatcherUnitTests.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ HostMode mode
7272
/// on hot reload.
7373
/// </summary>
7474
[TestMethod]
75-
[Ignore]
7675
public void HotReloadConfigRestRuntimeOptions()
7776
{
7877
// Arrange
@@ -124,7 +123,6 @@ public void HotReloadConfigRestRuntimeOptions()
124123

125124
// Must GetConfig() to start file watching
126125
RuntimeConfig runtimeConfig = configProvider.GetConfig();
127-
string initialDefaultDataSourceName = runtimeConfig.DefaultDataSourceName;
128126

129127
// assert we have a valid config
130128
Assert.IsNotNull(runtimeConfig);
@@ -144,7 +142,6 @@ public void HotReloadConfigRestRuntimeOptions()
144142
// Act
145143
// 1. Hot reload the runtime config
146144
runtimeConfig = configProvider.GetConfig();
147-
string updatedDefaultDataSourceName = runtimeConfig.DefaultDataSourceName;
148145

149146
// Assert
150147
// 1. Assert we have the correct values after a hot reload.
@@ -155,8 +152,6 @@ public void HotReloadConfigRestRuntimeOptions()
155152
Assert.AreEqual(updatedGQLIntrospection, runtimeConfig.Runtime.GraphQL.AllowIntrospection);
156153
Assert.AreEqual(updatedMode, runtimeConfig.Runtime.Host.Mode);
157154

158-
// DefaultDataSourceName should not change after a hot reload.
159-
Assert.AreEqual(initialDefaultDataSourceName, updatedDefaultDataSourceName);
160155
if (File.Exists(configName))
161156
{
162157
File.Delete(configName);

0 commit comments

Comments
 (0)