Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5a7a2df
MsSql Passing Integration Tests + Integration Test Framework
seantleonard Oct 19, 2021
669b588
Merge branch 'main' into dev/seleonar/mssql_integrationtests
seantleonard Oct 19, 2021
7ad8f7b
Added LocalDB setup to Azure Pipeline (mssql) yml config. Removed Lin…
seantleonard Oct 20, 2021
e673151
Added named parameter for specific JSON serialization options in HotC…
seantleonard Oct 20, 2021
677bcd7
remove unneccessary "using" directives in mssql integration test clas…
seantleonard Oct 20, 2021
efe99a8
Revert "remove unneccessary "using" directives in mssql integration t…
seantleonard Oct 20, 2021
9608b30
Remove unneccessary "using" directives in mssqlintegration tests clas…
seantleonard Oct 20, 2021
1953b4b
Added dedicated mssqlintegration test config file and added config se…
seantleonard Oct 20, 2021
4cdc94e
Fixed build errors resulting from extra whitespaces. (analyzer issues)
seantleonard Oct 20, 2021
800fe27
Merge branch 'main' into dev/seleonar/mssql_integrationtests
seantleonard Oct 20, 2021
5ec8869
Address PR comments. MsSql named classes. namedParams for nulls, upda…
seantleonard Oct 20, 2021
ab3e65b
address whitespace issue in build pipeline format check
seantleonard Oct 20, 2021
55d273d
Updating per PR comments. Refactored Testclass to fit MSTest paradigm…
Oct 25, 2021
bcfc05f
Updated config for test runs as Local test explorer cannot run both s…
Oct 25, 2021
999029e
Remove Creds from new config. and add linux testing back
Oct 25, 2021
22a1452
testing Pipeline run with outputting connectionstring to console.
Oct 25, 2021
23af474
More test of pipeline connection to sql db
Oct 25, 2021
9a58d0c
Removed Console logging for pipeline testing. tests now pass linux/wi…
Oct 25, 2021
29af7ea
Merge branch 'main' into dev/seleonar/mssql_integrationtests
Oct 25, 2021
7436b8d
Update Cosmos.GraphQL.Service/Cosmos.GraphQL.Service.Tests/MsSqlTests…
seantleonard Oct 26, 2021
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Cosmos.GraphQL.Service.Resolvers;

namespace Cosmos.GraphQL.Service.Tests.MsSql
{
/// <summary>
/// Class that provides functions to interact with a database.
/// </summary>
public class DatabaseInteractor
{
public IQueryExecutor QueryExecutor { get; private set; }

public DatabaseInteractor(IQueryExecutor queryExecutor)
{
QueryExecutor = queryExecutor;
}

/// <summary>
/// Inserts data into the database.
/// </summary>
public void InsertData(string tableName, string values)
{
_ = QueryExecutor.ExecuteQueryAsync($"INSERT INTO {tableName} VALUES({values});", null).Result;
}

/// <summary>
/// Creates a table in the database with provided name and columns
/// </summary>
public void CreateTable(string tableName, string columns)
{
_ = QueryExecutor.ExecuteQueryAsync($"CREATE TABLE {tableName} ({columns});", null).Result;
}

/// <summary>
/// Drops all tables in the database
/// </summary>
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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Test GraphQL Queries validating proper resolver/engine operation.
/// </summary>
[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";

/// <summary>
/// Sets up test fixture for class, only to be run once per test run, as defined by
/// MSTest decorator.
/// </summary>
/// <param name="context"></param>
[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<SqlConnection>(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);
}

/// <summary>
/// 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.
/// </summary>
[ClassCleanup]
public static void CleanupTestFixture()
{
_databaseInteractor.DropTable(IntegrationTableName);
}
#endregion
#region Tests
/// <summary>
/// Get result of quering singular object
/// </summary>
/// <returns></returns>
[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);
}

/// <summary>
/// Gets array of results for querying more than one item.
/// </summary>
/// <returns></returns>
[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
/// <summary>
/// Sends graphQL query through graphQL service, consisting of gql engine processing (resolvers, object serialization)
/// returning JSON formatted result from 'data' property.
/// </summary>
/// <param name="graphQLQuery"></param>
/// <param name="graphQLQueryName"></param>
/// <returns>string in JSON format</returns>
public async Task<string> 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();
}

/// <summary>
/// Sends raw SQL query to database engine to retrieve expected result in JSON format
/// </summary>
/// <param name="queryText">raw database query</param>
/// <returns>string in JSON format</returns>
public async Task<string> 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
/// <summary>
/// Creates a default table
/// </summary>
private static void CreateTable()
{
_databaseInteractor.CreateTable(IntegrationTableName, "id int, name varchar(20), type varchar(20), homePlanet int, primaryFunction varchar(20)");
}

/// <summary>
/// Inserts some default data into the table
/// </summary>
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'");
}
/// <summary>
/// returns httpcontext with body consisting of GraphQLQuery
/// </summary>
/// <param name="data">GraphQLQuery</param>
/// <returns>The http context with given data as stream of utf-8 bytes.</returns>
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
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Helper functions for setting up test scenarios
/// </summary>
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<IOptions<DataGatewayConfig>> _dataGatewayConfig = new Lazy<IOptions<DataGatewayConfig>>(() => MsSqlTestHelper.LoadConfig());

/// <summary>
/// Converts Raw JSON resolver to Resolver class object
/// </summary>
/// <param name="rawResolverText">escaped JSON string</param>
/// <returns>GraphQLQueryResolver object</returns>
public static GraphQLQueryResolver GetQueryResolverJson(string rawResolverText)
{
return JsonConvert.DeserializeObject<GraphQLQueryResolver>(rawResolverText);
}

/// <summary>
/// Sets up configuration object as defined by appsettings.ENV.json file
/// </summary>
/// <returns></returns>
private static IOptions<DataGatewayConfig> 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);
}

/// <summary>
/// Returns configuration value loaded from file.
/// </summary>
public static IOptions<DataGatewayConfig> DataGatewayConfig
{
get { return _dataGatewayConfig.Value; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ internal async Task<string> ExecuteAsync(String requestBody)
IExecutionResult result =
await Executor.ExecuteAsync(queryRequest);

return result.ToJson();
return result.ToJson(withIndentations: false);
}

private static bool IsIntrospectionPath(IEnumerable<object> path)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"DataGatewayConfig": {
"DatabaseType": "MsSql",
"DatabaseConnection": {
"ConnectionString": "Server=tcp:127.0.0.1,1433;Persist Security Info=False;User ID=sa;Password=REPLACEME;MultipleActiveResultSets=False;Connection Timeout=5;"
}
}
}