diff --git a/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service.Tests/MsSqlTests/DatabaseInteractor.cs b/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service.Tests/MsSqlTests/DatabaseInteractor.cs
new file mode 100644
index 0000000000..2f96c6c8fb
--- /dev/null
+++ b/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service.Tests/MsSqlTests/DatabaseInteractor.cs
@@ -0,0 +1,45 @@
+using Cosmos.GraphQL.Service.Resolvers;
+
+namespace Cosmos.GraphQL.Service.Tests.MsSql
+{
+ ///
+ /// Class that provides functions to interact with a database.
+ ///
+ public class DatabaseInteractor
+ {
+ public IQueryExecutor QueryExecutor { get; private set; }
+
+ public DatabaseInteractor(IQueryExecutor queryExecutor)
+ {
+ QueryExecutor = queryExecutor;
+ }
+
+ ///
+ /// Inserts data into the database.
+ ///
+ public void InsertData(string tableName, string values)
+ {
+ _ = QueryExecutor.ExecuteQueryAsync($"INSERT INTO {tableName} VALUES({values});", null).Result;
+ }
+
+ ///
+ /// Creates a table in the database with provided name and columns
+ ///
+ public void CreateTable(string tableName, string columns)
+ {
+ _ = QueryExecutor.ExecuteQueryAsync($"CREATE TABLE {tableName} ({columns});", null).Result;
+ }
+
+ ///
+ /// Drops all tables in the database
+ ///
+ public void DropTable(string tableName)
+ {
+ // Drops all tables in the database.
+ string dropTable = string.Format(
+ @"DROP TABLE IF EXISTS {0};", tableName);
+
+ _ = QueryExecutor.ExecuteQueryAsync(sqltext: dropTable, parameters: null).Result;
+ }
+ }
+}
diff --git a/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service.Tests/MsSqlTests/MsSqlQueryTests.cs b/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service.Tests/MsSqlTests/MsSqlQueryTests.cs
new file mode 100644
index 0000000000..7de49404e6
--- /dev/null
+++ b/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service.Tests/MsSqlTests/MsSqlQueryTests.cs
@@ -0,0 +1,180 @@
+using System.Data.Common;
+using System.IO;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Cosmos.GraphQL.Service.Controllers;
+using Cosmos.GraphQL.Service.Resolvers;
+using Cosmos.GraphQL.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Data.SqlClient;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Cosmos.GraphQL.Service.Tests.MsSql
+{
+ ///
+ /// Test GraphQL Queries validating proper resolver/engine operation.
+ ///
+ [TestClass, TestCategory(TestCategory.MsSql)]
+ public class MsSqlQueryTests
+ {
+ #region Test Fixture Setup
+ private static IMetadataStoreProvider _metadataStoreProvider;
+ private static IQueryExecutor _queryExecutor;
+ private static IQueryBuilder _queryBuilder;
+ private static IQueryEngine _queryEngine;
+ private static GraphQLService _graphQLService;
+ private static GraphQLController _graphQLController;
+ private static DatabaseInteractor _databaseInteractor;
+
+ public static string IntegrationTableName { get; } = "character";
+
+ ///
+ /// Sets up test fixture for class, only to be run once per test run, as defined by
+ /// MSTest decorator.
+ ///
+ ///
+ [ClassInitialize]
+ public static void InitializeTestFixure(TestContext context)
+ {
+ // Setup Schema and Resolvers
+ //
+ _metadataStoreProvider = new MetadataStoreProviderForTest();
+ _metadataStoreProvider.StoreGraphQLSchema(MsSqlTestHelper.GraphQLSchema);
+ _metadataStoreProvider.StoreQueryResolver(MsSqlTestHelper.GetQueryResolverJson(MsSqlTestHelper.CharacterByIdResolver));
+ _metadataStoreProvider.StoreQueryResolver(MsSqlTestHelper.GetQueryResolverJson(MsSqlTestHelper.CharacterListResolver));
+
+ // Setup Database Components
+ //
+ _queryExecutor = new QueryExecutor(MsSqlTestHelper.DataGatewayConfig);
+ _queryBuilder = new MsSqlQueryBuilder();
+ _queryEngine = new SqlQueryEngine(_metadataStoreProvider, _queryExecutor, _queryBuilder);
+
+ // Setup Integration DB Components
+ //
+ _databaseInteractor = new DatabaseInteractor(_queryExecutor);
+ CreateTable();
+ InsertData();
+
+ // Setup GraphQL Components
+ //
+ _graphQLService = new GraphQLService(_queryEngine, mutationEngine: null, _metadataStoreProvider);
+ _graphQLController = new GraphQLController(logger: null, _queryEngine, mutationEngine: null, _graphQLService);
+ }
+
+ ///
+ /// Cleans up querying table used for Tests in this class. Only to be run once at
+ /// conclusion of test run, as defined by MSTest decorator.
+ ///
+ [ClassCleanup]
+ public static void CleanupTestFixture()
+ {
+ _databaseInteractor.DropTable(IntegrationTableName);
+ }
+ #endregion
+ #region Tests
+ ///
+ /// Get result of quering singular object
+ ///
+ ///
+ [TestMethod]
+ public async Task SingleResultQuery()
+ {
+ string graphQLQueryName = "characterById";
+ string graphQLQuery = "{\"query\":\"{\\n characterById(id:2){\\n name\\n primaryFunction\\n}\\n}\\n\"}";
+ string msSqlQuery = $"SELECT name, primaryFunction FROM { IntegrationTableName} WHERE id = 2 FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER";
+
+ string actual = await getGraphQLResultAsync(graphQLQuery, graphQLQueryName);
+ string expected = await getDatabaseResultAsync(msSqlQuery);
+
+ Assert.AreEqual(actual, expected);
+ }
+
+ ///
+ /// Gets array of results for querying more than one item.
+ ///
+ ///
+ [TestMethod]
+ public async Task MultipleResultQuery()
+ {
+ string graphQLQueryName = "characterList";
+ string graphQLQuery = "{\"query\":\"{\\n characterList {\\n name\\n primaryFunction\\n }\\n}\\n\"}";
+ string msSqlQuery = $"SELECT name, primaryFunction FROM character FOR JSON PATH, INCLUDE_NULL_VALUES";
+
+ string actual = await getGraphQLResultAsync(graphQLQuery, graphQLQueryName);
+ string expected = await getDatabaseResultAsync(msSqlQuery);
+
+ Assert.AreEqual(actual, expected);
+ }
+ #endregion
+ #region Query Test Helper Functions
+ ///
+ /// Sends graphQL query through graphQL service, consisting of gql engine processing (resolvers, object serialization)
+ /// returning JSON formatted result from 'data' property.
+ ///
+ ///
+ ///
+ /// string in JSON format
+ public async Task getGraphQLResultAsync(string graphQLQuery, string graphQLQueryName)
+ {
+ _graphQLController.ControllerContext.HttpContext = GetHttpContextWithBody(graphQLQuery);
+ JsonDocument graphQLResult = await _graphQLController.PostAsync();
+ JsonElement graphQLResultData = graphQLResult.RootElement.GetProperty("data").GetProperty(graphQLQueryName);
+ return graphQLResultData.ToString();
+ }
+
+ ///
+ /// Sends raw SQL query to database engine to retrieve expected result in JSON format
+ ///
+ /// raw database query
+ /// string in JSON format
+ public async Task getDatabaseResultAsync(string queryText)
+ {
+ JsonDocument sqlResult = JsonDocument.Parse("{ }");
+ using DbDataReader reader = _databaseInteractor.QueryExecutor.ExecuteQueryAsync(queryText, parameters: null).Result;
+
+ if (await reader.ReadAsync())
+ {
+ sqlResult = JsonDocument.Parse(reader.GetString(0));
+ }
+
+ JsonElement sqlResultData = sqlResult.RootElement;
+
+ return sqlResultData.ToString();
+ }
+ #endregion
+ #region Helper Functions
+ ///
+ /// Creates a default table
+ ///
+ private static void CreateTable()
+ {
+ _databaseInteractor.CreateTable(IntegrationTableName, "id int, name varchar(20), type varchar(20), homePlanet int, primaryFunction varchar(20)");
+ }
+
+ ///
+ /// Inserts some default data into the table
+ ///
+ private static void InsertData()
+ {
+ _databaseInteractor.InsertData(IntegrationTableName, "'1', 'Mace', 'Jedi','1','Master'");
+ _databaseInteractor.InsertData(IntegrationTableName, "'2', 'Plo Koon', 'Jedi','2','Master'");
+ _databaseInteractor.InsertData(IntegrationTableName, "'3', 'Yoda', 'Jedi','3','Master'");
+ }
+ ///
+ /// returns httpcontext with body consisting of GraphQLQuery
+ ///
+ /// GraphQLQuery
+ /// The http context with given data as stream of utf-8 bytes.
+ private DefaultHttpContext GetHttpContextWithBody(string data)
+ {
+ var stream = new MemoryStream(Encoding.UTF8.GetBytes(data));
+ var httpContext = new DefaultHttpContext()
+ {
+ Request = { Body = stream, ContentLength = stream.Length }
+ };
+ return httpContext;
+ }
+ #endregion
+ }
+}
diff --git a/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service.Tests/MsSqlTests/MsSqlTestHelper.cs b/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service.Tests/MsSqlTests/MsSqlTestHelper.cs
new file mode 100644
index 0000000000..b0f927fcc0
--- /dev/null
+++ b/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service.Tests/MsSqlTests/MsSqlTestHelper.cs
@@ -0,0 +1,69 @@
+using Cosmos.GraphQL.Service.configurations;
+using Cosmos.GraphQL.Service.Models;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
+using System;
+using System.IO;
+
+namespace Cosmos.GraphQL.Service.Tests.MsSql
+{
+ ///
+ /// Helper functions for setting up test scenarios
+ ///
+ public class MsSqlTestHelper
+ {
+ public static readonly string GraphQLSchema = @"
+ type Query {
+ characterList: [Character]
+ characterById (id : ID!): Character
+ }
+ type Character {
+ id : ID,
+ name : String,
+ type: String,
+ homePlanet: Int,
+ primaryFunction: String
+ }
+ ";
+
+ public static readonly string CharacterListResolver = "{\r\n \"id\": \"characterList\",\r\n \"parametrizedQuery\": \"SELECT id, name, type, homePlanet, primaryFunction FROM character\"\r\n }";
+ public static readonly string CharacterByIdResolver = "{\r\n \"id\": \"characterById\",\r\n \"parametrizedQuery\": \"SELECT id, name, type, homePlanet, primaryFunction FROM character WHERE id = @id\"\r\n}";
+ private static Lazy> _dataGatewayConfig = new Lazy>(() => MsSqlTestHelper.LoadConfig());
+
+ ///
+ /// Converts Raw JSON resolver to Resolver class object
+ ///
+ /// escaped JSON string
+ /// GraphQLQueryResolver object
+ public static GraphQLQueryResolver GetQueryResolverJson(string rawResolverText)
+ {
+ return JsonConvert.DeserializeObject(rawResolverText);
+ }
+
+ ///
+ /// Sets up configuration object as defined by appsettings.ENV.json file
+ ///
+ ///
+ private static IOptions LoadConfig()
+ {
+ DataGatewayConfig datagatewayConfig = new DataGatewayConfig();
+ IConfigurationRoot config = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.Test.json")
+ .Build();
+
+ config.Bind(nameof(DataGatewayConfig), datagatewayConfig);
+
+ return Options.Create(datagatewayConfig);
+ }
+
+ ///
+ /// Returns configuration value loaded from file.
+ ///
+ public static IOptions DataGatewayConfig
+ {
+ get { return _dataGatewayConfig.Value; }
+ }
+ }
+}
diff --git a/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service/Services/GraphQLService.cs b/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service/Services/GraphQLService.cs
index acc5477564..1e3b03340d 100644
--- a/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service/Services/GraphQLService.cs
+++ b/Cosmos.GraphQL.Service/Cosmos.GraphQL.Service/Services/GraphQLService.cs
@@ -55,7 +55,7 @@ internal async Task ExecuteAsync(String requestBody)
IExecutionResult result =
await Executor.ExecuteAsync(queryRequest);
- return result.ToJson();
+ return result.ToJson(withIndentations: false);
}
private static bool IsIntrospectionPath(IEnumerable