Skip to content

Commit cfbcc45

Browse files
seantleonardSean LeonardAniruddh25
authored
MsSql Integration Tests (#62)
* MsSql Passing Integration Tests + Integration Test Framework * Added named parameter for specific JSON serialization options in HotChocolate ToJson() method call. * remove unneccessary "using" directives in mssql integration test classes. * Revert "remove unneccessary "using" directives in mssql integration test classes." * Remove unneccessary "using" directives in mssqlintegration tests classes. * Added dedicated mssqlintegration test config file and added config setup in sqltesthelperclass to pull from new file. * Fixed build errors resulting from extra whitespaces. (analyzer issues) * Address PR comments. MsSql named classes. namedParams for nulls, updated summary comment for dropping tables * address whitespace issue in build pipeline format check * Updating per PR comments. Refactored Testclass to fit MSTest paradigm for initializing assets on class basis/test run, and not per test. * Updated config for test runs as Local test explorer cannot run both sets of tests simulataneously when appsettings.Test.json has singular engine connection string. * testing Pipeline run with outputting connectionstring to console. * More test of pipeline connection to sql db * Update Cosmos.GraphQL.Service/Cosmos.GraphQL.Service.Tests/MsSqlTests/MsSqlQueryTests.cs Co-authored-by: Aniruddh Munde <[email protected]> Co-authored-by: Sean Leonard <[email protected]> Co-authored-by: Aniruddh Munde <[email protected]>
1 parent 0520eee commit cfbcc45

File tree

5 files changed

+303
-1
lines changed

5 files changed

+303
-1
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Cosmos.GraphQL.Service.Resolvers;
2+
3+
namespace Cosmos.GraphQL.Service.Tests.MsSql
4+
{
5+
/// <summary>
6+
/// Class that provides functions to interact with a database.
7+
/// </summary>
8+
public class DatabaseInteractor
9+
{
10+
public IQueryExecutor QueryExecutor { get; private set; }
11+
12+
public DatabaseInteractor(IQueryExecutor queryExecutor)
13+
{
14+
QueryExecutor = queryExecutor;
15+
}
16+
17+
/// <summary>
18+
/// Inserts data into the database.
19+
/// </summary>
20+
public void InsertData(string tableName, string values)
21+
{
22+
_ = QueryExecutor.ExecuteQueryAsync($"INSERT INTO {tableName} VALUES({values});", null).Result;
23+
}
24+
25+
/// <summary>
26+
/// Creates a table in the database with provided name and columns
27+
/// </summary>
28+
public void CreateTable(string tableName, string columns)
29+
{
30+
_ = QueryExecutor.ExecuteQueryAsync($"CREATE TABLE {tableName} ({columns});", null).Result;
31+
}
32+
33+
/// <summary>
34+
/// Drops all tables in the database
35+
/// </summary>
36+
public void DropTable(string tableName)
37+
{
38+
// Drops all tables in the database.
39+
string dropTable = string.Format(
40+
@"DROP TABLE IF EXISTS {0};", tableName);
41+
42+
_ = QueryExecutor.ExecuteQueryAsync(sqltext: dropTable, parameters: null).Result;
43+
}
44+
}
45+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
using System.Data.Common;
2+
using System.IO;
3+
using System.Text;
4+
using System.Text.Json;
5+
using System.Threading.Tasks;
6+
using Cosmos.GraphQL.Service.Controllers;
7+
using Cosmos.GraphQL.Service.Resolvers;
8+
using Cosmos.GraphQL.Services;
9+
using Microsoft.AspNetCore.Http;
10+
using Microsoft.Data.SqlClient;
11+
using Microsoft.VisualStudio.TestTools.UnitTesting;
12+
13+
namespace Cosmos.GraphQL.Service.Tests.MsSql
14+
{
15+
/// <summary>
16+
/// Test GraphQL Queries validating proper resolver/engine operation.
17+
/// </summary>
18+
[TestClass, TestCategory(TestCategory.MsSql)]
19+
public class MsSqlQueryTests
20+
{
21+
#region Test Fixture Setup
22+
private static IMetadataStoreProvider _metadataStoreProvider;
23+
private static IQueryExecutor _queryExecutor;
24+
private static IQueryBuilder _queryBuilder;
25+
private static IQueryEngine _queryEngine;
26+
private static GraphQLService _graphQLService;
27+
private static GraphQLController _graphQLController;
28+
private static DatabaseInteractor _databaseInteractor;
29+
30+
public static string IntegrationTableName { get; } = "character";
31+
32+
/// <summary>
33+
/// Sets up test fixture for class, only to be run once per test run, as defined by
34+
/// MSTest decorator.
35+
/// </summary>
36+
/// <param name="context"></param>
37+
[ClassInitialize]
38+
public static void InitializeTestFixure(TestContext context)
39+
{
40+
// Setup Schema and Resolvers
41+
//
42+
_metadataStoreProvider = new MetadataStoreProviderForTest();
43+
_metadataStoreProvider.StoreGraphQLSchema(MsSqlTestHelper.GraphQLSchema);
44+
_metadataStoreProvider.StoreQueryResolver(MsSqlTestHelper.GetQueryResolverJson(MsSqlTestHelper.CharacterByIdResolver));
45+
_metadataStoreProvider.StoreQueryResolver(MsSqlTestHelper.GetQueryResolverJson(MsSqlTestHelper.CharacterListResolver));
46+
47+
// Setup Database Components
48+
//
49+
_queryExecutor = new QueryExecutor<SqlConnection>(MsSqlTestHelper.DataGatewayConfig);
50+
_queryBuilder = new MsSqlQueryBuilder();
51+
_queryEngine = new SqlQueryEngine(_metadataStoreProvider, _queryExecutor, _queryBuilder);
52+
53+
// Setup Integration DB Components
54+
//
55+
_databaseInteractor = new DatabaseInteractor(_queryExecutor);
56+
CreateTable();
57+
InsertData();
58+
59+
// Setup GraphQL Components
60+
//
61+
_graphQLService = new GraphQLService(_queryEngine, mutationEngine: null, _metadataStoreProvider);
62+
_graphQLController = new GraphQLController(logger: null, _queryEngine, mutationEngine: null, _graphQLService);
63+
}
64+
65+
/// <summary>
66+
/// Cleans up querying table used for Tests in this class. Only to be run once at
67+
/// conclusion of test run, as defined by MSTest decorator.
68+
/// </summary>
69+
[ClassCleanup]
70+
public static void CleanupTestFixture()
71+
{
72+
_databaseInteractor.DropTable(IntegrationTableName);
73+
}
74+
#endregion
75+
#region Tests
76+
/// <summary>
77+
/// Get result of quering singular object
78+
/// </summary>
79+
/// <returns></returns>
80+
[TestMethod]
81+
public async Task SingleResultQuery()
82+
{
83+
string graphQLQueryName = "characterById";
84+
string graphQLQuery = "{\"query\":\"{\\n characterById(id:2){\\n name\\n primaryFunction\\n}\\n}\\n\"}";
85+
string msSqlQuery = $"SELECT name, primaryFunction FROM { IntegrationTableName} WHERE id = 2 FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER";
86+
87+
string actual = await getGraphQLResultAsync(graphQLQuery, graphQLQueryName);
88+
string expected = await getDatabaseResultAsync(msSqlQuery);
89+
90+
Assert.AreEqual(actual, expected);
91+
}
92+
93+
/// <summary>
94+
/// Gets array of results for querying more than one item.
95+
/// </summary>
96+
/// <returns></returns>
97+
[TestMethod]
98+
public async Task MultipleResultQuery()
99+
{
100+
string graphQLQueryName = "characterList";
101+
string graphQLQuery = "{\"query\":\"{\\n characterList {\\n name\\n primaryFunction\\n }\\n}\\n\"}";
102+
string msSqlQuery = $"SELECT name, primaryFunction FROM character FOR JSON PATH, INCLUDE_NULL_VALUES";
103+
104+
string actual = await getGraphQLResultAsync(graphQLQuery, graphQLQueryName);
105+
string expected = await getDatabaseResultAsync(msSqlQuery);
106+
107+
Assert.AreEqual(actual, expected);
108+
}
109+
#endregion
110+
#region Query Test Helper Functions
111+
/// <summary>
112+
/// Sends graphQL query through graphQL service, consisting of gql engine processing (resolvers, object serialization)
113+
/// returning JSON formatted result from 'data' property.
114+
/// </summary>
115+
/// <param name="graphQLQuery"></param>
116+
/// <param name="graphQLQueryName"></param>
117+
/// <returns>string in JSON format</returns>
118+
public async Task<string> getGraphQLResultAsync(string graphQLQuery, string graphQLQueryName)
119+
{
120+
_graphQLController.ControllerContext.HttpContext = GetHttpContextWithBody(graphQLQuery);
121+
JsonDocument graphQLResult = await _graphQLController.PostAsync();
122+
JsonElement graphQLResultData = graphQLResult.RootElement.GetProperty("data").GetProperty(graphQLQueryName);
123+
return graphQLResultData.ToString();
124+
}
125+
126+
/// <summary>
127+
/// Sends raw SQL query to database engine to retrieve expected result in JSON format
128+
/// </summary>
129+
/// <param name="queryText">raw database query</param>
130+
/// <returns>string in JSON format</returns>
131+
public async Task<string> getDatabaseResultAsync(string queryText)
132+
{
133+
JsonDocument sqlResult = JsonDocument.Parse("{ }");
134+
using DbDataReader reader = _databaseInteractor.QueryExecutor.ExecuteQueryAsync(queryText, parameters: null).Result;
135+
136+
if (await reader.ReadAsync())
137+
{
138+
sqlResult = JsonDocument.Parse(reader.GetString(0));
139+
}
140+
141+
JsonElement sqlResultData = sqlResult.RootElement;
142+
143+
return sqlResultData.ToString();
144+
}
145+
#endregion
146+
#region Helper Functions
147+
/// <summary>
148+
/// Creates a default table
149+
/// </summary>
150+
private static void CreateTable()
151+
{
152+
_databaseInteractor.CreateTable(IntegrationTableName, "id int, name varchar(20), type varchar(20), homePlanet int, primaryFunction varchar(20)");
153+
}
154+
155+
/// <summary>
156+
/// Inserts some default data into the table
157+
/// </summary>
158+
private static void InsertData()
159+
{
160+
_databaseInteractor.InsertData(IntegrationTableName, "'1', 'Mace', 'Jedi','1','Master'");
161+
_databaseInteractor.InsertData(IntegrationTableName, "'2', 'Plo Koon', 'Jedi','2','Master'");
162+
_databaseInteractor.InsertData(IntegrationTableName, "'3', 'Yoda', 'Jedi','3','Master'");
163+
}
164+
/// <summary>
165+
/// returns httpcontext with body consisting of GraphQLQuery
166+
/// </summary>
167+
/// <param name="data">GraphQLQuery</param>
168+
/// <returns>The http context with given data as stream of utf-8 bytes.</returns>
169+
private DefaultHttpContext GetHttpContextWithBody(string data)
170+
{
171+
var stream = new MemoryStream(Encoding.UTF8.GetBytes(data));
172+
var httpContext = new DefaultHttpContext()
173+
{
174+
Request = { Body = stream, ContentLength = stream.Length }
175+
};
176+
return httpContext;
177+
}
178+
#endregion
179+
}
180+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using Cosmos.GraphQL.Service.configurations;
2+
using Cosmos.GraphQL.Service.Models;
3+
using Microsoft.Extensions.Configuration;
4+
using Microsoft.Extensions.Options;
5+
using Newtonsoft.Json;
6+
using System;
7+
using System.IO;
8+
9+
namespace Cosmos.GraphQL.Service.Tests.MsSql
10+
{
11+
/// <summary>
12+
/// Helper functions for setting up test scenarios
13+
/// </summary>
14+
public class MsSqlTestHelper
15+
{
16+
public static readonly string GraphQLSchema = @"
17+
type Query {
18+
characterList: [Character]
19+
characterById (id : ID!): Character
20+
}
21+
type Character {
22+
id : ID,
23+
name : String,
24+
type: String,
25+
homePlanet: Int,
26+
primaryFunction: String
27+
}
28+
";
29+
30+
public static readonly string CharacterListResolver = "{\r\n \"id\": \"characterList\",\r\n \"parametrizedQuery\": \"SELECT id, name, type, homePlanet, primaryFunction FROM character\"\r\n }";
31+
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}";
32+
private static Lazy<IOptions<DataGatewayConfig>> _dataGatewayConfig = new Lazy<IOptions<DataGatewayConfig>>(() => MsSqlTestHelper.LoadConfig());
33+
34+
/// <summary>
35+
/// Converts Raw JSON resolver to Resolver class object
36+
/// </summary>
37+
/// <param name="rawResolverText">escaped JSON string</param>
38+
/// <returns>GraphQLQueryResolver object</returns>
39+
public static GraphQLQueryResolver GetQueryResolverJson(string rawResolverText)
40+
{
41+
return JsonConvert.DeserializeObject<GraphQLQueryResolver>(rawResolverText);
42+
}
43+
44+
/// <summary>
45+
/// Sets up configuration object as defined by appsettings.ENV.json file
46+
/// </summary>
47+
/// <returns></returns>
48+
private static IOptions<DataGatewayConfig> LoadConfig()
49+
{
50+
DataGatewayConfig datagatewayConfig = new DataGatewayConfig();
51+
IConfigurationRoot config = new ConfigurationBuilder()
52+
.SetBasePath(Directory.GetCurrentDirectory())
53+
.AddJsonFile("appsettings.Test.json")
54+
.Build();
55+
56+
config.Bind(nameof(DataGatewayConfig), datagatewayConfig);
57+
58+
return Options.Create(datagatewayConfig);
59+
}
60+
61+
/// <summary>
62+
/// Returns configuration value loaded from file.
63+
/// </summary>
64+
public static IOptions<DataGatewayConfig> DataGatewayConfig
65+
{
66+
get { return _dataGatewayConfig.Value; }
67+
}
68+
}
69+
}

Cosmos.GraphQL.Service/Cosmos.GraphQL.Service/Services/GraphQLService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ internal async Task<string> ExecuteAsync(String requestBody)
5555
IExecutionResult result =
5656
await Executor.ExecuteAsync(queryRequest);
5757

58-
return result.ToJson();
58+
return result.ToJson(withIndentations: false);
5959
}
6060

6161
private static bool IsIntrospectionPath(IEnumerable<object> path)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"DataGatewayConfig": {
3+
"DatabaseType": "MsSql",
4+
"DatabaseConnection": {
5+
"ConnectionString": "Server=tcp:127.0.0.1,1433;Persist Security Info=False;User ID=sa;Password=REPLACEME;MultipleActiveResultSets=False;Connection Timeout=5;"
6+
}
7+
}
8+
}

0 commit comments

Comments
 (0)