diff --git a/DataGateway.Config/Action.cs b/DataGateway.Config/Action.cs
index 67794613b3..34369fffdd 100644
--- a/DataGateway.Config/Action.cs
+++ b/DataGateway.Config/Action.cs
@@ -31,7 +31,7 @@ public enum Operation
Upsert, Create,
// Sql operations
- Insert, Update,
+ Insert, Update, UpdateGraphQL,
// Additional
UpsertIncremental, UpdateIncremental
diff --git a/DataGateway.Config/DataSource.cs b/DataGateway.Config/DataSource.cs
index 20772c43f3..aef394d0dd 100644
--- a/DataGateway.Config/DataSource.cs
+++ b/DataGateway.Config/DataSource.cs
@@ -10,14 +10,11 @@ namespace Azure.DataGateway.Config
/// will use to connect to the backend database.
public record DataSource(
[property: JsonPropertyName(DataSource.DATABASE_PROPERTY_NAME)]
- DatabaseType DatabaseType,
- [property: JsonPropertyName(DataSource.RESOLVER_JSON_PROPERTY_NAME)]
- string? ResolverConfigFile)
+ DatabaseType DatabaseType)
{
public const string JSON_PROPERTY_NAME = "data-source";
public const string DATABASE_PROPERTY_NAME = "database-type";
public const string CONNSTRING_PROPERTY_NAME = "connection-string";
- public const string RESOLVER_JSON_PROPERTY_NAME = "resolver-config-file";
public string GetDatabaseTypeNotSupportedMessage()
{
@@ -32,8 +29,12 @@ public string GetDatabaseTypeNotSupportedMessage()
///
/// Options for CosmosDb database.
///
- public record CosmosDbOptions(string Database)
+ public record CosmosDbOptions(
+ string Database,
+ [property: JsonPropertyName(CosmosDbOptions.RESOLVER_JSON_PROPERTY_NAME)]
+ string ResolverConfigFile)
{
+ public const string RESOLVER_JSON_PROPERTY_NAME = "resolver-config-file";
public const string JSON_PROPERTY_NAME = nameof(DatabaseType.cosmos);
}
diff --git a/DataGateway.Config/DatabaseObject.cs b/DataGateway.Config/DatabaseObject.cs
index 757fea28d2..34fa2066b4 100644
--- a/DataGateway.Config/DatabaseObject.cs
+++ b/DataGateway.Config/DatabaseObject.cs
@@ -11,6 +11,14 @@ public class DatabaseObject
public TableDefinition TableDefinition { get; set; } = null!;
+ public DatabaseObject(string schemaName, string tableName)
+ {
+ SchemaName = schemaName;
+ Name = tableName;
+ }
+
+ public DatabaseObject() { }
+
public string FullName
{
get
@@ -18,6 +26,23 @@ public string FullName
return string.IsNullOrEmpty(SchemaName) ? Name : $"{SchemaName}.{Name}";
}
}
+
+ public override bool Equals(object? other)
+ {
+ return Equals(other as DatabaseObject);
+ }
+
+ public bool Equals(DatabaseObject? other)
+ {
+ return other is not null &&
+ SchemaName.Equals(other.SchemaName) &&
+ Name.Equals(other.Name);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(SchemaName, Name);
+ }
}
public class TableDefinition
@@ -26,10 +51,34 @@ public class TableDefinition
/// The list of columns that together form the primary key of the table.
///
public List PrimaryKey { get; set; } = new();
- public Dictionary Columns { get; set; } =
+
+ ///
+ /// The list of columns in this table.
+ ///
+ public Dictionary Columns { get; private set; } =
new(StringComparer.InvariantCultureIgnoreCase);
- public Dictionary ForeignKeys { get; set; } = new();
- public Dictionary HttpVerbs { get; set; } = new();
+
+ ///
+ /// A dictionary mapping all the source entities to their relationship metadata.
+ /// All these entities share this table definition
+ /// as their underlying database object
+ ///
+ public Dictionary SourceEntityRelationshipMap { get; private set; } =
+ new(StringComparer.InvariantCultureIgnoreCase);
+
+ public Dictionary HttpVerbs { get; private set; } = new();
+ }
+
+ ///
+ /// Class encapsulating foreign keys corresponding to target entities.
+ ///
+ public class RelationshipMetadata
+ {
+ ///
+ /// Dictionary of target entity name to ForeignKeyDefinition.
+ ///
+ public Dictionary> TargetEntityToFkDefinitionMap { get; private set; }
+ = new(StringComparer.InvariantCultureIgnoreCase);
}
public class ColumnDefinition
@@ -46,7 +95,10 @@ public class ColumnDefinition
public class ForeignKeyDefinition
{
- public string ReferencedTable { get; set; } = string.Empty;
+ ///
+ /// The referencing and referenced table pair.
+ ///
+ public RelationShipPair Pair { get; set; } = new();
///
/// The list of columns referenced in the reference table.
@@ -70,7 +122,7 @@ public override bool Equals(object? other)
public bool Equals(ForeignKeyDefinition? other)
{
return other != null &&
- ReferencedTable.Equals(other.ReferencedTable) &&
+ Pair.Equals(other.Pair) &&
ReferencedColumns.SequenceEqual(other.ReferencedColumns) &&
ReferencingColumns.SequenceEqual(other.ReferencingColumns);
}
@@ -78,7 +130,42 @@ public bool Equals(ForeignKeyDefinition? other)
public override int GetHashCode()
{
return HashCode.Combine(
- ReferencedTable, ReferencedColumns, ReferencingColumns);
+ Pair, ReferencedColumns, ReferencingColumns);
+ }
+ }
+
+ public class RelationShipPair
+ {
+ public RelationShipPair() { }
+
+ public RelationShipPair(
+ DatabaseObject referencingDbObject,
+ DatabaseObject referencedDbObject)
+ {
+ ReferencingDbObject = referencingDbObject;
+ ReferencedDbObject = referencedDbObject;
+ }
+
+ public DatabaseObject ReferencingDbObject { get; set; } = new();
+
+ public DatabaseObject ReferencedDbObject { get; set; } = new();
+
+ public override bool Equals(object? other)
+ {
+ return Equals(other as RelationShipPair);
+ }
+
+ public bool Equals(RelationShipPair? other)
+ {
+ return other != null &&
+ ReferencedDbObject.Equals(other.ReferencedDbObject) &&
+ ReferencingDbObject.Equals(other.ReferencingDbObject);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(
+ ReferencedDbObject, ReferencingDbObject);
}
}
diff --git a/DataGateway.Service.GraphQLBuilder/Mutations/CreateMutationBuilder.cs b/DataGateway.Service.GraphQLBuilder/Mutations/CreateMutationBuilder.cs
index 21eefff4ce..ce0e224b3a 100644
--- a/DataGateway.Service.GraphQLBuilder/Mutations/CreateMutationBuilder.cs
+++ b/DataGateway.Service.GraphQLBuilder/Mutations/CreateMutationBuilder.cs
@@ -196,7 +196,7 @@ private static ITypeNode GenerateListType(ITypeNode type, ITypeNode fieldType)
private static NameNode GenerateInputTypeName(string typeName, Entity entity)
{
- return new($"Create{FormatNameForObject(typeName, entity)}Input");
+ return new($"{Operation.Create}{FormatNameForObject(typeName, entity)}Input");
}
///
diff --git a/DataGateway.Service.GraphQLBuilder/Mutations/DeleteMutationBuilder.cs b/DataGateway.Service.GraphQLBuilder/Mutations/DeleteMutationBuilder.cs
index 4f20a3c871..af93e8f9fa 100644
--- a/DataGateway.Service.GraphQLBuilder/Mutations/DeleteMutationBuilder.cs
+++ b/DataGateway.Service.GraphQLBuilder/Mutations/DeleteMutationBuilder.cs
@@ -10,20 +10,34 @@ internal static class DeleteMutationBuilder
{
public static FieldDefinitionNode Build(NameNode name, ObjectTypeDefinitionNode objectTypeDefinitionNode, Entity configEntity)
{
- FieldDefinitionNode idField = FindPrimaryKeyField(objectTypeDefinitionNode);
+ List idFields = FindPrimaryKeyFields(objectTypeDefinitionNode);
+ string description;
+ if (idFields.Count > 1)
+ {
+ description = "One of the ids of the item being deleted.";
+ }
+ else
+ {
+ description = "The ID of the item being deleted.";
+ }
+
+ List inputValues = new();
+ foreach (FieldDefinitionNode idField in idFields)
+ {
+ inputValues.Add(new InputValueDefinitionNode(
+ location: null,
+ idField.Name,
+ new StringValueNode(description),
+ new NonNullTypeNode(idField.Type.NamedType()),
+ defaultValue: null,
+ new List()));
+ }
+
return new(
null,
new NameNode($"delete{FormatNameForObject(name, configEntity)}"),
new StringValueNode($"Delete a {name}"),
- new List {
- new InputValueDefinitionNode(
- null,
- idField.Name,
- new StringValueNode($"Id of the item to delete"),
- new NonNullTypeNode(idField.Type.NamedType()),
- null,
- new List())
- },
+ inputValues,
new NamedTypeNode(FormatNameForObject(name, configEntity)),
new List()
);
diff --git a/DataGateway.Service.GraphQLBuilder/Mutations/MutationBuilder.cs b/DataGateway.Service.GraphQLBuilder/Mutations/MutationBuilder.cs
index e9c5a017ab..95c926bcff 100644
--- a/DataGateway.Service.GraphQLBuilder/Mutations/MutationBuilder.cs
+++ b/DataGateway.Service.GraphQLBuilder/Mutations/MutationBuilder.cs
@@ -33,5 +33,15 @@ public static DocumentNode Build(DocumentNode root, DatabaseType databaseType, I
definitionNodes.AddRange(inputs.Values);
return new(definitionNodes);
}
+
+ public static Operation DetermineMutationOperationTypeBasedOnInputType(string inputTypeName)
+ {
+ return inputTypeName switch
+ {
+ string s when s.StartsWith(Operation.Create.ToString(), StringComparison.OrdinalIgnoreCase) => Operation.Create,
+ string s when s.StartsWith(Operation.Update.ToString(), StringComparison.OrdinalIgnoreCase) => Operation.UpdateGraphQL,
+ _ => Operation.Delete
+ };
+ }
}
}
diff --git a/DataGateway.Service.GraphQLBuilder/Mutations/UpdateMutationBuilder.cs b/DataGateway.Service.GraphQLBuilder/Mutations/UpdateMutationBuilder.cs
index 44b814f11a..55ebae4fe3 100644
--- a/DataGateway.Service.GraphQLBuilder/Mutations/UpdateMutationBuilder.cs
+++ b/DataGateway.Service.GraphQLBuilder/Mutations/UpdateMutationBuilder.cs
@@ -137,7 +137,7 @@ private static InputValueDefinitionNode GetComplexInputType(
private static NameNode GenerateInputTypeName(string typeName, Entity entity)
{
- return new($"Update{FormatNameForObject(typeName, entity)}Input");
+ return new($"{Operation.Update}{FormatNameForObject(typeName, entity)}Input");
}
///
@@ -158,29 +158,42 @@ public static FieldDefinitionNode Build(
DatabaseType databaseType)
{
InputObjectTypeDefinitionNode input = GenerateUpdateInputType(inputs, objectTypeDefinitionNode, name, root.Definitions.Where(d => d is HotChocolate.Language.IHasName).Cast(), entity, databaseType);
+ List idFields = FindPrimaryKeyFields(objectTypeDefinitionNode);
+ string description;
+ if (idFields.Count() > 1)
+ {
+ description = "One of the ids of the item being updated.";
+ }
+ else
+ {
+ description = "The ID of the item being updated.";
+ }
- FieldDefinitionNode idField = FindPrimaryKeyField(objectTypeDefinitionNode);
-
- return new(
- location: null,
- new NameNode($"update{FormatNameForObject(name, entity)}"),
- new StringValueNode($"Updates a {name}"),
- new List {
- new InputValueDefinitionNode(
+ List inputValues = new();
+ foreach (FieldDefinitionNode idField in idFields)
+ {
+ inputValues.Add(new InputValueDefinitionNode(
location: null,
idField.Name,
- new("The ID of the item being updated"),
+ new StringValueNode(description),
new NonNullTypeNode(idField.Type.NamedType()),
defaultValue: null,
- new List()),
- new InputValueDefinitionNode(
+ new List()));
+ }
+
+ inputValues.Add(new InputValueDefinitionNode(
location: null,
new NameNode(INPUT_ARGUMENT_NAME),
new StringValueNode($"Input representing all the fields for updating {name}"),
new NonNullTypeNode(new NamedTypeNode(input.Name)),
defaultValue: null,
- new List())
- },
+ new List()));
+
+ return new(
+ location: null,
+ new NameNode($"update{FormatNameForObject(name, entity)}"),
+ new StringValueNode($"Updates a {name}"),
+ inputValues,
new NamedTypeNode(FormatNameForObject(name, entity)),
new List()
);
diff --git a/DataGateway.Service.GraphQLBuilder/Queries/QueryBuilder.cs b/DataGateway.Service.GraphQLBuilder/Queries/QueryBuilder.cs
index c91b15bad2..3f09b23dd5 100644
--- a/DataGateway.Service.GraphQLBuilder/Queries/QueryBuilder.cs
+++ b/DataGateway.Service.GraphQLBuilder/Queries/QueryBuilder.cs
@@ -10,7 +10,8 @@ namespace Azure.DataGateway.Service.GraphQLBuilder.Queries
public static class QueryBuilder
{
public const string PAGINATION_FIELD_NAME = "items";
- public const string PAGINATION_TOKEN_FIELD_NAME = "after";
+ public const string PAGINATION_TOKEN_FIELD_NAME = "endCursor";
+ public const string PAGINATION_TOKEN_ARGUMENT_NAME = "after";
public const string HAS_NEXT_PAGE_FIELD_NAME = "hasNextPage";
public const string PAGE_START_ARGUMENT_NAME = "first";
public const string PAGINATION_OBJECT_TYPE_SUFFIX = "Connection";
@@ -48,20 +49,26 @@ public static DocumentNode Build(DocumentNode root, IDictionary
private static FieldDefinitionNode GenerateByPKQuery(ObjectTypeDefinitionNode objectTypeDefinitionNode, NameNode name)
{
- FieldDefinitionNode primaryKeyField = FindPrimaryKeyField(objectTypeDefinitionNode);
- return new(
- location: null,
- new NameNode($"{FormatNameForField(name)}_by_pk"),
- new StringValueNode($"Get a {name} from the database by its ID/primary key"),
- new List {
- new InputValueDefinitionNode(
- location : null,
+ IEnumerable primaryKeyFields =
+ FindPrimaryKeyFields(objectTypeDefinitionNode);
+ List inputValues = new();
+
+ foreach (FieldDefinitionNode primaryKeyField in primaryKeyFields)
+ {
+ inputValues.Add(new InputValueDefinitionNode(
+ location: null,
primaryKeyField.Name,
description: null,
primaryKeyField.Type,
defaultValue: null,
- new List())
- },
+ new List()));
+ }
+
+ return new(
+ location: null,
+ new NameNode($"{FormatNameForField(name)}_by_pk"),
+ new StringValueNode($"Get a {name} from the database by its ID/primary key"),
+ inputValues,
new NamedTypeNode(name),
new List()
);
@@ -99,7 +106,7 @@ private static List QueryArgumentsForField(string filt
return new()
{
new(location: null, new NameNode(PAGE_START_ARGUMENT_NAME), description: new StringValueNode("The number of items to return from the page start point"), new IntType().ToTypeNode(), defaultValue: null, new List()),
- new(location: null, new NameNode(PAGINATION_TOKEN_FIELD_NAME), new StringValueNode("A pagination token from a previous query to continue through a paginated list"), new StringType().ToTypeNode(), defaultValue: null, new List()),
+ new(location: null, new NameNode(PAGINATION_TOKEN_ARGUMENT_NAME), new StringValueNode("A pagination token from a previous query to continue through a paginated list"), new StringType().ToTypeNode(), defaultValue: null, new List()),
new(location: null, new NameNode(FILTER_FIELD_NAME), new StringValueNode("Filter options for query"), new NamedTypeNode(filterInputName), defaultValue: null, new List()),
new(location: null, new NameNode(ODATA_FILTER_FIELD_NAME), new StringValueNode("Filter options for query expressed as OData query language"), new StringType().ToTypeNode(), defaultValue: null, new List())
};
diff --git a/DataGateway.Service.GraphQLBuilder/Sql/SchemaConverter.cs b/DataGateway.Service.GraphQLBuilder/Sql/SchemaConverter.cs
index c4b9943bdd..eb6cc63b31 100644
--- a/DataGateway.Service.GraphQLBuilder/Sql/SchemaConverter.cs
+++ b/DataGateway.Service.GraphQLBuilder/Sql/SchemaConverter.cs
@@ -69,15 +69,15 @@ public static ObjectTypeDefinitionNode FromTableDefinition(string entityName, Ta
{
// Generate the field that represents the relationship to ObjectType, so you can navigate through it
// and walk the graph
- string targetTableName = relationship.TargetEntity.Split('.').Last();
- Entity referencedEntity = entities[targetTableName];
+ string targetEntityName = relationship.TargetEntity.Split('.').Last();
+ Entity referencedEntity = entities[targetEntityName];
INullableTypeNode targetField = relationship.Cardinality switch
{
Cardinality.One =>
- new NamedTypeNode(FormatNameForObject(targetTableName, referencedEntity)),
+ new NamedTypeNode(FormatNameForObject(targetEntityName, referencedEntity)),
Cardinality.Many =>
- new NamedTypeNode(QueryBuilder.GeneratePaginationTypeName(FormatNameForObject(targetTableName, referencedEntity))),
+ new NamedTypeNode(QueryBuilder.GeneratePaginationTypeName(FormatNameForObject(targetEntityName, referencedEntity))),
_ =>
throw new DataGatewayException("Specified cardinality isn't supported", HttpStatusCode.InternalServerError, DataGatewayException.SubStatusCodes.GraphQLMapping),
};
@@ -90,7 +90,9 @@ public static ObjectTypeDefinitionNode FromTableDefinition(string entityName, Ta
// TODO: Check for whether it should be a nullable relationship based on the relationship fields
new NonNullTypeNode(targetField),
new List {
- new(RelationshipDirectiveType.DirectiveName, new ArgumentNode("target", FormatNameForObject(targetTableName, referencedEntity)), new ArgumentNode("cardinality", relationship.Cardinality.ToString()))
+ new(RelationshipDirectiveType.DirectiveName,
+ new ArgumentNode("target", FormatNameForObject(targetEntityName, referencedEntity)),
+ new ArgumentNode("cardinality", relationship.Cardinality.ToString()))
});
fields.Add(relationshipField.Name.Value, relationshipField);
diff --git a/DataGateway.Service.GraphQLBuilder/Utils.cs b/DataGateway.Service.GraphQLBuilder/Utils.cs
index 607a110711..cd999460a1 100644
--- a/DataGateway.Service.GraphQLBuilder/Utils.cs
+++ b/DataGateway.Service.GraphQLBuilder/Utils.cs
@@ -1,3 +1,4 @@
+using Azure.DataGateway.Service.Exceptions;
using Azure.DataGateway.Service.GraphQLBuilder.Directives;
using HotChocolate.Language;
using HotChocolate.Types;
@@ -6,6 +7,8 @@ namespace Azure.DataGateway.Service.GraphQLBuilder
{
internal static class Utils
{
+ const string DEFAULT_PRIMARY_KEY_NAME = "id";
+
public static bool IsModelType(ObjectTypeDefinitionNode objectTypeDefinitionNode)
{
string modelDirectiveName = ModelDirectiveType.DirectiveName;
@@ -29,25 +32,39 @@ public static bool IsBuiltInType(ITypeNode typeNode)
return false;
}
- public static FieldDefinitionNode FindPrimaryKeyField(ObjectTypeDefinitionNode node)
+ ///
+ /// Find all the primary keys for a given object node
+ /// using the information available in the directives.
+ /// If no directives present, default to a field named "id" as the primary key.
+ /// If even that doesn't exist, throw an exception in initialization.
+ ///
+ public static List FindPrimaryKeyFields(ObjectTypeDefinitionNode node)
{
- FieldDefinitionNode? fieldDefinitionNode = node.Fields.FirstOrDefault(f => f.Directives.Any(d => d.Name.Value == PrimaryKeyDirectiveType.DirectiveName));
+ List fieldDefinitionNodes =
+ new(node.Fields.Where(f => f.Directives.Any(d => d.Name.Value == PrimaryKeyDirectiveType.DirectiveName)));
// By convention we look for a `@primaryKey` directive, if that didn't exist
// fallback to using an expected field name on the GraphQL object
- if (fieldDefinitionNode == null)
+ if (fieldDefinitionNodes.Count == 0)
{
- fieldDefinitionNode = node.Fields.FirstOrDefault(f => f.Name.Value == "id");
+ FieldDefinitionNode? fieldDefinitionNode =
+ node.Fields.FirstOrDefault(f => f.Name.Value == DEFAULT_PRIMARY_KEY_NAME);
+ if (fieldDefinitionNode is not null)
+ {
+ fieldDefinitionNodes.Add(fieldDefinitionNode);
+ }
}
// Nothing explicitly defined nor could we find anything using our conventions, fail out
- if (fieldDefinitionNode == null)
+ if (fieldDefinitionNodes.Count == 0)
{
- // TODO: Proper exception type
- throw new Exception("No primary key defined and conventions couldn't locate a fallback");
+ throw new DataGatewayException(
+ message: "No primary key defined and conventions couldn't locate a fallback",
+ subStatusCode: DataGatewayException.SubStatusCodes.ErrorInInitialization,
+ statusCode: System.Net.HttpStatusCode.ServiceUnavailable);
}
- return fieldDefinitionNode;
+ return fieldDefinitionNodes;
}
///
diff --git a/DataGateway.Service.Tests/Authorization/AuthorizationResolverUnitTests.cs b/DataGateway.Service.Tests/Authorization/AuthorizationResolverUnitTests.cs
index e474727d68..db378b2409 100644
--- a/DataGateway.Service.Tests/Authorization/AuthorizationResolverUnitTests.cs
+++ b/DataGateway.Service.Tests/Authorization/AuthorizationResolverUnitTests.cs
@@ -392,7 +392,7 @@ private static RuntimeConfig InitRuntimeConfig(
CosmosDb: null,
PostgreSql: null,
MySql: null,
- DataSource: new DataSource(DatabaseType: DatabaseType.mssql, ResolverConfigFile: null),
+ DataSource: new DataSource(DatabaseType: DatabaseType.mssql),
RuntimeSettings: new Dictionary(),
Entities: entityMap
);
diff --git a/DataGateway.Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs b/DataGateway.Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs
index ec9d48f167..da60c09975 100644
--- a/DataGateway.Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs
+++ b/DataGateway.Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs
@@ -12,7 +12,6 @@ namespace Azure.DataGateway.Service.Tests.Configuration
public class AuthenticationConfigValidatorUnitTests
{
private const string DEFAULT_CONNECTION_STRING = "Server=tcp:127.0.0.1";
- private const string DEFAULT_RESOLVER_FILE = "sql-config.json";
private const string DEFAULT_ISSUER = "https://login.microsoftonline.com";
#region Positive Tests
@@ -127,8 +126,7 @@ public void ValidateFailureWithUnneededEasyAuthConfig()
private static RuntimeConfig CreateRuntimeConfigWithAuthN(AuthenticationConfig authNConfig)
{
DataSource dataSource = new(
- DatabaseType: DatabaseType.mssql,
- ResolverConfigFile: DEFAULT_RESOLVER_FILE)
+ DatabaseType: DatabaseType.mssql)
{
ConnectionString = DEFAULT_CONNECTION_STRING
};
diff --git a/DataGateway.Service.Tests/Configuration/ConfigurationTests.cs b/DataGateway.Service.Tests/Configuration/ConfigurationTests.cs
index 9caeb6c5a2..b2a7cd04a1 100644
--- a/DataGateway.Service.Tests/Configuration/ConfigurationTests.cs
+++ b/DataGateway.Service.Tests/Configuration/ConfigurationTests.cs
@@ -12,8 +12,10 @@
using Azure.DataGateway.Service.Parsers;
using Azure.DataGateway.Service.Resolvers;
using Azure.DataGateway.Service.Services;
+using Azure.DataGateway.Service.Tests.SqlTests;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MySqlConnector;
@@ -155,18 +157,12 @@ public void TestLoadingLocalMsSqlSettings()
object mutationEngine = server.Services.GetService(typeof(IMutationEngine));
Assert.IsInstanceOfType(mutationEngine, typeof(SqlMutationEngine));
- object configValidator = server.Services.GetService(typeof(IConfigValidator));
- Assert.IsInstanceOfType(configValidator, typeof(SqlConfigValidator));
-
object queryBuilder = server.Services.GetService(typeof(IQueryBuilder));
Assert.IsInstanceOfType(queryBuilder, typeof(MsSqlQueryBuilder));
object queryExecutor = server.Services.GetService(typeof(IQueryExecutor));
Assert.IsInstanceOfType(queryExecutor, typeof(QueryExecutor));
- object graphQLMetadataProvider = server.Services.GetService(typeof(IGraphQLMetadataProvider));
- Assert.IsInstanceOfType(graphQLMetadataProvider, typeof(GraphQLFileMetadataProvider));
-
object sqlMetadataProvider = server.Services.GetService(typeof(ISqlMetadataProvider));
Assert.IsInstanceOfType(sqlMetadataProvider, typeof(MsSqlMetadataProvider));
}
@@ -183,18 +179,12 @@ public void TestLoadingLocalPostgresSettings()
object mutationEngine = server.Services.GetService(typeof(IMutationEngine));
Assert.IsInstanceOfType(mutationEngine, typeof(SqlMutationEngine));
- object configValidator = server.Services.GetService(typeof(IConfigValidator));
- Assert.IsInstanceOfType(configValidator, typeof(SqlConfigValidator));
-
object queryBuilder = server.Services.GetService(typeof(IQueryBuilder));
Assert.IsInstanceOfType(queryBuilder, typeof(PostgresQueryBuilder));
object queryExecutor = server.Services.GetService(typeof(IQueryExecutor));
Assert.IsInstanceOfType(queryExecutor, typeof(QueryExecutor));
- object graphQLMetadataProvider = server.Services.GetService(typeof(IGraphQLMetadataProvider));
- Assert.IsInstanceOfType(graphQLMetadataProvider, typeof(GraphQLFileMetadataProvider));
-
object sqlMetadataProvider = server.Services.GetService(typeof(ISqlMetadataProvider));
Assert.IsInstanceOfType(sqlMetadataProvider, typeof(PostgreSqlMetadataProvider));
}
@@ -211,18 +201,12 @@ public void TestLoadingLocalMySqlSettings()
object mutationEngine = server.Services.GetService(typeof(IMutationEngine));
Assert.IsInstanceOfType(mutationEngine, typeof(SqlMutationEngine));
- object configValidator = server.Services.GetService(typeof(IConfigValidator));
- Assert.IsInstanceOfType(configValidator, typeof(SqlConfigValidator));
-
object queryBuilder = server.Services.GetService(typeof(IQueryBuilder));
Assert.IsInstanceOfType(queryBuilder, typeof(MySqlQueryBuilder));
object queryExecutor = server.Services.GetService(typeof(IQueryExecutor));
Assert.IsInstanceOfType(queryExecutor, typeof(QueryExecutor));
- object graphQLMetadataProvider = server.Services.GetService(typeof(IGraphQLMetadataProvider));
- Assert.IsInstanceOfType(graphQLMetadataProvider, typeof(GraphQLFileMetadataProvider));
-
object sqlMetadataProvider = server.Services.GetService(typeof(ISqlMetadataProvider));
Assert.IsInstanceOfType(sqlMetadataProvider, typeof(MySqlMetadataProvider));
}
@@ -471,6 +455,15 @@ public void TestRuntimeEnvironmentVariable()
ValidateCosmosDbSetup(server);
}
+ [TestMethod("Validates the runtime configuration file.")]
+ public void TestConfigIsValid()
+ {
+ IOptionsMonitor configPath =
+ SqlTestHelper.LoadConfig(MSSQL_ENVIRONMENT);
+ IConfigValidator configValidator = new RuntimeConfigValidator(configPath);
+ configValidator.ValidateConfig();
+ }
+
///
/// Set the connection string to an invalid value and expect the service to be unavailable
// since without this env var, it would be available - guaranteeing this env variable
@@ -512,9 +505,6 @@ private static void ValidateCosmosDbSetup(TestServer server)
object mutationEngine = server.Services.GetService(typeof(IMutationEngine));
Assert.IsInstanceOfType(mutationEngine, typeof(CosmosMutationEngine));
- object configValidator = server.Services.GetService(typeof(IConfigValidator));
- Assert.IsInstanceOfType(configValidator, typeof(CosmosConfigValidator));
-
CosmosClientProvider cosmosClientProvider = server.Services.GetService(typeof(CosmosClientProvider)) as CosmosClientProvider;
Assert.IsNotNull(cosmosClientProvider);
Assert.IsNotNull(cosmosClientProvider.Client);
diff --git a/DataGateway.Service.Tests/CosmosTests/MetadataStoreProviderForTest.cs b/DataGateway.Service.Tests/CosmosTests/MetadataStoreProviderForTest.cs
index a3115d85ff..39c7f2fee9 100644
--- a/DataGateway.Service.Tests/CosmosTests/MetadataStoreProviderForTest.cs
+++ b/DataGateway.Service.Tests/CosmosTests/MetadataStoreProviderForTest.cs
@@ -1,6 +1,4 @@
using System.Collections.Generic;
-using System.Threading.Tasks;
-using Azure.DataGateway.Config;
using Azure.DataGateway.Service.Models;
using Azure.DataGateway.Service.Services;
@@ -10,7 +8,6 @@ public class MetadataStoreProviderForTest : IGraphQLMetadataProvider
{
public string GraphQLSchema { get; set; }
public Dictionary MutationResolvers { get; set; } = new();
- public Dictionary Tables { get; set; } = new();
public Dictionary GraphQLTypes { get; set; } = new();
public string GetGraphQLSchema()
@@ -25,13 +22,6 @@ public MutationResolver GetMutationResolver(string name)
return result;
}
- public TableDefinition GetTableDefinition(string name)
- {
- TableDefinition result;
- Tables.TryGetValue(name, out result);
- return result;
- }
-
public void StoreMutationResolver(MutationResolver mutationResolver)
{
MutationResolvers.Add(mutationResolver.Id, mutationResolver);
@@ -46,16 +36,5 @@ public GraphQLType GetGraphQLType(string name)
{
return GraphQLTypes.TryGetValue(name, out GraphQLType graphqlType) ? graphqlType : null;
}
-
- public ResolverConfig GetResolvedConfig()
- {
- throw new System.NotImplementedException();
- }
-
- public static Task InitializeAsync()
- {
- // no-op
- return Task.CompletedTask;
- }
}
}
diff --git a/DataGateway.Service.Tests/CosmosTests/MutationTests.cs b/DataGateway.Service.Tests/CosmosTests/MutationTests.cs
index f3abe6e78a..fa203a1488 100644
--- a/DataGateway.Service.Tests/CosmosTests/MutationTests.cs
+++ b/DataGateway.Service.Tests/CosmosTests/MutationTests.cs
@@ -9,20 +9,16 @@ namespace Azure.DataGateway.Service.Tests.CosmosTests
public class MutationTests : TestBase
{
private static readonly string _containerName = Guid.NewGuid().ToString();
- private static readonly string _mutationStringFormat = @"
- mutation ($id: String, $name: String)
- {
- addPlanet (id: $id, name: $name)
- {
+ private static readonly string _createPlanetMutation = @"
+ mutation ($item: CreatePlanetInput!) {
+ createPlanet (item: $item) {
id
name
}
}";
- private static readonly string _mutationDeleteItemStringFormat = @"
- mutation ($id: String)
- {
- deletePlanet (id: $id)
- {
+ private static readonly string _deletePlanetMutation = @"
+ mutation ($id: ID!) {
+ deletePlanet (id: $id) {
id
name
}
@@ -39,7 +35,7 @@ public static void TestFixtureSetup(TestContext context)
Client.CreateDatabaseIfNotExistsAsync(DATABASE_NAME).Wait();
Client.GetDatabase(DATABASE_NAME).CreateContainerIfNotExistsAsync(_containerName, "/id").Wait();
CreateItems(DATABASE_NAME, _containerName, 10);
- RegisterMutationResolver("addPlanet", DATABASE_NAME, _containerName);
+ RegisterMutationResolver("createPlanet", DATABASE_NAME, _containerName);
RegisterMutationResolver("deletePlanet", DATABASE_NAME, _containerName, "Delete");
}
@@ -48,7 +44,12 @@ public async Task CanCreateItemWithVariables()
{
// Run mutation Add planet;
string id = Guid.NewGuid().ToString();
- JsonElement response = await ExecuteGraphQLRequestAsync("addPlanet", _mutationStringFormat, new() { { "id", id }, { "name", "test_name" } });
+ var input = new
+ {
+ id,
+ name = "test_name"
+ };
+ JsonElement response = await ExecuteGraphQLRequestAsync("createPlanet", _createPlanetMutation, new() { { "item", input } });
// Validate results
Assert.AreEqual(id, response.GetProperty("id").GetString());
@@ -59,10 +60,15 @@ public async Task CanDeleteItemWithVariables()
{
// Pop an item in to delete
string id = Guid.NewGuid().ToString();
- _ = await ExecuteGraphQLRequestAsync("addPlanet", _mutationStringFormat, new() { { "id", id }, { "name", "test_name" } });
+ var input = new
+ {
+ id,
+ name = "test_name"
+ };
+ _ = await ExecuteGraphQLRequestAsync("createPlanet", _createPlanetMutation, new() { { "item", input } });
// Run mutation delete item;
- JsonElement response = await ExecuteGraphQLRequestAsync("deletePlanet", _mutationDeleteItemStringFormat, new() { { "id", id } });
+ JsonElement response = await ExecuteGraphQLRequestAsync("deletePlanet", _deletePlanetMutation, new() { { "id", id } });
// Validate results
Assert.IsNull(response.GetProperty("id").GetString());
@@ -76,12 +82,12 @@ public async Task CanCreateItemWithoutVariables()
const string name = "test_name";
string mutation = $@"
mutation {{
- addPlanet (id: ""{id}"", name: ""{name}"") {{
+ createPlanet (item: {{ id: ""{id}"", name: ""{name}"" }}) {{
id
name
}}
}}";
- JsonElement response = await ExecuteGraphQLRequestAsync("addPlanet", mutation, variables: new());
+ JsonElement response = await ExecuteGraphQLRequestAsync("createPlanet", mutation, variables: new());
// Validate results
Assert.AreEqual(id, response.GetProperty("id").GetString());
@@ -93,14 +99,14 @@ public async Task CanDeleteItemWithoutVariables()
// Pop an item in to delete
string id = Guid.NewGuid().ToString();
const string name = "test_name";
- string addMutation = $@"
+ string mutation = $@"
mutation {{
- addPlanet (id: ""{id}"", name: ""{name}"") {{
+ createPlanet (item: {{ id: ""{id}"", name: ""{name}"" }}) {{
id
name
}}
}}";
- _ = await ExecuteGraphQLRequestAsync("addPlanet", addMutation, variables: new());
+ _ = await ExecuteGraphQLRequestAsync("createPlanet", mutation, variables: new());
// Run mutation delete item;
string deleteMutation = $@"
@@ -122,13 +128,14 @@ public async Task MutationMissingInputReturnError()
// Run mutation Add planet without any input
string mutation = $@"
mutation {{
- addPlanet {{
+ createPlanet {{
id
name
}}
}}";
- JsonElement response = await ExecuteGraphQLRequestAsync("addPlanet", mutation, variables: new());
- Assert.AreEqual("inputDict is missing", response[0].GetProperty("message").ToString());
+ JsonElement response = await ExecuteGraphQLRequestAsync("createPlanet", mutation, variables: new());
+ string errorMessage = response[0].GetProperty("message").ToString();
+ Assert.IsTrue(errorMessage.Contains("The argument `item` is required."), $"The actual error is {errorMessage}");
}
[TestMethod]
@@ -138,12 +145,12 @@ public async Task MutationMissingRequiredIdReturnError()
const string name = "test_name";
string mutation = $@"
mutation {{
- addPlanet ( name: ""{name}"") {{
+ createPlanet (item: {{ name: ""{name}"" }}) {{
id
name
}}
}}";
- JsonElement response = await ExecuteGraphQLRequestAsync("addPlanet", mutation, variables: new());
+ JsonElement response = await ExecuteGraphQLRequestAsync("createPlanet", mutation, variables: new());
Assert.AreEqual("id field is mandatory", response[0].GetProperty("message").ToString());
}
diff --git a/DataGateway.Service.Tests/CosmosTests/QueryFilterTests.cs b/DataGateway.Service.Tests/CosmosTests/QueryFilterTests.cs
index c2c38a6eef..7edc8aeecb 100644
--- a/DataGateway.Service.Tests/CosmosTests/QueryFilterTests.cs
+++ b/DataGateway.Service.Tests/CosmosTests/QueryFilterTests.cs
@@ -12,7 +12,7 @@ public class QueryFilterTests : TestBase
{
private static readonly string _containerName = Guid.NewGuid().ToString();
private static int _pageSize = 10;
- private static readonly string _graphQLQueryName = "getPlanetsWithFilter";
+ private static readonly string _graphQLQueryName = "planets";
[ClassInitialize]
public static void TestFixtureSetup(TestContext context)
@@ -32,7 +32,7 @@ public static void TestFixtureSetup(TestContext context)
public async Task TestStringFiltersEq()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {name: {eq: ""Endor""}})
+ planets(first: 10, _filter: {name: {eq: ""Endor""}})
{
items {
name
@@ -55,7 +55,6 @@ private static async Task ExecuteAndValidateResult(string graphQLQueryName, stri
private static void ValidateResults(JsonElement actual, JsonElement expected)
{
-
Assert.IsNotNull(expected);
Assert.IsNotNull(actual);
Assert.IsTrue(JToken.DeepEquals(JToken.Parse(actual.ToString()), JToken.Parse(expected.ToString())));
@@ -69,7 +68,7 @@ public async Task TestStringFiltersNeq()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {name: {neq: ""Endor""}})
+ planets(first: 10, _filter: {name: {neq: ""Endor""}})
{
items {
name
@@ -89,7 +88,7 @@ public async Task TestStringFiltersNeq()
public async Task TestStringFiltersStartsWith()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {name: {startsWith: ""En""}})
+ planets(first: 10, _filter: {name: {startsWith: ""En""}})
{
items {
name
@@ -109,7 +108,7 @@ public async Task TestStringFiltersStartsWith()
public async Task TestStringFiltersEndsWith()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {name: {endsWith: ""h""}})
+ planets(first: 10, _filter: {name: {endsWith: ""h""}})
{
items {
name
@@ -129,7 +128,7 @@ public async Task TestStringFiltersEndsWith()
public async Task TestStringFiltersContains()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {name: {contains: ""pi""}})
+ planets(first: 10, _filter: {name: {contains: ""pi""}})
{
items {
name
@@ -149,7 +148,7 @@ public async Task TestStringFiltersContains()
public async Task TestStringFiltersNotContains()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {name: {notContains: ""pi""}})
+ planets(first: 10, _filter: {name: {notContains: ""pi""}})
{
items {
name
@@ -171,7 +170,7 @@ public async Task TestStringFiltersNotContains()
public async Task TestStringFiltersContainsWithSpecialChars()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {name: {contains: ""%""}})
+ planets(first: 10, _filter: {name: {contains: ""%""}})
{
items {
name
@@ -191,7 +190,7 @@ public async Task TestStringFiltersContainsWithSpecialChars()
public async Task TestIntFiltersEq()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {age: {eq: 4}})
+ planets(first: 10, _filter: {age: {eq: 4}})
{
items {
age
@@ -210,7 +209,7 @@ public async Task TestIntFiltersEq()
public async Task TestIntFiltersNeq()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {age: {neq: 4}})
+ planets(first: 10, _filter: {age: {neq: 4}})
{
items {
age
@@ -229,7 +228,7 @@ public async Task TestIntFiltersNeq()
public async Task TestIntFiltersGtLt()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {age: {gt: 2 lt: 5}})
+ planets(first: 10, _filter: {age: {gt: 2 lt: 5}})
{
items {
age
@@ -248,7 +247,7 @@ public async Task TestIntFiltersGtLt()
public async Task TestIntFiltersGteLte()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {age: {gte: 2 lte: 5}})
+ planets(first: 10, _filter: {age: {gte: 2 lte: 5}})
{
items {
age
@@ -275,7 +274,7 @@ public async Task TestIntFiltersGteLte()
public async Task TestCreatingParenthesis1()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {
+ planets(first: 10, _filter: {
name: {contains: ""En""}
or: [
{age:{gt: 2 lt: 4}},
@@ -311,7 +310,7 @@ public async Task TestCreatingParenthesis1()
public async Task TestCreatingParenthesis2()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {
+ planets(first: 10, _filter: {
or: [
{age: {gt: 2} and: [{age: {lt: 4}}]},
{age: {gte: 2} name: {contains: ""En""}}
@@ -342,7 +341,7 @@ public async Task TestCreatingParenthesis2()
public async Task TestComplicatedFilter()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {
+ planets(first: 10, _filter: {
age: {gte: 1}
name: {notContains: ""En""}
and: [
@@ -381,9 +380,9 @@ public async Task TestComplicatedFilter()
[TestMethod]
public async Task TestOnlyEmptyAnd()
{
- string graphQLQueryName = "getPlanetsWithFilter";
+ string graphQLQueryName = "planets";
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {and: []})
+ planets(first: 10, _filter: {and: []})
{
items {
id
@@ -401,9 +400,9 @@ public async Task TestOnlyEmptyAnd()
[TestMethod]
public async Task TestOnlyEmptyOr()
{
- string graphQLQueryName = "getPlanetsWithFilter";
+ string graphQLQueryName = "planets";
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {or: []})
+ planets(first: 10, _filter: {or: []})
{
items {
id
@@ -422,7 +421,7 @@ public async Task TestOnlyEmptyOr()
public async Task TestGetNullIntFields()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {age: {isNull: false}})
+ planets(first: 10, _filter: {age: {isNull: false}})
{
items {
name
@@ -442,7 +441,7 @@ public async Task TestGetNullIntFields()
public async Task TestGetNonNullIntFields()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {age: {isNull: true}})
+ planets(first: 10, _filter: {age: {isNull: true}})
{
items {
name
@@ -462,7 +461,7 @@ public async Task TestGetNonNullIntFields()
public async Task TestGetNullStringFields()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {name: {isNull: true}})
+ planets(first: 10, _filter: {name: {isNull: true}})
{
items {
name
@@ -482,7 +481,7 @@ public async Task TestGetNullStringFields()
public async Task TestGetNonNullStringFields()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {name: {isNull: false}})
+ planets(first: 10, _filter: {name: {isNull: false}})
{
items {
name
@@ -504,7 +503,7 @@ public async Task TestGetNonNullStringFields()
public async Task TestExplicitNullFieldsAreIgnored()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {age: {gte:2 lte: null}
+ planets(first: 10, _filter: {age: {gte:2 lte: null}
name: null
or: null })
{
@@ -526,7 +525,7 @@ public async Task TestExplicitNullFieldsAreIgnored()
public async Task TestInputObjectWithOnlyNullFieldsEvaluatesToFalse()
{
string gqlQuery = @"{
- getPlanetsWithFilter(first: 10, _filter: {age: {lte: null}})
+ planets(first: 10, _filter: {age: {lte: null}})
{
items {
name
diff --git a/DataGateway.Service.Tests/CosmosTests/QueryTests.cs b/DataGateway.Service.Tests/CosmosTests/QueryTests.cs
index b440040261..acdb6bab10 100644
--- a/DataGateway.Service.Tests/CosmosTests/QueryTests.cs
+++ b/DataGateway.Service.Tests/CosmosTests/QueryTests.cs
@@ -3,6 +3,7 @@
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
+using Azure.DataGateway.Service.GraphQLBuilder.Queries;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Azure.DataGateway.Service.Tests.CosmosTests
@@ -12,16 +13,14 @@ public class QueryTests : TestBase
{
private static readonly string _containerName = Guid.NewGuid().ToString();
- public static readonly string PlanetByIdQueryFormat = @"
+ public static readonly string PlanetByPKQuery = @"
query ($id: ID) {
- planetById (id: $id) {
+ planet_by_pk (id: $id) {
id
name
}
}";
-
- public static readonly string PlanetListQuery = @"{planetList{ id, name}}";
- public static readonly string PlanetConnectionQueryStringFormat = @"
+ public static readonly string PlanetsQuery = @"
query ($first: Int!, $after: String) {
planets (first: $first, after: $after) {
items {
@@ -45,18 +44,15 @@ public class QueryTests : TestBase
}
";
private static List _idList;
+ private const int TOTAL_ITEM_COUNT = 10;
- ///
- /// Executes once for the test class.
- ///
- ///
[ClassInitialize]
public static void TestFixtureSetup(TestContext context)
{
Init(context);
Client.CreateDatabaseIfNotExistsAsync(DATABASE_NAME).Wait();
Client.GetDatabase(DATABASE_NAME).CreateContainerIfNotExistsAsync(_containerName, "/id").Wait();
- _idList = CreateItems(DATABASE_NAME, _containerName, 10);
+ _idList = CreateItems(DATABASE_NAME, _containerName, TOTAL_ITEM_COUNT);
RegisterGraphQLType("Planet", DATABASE_NAME, _containerName);
RegisterGraphQLType("PlanetConnection", DATABASE_NAME, _containerName, true);
}
@@ -66,43 +62,31 @@ public async Task GetByPrimaryKeyWithVariables()
{
// Run query
string id = _idList[0];
- JsonElement response = await ExecuteGraphQLRequestAsync("planetById", PlanetByIdQueryFormat, new() { { "id", id } });
+ JsonElement response = await ExecuteGraphQLRequestAsync("planet_by_pk", PlanetByPKQuery, new() { { "id", id } });
// Validate results
Assert.AreEqual(id, response.GetProperty("id").GetString());
}
- ///
- /// This test runs a query to list all the items in a container. Then, gets all the items by
- /// running a paginated query that gets n items per page. We then make sure the number of documents match
- ///
[TestMethod]
public async Task GetPaginatedWithVariables()
{
- // Run query
- JsonElement response = await ExecuteGraphQLRequestAsync("planetList", PlanetListQuery);
- int actualElements = response.GetArrayLength();
- List responseTotal = new();
- ConvertJsonElementToStringList(response, responseTotal);
-
// Run paginated query
+ const int pagesize = TOTAL_ITEM_COUNT / 2;
int totalElementsFromPaginatedQuery = 0;
- string continuationToken = null;
- const int pagesize = 5;
- List pagedResponse = new();
+ string afterToken = null;
do
{
- JsonElement page = await ExecuteGraphQLRequestAsync("planets", PlanetConnectionQueryStringFormat, new() { { "first", pagesize }, { "after", continuationToken } });
- JsonElement continuation = page.GetProperty("endCursor");
- continuationToken = continuation.ToString();
- totalElementsFromPaginatedQuery += page.GetProperty("items").GetArrayLength();
- ConvertJsonElementToStringList(page.GetProperty("items"), pagedResponse);
- } while (!string.IsNullOrEmpty(continuationToken));
+ JsonElement page = await ExecuteGraphQLRequestAsync("planets",
+ PlanetsQuery, new() { { "first", pagesize }, { "after", afterToken } });
+ JsonElement after = page.GetProperty(QueryBuilder.PAGINATION_TOKEN_FIELD_NAME);
+ afterToken = after.ToString();
+ totalElementsFromPaginatedQuery += page.GetProperty(QueryBuilder.PAGINATION_FIELD_NAME).GetArrayLength();
+ } while (!string.IsNullOrEmpty(afterToken));
// Validate results
- Assert.AreEqual(actualElements, totalElementsFromPaginatedQuery);
- Assert.IsTrue(responseTotal.SequenceEqual(pagedResponse));
+ Assert.AreEqual(TOTAL_ITEM_COUNT, totalElementsFromPaginatedQuery);
}
[TestMethod]
@@ -112,12 +96,12 @@ public async Task GetByPrimaryKeyWithoutVariables()
string id = _idList[0];
string query = @$"
query {{
- planetById (id: ""{id}"") {{
+ planet_by_pk (id: ""{id}"") {{
id
name
}}
}}";
- JsonElement response = await ExecuteGraphQLRequestAsync("planetById", query);
+ JsonElement response = await ExecuteGraphQLRequestAsync("planet_by_pk", query);
// Validate results
Assert.AreEqual(id, response.GetProperty("id").GetString());
@@ -126,23 +110,17 @@ public async Task GetByPrimaryKeyWithoutVariables()
[TestMethod]
public async Task GetPaginatedWithoutVariables()
{
- // Run query
- JsonElement response = await ExecuteGraphQLRequestAsync("planetList", PlanetListQuery);
- int actualElements = response.GetArrayLength();
- List responseTotal = new();
- ConvertJsonElementToStringList(response, responseTotal);
-
// Run paginated query
+ const int pagesize = TOTAL_ITEM_COUNT / 2;
int totalElementsFromPaginatedQuery = 0;
- string continuationToken = null;
- const int pagesize = 5;
+ string afterToken = null;
List pagedResponse = new();
do
{
string planetConnectionQueryStringFormat = @$"
query {{
- planets (first: {pagesize}, after: {(continuationToken == null ? "null" : "\"" + continuationToken + "\"")}) {{
+ planets (first: {pagesize}, after: {(afterToken == null ? "null" : "\"" + afterToken + "\"")}) {{
items {{
id
name
@@ -151,84 +129,14 @@ public async Task GetPaginatedWithoutVariables()
hasNextPage
}}
}}";
-
JsonElement page = await ExecuteGraphQLRequestAsync("planets", planetConnectionQueryStringFormat, variables: new());
- JsonElement continuation = page.GetProperty("endCursor");
- continuationToken = continuation.ToString();
- totalElementsFromPaginatedQuery += page.GetProperty("items").GetArrayLength();
- ConvertJsonElementToStringList(page.GetProperty("items"), pagedResponse);
- } while (!string.IsNullOrEmpty(continuationToken));
-
- // Validate results
- Assert.AreEqual(actualElements, totalElementsFromPaginatedQuery);
- Assert.IsTrue(responseTotal.SequenceEqual(pagedResponse));
- }
-
- ///
- /// Query List Type with input parameters
- ///
- ///
- [TestMethod]
- public async Task GetListTypeWithParameters()
- {
- string id = _idList[0];
- string query = @$"
-query {{
- getPlanetListById (id: ""{id}"") {{
- id
- name
- }}
-}}";
-
- JsonElement response = await ExecuteGraphQLRequestAsync("getPlanetListById", query);
-
- // Validate results
- Assert.AreEqual(1, response.GetArrayLength());
- Assert.AreEqual(id, response[0].GetProperty("id").ToString());
- }
-
- ///
- /// Query single item by non-primary key field, found no match
- ///
- ///
- [TestMethod]
- public async Task GetByNonePrimaryFieldResultNotFound()
- {
- string name = "non-existed name";
- string query = @$"
-query {{
- getPlanetByName (name: ""{name}"") {{
- id
- name
- }}
-}}";
-
- JsonElement response = await ExecuteGraphQLRequestAsync("getPlanetByName", query);
-
- // Validate results
- Assert.IsNull(response.Deserialize());
- }
-
- ///
- /// Query single item by non-primary key field, found record back
- ///
- ///
- [TestMethod]
- public async Task GetByNonPrimaryFieldReturnsResult()
- {
- string name = "Earth";
- string query = @$"
-query {{
- getPlanetByName (name: ""{name}"") {{
- id
- name
- }}
-}}";
-
- JsonElement response = await ExecuteGraphQLRequestAsync("getPlanetByName", query);
+ JsonElement after = page.GetProperty(QueryBuilder.PAGINATION_TOKEN_FIELD_NAME);
+ afterToken = after.ToString();
+ totalElementsFromPaginatedQuery += page.GetProperty(QueryBuilder.PAGINATION_FIELD_NAME).GetArrayLength();
+ ConvertJsonElementToStringList(page.GetProperty(QueryBuilder.PAGINATION_FIELD_NAME), pagedResponse);
+ } while (!string.IsNullOrEmpty(afterToken));
- // Validate results
- Assert.AreEqual(name, response.GetProperty("name").ToString());
+ Assert.AreEqual(TOTAL_ITEM_COUNT, totalElementsFromPaginatedQuery);
}
///
@@ -242,7 +150,7 @@ public async Task GetByPrimaryKeyWithInnerObject()
string id = _idList[0];
string query = @$"
query {{
- planetById (id: ""{id}"") {{
+ planet_by_pk (id: ""{id}"") {{
id
name
character {{
@@ -251,12 +159,13 @@ public async Task GetByPrimaryKeyWithInnerObject()
}}
}}
}}";
- JsonElement response = await ExecuteGraphQLRequestAsync("planetById", query);
+ JsonElement response = await ExecuteGraphQLRequestAsync("planet_by_pk", query);
// Validate results
Assert.AreEqual(id, response.GetProperty("id").GetString());
}
+ [Ignore]
[TestMethod]
public async Task GetWithOrderBy()
{
@@ -284,14 +193,10 @@ private static void ConvertJsonElementToStringList(JsonElement ele, List
}
}
- ///
- /// Runs once after all tests in this class are executed
- ///
[ClassCleanup]
public static void TestFixtureTearDown()
{
Client.GetDatabase(DATABASE_NAME).GetContainer(_containerName).DeleteContainerAsync().Wait();
}
-
}
}
diff --git a/DataGateway.Service.Tests/CosmosTests/TestBase.cs b/DataGateway.Service.Tests/CosmosTests/TestBase.cs
index af92febdce..ee314a59f1 100644
--- a/DataGateway.Service.Tests/CosmosTests/TestBase.cs
+++ b/DataGateway.Service.Tests/CosmosTests/TestBase.cs
@@ -34,10 +34,25 @@ public class TestBase
public static void Init(TestContext context)
{
_clientProvider = new CosmosClientProvider(TestHelper.ConfigPath);
- _metadataStoreProvider = new MetadataStoreProviderForTest
- {
- GraphQLSchema = File.ReadAllText("schema.gql")
- };
+ _metadataStoreProvider = new MetadataStoreProviderForTest();
+ string jsonString = @"
+type Character @model {
+ id : ID,
+ name : String,
+ type: String,
+ homePlanet: Int,
+ primaryFunction: String
+}
+
+type Planet @model {
+ id : ID,
+ name : String,
+ character: Character,
+ age : Int,
+ dimension : String
+}";
+
+ _metadataStoreProvider.GraphQLSchema = jsonString;
_queryEngine = new CosmosQueryEngine(_clientProvider, _metadataStoreProvider);
_mutationEngine = new CosmosMutationEngine(_clientProvider, _metadataStoreProvider);
_graphQLService = new GraphQLService(
diff --git a/DataGateway.Service.Tests/GraphQLBuilder/MutationBuilderTests.cs b/DataGateway.Service.Tests/GraphQLBuilder/MutationBuilderTests.cs
index 1dbfc6a769..ba1ea003cd 100644
--- a/DataGateway.Service.Tests/GraphQLBuilder/MutationBuilderTests.cs
+++ b/DataGateway.Service.Tests/GraphQLBuilder/MutationBuilderTests.cs
@@ -458,7 +458,8 @@ type Foo @model {
DocumentNode root = Utf8GraphQLParser.Parse(gql);
- DocumentNode mutationRoot = MutationBuilder.Build(root, DatabaseType.cosmos, new Dictionary { { "Foo", GenerateEmptyEntity() } });
+ DocumentNode mutationRoot = MutationBuilder.Build(
+ root, DatabaseType.cosmos, new Dictionary { { "Foo", GenerateEmptyEntity() } });
ObjectTypeDefinitionNode query = GetMutationNode(mutationRoot);
FieldDefinitionNode field = query.Fields.First(f => f.Name.Value == $"deleteFoo");
diff --git a/DataGateway.Service.Tests/GraphQLBuilder/QueryBuilderTests.cs b/DataGateway.Service.Tests/GraphQLBuilder/QueryBuilderTests.cs
index 11a8f3dc47..e1563e39c8 100644
--- a/DataGateway.Service.Tests/GraphQLBuilder/QueryBuilderTests.cs
+++ b/DataGateway.Service.Tests/GraphQLBuilder/QueryBuilderTests.cs
@@ -153,7 +153,7 @@ type Table @model(name: ""table"") {
FieldDefinitionNode field = updatedNode.Fields[0];
Assert.AreEqual(4, field.Arguments.Count, "Query fields should have 4 arguments");
Assert.AreEqual(QueryBuilder.PAGE_START_ARGUMENT_NAME, field.Arguments[0].Name.Value, "First argument should be the page start");
- Assert.AreEqual(QueryBuilder.PAGINATION_TOKEN_FIELD_NAME, field.Arguments[1].Name.Value, "Second argument is pagination token");
+ Assert.AreEqual(QueryBuilder.PAGINATION_TOKEN_ARGUMENT_NAME, field.Arguments[1].Name.Value, "Second argument is pagination token");
Assert.AreEqual(QueryBuilder.FILTER_FIELD_NAME, field.Arguments[2].Name.Value, "Third argument is typed filter field");
Assert.AreEqual("FkTableFilter", field.Arguments[2].Type.NamedType().Name.Value, "Typed filter field should be filter type of target object type");
Assert.AreEqual(QueryBuilder.ODATA_FILTER_FIELD_NAME, field.Arguments[3].Name.Value, "Forth field is odata query field");
diff --git a/DataGateway.Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs b/DataGateway.Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs
index 9409970a60..4d20391612 100644
--- a/DataGateway.Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs
+++ b/DataGateway.Service.Tests/GraphQLBuilder/Sql/SchemaConverterTests.cs
@@ -14,10 +14,16 @@ namespace Azure.DataGateway.Service.Tests.GraphQLBuilder.Sql
[TestCategory("GraphQL Schema Builder")]
public class SchemaConverterTests
{
- private static Entity GenerateEmptyEntity()
- {
- return new Entity("dbo.entity", Rest: null, GraphQL: null, Array.Empty(), Relationships: new(), Mappings: new());
- }
+ const string SCHEMA_NAME = "dbo";
+ const string TABLE_NAME = "tableName";
+ const string COLUMN_NAME = "columnName";
+ const string REF_COLNAME = "ref_col_in_source";
+ const string SOURCE_ENTITY = "sourceEntity";
+ const string FIELD_NAME_FOR_TARGET = "target";
+
+ const string TARGET_ENTITY = "TargetEntity";
+ const string REFERENCED_TABLE = "fkTable";
+ const string REFD_COLNAME = "fk_col";
[DataTestMethod]
[DataRow("test", "Test")]
@@ -184,130 +190,27 @@ public void NonNullColumnBecomesNonNullField()
[TestMethod]
public void ForeignKeyGeneratesObjectAndColumnField()
{
- TableDefinition table = new();
-
- string columnName = "columnName";
- table.Columns.Add(columnName, new ColumnDefinition
- {
- SystemType = typeof(string),
- IsNullable = false,
- });
- const string refColName = "ref_col";
- const string foreignKeyTable = "fkTable";
- table.ForeignKeys.Add("forign_key", new ForeignKeyDefinition { ReferencedTable = foreignKeyTable, ReferencingColumns = new List { refColName } });
- table.Columns.Add(refColName, new ColumnDefinition
- {
- SystemType = typeof(long)
- });
-
- Dictionary relationships =
- new()
- {
- {
- foreignKeyTable,
- new Relationship(
- Cardinality.One,
- foreignKeyTable,
- SourceFields: null,
- TargetFields: null,
- LinkingObject: null,
- LinkingSourceFields: null,
- LinkingTargetFields: null)
- }
- };
- Entity configEntity = GenerateEmptyEntity() with { Relationships = relationships };
- Entity relationshipEntity = GenerateEmptyEntity();
-
- ObjectTypeDefinitionNode od = SchemaConverter.FromTableDefinition("table", table, configEntity, new() { { foreignKeyTable, relationshipEntity } });
-
+ ObjectTypeDefinitionNode od = GenerateObjectWithRelationship(Cardinality.Many);
Assert.AreEqual(3, od.Fields.Count);
}
[TestMethod]
public void ForeignKeyObjectFieldNameAndTypeMatchesReferenceTable()
{
- TableDefinition table = new();
-
- string columnName = "columnName";
- table.Columns.Add(columnName, new ColumnDefinition
- {
- SystemType = typeof(string),
- IsNullable = false,
- });
- const string foreignKeyTable = "FkTable";
- const string refColName = "ref_col";
- table.ForeignKeys.Add("foreign_key", new ForeignKeyDefinition { ReferencedTable = foreignKeyTable, ReferencingColumns = new List { refColName } });
- table.Columns.Add(refColName, new ColumnDefinition
- {
- SystemType = typeof(long)
- });
- Dictionary relationships =
- new()
- {
- {
- foreignKeyTable,
- new Relationship(
- Cardinality.One,
- foreignKeyTable,
- SourceFields: null,
- TargetFields: null,
- LinkingObject: null,
- LinkingSourceFields: null,
- LinkingTargetFields: null)
- }
- };
- Entity configEntity = GenerateEmptyEntity() with { Relationships = relationships };
- Entity relationshipEntity = GenerateEmptyEntity();
+ ObjectTypeDefinitionNode od = GenerateObjectWithRelationship(Cardinality.One);
+ FieldDefinitionNode field
+ = od.Fields.First(f => f.Name.Value != REF_COLNAME && f.Name.Value != COLUMN_NAME);
- ObjectTypeDefinitionNode od = SchemaConverter.FromTableDefinition("table", table, configEntity, new() { { foreignKeyTable, relationshipEntity } });
-
- FieldDefinitionNode field = od.Fields.First(f => f.Name.Value != refColName && f.Name.Value != columnName);
-
- Assert.AreEqual("fkTable", field.Name.Value);
- Assert.AreEqual(foreignKeyTable, field.Type.NamedType().Name.Value);
+ Assert.AreEqual(FIELD_NAME_FOR_TARGET, field.Name.Value);
+ Assert.AreEqual(TARGET_ENTITY, field.Type.NamedType().Name.Value);
}
[TestMethod]
public void ForeignKeyFieldWillHaveRelationshipDirective()
{
- TableDefinition table = new();
-
- string columnName = "columnName";
- table.Columns.Add(columnName, new ColumnDefinition
- {
- SystemType = typeof(string),
- IsNullable = false,
- });
- const string foreignKeyTable = "FkTable";
- const string refColName = "ref_col";
- table.ForeignKeys.Add("foreign_key", new ForeignKeyDefinition { ReferencedTable = foreignKeyTable, ReferencingColumns = new List { refColName } });
- table.Columns.Add(refColName, new ColumnDefinition
- {
- SystemType = typeof(long)
- });
- string relationshipName = "otherTable";
- Dictionary relationships =
- new()
- {
- {
- relationshipName,
- new Relationship(
- Cardinality.One,
- foreignKeyTable,
- SourceFields: null,
- TargetFields: null,
- LinkingObject: null,
- LinkingSourceFields: null,
- LinkingTargetFields: null)
- }
- };
- Entity configEntity = GenerateEmptyEntity() with { Relationships = relationships };
- Entity relationshipEntity = GenerateEmptyEntity();
-
- ObjectTypeDefinitionNode od = SchemaConverter.FromTableDefinition("table", table, configEntity, new() { { foreignKeyTable, relationshipEntity } });
-
- FieldDefinitionNode field = od.Fields.First(f => f.Name.Value == relationshipName);
+ ObjectTypeDefinitionNode od = GenerateObjectWithRelationship(Cardinality.One);
+ FieldDefinitionNode field = od.Fields.First(f => f.Name.Value == FIELD_NAME_FOR_TARGET);
Assert.AreEqual(1, field.Directives.Count);
Assert.AreEqual(RelationshipDirectiveType.DirectiveName, field.Directives[0].Name.Value);
@@ -316,69 +219,22 @@ public void ForeignKeyFieldWillHaveRelationshipDirective()
[TestMethod]
public void CardinalityOfManyWillBeConnectionRelationship()
{
- TableDefinition table = new();
-
- string columnName = "columnName";
- table.Columns.Add(columnName, new ColumnDefinition
- {
- SystemType = typeof(string),
- IsNullable = false,
- });
- const string foreignKeyTable = "FkTable";
- const string refColName = "ref_col";
- table.ForeignKeys.Add("foreign_key", new ForeignKeyDefinition { ReferencedTable = foreignKeyTable, ReferencingColumns = new List { refColName } });
- table.Columns.Add(refColName, new ColumnDefinition
- {
- SystemType = typeof(long)
- });
-
- Dictionary relationships =
- new()
- {
- {
- foreignKeyTable,
- new Relationship(
- Cardinality.Many,
- foreignKeyTable,
- SourceFields: null,
- TargetFields: null,
- LinkingObject: null,
- LinkingSourceFields: null,
- LinkingTargetFields: null)
- }
- };
- Entity configEntity = GenerateEmptyEntity() with { Relationships = relationships };
- Entity relationshipEntity = GenerateEmptyEntity();
-
- ObjectTypeDefinitionNode od = SchemaConverter.FromTableDefinition("table", table, configEntity, new() { { foreignKeyTable, relationshipEntity } });
-
- FieldDefinitionNode field = od.Fields.First(f => f.Name.Value == "fkTable");
+ ObjectTypeDefinitionNode od = GenerateObjectWithRelationship(Cardinality.Many);
+ FieldDefinitionNode field = od.Fields.First(f => f.Name.Value == FIELD_NAME_FOR_TARGET);
Assert.IsTrue(QueryBuilder.IsPaginationType(field.Type.NamedType()));
}
[TestMethod]
public void WhenForeignKeyDefinedButNoRelationship_GraphQLWontModelIt()
{
- TableDefinition table = new();
-
- string columnName = "columnName";
- table.Columns.Add(columnName, new ColumnDefinition
- {
- SystemType = typeof(string),
- IsNullable = false,
- });
- const string foreignKeyTable = "FkTable";
- const string refColName = "ref_col";
- table.ForeignKeys.Add("foreign_key", new ForeignKeyDefinition { ReferencedTable = foreignKeyTable, ReferencingColumns = new List { refColName } });
- table.Columns.Add(refColName, new ColumnDefinition
- {
- SystemType = typeof(long)
- });
+ TableDefinition table = GenerateTableWithForeignKeyDefinition();
Entity configEntity = GenerateEmptyEntity() with { Relationships = new() };
Entity relationshipEntity = GenerateEmptyEntity();
- ObjectTypeDefinitionNode od = SchemaConverter.FromTableDefinition("table", table, configEntity, new() { { foreignKeyTable, relationshipEntity } });
+ ObjectTypeDefinitionNode od =
+ SchemaConverter.FromTableDefinition(
+ SOURCE_ENTITY, table, configEntity, new() { { TARGET_ENTITY, relationshipEntity } });
Assert.AreEqual(2, od.Fields.Count);
}
@@ -440,5 +296,72 @@ public void DefaultValueGetsSetOnDirective(object defaultValue, string fieldName
Assert.AreEqual(fieldName, value.Fields[0].Name.Value);
Assert.AreEqual(kind, value.Fields[0].Value.Kind);
}
+
+ private static Entity GenerateEmptyEntity()
+ {
+ return new Entity("dbo.entity", Rest: null, GraphQL: null, Array.Empty(), Relationships: new(), Mappings: new());
+ }
+
+ private static ObjectTypeDefinitionNode GenerateObjectWithRelationship(Cardinality cardinality)
+ {
+ TableDefinition table = GenerateTableWithForeignKeyDefinition();
+
+ Dictionary relationships =
+ new()
+ {
+ {
+ FIELD_NAME_FOR_TARGET,
+ new Relationship(
+ cardinality,
+ TARGET_ENTITY,
+ SourceFields: null,
+ TargetFields: null,
+ LinkingObject: null,
+ LinkingSourceFields: null,
+ LinkingTargetFields: null)
+ }
+ };
+ Entity configEntity = GenerateEmptyEntity() with { Relationships = relationships };
+ Entity relationshipEntity = GenerateEmptyEntity();
+
+ return SchemaConverter.FromTableDefinition
+ (SOURCE_ENTITY,
+ table,
+ configEntity, new() { { TARGET_ENTITY, relationshipEntity } });
+ }
+
+ private static TableDefinition GenerateTableWithForeignKeyDefinition()
+ {
+ TableDefinition table = new();
+ table.Columns.Add(COLUMN_NAME, new ColumnDefinition
+ {
+ SystemType = typeof(string),
+ IsNullable = false,
+ });
+
+ RelationshipMetadata
+ relationshipMetadata = new();
+
+ table.SourceEntityRelationshipMap.Add(SOURCE_ENTITY, relationshipMetadata);
+ List fkDefinitions = new();
+ fkDefinitions.Add(new ForeignKeyDefinition()
+ {
+ Pair = new()
+ {
+ ReferencingDbObject = new(SCHEMA_NAME, TABLE_NAME),
+ ReferencedDbObject = new(SCHEMA_NAME, REFERENCED_TABLE)
+ },
+ ReferencingColumns = new List { REF_COLNAME },
+ ReferencedColumns = new List { REFD_COLNAME }
+ });
+ relationshipMetadata.TargetEntityToFkDefinitionMap.Add(TARGET_ENTITY, fkDefinitions);
+
+ table.Columns.Add(REF_COLNAME, new ColumnDefinition
+ {
+ SystemType = typeof(long)
+ });
+
+ return table;
+ }
}
}
diff --git a/DataGateway.Service.Tests/REST/ODataASTVisitorUnitTests.cs b/DataGateway.Service.Tests/REST/ODataASTVisitorUnitTests.cs
index aa7c90cf75..fc2d8a7cee 100644
--- a/DataGateway.Service.Tests/REST/ODataASTVisitorUnitTests.cs
+++ b/DataGateway.Service.Tests/REST/ODataASTVisitorUnitTests.cs
@@ -245,7 +245,7 @@ private static ODataASTVisitor CreateVisitor(
Name = tableName
};
FindRequestContext context = new(entityName, dbo, isList);
- Mock structure = new(context, _metadataStoreProvider, _sqlMetadataProvider);
+ Mock structure = new(context, _sqlMetadataProvider);
return new ODataASTVisitor(structure.Object);
}
diff --git a/DataGateway.Service.Tests/SqlTests/GraphQLFilterTestBase.cs b/DataGateway.Service.Tests/SqlTests/GraphQLFilterTestBase.cs
index f965b22576..1ff3d5d64e 100644
--- a/DataGateway.Service.Tests/SqlTests/GraphQLFilterTestBase.cs
+++ b/DataGateway.Service.Tests/SqlTests/GraphQLFilterTestBase.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Text.Json;
using System.Threading.Tasks;
using Azure.DataGateway.Service.Controllers;
using Azure.DataGateway.Service.Services;
@@ -25,11 +26,13 @@ public abstract class GraphQLFilterTestBase : SqlTestBase
[TestMethod]
public async Task TestStringFiltersEq()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {title: {eq: ""Awesome book""}})
+ books(_filter: {title: {eq: ""Awesome book""}})
{
- title
+ items {
+ title
+ }
}
}";
@@ -50,11 +53,13 @@ public async Task TestStringFiltersEq()
[TestMethod]
public async Task TestStringFiltersNeq()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {title: {neq: ""Awesome book""}})
+ books(_filter: {title: {neq: ""Awesome book""}})
{
- title
+ items {
+ title
+ }
}
}";
@@ -75,11 +80,13 @@ public async Task TestStringFiltersNeq()
[TestMethod]
public async Task TestStringFiltersStartsWith()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {title: {startsWith: ""Awe""}})
+ books(_filter: {title: {startsWith: ""Awe""}})
{
- title
+ items {
+ title
+ }
}
}";
@@ -100,11 +107,13 @@ public async Task TestStringFiltersStartsWith()
[TestMethod]
public async Task TestStringFiltersEndsWith()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {title: {endsWith: ""book""}})
+ books(_filter: {title: {endsWith: ""book""}})
{
- title
+ items {
+ title
+ }
}
}";
@@ -125,11 +134,13 @@ public async Task TestStringFiltersEndsWith()
[TestMethod]
public async Task TestStringFiltersContains()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {title: {contains: ""some""}})
+ books(_filter: {title: {contains: ""some""}})
{
- title
+ items {
+ title
+ }
}
}";
@@ -150,11 +161,13 @@ public async Task TestStringFiltersContains()
[TestMethod]
public async Task TestStringFiltersNotContains()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {title: {notContains: ""book""}})
+ books(_filter: {title: {notContains: ""book""}})
{
- title
+ items {
+ title
+ }
}
}";
@@ -175,11 +188,13 @@ public async Task TestStringFiltersNotContains()
[TestMethod]
public async Task TestStringFiltersContainsWithSpecialChars()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {title: {contains: ""%""}})
+ books(_filter: {title: {contains: ""%""}})
{
- title
+ items {
+ title
+ }
}
}";
@@ -193,11 +208,13 @@ public async Task TestStringFiltersContainsWithSpecialChars()
[TestMethod]
public async Task TestIntFiltersEq()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {id: {eq: 2}})
+ books(_filter: {id: {eq: 2}})
{
- id
+ items {
+ id
+ }
}
}";
@@ -218,11 +235,13 @@ public async Task TestIntFiltersEq()
[TestMethod]
public async Task TestIntFiltersNeq()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {id: {neq: 2}})
+ books(_filter: {id: {neq: 2}})
{
- id
+ items {
+ id
+ }
}
}";
@@ -243,11 +262,13 @@ public async Task TestIntFiltersNeq()
[TestMethod]
public async Task TestIntFiltersGtLt()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {id: {gt: 2 lt: 4}})
+ books(_filter: {id: {gt: 2 lt: 4}})
{
- id
+ items {
+ id
+ }
}
}";
@@ -268,11 +289,13 @@ public async Task TestIntFiltersGtLt()
[TestMethod]
public async Task TestIntFiltersGteLte()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {id: {gte: 2 lte: 4}})
+ books(_filter: {id: {gte: 2 lte: 4}})
{
- id
+ items {
+ id
+ }
}
}";
@@ -300,9 +323,9 @@ public async Task TestIntFiltersGteLte()
///
public async Task TestCreatingParenthesis1()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {
+ books(_filter: {
title: {contains: ""book""}
or: [
{id:{gt: 2 lt: 4}},
@@ -310,8 +333,10 @@ public async Task TestCreatingParenthesis1()
]
})
{
- id
- title
+ items {
+ id
+ title
+ }
}
}";
@@ -339,17 +364,19 @@ public async Task TestCreatingParenthesis1()
///
public async Task TestCreatingParenthesis2()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {
+ books(_filter: {
or: [
{id: {gt: 2} and: [{id: {lt: 4}}]},
{id: {gte: 4} title: {contains: ""book""}}
]
})
{
- id
- title
+ items {
+ id
+ title
+ }
}
}";
@@ -373,9 +400,9 @@ public async Task TestCreatingParenthesis2()
///
public async Task TestComplicatedFilter()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {
+ books(_filter: {
id: {gte: 2}
title: {notContains: ""book""}
and: [
@@ -394,9 +421,11 @@ public async Task TestComplicatedFilter()
]
})
{
- id
- title
- publisher_id
+ items {
+ id
+ title
+ publisher_id
+ }
}
}";
@@ -419,11 +448,13 @@ public async Task TestComplicatedFilter()
[TestMethod]
public async Task TestOnlyEmptyAnd()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {and: []})
+ books(_filter: {and: []})
{
- id
+ items {
+ id
+ }
}
}";
@@ -437,11 +468,13 @@ public async Task TestOnlyEmptyAnd()
[TestMethod]
public async Task TestOnlyEmptyOr()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {or: []})
+ books(_filter: {or: []})
{
- id
+ items {
+ id
+ }
}
}";
@@ -456,11 +489,13 @@ public async Task TestOnlyEmptyOr()
[TestMethod]
public async Task TestFilterAndFilterODataUsedTogether()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {id: {gte: 2}}, _filterOData: ""id lt 4"")
+ books(_filter: {id: {gte: 2}}, _filterOData: ""id lt 4"")
{
- id
+ items {
+ id
+ }
}
}";
@@ -481,15 +516,16 @@ public async Task TestFilterAndFilterODataUsedTogether()
[TestMethod]
public async Task TestGetNullIntFields()
{
- string graphQLQueryName = "getMagazines";
+ string graphQLQueryName = "magazines";
string gqlQuery = @"{
- getMagazines(_filter: {issue_number: {isNull: true}})
- {
- id
- title
- issue_number
- }
- }";
+ magazines(_filter: {issue_number: {isNull: true}}) {
+ items {
+ id
+ title
+ issue_number
+ }
+ }
+ }";
string dbQuery = MakeQueryOn(
"magazines",
@@ -508,13 +544,14 @@ public async Task TestGetNullIntFields()
[TestMethod]
public async Task TestGetNonNullIntFields()
{
- string graphQLQueryName = "getMagazines";
+ string graphQLQueryName = "magazines";
string gqlQuery = @"{
- getMagazines(_filter: {issue_number: {isNull: false}})
- {
- id
- title
- issue_number
+ magazines(_filter: {issue_number: {isNull: false}}) {
+ items {
+ id
+ title
+ issue_number
+ }
}
}";
@@ -535,12 +572,13 @@ public async Task TestGetNonNullIntFields()
[TestMethod]
public async Task TestGetNullStringFields()
{
- string graphQLQueryName = "getWebsiteUsers";
+ string graphQLQueryName = "websiteUsers";
string gqlQuery = @"{
- getWebsiteUsers(_filter: {username: {isNull: true}})
- {
- id
- username
+ websiteUsers(_filter: {username: {isNull: true}}) {
+ items {
+ id
+ username
+ }
}
}";
@@ -561,12 +599,13 @@ public async Task TestGetNullStringFields()
[TestMethod]
public async Task TestGetNonNullStringFields()
{
- string graphQLQueryName = "getWebsiteUsers";
+ string graphQLQueryName = "websiteUsers";
string gqlQuery = @"{
- getWebsiteUsers(_filter: {username: {isNull: false}})
- {
- id
- username
+ websiteUsers(_filter: {username: {isNull: false}}) {
+ items {
+ id
+ username
+ }
}
}";
@@ -586,9 +625,9 @@ public async Task TestGetNonNullStringFields()
///
public async Task TestExplicitNullFieldsAreIgnored()
{
- string graphQLQueryName = "getBooks";
+ string graphQLQueryName = "books";
string gqlQuery = @"{
- getBooks(_filter: {
+ books(_filter: {
id: {gte: 2 lte: null}
title: null
or: null
@@ -645,5 +684,12 @@ protected abstract string MakeQueryOn(
string predicate,
string schema = "",
List pkColumns = null);
+
+ protected override async Task GetGraphQLResultAsync(string graphQLQuery, string graphQLQueryName, GraphQLController graphQLController, Dictionary variables = null, bool failOnErrors = true)
+ {
+ string dataResult = await base.GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, graphQLController, variables, failOnErrors);
+
+ return JsonDocument.Parse(dataResult).RootElement.GetProperty("items").ToString();
+ }
}
}
diff --git a/DataGateway.Service.Tests/SqlTests/GraphQLMutationTestBase.cs b/DataGateway.Service.Tests/SqlTests/GraphQLMutationTestBase.cs
new file mode 100644
index 0000000000..424acc0f03
--- /dev/null
+++ b/DataGateway.Service.Tests/SqlTests/GraphQLMutationTestBase.cs
@@ -0,0 +1,404 @@
+using System.Text.Json;
+using System.Threading.Tasks;
+using Azure.DataGateway.Service.Controllers;
+using Azure.DataGateway.Service.Exceptions;
+using Azure.DataGateway.Service.Services;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Azure.DataGateway.Service.Tests.SqlTests
+{
+ ///
+ /// Base class for GraphQL Mutation tests targetting Sql databases.
+ ///
+ [TestClass]
+ public abstract class GraphQLMutationTestBase : SqlTestBase
+ {
+
+ #region Test Fixture Setup
+ protected static GraphQLService _graphQLService;
+ protected static GraphQLController _graphQLController;
+ #endregion
+
+ #region Positive Tests
+
+ ///
+ /// Do: Inserts new book and return its id and title
+ /// Check: If book with the expected values of the new book is present in the database and
+ /// if the mutation query has returned the correct information
+ ///
+ public async Task InsertMutation(string dbQuery)
+ {
+ string graphQLMutationName = "createBook";
+ string graphQLMutation = @"
+ mutation {
+ createBook(item: { title: ""My New Book"", publisher_id: 1234 }) {
+ id
+ title
+ }
+ }
+ ";
+
+ string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Do: Inserts new review with default content for a Review and return its id and content
+ /// Check: If book with the given id is present in the database then
+ /// the mutation query will return the review Id with the content of the review added
+ ///
+ public async Task InsertMutationForConstantdefaultValue(string dbQuery)
+ {
+ string graphQLMutationName = "createReview";
+ string graphQLMutation = @"
+ mutation {
+ createReview(item: { book_id: 1 }) {
+ id
+ content
+ }
+ }
+ ";
+
+ string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Do: Update book in database and return its updated fields
+ /// Check: if the book with the id of the edited book and the new values exists in the database
+ /// and if the mutation query has returned the values correctly
+ ///
+ public async Task UpdateMutation(string dbQuery)
+ {
+ string graphQLMutationName = "updateBook";
+ string graphQLMutation = @"
+ mutation {
+ updateBook(id: 1, item: { title: ""Even Better Title"", publisher_id: 2345} ) {
+ title
+ publisher_id
+ }
+ }
+ ";
+
+ string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Do: Delete book by id
+ /// Check: if the mutation returned result is as expected and if book by that id has been deleted
+ ///
+ public async Task DeleteMutation(string dbQueryForResult, string dbQueryToVerifyDeletion)
+ {
+ string graphQLMutationName = "deleteBook";
+ string graphQLMutation = @"
+ mutation {
+ deleteBook(id: 1) {
+ title
+ publisher_id
+ }
+ }
+ ";
+
+ // query the table before deletion is performed to see if what the mutation
+ // returns is correct
+ string expected = await GetDatabaseResultAsync(dbQueryForResult);
+ string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+
+ string dbResponse = await GetDatabaseResultAsync(dbQueryToVerifyDeletion);
+
+ using JsonDocument result = JsonDocument.Parse(dbResponse);
+ Assert.AreEqual(result.RootElement.GetProperty("count").GetInt64(), 0);
+ }
+
+ ///
+ /// Do: run a mutation which mutates a relationship instead of a graphql type
+ /// Check: that the insertion of the entry in the appropriate link table was successful
+ ///
+ // IGNORE FOR NOW, SEE: Issue #285
+ public async Task InsertMutationForNonGraphQLTypeTable(string dbQuery)
+ {
+ string graphQLMutationName = "addAuthorToBook";
+ string graphQLMutation = @"
+ mutation {
+ addAuthorToBook(author_id: 123, book_id: 2)
+ }
+ ";
+
+ await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ string dbResponse = await GetDatabaseResultAsync(dbQuery);
+
+ using JsonDocument result = JsonDocument.Parse(dbResponse);
+ Assert.AreEqual(result.RootElement.GetProperty("count").GetInt64(), 1);
+ }
+
+ ///
+ /// Do: a new Book insertion and do a nested querying of the returned book
+ /// Check: if the result returned from the mutation is correct
+ ///
+ public async Task NestedQueryingInMutation(string dbQuery)
+ {
+ string graphQLMutationName = "createBook";
+ string graphQLMutation = @"
+ mutation {
+ createBook(item: {title: ""My New Book"", publisher_id: 1234}) {
+ id
+ title
+ publishers {
+ name
+ }
+ }
+ }
+ ";
+
+ string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Test explicitly inserting a null column
+ ///
+ public async Task TestExplicitNullInsert(string dbQuery)
+ {
+ string graphQLMutationName = "createMagazine";
+ string graphQLMutation = @"
+ mutation {
+ createMagazine(item: { id: 800, title: ""New Magazine"", issue_number: null }) {
+ id
+ title
+ issue_number
+ }
+ }
+ ";
+
+ string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Test implicitly inserting a null column
+ ///
+ public async Task TestImplicitNullInsert(string dbQuery)
+ {
+ string graphQLMutationName = "createMagazine";
+ string graphQLMutation = @"
+ mutation {
+ createMagazine(item: {id: 801, title: ""New Magazine 2""}) {
+ id
+ title
+ issue_number
+ }
+ }
+ ";
+
+ string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Test updating a column to null
+ ///
+ public async Task TestUpdateColumnToNull(string dbQuery)
+ {
+ string graphQLMutationName = "updateMagazine";
+ string graphQLMutation = @"
+ mutation {
+ updateMagazine(id: 1, item: { issue_number: null} ) {
+ id
+ issue_number
+ }
+ }
+ ";
+
+ string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Test updating a missing column in the update mutation will not be updated to null
+ ///
+ public async Task TestMissingColumnNotUpdatedToNull(string dbQuery)
+ {
+ string graphQLMutationName = "updateMagazine";
+ string graphQLMutation = @"
+ mutation {
+ updateMagazine(id: 1, item: {id: 1, title: ""Newest Magazine""}) {
+ id
+ title
+ issue_number
+ }
+ }
+ ";
+
+ string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Test to check graphQL support for aliases(arbitrarily set by user while making request).
+ /// book_id and book_title are aliases used for corresponding query fields.
+ /// The response for the query will use the alias instead of raw db column.
+ ///
+ public async Task TestAliasSupportForGraphQLMutationQueryFields(string dbQuery)
+ {
+ string graphQLMutationName = "createBook";
+ string graphQLMutation = @"
+ mutation {
+ createBook(item: { title: ""My New Book"", publisher_id: 1234 }) {
+ book_id: id
+ book_title: title
+ }
+ }
+ ";
+
+ string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ #endregion
+
+ #region Negative Tests
+
+ ///
+ /// Do: insert a new Book with an invalid foreign key
+ /// Check: that GraphQL returns an error and that the book has not actually been added
+ ///
+ public static async Task InsertWithInvalidForeignKey(string dbQuery, string errorMessage)
+ {
+ string graphQLMutationName = "createBook";
+ string graphQLMutation = @"
+ mutation {
+ createBook(item: { title: ""My New Book"", publisher_id: -1}) {
+ id
+ title
+ }
+ }
+ ";
+
+ JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+
+ SqlTestHelper.TestForErrorInGraphQLResponse(
+ result.ToString(),
+ message: errorMessage,
+ statusCode: $"{DataGatewayException.SubStatusCodes.DatabaseOperationFailed}"
+ );
+
+ string dbResponse = await GetDatabaseResultAsync(dbQuery);
+ using JsonDocument dbResponseJson = JsonDocument.Parse(dbResponse);
+ Assert.AreEqual(dbResponseJson.RootElement.GetProperty("count").GetInt64(), 0);
+ }
+
+ ///
+ /// Do: edit a book with an invalid foreign key
+ /// Check: that GraphQL returns an error and the book has not been editted
+ ///
+ public static async Task UpdateWithInvalidForeignKey(string dbQuery, string errorMessage)
+ {
+ string graphQLMutationName = "updateBook";
+ string graphQLMutation = @"
+ mutation {
+ updateBook(id: 1, item: {publisher_id: -1 }) {
+ id
+ title
+ }
+ }
+ ";
+
+ JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+
+ SqlTestHelper.TestForErrorInGraphQLResponse(
+ result.ToString(),
+ message: errorMessage,
+ statusCode: $"{DataGatewayException.SubStatusCodes.DatabaseOperationFailed}"
+ );
+
+ string dbResponse = await GetDatabaseResultAsync(dbQuery);
+ using JsonDocument dbResponseJson = JsonDocument.Parse(dbResponse);
+ Assert.AreEqual(dbResponseJson.RootElement.GetProperty("count").GetInt64(), 0);
+ }
+
+ ///
+ /// Do: use an update mutation without passing any of the optional new values to update
+ /// Check: check that GraphQL returns an appropriate exception to the user
+ ///
+ public virtual async Task UpdateWithNoNewValues()
+ {
+ string graphQLMutationName = "updateBook";
+ string graphQLMutation = @"
+ mutation {
+ updateBook(id: 1) {
+ id
+ title
+ }
+ }
+ ";
+
+ JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), message: $"item");
+ }
+
+ ///
+ /// Do: use an update mutation with an invalid id to update
+ /// Check: check that GraphQL returns an appropriate exception to the user
+ ///
+ public virtual async Task UpdateWithInvalidIdentifier()
+ {
+ string graphQLMutationName = "updateBook";
+ string graphQLMutation = @"
+ mutation {
+ updateBook(id: -1, item: { title: ""Even Better Title"" }) {
+ id
+ title
+ }
+ }
+ ";
+
+ JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.EntityNotFound}");
+ }
+
+ ///
+ /// Test adding a website placement to a book which already has a website
+ /// placement
+ ///
+ public static async Task TestViolatingOneToOneRelashionShip(string errorMessage)
+ {
+ string graphQLMutationName = "createBookWebsitePlacement";
+ string graphQLMutation = @"
+ mutation {
+ createBookWebsitePlacement(item: {book_id: 1, price: 25 }) {
+ id
+ }
+ }
+ ";
+
+ JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
+ SqlTestHelper.TestForErrorInGraphQLResponse(
+ result.ToString(),
+ message: errorMessage,
+ statusCode: $"{DataGatewayException.SubStatusCodes.DatabaseOperationFailed}"
+ );
+ }
+ #endregion
+ }
+}
diff --git a/DataGateway.Service.Tests/SqlTests/GraphQLPaginationTestBase.cs b/DataGateway.Service.Tests/SqlTests/GraphQLPaginationTestBase.cs
index 452e4f11a8..8e64de3653 100644
--- a/DataGateway.Service.Tests/SqlTests/GraphQLPaginationTestBase.cs
+++ b/DataGateway.Service.Tests/SqlTests/GraphQLPaginationTestBase.cs
@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Azure.DataGateway.Service.Controllers;
using Azure.DataGateway.Service.Exceptions;
+using Azure.DataGateway.Service.GraphQLBuilder.Queries;
using Azure.DataGateway.Service.Resolvers;
using Azure.DataGateway.Service.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -25,7 +26,7 @@ public abstract class GraphQLPaginationTestBase : SqlTestBase
#region Tests
///
- /// Request a full connection object {items, endCursor, hasNextPage}
+ /// Request a full connection object {items, after, hasNextPage}
///
[TestMethod]
public async Task RequestFullConnection()
@@ -36,7 +37,7 @@ public async Task RequestFullConnection()
books(first: 2," + $"after: \"{after}\")" + @"{
items {
title
- publisher {
+ publishers {
name
}
}
@@ -50,13 +51,13 @@ public async Task RequestFullConnection()
""items"": [
{
""title"": ""Also Awesome book"",
- ""publisher"": {
+ ""publishers"": {
""name"": ""Big Company""
}
},
{
""title"": ""Great wall of china explained"",
- ""publisher"": {
+ ""publishers"": {
""name"": ""Small Town Publisher""
}
}
@@ -69,7 +70,7 @@ public async Task RequestFullConnection()
}
///
- /// Request a full connection object {items, endCursor, hasNextPage}
+ /// Request a full connection object {items, after, hasNextPage}
/// without providing any parameters
///
[TestMethod]
@@ -165,14 +166,14 @@ public async Task RequestItemsOnly()
}
///
- /// Request only endCursor from the pagination
+ /// Request only after from the pagination
///
///
/// This is probably not a common use case, but it is necessary to test graphql's capabilites to only
/// selectively retreive data
///
[TestMethod]
- public async Task RequestEndCursorOnly()
+ public async Task RequestAfterTokenOnly()
{
string graphQLQueryName = "books";
string after = SqlPaginationUtil.Base64Encode("[{\"Value\":1,\"Direction\":0,\"ColumnName\":\"id\"}]");
@@ -184,8 +185,8 @@ public async Task RequestEndCursorOnly()
JsonElement root = await GetGraphQLControllerResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
root = root.GetProperty("data").GetProperty(graphQLQueryName);
- string actual = SqlPaginationUtil.Base64Decode(root.GetProperty("endCursor").GetString());
- string expected = "[{\"Value\":3,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"id\"}]";
+ string actual = SqlPaginationUtil.Base64Decode(root.GetProperty(QueryBuilder.PAGINATION_TOKEN_FIELD_NAME).GetString());
+ string expected = "[{\"Value\":3,\"Direction\":0, \"TableSchema\":\"\",\"TableName\":\"\", \"ColumnName\":\"id\"}]";
SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
}
@@ -210,7 +211,7 @@ public async Task RequestHasNextPageOnly()
JsonElement root = await GetGraphQLControllerResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
root = root.GetProperty("data").GetProperty(graphQLQueryName);
- bool actual = root.GetProperty("hasNextPage").GetBoolean();
+ bool actual = root.GetProperty(QueryBuilder.HAS_NEXT_PAGE_FIELD_NAME).GetBoolean();
Assert.AreEqual(true, actual);
}
@@ -222,7 +223,7 @@ public async Task RequestHasNextPageOnly()
public async Task RequestEmptyPage()
{
string graphQLQueryName = "books";
- string after = SqlPaginationUtil.Base64Encode("[{\"Value\":1000000,\"Direction\":0,\"ColumnName\":\"id\"}]");
+ string after = SqlPaginationUtil.Base64Encode("[{\"Value\":1000000,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"id\"}]");
string graphQLQuery = @"{
books(first: 2," + $"after: \"{after}\")" + @"{
items {
@@ -237,8 +238,8 @@ public async Task RequestEmptyPage()
root = root.GetProperty("data").GetProperty(graphQLQueryName);
SqlTestHelper.PerformTestEqualJsonStrings(expected: "[]", root.GetProperty("items").ToString());
- Assert.AreEqual(null, root.GetProperty("endCursor").GetString());
- Assert.AreEqual(false, root.GetProperty("hasNextPage").GetBoolean());
+ Assert.AreEqual(null, root.GetProperty(QueryBuilder.PAGINATION_TOKEN_FIELD_NAME).GetString());
+ Assert.AreEqual(false, root.GetProperty(QueryBuilder.HAS_NEXT_PAGE_FIELD_NAME).GetBoolean());
}
///
@@ -253,9 +254,9 @@ public async Task RequestNestedPaginationQueries()
books(first: 2," + $"after: \"{after}\")" + @"{
items {
title
- publisher {
+ publishers {
name
- paginatedBooks(first: 2, after:""" + after + @"""){
+ books(first: 2, after:""" + after + @"""){
items {
id
title
@@ -275,9 +276,9 @@ public async Task RequestNestedPaginationQueries()
""items"": [
{
""title"": ""Also Awesome book"",
- ""publisher"": {
+ ""publishers"": {
""name"": ""Big Company"",
- ""paginatedBooks"": {
+ ""books"": {
""items"": [
{
""id"": 2,
@@ -291,9 +292,9 @@ public async Task RequestNestedPaginationQueries()
},
{
""title"": ""Great wall of china explained"",
- ""publisher"": {
+ ""publishers"": {
""name"": ""Small Town Publisher"",
- ""paginatedBooks"": {
+ ""books"": {
""items"": [
{
""id"": 3,
@@ -323,13 +324,13 @@ public async Task RequestNestedPaginationQueries()
[TestMethod]
public async Task RequestPaginatedQueryFromMutationResult()
{
- string graphQLMutationName = "insertBook";
- string after = SqlPaginationUtil.Base64Encode("[{\"Value\":1,\"Direction\":0,\"ColumnName\":\"id\"}]");
+ string graphQLMutationName = "createBook";
+ string after = SqlPaginationUtil.Base64Encode("[{\"Value\":1,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"id\"}]");
string graphQLMutation = @"
mutation {
- insertBook(title: ""Books, Pages, and Pagination. The Book"", publisher_id: 1234) {
- publisher {
- paginatedBooks(first: 2, after: """ + after + @""") {
+ createBook(item: { title: ""Books, Pages, and Pagination. The Book"", publisher_id: 1234 }) {
+ publishers {
+ books(first: 2, after: """ + after + @""") {
items {
id
title
@@ -343,8 +344,8 @@ public async Task RequestPaginatedQueryFromMutationResult()
";
string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
string expected = @"{
- ""publisher"": {
- ""paginatedBooks"": {
+ ""publishers"": {
+ ""books"": {
""items"": [
{
""id"": 2,
@@ -376,29 +377,30 @@ public async Task RequestDeeplyNestedPaginationQueries()
string graphQLQueryName = "books";
string graphQLQuery = @"{
books(first: 2){
- items{
+ items {
id
authors(first: 2) {
- name
- paginatedBooks(first: 2) {
- items {
- id
- title
- paginatedReviews(first: 2)
- {
- items {
- id
- book{
+ items {
+ name
+ books(first: 2) {
+ items {
+ id
+ title
+ reviews(first: 2) {
+ items {
id
- }
+ books {
+ id
+ }
content
+ }
+ endCursor
+ hasNextPage
}
- endCursor
- hasNextPage
}
+ hasNextPage
+ endCursor
}
- hasNextPage
- endCursor
}
}
}
@@ -410,91 +412,96 @@ public async Task RequestDeeplyNestedPaginationQueries()
string after = "[{\"Value\":1,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"book_id\"}," +
"{\"Value\":568,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"id\"}]";
string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = @"{
+ string expected = @"
+{
+ ""items"": [
+ {
+ ""id"": 1,
+ ""authors"": {
+ ""items"": [
+ {
+ ""name"": ""Jelte"",
+ ""books"": {
""items"": [
{
""id"": 1,
- ""authors"": [
- {
- ""name"": ""Jelte"",
- ""paginatedBooks"": {
- ""items"": [
- {
- ""id"": 1,
- ""title"": ""Awesome book"",
- ""paginatedReviews"": {
- ""items"": [
- {
- ""id"": 567,
- ""book"": {
- ""id"": 1
- },
- ""content"": ""Indeed a great book""
- },
- {
- ""id"": 568,
- ""book"": {
- ""id"": 1
- },
- ""content"": ""I loved it""
- }
- ],
- ""endCursor"": """ + SqlPaginationUtil.Base64Encode(after) + @""",
- ""hasNextPage"": true
- }
- },
- {
- ""id"": 3,
- ""title"": ""Great wall of china explained"",
- ""paginatedReviews"": {
- ""items"": [],
- ""endCursor"": null,
- ""hasNextPage"": false
- }
- }
- ],
- ""hasNextPage"": true,
- ""endCursor"": """ + SqlPaginationUtil.Base64Encode("[{\"Value\":3,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"id\"}]") + @"""
+ ""title"": ""Awesome book"",
+ ""reviews"": {
+ ""items"": [
+ {
+ ""id"": 567,
+ ""books"": {
+ ""id"": 1
+ },
+ ""content"": ""Indeed a great book""
+ },
+ {
+ ""id"": 568,
+ ""books"": {
+ ""id"": 1
+ },
+ ""content"": ""I loved it""
}
- }
- ]
+ ],
+ ""endCursor"": """ + SqlPaginationUtil.Base64Encode(after) + @""",
+ ""hasNextPage"": true
+ }
},
+ {
+ ""id"": 3,
+ ""title"": ""Great wall of china explained"",
+ ""reviews"": {
+ ""items"": [],
+ ""endCursor"": null,
+ ""hasNextPage"": false
+ }
+ }
+ ],
+ ""hasNextPage"": true,
+ ""endCursor"": """ + SqlPaginationUtil.Base64Encode("[{\"Value\":3,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"id\"}]") + @"""
+ }
+ }
+ ]
+ }
+ },
+ {
+ ""id"": 2,
+ ""authors"": {
+ ""items"": [
+ {
+ ""name"": ""Aniruddh"",
+ ""books"": {
+ ""items"": [
{
""id"": 2,
- ""authors"": [
- {
- ""name"": ""Aniruddh"",
- ""paginatedBooks"": {
- ""items"": [
- {
- ""id"": 2,
- ""title"": ""Also Awesome book"",
- ""paginatedReviews"": {
- ""items"": [],
- ""endCursor"": null,
- ""hasNextPage"": false
- }
- },
- {
- ""id"": 3,
- ""title"": ""Great wall of china explained"",
- ""paginatedReviews"": {
- ""items"": [],
- ""endCursor"": null,
- ""hasNextPage"": false
- }
- }
- ],
- ""hasNextPage"": true,
- ""endCursor"": """ + SqlPaginationUtil.Base64Encode("[{\"Value\":3,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"id\"}]") + @"""
- }
- }
- ]
+ ""title"": ""Also Awesome book"",
+ ""reviews"": {
+ ""items"": [],
+ ""endCursor"": null,
+ ""hasNextPage"": false
+ }
+ },
+ {
+ ""id"": 3,
+ ""title"": ""Great wall of china explained"",
+ ""reviews"": {
+ ""items"": [],
+ ""endCursor"": null,
+ ""hasNextPage"": false
+ }
}
],
""hasNextPage"": true,
- ""endCursor"": """ + SqlPaginationUtil.Base64Encode("[{\"Value\":2,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"id\"}]") + @"""
- }";
+ ""endCursor"": """ + SqlPaginationUtil.Base64Encode("[{\"Value\":3,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"id\"}]") + @"""
+ }
+ }
+ ]
+ }
+ }
+ ],
+ ""hasNextPage"": true,
+ ""endCursor"": """ + SqlPaginationUtil.Base64Encode("[{\"Value\":2,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"id\"}]") + @"""
+}";
SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
}
@@ -506,8 +513,8 @@ public async Task RequestDeeplyNestedPaginationQueries()
public async Task PaginateCompositePkTable()
{
string graphQLQueryName = "reviews";
- string after = SqlPaginationUtil.Base64Encode("[{\"Value\":1,\"Direction\":0,\"ColumnName\":\"book_id\"}," +
- "{\"Value\":567,\"Direction\":0,\"ColumnName\":\"id\"}]");
+ string after = SqlPaginationUtil.Base64Encode("[{\"Value\":1,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"book_id\"}," +
+ "{\"Value\":567,\"Direction\":0,\"TableSchema\":\"\",\"TableName\":\"\",\"ColumnName\":\"id\"}]");
string graphQLQuery = @"{
reviews(first: 2, after: """ + after + @""") {
items {
@@ -581,6 +588,7 @@ public async Task PaginationWithFilterArgument()
///
/// Test paginating while ordering by a subset of columns of a composite pk
///
+ [Ignore]
[TestMethod]
public async Task TestPaginationWithOrderByWithPartialPk()
{
@@ -621,6 +629,7 @@ public async Task TestPaginationWithOrderByWithPartialPk()
/// Paginate first two entries then paginate again with the returned after token.
/// Verify both pagination query results
///
+ [Ignore]
[TestMethod]
public async Task TestCallingPaginationTwiceWithOrderBy()
{
@@ -706,6 +715,7 @@ public async Task TestCallingPaginationTwiceWithOrderBy()
/// Paginate ordering with a column for which multiple entries
/// have the same value, and check that the column tie break is resolved properly
///
+ [Ignore]
[TestMethod]
public async Task TestColumnTieBreak()
{
@@ -912,6 +922,7 @@ public async Task RequestInvalidAfterWithIncorrectType()
///
/// Test with after which does not include all orderBy columns
///
+ [Ignore]
[TestMethod]
public async Task RequestInvalidAfterWithUnmatchingOrderByColumns1()
{
@@ -933,6 +944,7 @@ public async Task RequestInvalidAfterWithUnmatchingOrderByColumns1()
///
/// Test with after which has unnecessary columns
///
+ [Ignore]
[TestMethod]
public async Task RequestInvalidAfterWithUnmatchingOrderByColumns2()
{
@@ -957,6 +969,7 @@ public async Task RequestInvalidAfterWithUnmatchingOrderByColumns2()
/// Test with after which has columns which don't match the direction of
/// orderby columns
///
+ [Ignore]
[TestMethod]
public async Task RequestInvalidAfterWithUnmatchingOrderByColumns3()
{
diff --git a/DataGateway.Service.Tests/SqlTests/GraphQLQueryTestBase.cs b/DataGateway.Service.Tests/SqlTests/GraphQLQueryTestBase.cs
new file mode 100644
index 0000000000..ccc50ba35d
--- /dev/null
+++ b/DataGateway.Service.Tests/SqlTests/GraphQLQueryTestBase.cs
@@ -0,0 +1,823 @@
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Azure.DataGateway.Service.Controllers;
+using Azure.DataGateway.Service.Exceptions;
+using Azure.DataGateway.Service.Services;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Azure.DataGateway.Service.Tests.SqlTests
+{
+ ///
+ /// Base class for GraphQL Query tests targetting Sql databases.
+ ///
+ [TestClass]
+ public abstract class GraphQLQueryTestBase : SqlTestBase
+ {
+ #region Test Fixture Setup
+ protected static GraphQLService _graphQLService;
+ protected static GraphQLController _graphQLController;
+ #endregion
+
+ #region Tests
+ ///
+ /// Gets array of results for querying more than one item.
+ ///
+ ///
+ public async Task MultipleResultQuery(string dbQuery)
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"{
+ books(first: 100) {
+ items {
+ id
+ title
+ }
+ }
+ }";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ [TestMethod]
+ public async Task MultipleResultQueryWithVariables(string dbQuery)
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"query ($first: Int!) {
+ books(first: $first) {
+ items {
+ id
+ title
+ }
+ }
+ }";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController, new() { { "first", 100 } });
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Gets array of results for querying more than one item.
+ ///
+ ///
+ [TestMethod]
+ public virtual async Task MultipleResultJoinQuery()
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"{
+ books(first: 100) {
+ items {
+ id
+ title
+ publisher_id
+ publishers {
+ id
+ name
+ }
+ reviews(first: 100) {
+ items {
+ id
+ content
+ }
+ }
+ authors(first: 100) {
+ items {
+ id
+ name
+ }
+ }
+ }
+ }
+ }";
+
+ string expected = @"
+[
+ {
+ ""id"": 1,
+ ""title"": ""Awesome book"",
+ ""publisher_id"": 1234,
+ ""publishers"": {
+ ""id"": 1234,
+ ""name"": ""Big Company""
+ },
+ ""reviews"": {
+ ""items"": [
+ {
+ ""id"": 567,
+ ""content"": ""Indeed a great book""
+ },
+ {
+ ""id"": 568,
+ ""content"": ""I loved it""
+ },
+ {
+ ""id"": 569,
+ ""content"": ""best book I read in years""
+ }
+ ]
+ },
+ ""authors"": {
+ ""items"": [
+ {
+ ""id"": 123,
+ ""name"": ""Jelte""
+ }
+ ]
+ }
+ },
+ {
+ ""id"": 2,
+ ""title"": ""Also Awesome book"",
+ ""publisher_id"": 1234,
+ ""publishers"": {
+ ""id"": 1234,
+ ""name"": ""Big Company""
+ },
+ ""reviews"": {
+ ""items"": []
+ },
+ ""authors"": {
+ ""items"": [
+ {
+ ""id"": 124,
+ ""name"": ""Aniruddh""
+ }
+ ]
+ }
+ },
+ {
+ ""id"": 3,
+ ""title"": ""Great wall of china explained"",
+ ""publisher_id"": 2345,
+ ""publishers"": {
+ ""id"": 2345,
+ ""name"": ""Small Town Publisher""
+ },
+ ""reviews"": {
+ ""items"": []
+ },
+ ""authors"": {
+ ""items"": [
+ {
+ ""id"": 123,
+ ""name"": ""Jelte""
+ },
+ {
+ ""id"": 124,
+ ""name"": ""Aniruddh""
+ }
+ ]
+ }
+},
+ {
+ ""id"": 4,
+ ""title"": ""US history in a nutshell"",
+ ""publisher_id"": 2345,
+ ""publishers"": {
+ ""id"": 2345,
+ ""name"": ""Small Town Publisher""
+ },
+ ""reviews"": {
+ ""items"": []
+ },
+ ""authors"": {
+ ""items"": [
+ {
+ ""id"": 123,
+ ""name"": ""Jelte""
+ },
+ {
+ ""id"": 124,
+ ""name"": ""Aniruddh""
+ }
+ ]
+ }
+},
+ {
+ ""id"": 5,
+ ""title"": ""Chernobyl Diaries"",
+ ""publisher_id"": 2323,
+ ""publishers"": {
+ ""id"": 2323,
+ ""name"": ""TBD Publishing One""
+ },
+ ""reviews"": {
+ ""items"": []
+ },
+ ""authors"": {
+ ""items"": []
+ }
+},
+ {
+ ""id"": 6,
+ ""title"": ""The Palace Door"",
+ ""publisher_id"": 2324,
+ ""publishers"": {
+ ""id"": 2324,
+ ""name"": ""TBD Publishing Two Ltd""
+ },
+ ""reviews"": {
+ ""items"": []
+ },
+ ""authors"": {
+ ""items"": []
+ }
+},
+ {
+ ""id"": 7,
+ ""title"": ""The Groovy Bar"",
+ ""publisher_id"": 2324,
+ ""publishers"": {
+ ""id"": 2324,
+ ""name"": ""TBD Publishing Two Ltd""
+ },
+ ""reviews"": {
+ ""items"": []
+ },
+ ""authors"": {
+ ""items"": []
+ }
+},
+ {
+ ""id"": 8,
+ ""title"": ""Time to Eat"",
+ ""publisher_id"": 2324,
+ ""publishers"": {
+ ""id"": 2324,
+ ""name"": ""TBD Publishing Two Ltd""
+ },
+ ""reviews"": {
+ ""items"": []
+ },
+ ""authors"": {
+ ""items"": []
+ }
+}
+]";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Test One-To-One relationship both directions
+ /// (book -> website placement, website placememnt -> book)
+ ///
+ [TestMethod]
+ public async Task OneToOneJoinQuery(string dbQuery)
+ {
+ string graphQLQueryName = "book_by_pk";
+ string graphQLQuery = @"query {
+ book_by_pk(id: 1) {
+ id
+ websiteplacement {
+ id
+ price
+ books {
+ id
+ }
+ }
+ }
+ }";
+
+ string actual = await base.GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// This deeply nests a many-to-one/one-to-many join multiple times to
+ /// show that it still results in a valid query.
+ ///
+ ///
+ [TestMethod]
+ public virtual async Task DeeplyNestedManyToOneJoinQuery()
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"{
+ books(first: 100) {
+ items {
+ title
+ publishers {
+ name
+ books(first: 100) {
+ items {
+ title
+ publishers {
+ name
+ books(first: 100) {
+ items {
+ title
+ publishers {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }";
+
+ // Too big of a result to check for the exact contents.
+ // For correctness of results, we use different tests.
+ // This test is only to validate we can handle deeply nested graphql queries.
+ await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ }
+
+ ///
+ /// This deeply nests a many-to-many join multiple times to show that
+ /// it still results in a valid query.
+ ///
+ ///
+ [TestMethod]
+ public virtual async Task DeeplyNestedManyToManyJoinQuery()
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"
+{
+ books(first: 100) {
+ items {
+ title
+ authors(first: 100) {
+ items {
+ name
+ books(first: 100) {
+ items {
+ title
+ authors(first: 100) {
+ items {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}";
+
+ await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ }
+
+ ///
+ /// This deeply nests a many-to-many join multiple times to show that
+ /// it still results in a valid query.
+ ///
+ ///
+ [TestMethod]
+ public virtual async Task DeeplyNestedManyToManyJoinQueryWithVariables()
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"
+ query ($first: Int) {
+ books(first: $first) {
+ items {
+ title
+ authors(first: $first) {
+ items {
+ name
+ books(first: $first) {
+ items {
+ title
+ authors(first: $first) {
+ items {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }";
+
+ await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController,
+ new() { { "first", 100 } });
+
+ }
+
+ [TestMethod]
+ public async Task QueryWithSingleColumnPrimaryKey(string dbQuery)
+ {
+ string graphQLQueryName = "book_by_pk";
+ string graphQLQuery = @"{
+ book_by_pk(id: 2) {
+ title
+ }
+ }";
+
+ string actual = await base.GetGraphQLResultAsync(
+ graphQLQuery, graphQLQueryName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ [TestMethod]
+ public async Task QueryWithMultileColumnPrimaryKey(string dbQuery)
+ {
+ string graphQLQueryName = "review_by_pk";
+ string graphQLQuery = @"{
+ review_by_pk(id: 568, book_id: 1) {
+ content
+ }
+ }";
+
+ string actual = await base.GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ [TestMethod]
+ public virtual async Task QueryWithNullResult()
+ {
+ string graphQLQueryName = "book_by_pk";
+ string graphQLQuery = @"{
+ book_by_pk(id: -9999) {
+ title
+ }
+ }";
+
+ string actual = await base.GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+
+ SqlTestHelper.PerformTestEqualJsonStrings("null", actual);
+ }
+
+ ///
+ /// Test if first param successfully limits list quries
+ ///
+ [TestMethod]
+ public virtual async Task TestFirstParamForListQueries()
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"{
+ books(first: 1) {
+ items {
+ title
+ publishers {
+ name
+ books(first: 3) {
+ items {
+ title
+ }
+ }
+ }
+ }
+ }
+ }";
+
+ string expected = @"
+[
+ {
+ ""title"": ""Awesome book"",
+ ""publishers"": {
+ ""name"": ""Big Company"",
+ ""books"": {
+ ""items"": [
+ {
+ ""title"": ""Awesome book""
+ },
+ {
+ ""title"": ""Also Awesome book""
+ }
+ ]
+ }
+ }
+ }
+]";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Test if filter and filterOData param successfully filters the query results
+ ///
+ [TestMethod]
+ public virtual async Task TestFilterAndFilterODataParamForListQueries()
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"{
+ books(_filter: {id: {gte: 1} and: [{id: {lte: 4}}]}) {
+ items {
+ id
+ publishers {
+ books(first: 3, _filterOData: ""id ne 2"") {
+ items {
+ id
+ }
+ }
+ }
+ }
+ }
+ }";
+
+ string expected = @"
+[
+ {
+ ""id"": 1,
+ ""publishers"": {
+ ""books"": {
+ ""items"": [
+ {
+ ""id"": 1
+ }
+ ]
+ }
+ }
+ },
+ {
+ ""id"": 2,
+ ""publishers"": {
+ ""books"": {
+ ""items"": [
+ {
+ ""id"": 1
+ }
+ ]
+ }
+ }
+ },
+ {
+ ""id"": 3,
+ ""publishers"": {
+ ""books"": {
+ ""items"": [
+ {
+ ""id"": 3
+ },
+ {
+ ""id"": 4
+ }
+ ]
+ }
+ }
+},
+ {
+ ""id"": 4,
+ ""publishers"": {
+ ""books"": {
+ ""items"": [
+ {
+ ""id"": 3
+ },
+ {
+ ""id"": 4
+ }
+ ]
+ }
+ }
+}
+]";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Get all instances of a type with nullable interger fields
+ ///
+ [TestMethod]
+ public async Task TestQueryingTypeWithNullableIntFields(string dbQuery)
+ {
+ string graphQLQueryName = "magazines";
+ string graphQLQuery = @"{
+ magazines {
+ items {
+ id
+ title
+ issue_number
+ }
+ }
+ }";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Get all instances of a type with nullable string fields
+ ///
+ [TestMethod]
+ public async Task TestQueryingTypeWithNullableStringFields(string dbQuery)
+ {
+ string graphQLQueryName = "websiteUsers";
+ string graphQLQuery = @"{
+ websiteUsers {
+ items {
+ id
+ username
+ }
+ }
+ }";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Test to check graphQL support for aliases(arbitrarily set by user while making request).
+ /// book_id and book_title are aliases used for corresponding query fields.
+ /// The response for the query will contain the alias instead of raw db column.
+ ///
+ [TestMethod]
+ public async Task TestAliasSupportForGraphQLQueryFields(string dbQuery)
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"{
+ books(first: 2) {
+ items {
+ book_id: id
+ book_title: title
+ }
+ }
+ }";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Test to check graphQL support for aliases(arbitrarily set by user while making request).
+ /// book_id is an alias, while title is the raw db field.
+ /// The response for the query will use the alias where it is provided in the query.
+ ///
+ [TestMethod]
+ public async Task TestSupportForMixOfRawDbFieldFieldAndAlias(string dbQuery)
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"{
+ books(first: 2) {
+ items {
+ book_id: id
+ title
+ }
+ }
+ }";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Tests orderBy on a list query
+ ///
+ [Ignore]
+ [TestMethod]
+ public async Task TestOrderByInListQuery(string dbQuery)
+ {
+ string graphQLQueryName = "getBooks";
+ string graphQLQuery = @"{
+ getBooks(first: 100 orderBy: {title: Desc}) {
+ id
+ title
+ }
+ }";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Use multiple order options and order an entity with a composite pk
+ ///
+ [Ignore]
+ [TestMethod]
+ public async Task TestOrderByInListQueryOnCompPkType(string dbQuery)
+ {
+ string graphQLQueryName = "getReviews";
+ string graphQLQuery = @"{
+ getReviews(orderBy: {content: Asc id: Desc}) {
+ id
+ content
+ }
+ }";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Tests null fields in orderBy are ignored
+ /// meaning that null pk columns are included in the ORDER BY clause
+ /// as ASC by default while null non-pk columns are completely ignored
+ ///
+ [Ignore]
+ [TestMethod]
+ public async Task TestNullFieldsInOrderByAreIgnored(string dbQuery)
+ {
+ string graphQLQueryName = "getBooks";
+ string graphQLQuery = @"{
+ getBooks(first: 100 orderBy: {title: Desc id: null publisher_id: null}) {
+ id
+ title
+ }
+ }";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ ///
+ /// Tests that an orderBy with only null fields results in default pk sorting
+ ///
+ [Ignore]
+ [TestMethod]
+ public async Task TestOrderByWithOnlyNullFieldsDefaultsToPkSorting(string dbQuery)
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"{
+ books(first: 100 orderBy: {title: null}) {
+ items {
+ id
+ title
+ }
+ }
+ }";
+
+ string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ string expected = await GetDatabaseResultAsync(dbQuery);
+
+ SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ }
+
+ #endregion
+
+ #region Negative Tests
+
+ [TestMethod]
+ public virtual async Task TestInvalidFirstParamQuery()
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"{
+ books(first: -1) {
+ items {
+ id
+ title
+ }
+ }
+ }";
+
+ JsonElement result = await GetGraphQLControllerResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.BadRequest}");
+ }
+
+ [TestMethod]
+ public virtual async Task TestInvalidFilterParamQuery()
+ {
+ string graphQLQueryName = "books";
+ string graphQLQuery = @"{
+ books(_filterOData: ""INVALID"") {
+ items {
+ id
+ title
+ }
+ }
+ }";
+
+ JsonElement result = await GetGraphQLControllerResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
+ SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.BadRequest}");
+ }
+
+ #endregion
+
+ protected override async Task GetGraphQLResultAsync(
+ string graphQLQuery, string graphQLQueryName,
+ GraphQLController graphQLController,
+ Dictionary variables = null,
+ bool failOnErrors = true)
+ {
+ string dataResult = await base.GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, graphQLController, variables, failOnErrors);
+
+ return JsonDocument.Parse(dataResult).RootElement.GetProperty("items").ToString();
+ }
+ }
+}
+
diff --git a/DataGateway.Service.Tests/SqlTests/MsSqlGQLFilterTests.cs b/DataGateway.Service.Tests/SqlTests/MsSqlGQLFilterTests.cs
index 455678ab15..d1e76a712a 100644
--- a/DataGateway.Service.Tests/SqlTests/MsSqlGQLFilterTests.cs
+++ b/DataGateway.Service.Tests/SqlTests/MsSqlGQLFilterTests.cs
@@ -29,7 +29,7 @@ public static async Task InitializeTestFixture(TestContext context)
_runtimeConfigPath,
_queryEngine,
_mutationEngine,
- _metadataStoreProvider,
+ graphQLMetadataProvider: null,
new DocumentCache(),
new Sha256DocumentHashProvider(),
_sqlMetadataProvider);
diff --git a/DataGateway.Service.Tests/SqlTests/MsSqlGraphQLMutationTests.cs b/DataGateway.Service.Tests/SqlTests/MsSqlGraphQLMutationTests.cs
index 3903bd2cd9..276960797d 100644
--- a/DataGateway.Service.Tests/SqlTests/MsSqlGraphQLMutationTests.cs
+++ b/DataGateway.Service.Tests/SqlTests/MsSqlGraphQLMutationTests.cs
@@ -1,7 +1,5 @@
-using System.Text.Json;
using System.Threading.Tasks;
using Azure.DataGateway.Service.Controllers;
-using Azure.DataGateway.Service.Exceptions;
using Azure.DataGateway.Service.Resolvers;
using Azure.DataGateway.Service.Services;
using HotChocolate.Language;
@@ -11,13 +9,10 @@ namespace Azure.DataGateway.Service.Tests.SqlTests
{
[TestClass, TestCategory(TestCategory.MSSQL)]
- public class MsSqlGraphQLMutationTests : SqlTestBase
+ public class MsSqlGraphQLMutationTests : GraphQLMutationTestBase
{
#region Test Fixture Setup
- private static GraphQLService _graphQLService;
- private static GraphQLController _graphQLController;
-
///
/// Sets up test fixture for class, only to be run once per test run, as defined by
/// MSTest decorator.
@@ -33,7 +28,7 @@ public static async Task InitializeTestFixture(TestContext context)
_runtimeConfigPath,
_queryEngine,
_mutationEngine,
- _metadataStoreProvider,
+ graphQLMetadataProvider: null,
new DocumentCache(),
new Sha256DocumentHashProvider(),
_sqlMetadataProvider);
@@ -61,16 +56,6 @@ public async Task TestCleanup()
[TestMethod]
public async Task InsertMutation()
{
- string graphQLMutationName = "insertBook";
- string graphQLMutation = @"
- mutation {
- insertBook(title: ""My New Book"", publisher_id: 1234) {
- id
- title
- }
- }
- ";
-
string msSqlQuery = @"
SELECT TOP 1 [table0].[id] AS [id],
[table0].[title] AS [title]
@@ -84,10 +69,7 @@ ORDER BY [id]
WITHOUT_ARRAY_WRAPPER
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await InsertMutation(msSqlQuery);
}
///
@@ -98,16 +80,6 @@ ORDER BY [id]
[TestMethod]
public async Task InsertMutationForConstantdefaultValue()
{
- string graphQLMutationName = "insertReview";
- string graphQLMutation = @"
- mutation {
- insertReview(book_id: 1) {
- id
- content
- }
- }
- ";
-
string msSqlQuery = @"
SELECT TOP 1 [table0].[id] AS [id],
[table0].[content] AS [content]
@@ -121,10 +93,7 @@ ORDER BY [id]
WITHOUT_ARRAY_WRAPPER
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await InsertMutationForConstantdefaultValue(msSqlQuery);
}
///
@@ -135,16 +104,6 @@ ORDER BY [id]
[TestMethod]
public async Task UpdateMutation()
{
- string graphQLMutationName = "editBook";
- string graphQLMutation = @"
- mutation {
- editBook(id: 1, title: ""Even Better Title"", publisher_id: 2345) {
- title
- publisher_id
- }
- }
- ";
-
string msSqlQuery = @"
SELECT TOP 1 [title],
[publisher_id]
@@ -158,10 +117,7 @@ ORDER BY [books].[id]
WITHOUT_ARRAY_WRAPPER
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await UpdateMutation(msSqlQuery);
}
///
@@ -171,16 +127,6 @@ ORDER BY [books].[id]
[TestMethod]
public async Task DeleteMutation()
{
- string graphQLMutationName = "deleteBook";
- string graphQLMutation = @"
- mutation {
- deleteBook(id: 1) {
- title
- publisher_id
- }
- }
- ";
-
string msSqlQueryForResult = @"
SELECT TOP 1 [title],
[publisher_id]
@@ -192,13 +138,6 @@ ORDER BY [books].[id]
WITHOUT_ARRAY_WRAPPER
";
- // query the table before deletion is performed to see if what the mutation
- // returns is correct
- string expected = await GetDatabaseResultAsync(msSqlQueryForResult);
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
-
string msSqlQueryToVerifyDeletion = @"
SELECT COUNT(*) AS count
FROM [books]
@@ -208,10 +147,7 @@ FROM [books]
WITHOUT_ARRAY_WRAPPER
";
- string dbResponse = await GetDatabaseResultAsync(msSqlQueryToVerifyDeletion);
-
- using JsonDocument result = JsonDocument.Parse(dbResponse);
- Assert.AreEqual(result.RootElement.GetProperty("count").GetInt64(), 0);
+ await DeleteMutation(msSqlQueryForResult, msSqlQueryToVerifyDeletion);
}
///
@@ -223,13 +159,6 @@ FROM [books]
[Ignore]
public async Task InsertMutationForNonGraphQLTypeTable()
{
- string graphQLMutationName = "addAuthorToBook";
- string graphQLMutation = @"
- mutation {
- addAuthorToBook(author_id: 123, book_id: 2)
- }
- ";
-
string msSqlQuery = @"
SELECT COUNT(*) AS count
FROM [book_author_link]
@@ -240,11 +169,7 @@ FROM [book_author_link]
WITHOUT_ARRAY_WRAPPER
";
- await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string dbResponse = await GetDatabaseResultAsync(msSqlQuery);
-
- using JsonDocument result = JsonDocument.Parse(dbResponse);
- Assert.AreEqual(result.RootElement.GetProperty("count").GetInt64(), 1);
+ await InsertMutationForNonGraphQLTypeTable(msSqlQuery);
}
///
@@ -254,23 +179,10 @@ FROM [book_author_link]
[TestMethod]
public async Task NestedQueryingInMutation()
{
- string graphQLMutationName = "insertBook";
- string graphQLMutation = @"
- mutation {
- insertBook(title: ""My New Book"", publisher_id: 1234) {
- id
- title
- publisher {
- name
- }
- }
- }
- ";
-
string msSqlQuery = @"
SELECT TOP 1 [table0].[id] AS [id],
[table0].[title] AS [title],
- JSON_QUERY([table1_subq].[data]) AS [publisher]
+ JSON_QUERY([table1_subq].[data]) AS [publishers]
FROM [books] AS [table0]
OUTER APPLY (
SELECT TOP 1 [table1].[name] AS [name]
@@ -290,10 +202,7 @@ ORDER BY [id]
WITHOUT_ARRAY_WRAPPER
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await NestedQueryingInMutation(msSqlQuery);
}
///
@@ -302,17 +211,6 @@ ORDER BY [id]
[TestMethod]
public async Task TestExplicitNullInsert()
{
- string graphQLMutationName = "insertMagazine";
- string graphQLMutation = @"
- mutation {
- insertMagazine(id: 800, title: ""New Magazine"", issue_number: null) {
- id
- title
- issue_number
- }
- }
- ";
-
string msSqlQuery = @"
SELECT TOP 1 [id],
[title],
@@ -327,10 +225,7 @@ ORDER BY [foo].[magazines].[id]
WITHOUT_ARRAY_WRAPPER
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestExplicitNullInsert(msSqlQuery);
}
///
@@ -339,17 +234,6 @@ ORDER BY [foo].[magazines].[id]
[TestMethod]
public async Task TestImplicitNullInsert()
{
- string graphQLMutationName = "insertMagazine";
- string graphQLMutation = @"
- mutation {
- insertMagazine(id: 801, title: ""New Magazine 2"") {
- id
- title
- issue_number
- }
- }
- ";
-
string msSqlQuery = @"
SELECT TOP 1 [id],
[title],
@@ -364,10 +248,7 @@ ORDER BY [foo].[magazines].[id]
WITHOUT_ARRAY_WRAPPER
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestImplicitNullInsert(msSqlQuery);
}
///
@@ -376,16 +257,6 @@ ORDER BY [foo].[magazines].[id]
[TestMethod]
public async Task TestUpdateColumnToNull()
{
- string graphQLMutationName = "updateMagazine";
- string graphQLMutation = @"
- mutation {
- updateMagazine(id: 1, issue_number: null) {
- id
- issue_number
- }
- }
- ";
-
string msSqlQuery = @"
SELECT TOP 1 [id],
[issue_number]
@@ -398,10 +269,7 @@ ORDER BY [foo].[magazines].[id]
WITHOUT_ARRAY_WRAPPER
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestUpdateColumnToNull(msSqlQuery);
}
///
@@ -410,17 +278,6 @@ ORDER BY [foo].[magazines].[id]
[TestMethod]
public async Task TestMissingColumnNotUpdatedToNull()
{
- string graphQLMutationName = "updateMagazine";
- string graphQLMutation = @"
- mutation {
- updateMagazine(id: 1, title: ""Newest Magazine"") {
- id
- title
- issue_number
- }
- }
- ";
-
string msSqlQuery = @"
SELECT TOP 1 [id],
[title],
@@ -435,10 +292,7 @@ ORDER BY [foo].[magazines].[id]
WITHOUT_ARRAY_WRAPPER
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestMissingColumnNotUpdatedToNull(msSqlQuery);
}
///
@@ -449,16 +303,6 @@ ORDER BY [foo].[magazines].[id]
[TestMethod]
public async Task TestAliasSupportForGraphQLMutationQueryFields()
{
- string graphQLMutationName = "insertBook";
- string graphQLMutation = @"
- mutation {
- insertBook(title: ""My New Book"", publisher_id: 1234) {
- book_id: id
- book_title: title
- }
- }
- ";
-
string msSqlQuery = @"
SELECT TOP 1 [table0].[id] AS [book_id],
[table0].[title] AS [book_title]
@@ -472,10 +316,7 @@ ORDER BY [id]
WITHOUT_ARRAY_WRAPPER
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestAliasSupportForGraphQLMutationQueryFields(msSqlQuery);
}
#endregion
@@ -489,24 +330,6 @@ ORDER BY [id]
[TestMethod]
public async Task InsertWithInvalidForeignKey()
{
- string graphQLMutationName = "insertBook";
- string graphQLMutation = @"
- mutation {
- insertBook(title: ""My New Book"", publisher_id: -1) {
- id
- title
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
-
- SqlTestHelper.TestForErrorInGraphQLResponse(
- result.ToString(),
- message: DbExceptionParserBase.GENERIC_DB_EXCEPTION_MESSAGE,
- statusCode: $"{DataGatewayException.SubStatusCodes.DatabaseOperationFailed}"
- );
-
string msSqlQuery = @"
SELECT COUNT(*) AS count
FROM [books]
@@ -516,9 +339,7 @@ FROM [books]
WITHOUT_ARRAY_WRAPPER
";
- string dbResponse = await GetDatabaseResultAsync(msSqlQuery);
- using JsonDocument dbResponseJson = JsonDocument.Parse(dbResponse);
- Assert.AreEqual(dbResponseJson.RootElement.GetProperty("count").GetInt64(), 0);
+ await InsertWithInvalidForeignKey(msSqlQuery, DbExceptionParserBase.GENERIC_DB_EXCEPTION_MESSAGE);
}
///
@@ -528,24 +349,6 @@ FROM [books]
[TestMethod]
public async Task UpdateWithInvalidForeignKey()
{
- string graphQLMutationName = "editBook";
- string graphQLMutation = @"
- mutation {
- editBook(id: 1, publisher_id: -1) {
- id
- title
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
-
- SqlTestHelper.TestForErrorInGraphQLResponse(
- result.ToString(),
- message: DbExceptionParserBase.GENERIC_DB_EXCEPTION_MESSAGE,
- statusCode: $"{DataGatewayException.SubStatusCodes.DatabaseOperationFailed}"
- );
-
string msSqlQuery = @"
SELECT COUNT(*) AS count
FROM [books]
@@ -556,9 +359,7 @@ FROM [books]
WITHOUT_ARRAY_WRAPPER
";
- string dbResponse = await GetDatabaseResultAsync(msSqlQuery);
- using JsonDocument dbResponseJson = JsonDocument.Parse(dbResponse);
- Assert.AreEqual(dbResponseJson.RootElement.GetProperty("count").GetInt64(), 0);
+ await UpdateWithInvalidForeignKey(msSqlQuery, DbExceptionParserBase.GENERIC_DB_EXCEPTION_MESSAGE);
}
///
@@ -566,20 +367,9 @@ FROM [books]
/// Check: check that GraphQL returns an appropriate exception to the user
///
[TestMethod]
- public async Task UpdateWithNoNewValues()
+ public override async Task UpdateWithNoNewValues()
{
- string graphQLMutationName = "editBook";
- string graphQLMutation = @"
- mutation {
- editBook(id: 1) {
- id
- title
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.BadRequest}");
+ await base.UpdateWithNoNewValues();
}
///
@@ -587,20 +377,9 @@ public async Task UpdateWithNoNewValues()
/// Check: check that GraphQL returns an appropriate exception to the user
///
[TestMethod]
- public async Task UpdateWithInvalidIdentifier()
+ public override async Task UpdateWithInvalidIdentifier()
{
- string graphQLMutationName = "editBook";
- string graphQLMutation = @"
- mutation {
- editBook(id: -1, title: ""Even Better Title"") {
- id
- title
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.EntityNotFound}");
+ await base.UpdateWithInvalidIdentifier();
}
///
@@ -610,21 +389,7 @@ public async Task UpdateWithInvalidIdentifier()
[TestMethod]
public async Task TestViolatingOneToOneRelashionShip()
{
- string graphQLMutationName = "insertWebsitePlacement";
- string graphQLMutation = @"
- mutation {
- insertWebsitePlacement(book_id: 1, price: 25) {
- id
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(
- result.ToString(),
- message: DbExceptionParserBase.GENERIC_DB_EXCEPTION_MESSAGE,
- statusCode: $"{DataGatewayException.SubStatusCodes.DatabaseOperationFailed}"
- );
+ await TestViolatingOneToOneRelashionShip(DbExceptionParserBase.GENERIC_DB_EXCEPTION_MESSAGE);
}
#endregion
}
diff --git a/DataGateway.Service.Tests/SqlTests/MsSqlGraphQLPaginationTests.cs b/DataGateway.Service.Tests/SqlTests/MsSqlGraphQLPaginationTests.cs
index 1b71e6aa45..c447bf8086 100644
--- a/DataGateway.Service.Tests/SqlTests/MsSqlGraphQLPaginationTests.cs
+++ b/DataGateway.Service.Tests/SqlTests/MsSqlGraphQLPaginationTests.cs
@@ -28,7 +28,7 @@ public static async Task InitializeTestFixture(TestContext context)
_runtimeConfigPath,
_queryEngine,
_mutationEngine,
- _metadataStoreProvider,
+ graphQLMetadataProvider: null,
new DocumentCache(),
new Sha256DocumentHashProvider(),
_sqlMetadataProvider);
diff --git a/DataGateway.Service.Tests/SqlTests/MsSqlGraphQLQueryTests.cs b/DataGateway.Service.Tests/SqlTests/MsSqlGraphQLQueryTests.cs
index 8e90a5aa6b..a2b749e0d3 100644
--- a/DataGateway.Service.Tests/SqlTests/MsSqlGraphQLQueryTests.cs
+++ b/DataGateway.Service.Tests/SqlTests/MsSqlGraphQLQueryTests.cs
@@ -1,8 +1,5 @@
-using System.Text.Json;
using System.Threading.Tasks;
-using Azure.DataGateway.Service.Configurations;
using Azure.DataGateway.Service.Controllers;
-using Azure.DataGateway.Service.Exceptions;
using Azure.DataGateway.Service.Services;
using HotChocolate.Language;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -13,12 +10,9 @@ namespace Azure.DataGateway.Service.Tests.SqlTests
/// Test GraphQL Queries validating proper resolver/engine operation.
///
[TestClass, TestCategory(TestCategory.MSSQL)]
- public class MsSqlGraphQLQueryTests : SqlTestBase
+ public class MsSqlGraphQLQueryTests : GraphQLQueryTestBase
{
#region Test Fixture Setup
- private static GraphQLService _graphQLService;
- private static GraphQLController _graphQLController;
-
///
/// Sets up test fixture for class, only to be run once per test run, as defined by
/// MSTest decorator.
@@ -35,7 +29,7 @@ public static async Task InitializeTestFixture(TestContext context)
_runtimeConfigPath,
_queryEngine,
_mutationEngine,
- _metadataStoreProvider,
+ graphQLMetadataProvider: null,
new DocumentCache(),
new Sha256DocumentHashProvider(),
_sqlMetadataProvider);
@@ -45,14 +39,6 @@ public static async Task InitializeTestFixture(TestContext context)
#endregion
#region Tests
-
- [TestMethod]
- public void TestConfigIsValid()
- {
- IConfigValidator configValidator = new SqlConfigValidator(_metadataStoreProvider, _graphQLService, _sqlMetadataProvider);
- configValidator.ValidateConfig();
- }
-
///
/// Gets array of results for querying more than one item.
///
@@ -60,37 +46,15 @@ public void TestConfigIsValid()
[TestMethod]
public async Task MultipleResultQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- id
- title
- }
- }";
string msSqlQuery = $"SELECT id, title FROM books ORDER BY id FOR JSON PATH, INCLUDE_NULL_VALUES";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await MultipleResultQuery(msSqlQuery);
}
[TestMethod]
public async Task MultipleResultQueryWithVariables()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"query ($first: Int!) {
- getBooks(first: $first) {
- id
- title
- }
- }";
string msSqlQuery = $"SELECT id, title FROM books ORDER BY id FOR JSON PATH, INCLUDE_NULL_VALUES";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController, new() { { "first", 100 } });
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await MultipleResultQueryWithVariables(msSqlQuery);
}
///
@@ -98,74 +62,9 @@ public async Task MultipleResultQueryWithVariables()
///
///
[TestMethod]
- public async Task MultipleResultJoinQuery()
+ public override async Task MultipleResultJoinQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- id
- title
- publisher_id
- publisher {
- id
- name
- }
- reviews(first: 100) {
- id
- content
- }
- authors(first: 100) {
- id
- name
- }
- }
- }";
- string msSqlQuery = @"
- SELECT TOP 100 [table0].[id] AS [id],
- [table0].[title] AS [title],
- [table0].[publisher_id] AS [publisher_id],
- JSON_QUERY([table1_subq].[data]) AS [publisher],
- JSON_QUERY(COALESCE([table2_subq].[data], '[]')) AS [reviews],
- JSON_QUERY(COALESCE([table3_subq].[data], '[]')) AS [authors]
- FROM [books] AS [table0]
- OUTER APPLY (
- SELECT TOP 1 [table1].[id] AS [id],
- [table1].[name] AS [name]
- FROM [publishers] AS [table1]
- WHERE [table0].[publisher_id] = [table1].[id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES,
- WITHOUT_ARRAY_WRAPPER
- ) AS [table1_subq]([data])
- OUTER APPLY (
- SELECT TOP 100 [table2].[id] AS [id],
- [table2].[content] AS [content]
- FROM [reviews] AS [table2]
- WHERE [table0].[id] = [table2].[book_id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ) AS [table2_subq]([data])
- OUTER APPLY (
- SELECT TOP 100 [table3].[id] AS [id],
- [table3].[name] AS [name]
- FROM [authors] AS [table3]
- INNER JOIN [book_author_link] AS [table4] ON [table4].[author_id] = [table3].[id]
- WHERE [table0].[id] = [table4].[book_id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ) AS [table3_subq]([data])
- WHERE 1 = 1
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.MultipleResultJoinQuery();
}
///
@@ -175,54 +74,46 @@ ORDER BY [id]
[TestMethod]
public async Task OneToOneJoinQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"query {
- getBooks {
- id
- website_placement {
- id
- price
- book {
- id
- }
- }
- }
- }";
-
string msSqlQuery = @"
- SELECT TOP 100 [table0].[id] AS [id],
- JSON_QUERY([table1_subq].[data]) AS [website_placement]
- FROM [books] AS [table0]
- OUTER APPLY (
- SELECT TOP 1 [table1].[id] AS [id],
- [table1].[price] AS [price],
- JSON_QUERY([table2_subq].[data]) AS [book]
- FROM [book_website_placements] AS [table1]
- OUTER APPLY (
- SELECT TOP 1 [table2].[id] AS [id]
- FROM [books] AS [table2]
- WHERE [table1].[book_id] = [table2].[id]
- ORDER BY [table2].[id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES,
- WITHOUT_ARRAY_WRAPPER
- ) AS [table2_subq]([data])
- WHERE [table0].[id] = [table1].[book_id]
- ORDER BY [table1].[id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES,
- WITHOUT_ARRAY_WRAPPER
- ) AS [table1_subq]([data])
- WHERE 1 = 1
- ORDER BY [table0].[id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ SELECT
+ TOP 1 [table0].[id] AS [id],
+ JSON_QUERY ([table1_subq].[data]) AS [websiteplacement]
+ FROM
+ [books] AS [table0]
+ OUTER APPLY (
+ SELECT
+ TOP 1 [table1].[id] AS [id],
+ [table1].[price] AS [price],
+ JSON_QUERY ([table2_subq].[data]) AS [books]
+ FROM
+ [book_website_placements] AS [table1]
+ OUTER APPLY (
+ SELECT
+ TOP 1 [table2].[id] AS [id]
+ FROM
+ [books] AS [table2]
+ WHERE
+ [table1].[book_id] = [table2].[id]
+ ORDER BY
+ [table2].[id] Asc FOR JSON PATH,
+ INCLUDE_NULL_VALUES,
+ WITHOUT_ARRAY_WRAPPER
+ ) AS [table2_subq]([data])
+ WHERE
+ [table1].[book_id] = [table0].[id]
+ ORDER BY
+ [table1].[id] Asc FOR JSON PATH,
+ INCLUDE_NULL_VALUES,
+ WITHOUT_ARRAY_WRAPPER
+ ) AS [table1_subq]([data])
+ WHERE
+ [table0].[id] = 1
+ ORDER BY
+ [table0].[id] Asc FOR JSON PATH,
+ INCLUDE_NULL_VALUES,
+ WITHOUT_ARRAY_WRAPPER";
+
+ await OneToOneJoinQuery(msSqlQuery);
}
///
@@ -231,91 +122,9 @@ ORDER BY [table0].[id]
///
///
[TestMethod]
- public async Task DeeplyNestedManyToOneJoinQuery()
+ public override async Task DeeplyNestedManyToOneJoinQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- title
- publisher {
- name
- books(first: 100) {
- title
- publisher {
- name
- books(first: 100) {
- title
- publisher {
- name
- }
- }
- }
- }
- }
- }
- }";
-
- string msSqlQuery = @"
- SELECT TOP 100 [table0].[title] AS [title],
- JSON_QUERY([table1_subq].[data]) AS [publisher]
- FROM [books] AS [table0]
- OUTER APPLY (
- SELECT TOP 1 [table1].[name] AS [name],
- JSON_QUERY(COALESCE([table2_subq].[data], '[]')) AS [books]
- FROM [publishers] AS [table1]
- OUTER APPLY (
- SELECT TOP 100 [table2].[title] AS [title],
- JSON_QUERY([table3_subq].[data]) AS [publisher]
- FROM [books] AS [table2]
- OUTER APPLY (
- SELECT TOP 1 [table3].[name] AS [name],
- JSON_QUERY(COALESCE([table4_subq].[data], '[]')) AS [books]
- FROM [publishers] AS [table3]
- OUTER APPLY (
- SELECT TOP 100 [table4].[title] AS [title],
- JSON_QUERY([table5_subq].[data]) AS [publisher]
- FROM [books] AS [table4]
- OUTER APPLY (
- SELECT TOP 1 [table5].[name] AS [name]
- FROM [publishers] AS [table5]
- WHERE [table4].[publisher_id] = [table5].[id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES,
- WITHOUT_ARRAY_WRAPPER
- ) AS [table5_subq]([data])
- WHERE [table3].[id] = [table4].[publisher_id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ) AS [table4_subq]([data])
- WHERE [table2].[publisher_id] = [table3].[id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES,
- WITHOUT_ARRAY_WRAPPER
- ) AS [table3_subq]([data])
- WHERE [table1].[id] = [table2].[publisher_id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ) AS [table2_subq]([data])
- WHERE [table0].[publisher_id] = [table1].[id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES,
- WITHOUT_ARRAY_WRAPPER
- ) AS [table1_subq]([data])
- WHERE 1 = 1
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.DeeplyNestedManyToManyJoinQuery();
}
///
@@ -324,67 +133,9 @@ ORDER BY [id]
///
///
[TestMethod]
- public async Task DeeplyNestedManyToManyJoinQuery()
+ public override async Task DeeplyNestedManyToManyJoinQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- title
- authors(first: 100) {
- name
- books(first: 100) {
- title
- authors(first: 100) {
- name
- }
- }
- }
- }
- }";
-
- string msSqlQuery = @"
- SELECT TOP 100 [table0].[title] AS [title],
- JSON_QUERY(COALESCE([table6_subq].[data], '[]')) AS [authors]
- FROM [books] AS [table0]
- OUTER APPLY (
- SELECT TOP 100 [table6].[name] AS [name],
- JSON_QUERY(COALESCE([table7_subq].[data], '[]')) AS [books]
- FROM [authors] AS [table6]
- INNER JOIN [book_author_link] AS [table11] ON [table11].[author_id] = [table6].[id]
- OUTER APPLY (
- SELECT TOP 100 [table7].[title] AS [title],
- JSON_QUERY(COALESCE([table8_subq].[data], '[]')) AS [authors]
- FROM [books] AS [table7]
- INNER JOIN [book_author_link] AS [table10] ON [table10].[book_id] = [table7].[id]
- OUTER APPLY (
- SELECT TOP 100 [table8].[name] AS [name]
- FROM [authors] AS [table8]
- INNER JOIN [book_author_link] AS [table9] ON [table9].[author_id] = [table8].[id]
- WHERE [table7].[id] = [table9].[book_id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ) AS [table8_subq]([data])
- WHERE [table6].[id] = [table10].[author_id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ) AS [table7_subq]([data])
- WHERE [table0].[id] = [table11].[book_id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ) AS [table6_subq]([data])
- WHERE 1 = 1
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.DeeplyNestedManyToManyJoinQuery();
}
///
@@ -393,230 +144,55 @@ ORDER BY [id]
///
///
[TestMethod]
- public async Task DeeplyNestedManyToManyJoinQueryWithVariables()
+ public override async Task DeeplyNestedManyToManyJoinQueryWithVariables()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"query ($first: Int) {
- getBooks(first: $first) {
- title
- authors(first: $first) {
- name
- books(first: $first) {
- title
- authors(first: $first) {
- name
- }
- }
- }
- }
- }";
-
- string msSqlQuery = @"
- SELECT TOP 100 [table0].[title] AS [title],
- JSON_QUERY(COALESCE([table6_subq].[data], '[]')) AS [authors]
- FROM [books] AS [table0]
- OUTER APPLY (
- SELECT TOP 100 [table6].[name] AS [name],
- JSON_QUERY(COALESCE([table7_subq].[data], '[]')) AS [books]
- FROM [authors] AS [table6]
- INNER JOIN [book_author_link] AS [table11] ON [table11].[author_id] = [table6].[id]
- OUTER APPLY (
- SELECT TOP 100 [table7].[title] AS [title],
- JSON_QUERY(COALESCE([table8_subq].[data], '[]')) AS [authors]
- FROM [books] AS [table7]
- INNER JOIN [book_author_link] AS [table10] ON [table10].[book_id] = [table7].[id]
- OUTER APPLY (
- SELECT TOP 100 [table8].[name] AS [name]
- FROM [authors] AS [table8]
- INNER JOIN [book_author_link] AS [table9] ON [table9].[author_id] = [table8].[id]
- WHERE [table7].[id] = [table9].[book_id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ) AS [table8_subq]([data])
- WHERE [table6].[id] = [table10].[author_id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ) AS [table7_subq]([data])
- WHERE [table0].[id] = [table11].[book_id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ) AS [table6_subq]([data])
- WHERE 1 = 1
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController, new() { { "first", 100 } });
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.DeeplyNestedManyToManyJoinQueryWithVariables();
}
[TestMethod]
public async Task QueryWithSingleColumnPrimaryKey()
{
- string graphQLQueryName = "getBook";
- string graphQLQuery = @"{
- getBook(id: 2) {
- title
- }
- }";
string msSqlQuery = @"
SELECT title FROM books
WHERE id = 2 FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER
";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await QueryWithSingleColumnPrimaryKey(msSqlQuery);
}
[TestMethod]
public async Task QueryWithMultileColumnPrimaryKey()
{
- string graphQLQueryName = "getReview";
- string graphQLQuery = @"{
- getReview(id: 568, book_id: 1) {
- content
- }
- }";
string msSqlQuery = @"
SELECT TOP 1 content FROM reviews
WHERE id = 568 AND book_id = 1 FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER
";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await QueryWithMultileColumnPrimaryKey(msSqlQuery);
}
[TestMethod]
- public async Task QueryWithNullResult()
+ public override async Task QueryWithNullResult()
{
- string graphQLQueryName = "getBook";
- string graphQLQuery = @"{
- getBook(id: -9999) {
- title
- }
- }";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
-
- SqlTestHelper.PerformTestEqualJsonStrings("null", actual);
+ await base.QueryWithNullResult();
}
///
/// Test if first param successfully limits list quries
///
[TestMethod]
- public async Task TestFirstParamForListQueries()
+ public override async Task TestFirstParamForListQueries()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 1) {
- title
- publisher {
- name
- books(first: 3) {
- title
- }
- }
- }
- }";
-
- string msSqlQuery = @"
- SELECT TOP 1 [table0].[title] AS [title],
- JSON_QUERY([table1_subq].[data]) AS [publisher]
- FROM [books] AS [table0]
- OUTER APPLY (
- SELECT TOP 1 [table1].[name] AS [name],
- JSON_QUERY(COALESCE([table2_subq].[data], '[]')) AS [books]
- FROM [publishers] AS [table1]
- OUTER APPLY (
- SELECT TOP 3 [table2].[title] AS [title]
- FROM [books] AS [table2]
- WHERE [table1].[id] = [table2].[publisher_id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ) AS [table2_subq]([data])
- WHERE [table0].[publisher_id] = [table1].[id]
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES,
- WITHOUT_ARRAY_WRAPPER
- ) AS [table1_subq]([data])
- WHERE 1 = 1
- ORDER BY [id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.TestFirstParamForListQueries();
}
///
/// Test if filter and filterOData param successfully filters the query results
///
[TestMethod]
- public async Task TestFilterAndFilterODataParamForListQueries()
+ public override async Task TestFilterAndFilterODataParamForListQueries()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(_filter: {id: {gte: 1} and: [{id: {lte: 4}}]}) {
- id
- publisher {
- books(first: 3, _filterOData: ""id ne 2"") {
- id
- }
- }
- }
- }";
-
- string msSqlQuery = @"
- SELECT TOP 100 [table0].[id] AS [id],
- JSON_QUERY([table1_subq].[data]) AS [publisher]
- FROM [books] AS [table0]
- OUTER APPLY (
- SELECT TOP 1 JSON_QUERY(COALESCE([table2_subq].[data], '[]')) AS [books]
- FROM [publishers] AS [table1]
- OUTER APPLY (
- SELECT TOP 3 [table2].[id] AS [id]
- FROM [books] AS [table2]
- WHERE (id != 2)
- AND [table1].[id] = [table2].[publisher_id]
- ORDER BY [table2].[id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ) AS [table2_subq]([data])
- WHERE [table0].[publisher_id] = [table1].[id]
- ORDER BY [table1].[id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES,
- WITHOUT_ARRAY_WRAPPER
- ) AS [table1_subq]([data])
- WHERE (
- (id >= 1)
- AND (id <= 4)
- )
- ORDER BY [table0].[id]
- FOR JSON PATH,
- INCLUDE_NULL_VALUES
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.TestFilterAndFilterODataParamForListQueries();
}
///
@@ -625,21 +201,8 @@ ORDER BY [table0].[id]
[TestMethod]
public async Task TestQueryingTypeWithNullableIntFields()
{
- string graphQLQueryName = "getMagazines";
- string graphQLQuery = @"{
- getMagazines{
- id
- title
- issue_number
- }
- }";
-
string msSqlQuery = $"SELECT TOP 100 id, title, issue_number FROM [foo].[magazines] ORDER BY id FOR JSON PATH, INCLUDE_NULL_VALUES";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestQueryingTypeWithNullableIntFields(msSqlQuery);
}
///
@@ -648,20 +211,8 @@ public async Task TestQueryingTypeWithNullableIntFields()
[TestMethod]
public async Task TestQueryingTypeWithNullableStringFields()
{
- string graphQLQueryName = "getWebsiteUsers";
- string graphQLQuery = @"{
- getWebsiteUsers{
- id
- username
- }
- }";
-
string msSqlQuery = $"SELECT TOP 100 id, username FROM website_users ORDER BY id FOR JSON PATH, INCLUDE_NULL_VALUES";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestQueryingTypeWithNullableStringFields(msSqlQuery);
}
///
@@ -672,19 +223,8 @@ public async Task TestQueryingTypeWithNullableStringFields()
[TestMethod]
public async Task TestAliasSupportForGraphQLQueryFields()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 2) {
- book_id: id
- book_title: title
- }
- }";
string msSqlQuery = $"SELECT TOP 2 id AS book_id, title AS book_title FROM books ORDER by id FOR JSON PATH, INCLUDE_NULL_VALUES";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestAliasSupportForGraphQLQueryFields(msSqlQuery);
}
///
@@ -695,61 +235,30 @@ public async Task TestAliasSupportForGraphQLQueryFields()
[TestMethod]
public async Task TestSupportForMixOfRawDbFieldFieldAndAlias()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 2) {
- book_id: id
- title
- }
- }";
string msSqlQuery = $"SELECT TOP 2 id AS book_id, title AS title FROM books ORDER by id FOR JSON PATH, INCLUDE_NULL_VALUES";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestSupportForMixOfRawDbFieldFieldAndAlias(msSqlQuery);
}
///
/// Tests orderBy on a list query
///
+ [Ignore]
[TestMethod]
public async Task TestOrderByInListQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100 orderBy: {title: Desc}) {
- id
- title
- }
- }";
string msSqlQuery = $"SELECT TOP 100 id, title FROM books ORDER BY title DESC, id ASC FOR JSON PATH, INCLUDE_NULL_VALUES";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestOrderByInListQuery(msSqlQuery);
}
///
/// Use multiple order options and order an entity with a composite pk
///
+ [Ignore]
[TestMethod]
public async Task TestOrderByInListQueryOnCompPkType()
{
- string graphQLQueryName = "getReviews";
- string graphQLQuery = @"{
- getReviews(orderBy: {content: Asc id: Desc}) {
- id
- content
- }
- }";
string msSqlQuery = $"SELECT TOP 100 id, content FROM reviews ORDER BY content ASC, id DESC, book_id ASC FOR JSON PATH, INCLUDE_NULL_VALUES";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestOrderByInListQueryOnCompPkType(msSqlQuery);
}
///
@@ -757,43 +266,23 @@ public async Task TestOrderByInListQueryOnCompPkType()
/// meaning that null pk columns are included in the ORDER BY clause
/// as ASC by default while null non-pk columns are completely ignored
///
+ [Ignore]
[TestMethod]
public async Task TestNullFieldsInOrderByAreIgnored()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100 orderBy: {title: Desc id: null publisher_id: null}) {
- id
- title
- }
- }";
string msSqlQuery = $"SELECT TOP 100 id, title FROM books ORDER BY title DESC, id ASC FOR JSON PATH, INCLUDE_NULL_VALUES";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestNullFieldsInOrderByAreIgnored(msSqlQuery);
}
///
/// Tests that an orderBy with only null fields results in default pk sorting
///
+ [Ignore]
[TestMethod]
public async Task TestOrderByWithOnlyNullFieldsDefaultsToPkSorting()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100 orderBy: {title: null}) {
- id
- title
- }
- }";
string msSqlQuery = $"SELECT TOP 100 id, title FROM books ORDER BY id ASC FOR JSON PATH, INCLUDE_NULL_VALUES";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(msSqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestOrderByWithOnlyNullFieldsDefaultsToPkSorting(msSqlQuery);
}
#endregion
@@ -801,33 +290,15 @@ public async Task TestOrderByWithOnlyNullFieldsDefaultsToPkSorting()
#region Negative Tests
[TestMethod]
- public async Task TestInvalidFirstParamQuery()
+ public override async Task TestInvalidFirstParamQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: -1) {
- id
- title
- }
- }";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.BadRequest}");
+ await base.TestInvalidFirstParamQuery();
}
[TestMethod]
- public async Task TestInvalidFilterParamQuery()
+ public override async Task TestInvalidFilterParamQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(_filterOData: ""INVALID"") {
- id
- title
- }
- }";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.BadRequest}");
+ await base.TestInvalidFilterParamQuery();
}
#endregion
diff --git a/DataGateway.Service.Tests/SqlTests/MySqlGQLFilterTests.cs b/DataGateway.Service.Tests/SqlTests/MySqlGQLFilterTests.cs
index c8de7933e2..b7f8b0f749 100644
--- a/DataGateway.Service.Tests/SqlTests/MySqlGQLFilterTests.cs
+++ b/DataGateway.Service.Tests/SqlTests/MySqlGQLFilterTests.cs
@@ -29,7 +29,7 @@ public static async Task InitializeTestFixture(TestContext context)
_runtimeConfigPath,
_queryEngine,
_mutationEngine,
- _metadataStoreProvider,
+ graphQLMetadataProvider: null,
new DocumentCache(),
new Sha256DocumentHashProvider(),
_sqlMetadataProvider);
diff --git a/DataGateway.Service.Tests/SqlTests/MySqlGraphQLMutationTests.cs b/DataGateway.Service.Tests/SqlTests/MySqlGraphQLMutationTests.cs
index e4b8b345d7..c37b707739 100644
--- a/DataGateway.Service.Tests/SqlTests/MySqlGraphQLMutationTests.cs
+++ b/DataGateway.Service.Tests/SqlTests/MySqlGraphQLMutationTests.cs
@@ -1,7 +1,5 @@
-using System.Text.Json;
using System.Threading.Tasks;
using Azure.DataGateway.Service.Controllers;
-using Azure.DataGateway.Service.Exceptions;
using Azure.DataGateway.Service.Resolvers;
using Azure.DataGateway.Service.Services;
using HotChocolate.Language;
@@ -11,13 +9,10 @@ namespace Azure.DataGateway.Service.Tests.SqlTests
{
[TestClass, TestCategory(TestCategory.MYSQL)]
- public class MySqlGraphQLMutationTests : SqlTestBase
+ public class MySqlGraphQLMutationTests : GraphQLMutationTestBase
{
#region Test Fixture Setup
- private static GraphQLService _graphQLService;
- private static GraphQLController _graphQLController;
-
///
/// Sets up test fixture for class, only to be run once per test run, as defined by
/// MSTest decorator.
@@ -33,7 +28,7 @@ public static async Task InitializeTestFixture(TestContext context)
_runtimeConfigPath,
_queryEngine,
_mutationEngine,
- _metadataStoreProvider,
+ graphQLMetadataProvider: null,
new DocumentCache(),
new Sha256DocumentHashProvider(),
_sqlMetadataProvider);
@@ -61,16 +56,6 @@ public async Task TestCleanup()
[TestMethod]
public async Task InsertMutation()
{
- string graphQLMutationName = "insertBook";
- string graphQLMutation = @"
- mutation {
- insertBook(title: ""My New Book"", publisher_id: 1234) {
- id
- title
- }
- }
- ";
-
string mySqlQuery = @"
SELECT JSON_OBJECT('id', `subq`.`id`, 'title', `subq`.`title`) AS `data`
FROM (
@@ -84,10 +69,7 @@ ORDER BY `id` LIMIT 1
) AS `subq`
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await InsertMutation(mySqlQuery);
}
///
@@ -98,16 +80,6 @@ ORDER BY `id` LIMIT 1
[TestMethod]
public async Task InsertMutationForConstantdefaultValue()
{
- string graphQLMutationName = "insertReview";
- string graphQLMutation = @"
- mutation {
- insertReview(book_id: 1) {
- id
- content
- }
- }
- ";
-
string mySqlQuery = @"
SELECT JSON_OBJECT('id', `subq`.`id`, 'content', `subq`.`content`) AS `data`
FROM (
@@ -121,10 +93,7 @@ ORDER BY `id` LIMIT 1
) AS `subq`
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await InsertMutationForConstantdefaultValue(mySqlQuery);
}
///
@@ -135,16 +104,6 @@ ORDER BY `id` LIMIT 1
[TestMethod]
public async Task UpdateMutation()
{
- string graphQLMutationName = "editBook";
- string graphQLMutation = @"
- mutation {
- editBook(id: 1, title: ""Even Better Title"", publisher_id: 2345) {
- title
- publisher_id
- }
- }
- ";
-
string mySqlQuery = @"
SELECT JSON_OBJECT('title', `subq2`.`title`, 'publisher_id', `subq2`.`publisher_id`) AS `data`
FROM (
@@ -156,10 +115,7 @@ ORDER BY `table0`.`id` LIMIT 1
) AS `subq2`
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await UpdateMutation(mySqlQuery);
}
///
@@ -169,16 +125,6 @@ ORDER BY `table0`.`id` LIMIT 1
[TestMethod]
public async Task DeleteMutation()
{
- string graphQLMutationName = "deleteBook";
- string graphQLMutation = @"
- mutation {
- deleteBook(id: 1) {
- title
- publisher_id
- }
- }
- ";
-
string mySqlQueryForResult = @"
SELECT JSON_OBJECT('title', `subq2`.`title`, 'publisher_id', `subq2`.`publisher_id`) AS `data`
FROM (
@@ -190,13 +136,6 @@ ORDER BY `table0`.`id` LIMIT 1
) AS `subq2`
";
- // query the table before deletion is performed to see if what the mutation
- // returns is correct
- string expected = await GetDatabaseResultAsync(mySqlQueryForResult);
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
-
string mySqlQueryToVerifyDeletion = @"
SELECT JSON_OBJECT('count', `subq`.`count`) AS `data`
FROM (
@@ -206,10 +145,7 @@ SELECT COUNT(*) AS `count`
) AS `subq`
";
- string dbResponse = await GetDatabaseResultAsync(mySqlQueryToVerifyDeletion);
-
- using JsonDocument result = JsonDocument.Parse(dbResponse);
- Assert.AreEqual(result.RootElement.GetProperty("count").GetInt64(), 0);
+ await DeleteMutation(mySqlQueryForResult, mySqlQueryToVerifyDeletion);
}
///
@@ -221,13 +157,6 @@ SELECT COUNT(*) AS `count`
[Ignore]
public async Task InsertMutationForNonGraphQLTypeTable()
{
- string graphQLMutationName = "addAuthorToBook";
- string graphQLMutation = @"
- mutation {
- addAuthorToBook(author_id: 123, book_id: 2)
- }
- ";
-
string mySqlQuery = @"
SELECT JSON_OBJECT('count', `subq`.`count`) AS DATA
FROM
@@ -237,11 +166,7 @@ FROM book_author_link
AND author_id = 123) AS subq
";
- await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string dbResponse = await GetDatabaseResultAsync(mySqlQuery);
-
- using JsonDocument result = JsonDocument.Parse(dbResponse);
- Assert.AreEqual(result.RootElement.GetProperty("count").GetInt64(), 1);
+ await InsertMutationForNonGraphQLTypeTable(mySqlQuery);
}
///
@@ -251,26 +176,13 @@ FROM book_author_link
[TestMethod]
public async Task NestedQueryingInMutation()
{
- string graphQLMutationName = "insertBook";
- string graphQLMutation = @"
- mutation {
- insertBook(title: ""My New Book"", publisher_id: 1234) {
- id
- title
- publisher {
- name
- }
- }
- }
- ";
-
string mySqlQuery = @"
- SELECT JSON_OBJECT('id', `subq4`.`id`, 'title', `subq4`.`title`, 'publisher', JSON_EXTRACT(`subq4`.
- `publisher`, '$')) AS `data`
+ SELECT JSON_OBJECT('id', `subq4`.`id`, 'title', `subq4`.`title`, 'publishers', JSON_EXTRACT(`subq4`.
+ `publishers`, '$')) AS `data`
FROM (
SELECT `table0`.`id` AS `id`,
`table0`.`title` AS `title`,
- `table1_subq`.`data` AS `publisher`
+ `table1_subq`.`data` AS `publishers`
FROM `books` AS `table0`
LEFT OUTER JOIN LATERAL(SELECT JSON_OBJECT('name', `subq3`.`name`) AS `data` FROM (
SELECT `table1`.`name` AS `name`
@@ -283,10 +195,7 @@ ORDER BY `table0`.`id` LIMIT 1
) AS `subq4`
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await NestedQueryingInMutation(mySqlQuery);
}
///
@@ -295,17 +204,6 @@ ORDER BY `table0`.`id` LIMIT 1
[TestMethod]
public async Task TestExplicitNullInsert()
{
- string graphQLMutationName = "insertMagazine";
- string graphQLMutation = @"
- mutation {
- insertMagazine(id: 800, title: ""New Magazine"", issue_number: null) {
- id
- title
- issue_number
- }
- }
- ";
-
string mySqlQuery = @"
SELECT JSON_OBJECT('id', `subq2`.`id`, 'title', `subq2`.`title`, 'issue_number', `subq2`.`issue_number`) AS `data`
FROM (
@@ -320,10 +218,7 @@ ORDER BY `table0`.`id` LIMIT 1
) AS `subq2`
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestExplicitNullInsert(mySqlQuery);
}
///
@@ -332,17 +227,6 @@ ORDER BY `table0`.`id` LIMIT 1
[TestMethod]
public async Task TestImplicitNullInsert()
{
- string graphQLMutationName = "insertMagazine";
- string graphQLMutation = @"
- mutation {
- insertMagazine(id: 801, title: ""New Magazine 2"") {
- id
- title
- issue_number
- }
- }
- ";
-
string mySqlQuery = @"
SELECT JSON_OBJECT('id', `subq2`.`id`, 'title', `subq2`.`title`, 'issue_number', `subq2`.`issue_number`) AS `data`
FROM (
@@ -357,10 +241,7 @@ ORDER BY `table0`.`id` LIMIT 1
) AS `subq2`
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestImplicitNullInsert(mySqlQuery);
}
///
@@ -369,16 +250,6 @@ ORDER BY `table0`.`id` LIMIT 1
[TestMethod]
public async Task TestUpdateColumnToNull()
{
- string graphQLMutationName = "updateMagazine";
- string graphQLMutation = @"
- mutation {
- updateMagazine(id: 1, issue_number: null) {
- id
- issue_number
- }
- }
- ";
-
string mySqlQuery = @"
SELECT JSON_OBJECT('id', `subq2`.`id`, 'issue_number', `subq2`.`issue_number`) AS `data`
FROM (
@@ -391,10 +262,7 @@ ORDER BY `table0`.`id` LIMIT 1
) AS `subq2`
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestUpdateColumnToNull(mySqlQuery);
}
///
@@ -403,17 +271,6 @@ ORDER BY `table0`.`id` LIMIT 1
[TestMethod]
public async Task TestMissingColumnNotUpdatedToNull()
{
- string graphQLMutationName = "updateMagazine";
- string graphQLMutation = @"
- mutation {
- updateMagazine(id: 1, title: ""Newest Magazine"") {
- id
- title
- issue_number
- }
- }
- ";
-
string mySqlQuery = @"
SELECT JSON_OBJECT('id', `subq2`.`id`, 'title', `subq2`.`title`, 'issue_number', `subq2`.`issue_number`) AS `data`
FROM (
@@ -428,10 +285,7 @@ ORDER BY `table0`.`id` LIMIT 1
) AS `subq2`
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestMissingColumnNotUpdatedToNull(mySqlQuery);
}
///
@@ -442,16 +296,6 @@ ORDER BY `table0`.`id` LIMIT 1
[TestMethod]
public async Task TestAliasSupportForGraphQLMutationQueryFields()
{
- string graphQLMutationName = "insertBook";
- string graphQLMutation = @"
- mutation {
- insertBook(title: ""My New Book"", publisher_id: 1234) {
- book_id: id
- book_title: title
- }
- }
- ";
-
string mySqlQuery = @"
SELECT JSON_OBJECT('book_id', `subq`.`book_id`, 'book_title', `subq`.`book_title`) AS `data`
FROM (
@@ -465,10 +309,7 @@ ORDER BY `id` LIMIT 1
) AS `subq`
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.TestAliasSupportForGraphQLMutationQueryFields(mySqlQuery);
}
#endregion
@@ -482,24 +323,6 @@ ORDER BY `id` LIMIT 1
[TestMethod]
public async Task InsertWithInvalidForeignKey()
{
- string graphQLMutationName = "insertBook";
- string graphQLMutation = @"
- mutation {
- insertBook(title: ""My New Book"", publisher_id: -1) {
- id
- title
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
-
- SqlTestHelper.TestForErrorInGraphQLResponse(
- result.ToString(),
- message: MySqlDbExceptionParser.INTEGRITY_CONSTRAINT_VIOLATION_MESSAGE,
- statusCode: $"{DataGatewayException.SubStatusCodes.DatabaseOperationFailed}"
- );
-
string mySqlQuery = @"
SELECT JSON_OBJECT('count', `subq`.`count`) AS `data`
FROM (
@@ -509,9 +332,7 @@ SELECT COUNT(*) AS `count`
) AS `subq`
";
- string dbResponse = await GetDatabaseResultAsync(mySqlQuery);
- using JsonDocument dbResponseJson = JsonDocument.Parse(dbResponse);
- Assert.AreEqual(dbResponseJson.RootElement.GetProperty("count").GetInt64(), 0);
+ await InsertWithInvalidForeignKey(mySqlQuery, MySqlDbExceptionParser.INTEGRITY_CONSTRAINT_VIOLATION_MESSAGE);
}
///
@@ -521,24 +342,6 @@ SELECT COUNT(*) AS `count`
[TestMethod]
public async Task UpdateWithInvalidForeignKey()
{
- string graphQLMutationName = "editBook";
- string graphQLMutation = @"
- mutation {
- editBook(id: 1, publisher_id: -1) {
- id
- title
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
-
- SqlTestHelper.TestForErrorInGraphQLResponse(
- result.ToString(),
- message: MySqlDbExceptionParser.INTEGRITY_CONSTRAINT_VIOLATION_MESSAGE,
- statusCode: $"{DataGatewayException.SubStatusCodes.DatabaseOperationFailed}"
- );
-
string mySqlQuery = @"
SELECT JSON_OBJECT('count', `subq`.`count`) AS `data`
FROM (
@@ -549,9 +352,7 @@ SELECT COUNT(*) AS `count`
) AS `subq`
";
- string dbResponse = await GetDatabaseResultAsync(mySqlQuery);
- using JsonDocument dbResponseJson = JsonDocument.Parse(dbResponse);
- Assert.AreEqual(dbResponseJson.RootElement.GetProperty("count").GetInt64(), 0);
+ await UpdateWithInvalidForeignKey(mySqlQuery, MySqlDbExceptionParser.INTEGRITY_CONSTRAINT_VIOLATION_MESSAGE);
}
///
@@ -559,20 +360,9 @@ SELECT COUNT(*) AS `count`
/// Check: check that GraphQL returns the appropriate message to the user
///
[TestMethod]
- public async Task UpdateWithNoNewValues()
+ public override async Task UpdateWithNoNewValues()
{
- string graphQLMutationName = "editBook";
- string graphQLMutation = @"
- mutation {
- editBook(id: 1) {
- id
- title
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.BadRequest}");
+ await base.UpdateWithNoNewValues();
}
///
@@ -580,20 +370,9 @@ public async Task UpdateWithNoNewValues()
/// Check: check that GraphQL returns an appropriate exception to the user
///
[TestMethod]
- public async Task UpdateWithInvalidIdentifier()
+ public override async Task UpdateWithInvalidIdentifier()
{
- string graphQLMutationName = "editBook";
- string graphQLMutation = @"
- mutation {
- editBook(id: -1, title: ""Even Better Title"") {
- id
- title
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.EntityNotFound}");
+ await base.UpdateWithInvalidIdentifier();
}
///
@@ -603,21 +382,7 @@ public async Task UpdateWithInvalidIdentifier()
[TestMethod]
public async Task TestViolatingOneToOneRelashionShip()
{
- string graphQLMutationName = "insertWebsitePlacement";
- string graphQLMutation = @"
- mutation {
- insertWebsitePlacement(book_id: 1, price: 25) {
- id
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(
- result.ToString(),
- message: MySqlDbExceptionParser.INTEGRITY_CONSTRAINT_VIOLATION_MESSAGE,
- statusCode: $"{DataGatewayException.SubStatusCodes.DatabaseOperationFailed}"
- );
+ await TestViolatingOneToOneRelashionShip(MySqlDbExceptionParser.INTEGRITY_CONSTRAINT_VIOLATION_MESSAGE);
}
#endregion
}
diff --git a/DataGateway.Service.Tests/SqlTests/MySqlGraphQLPaginationTests.cs b/DataGateway.Service.Tests/SqlTests/MySqlGraphQLPaginationTests.cs
index d93294fc88..a4f8d86ece 100644
--- a/DataGateway.Service.Tests/SqlTests/MySqlGraphQLPaginationTests.cs
+++ b/DataGateway.Service.Tests/SqlTests/MySqlGraphQLPaginationTests.cs
@@ -28,7 +28,7 @@ public static async Task InitializeTestFixture(TestContext context)
_runtimeConfigPath,
_queryEngine,
_mutationEngine,
- _metadataStoreProvider,
+ graphQLMetadataProvider: null,
new DocumentCache(),
new Sha256DocumentHashProvider(),
_sqlMetadataProvider);
diff --git a/DataGateway.Service.Tests/SqlTests/MySqlGraphQLQueryTests.cs b/DataGateway.Service.Tests/SqlTests/MySqlGraphQLQueryTests.cs
index dc0d41d7d9..50548c63df 100644
--- a/DataGateway.Service.Tests/SqlTests/MySqlGraphQLQueryTests.cs
+++ b/DataGateway.Service.Tests/SqlTests/MySqlGraphQLQueryTests.cs
@@ -1,8 +1,5 @@
-using System.Text.Json;
using System.Threading.Tasks;
-using Azure.DataGateway.Service.Configurations;
using Azure.DataGateway.Service.Controllers;
-using Azure.DataGateway.Service.Exceptions;
using Azure.DataGateway.Service.Services;
using HotChocolate.Language;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -10,13 +7,10 @@
namespace Azure.DataGateway.Service.Tests.SqlTests
{
[TestClass, TestCategory(TestCategory.MYSQL)]
- public class MYSqlGraphQLQueryTests : SqlTestBase
+ public class MYSqlGraphQLQueryTests : GraphQLQueryTestBase
{
#region Test Fixture Setup
- private static GraphQLService _graphQLService;
- private static GraphQLController _graphQLController;
-
///
/// Sets up test fixture for class, only to be run once per test run, as defined by
/// MSTest decorator.
@@ -32,7 +26,7 @@ public static async Task InitializeTestFixture(TestContext context)
_runtimeConfigPath,
_queryEngine,
_mutationEngine,
- _metadataStoreProvider,
+ graphQLMetadataProvider: null,
new DocumentCache(),
new Sha256DocumentHashProvider(),
_sqlMetadataProvider);
@@ -43,23 +37,9 @@ public static async Task InitializeTestFixture(TestContext context)
#region Tests
- [TestMethod]
- public void TestConfigIsValid()
- {
- IConfigValidator configValidator = new SqlConfigValidator(_metadataStoreProvider, _graphQLService, _sqlMetadataProvider);
- configValidator.ValidateConfig();
- }
-
[TestMethod]
public async Task MultipleResultQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- id
- title
- }
- }";
string mySqlQuery = @"
SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'title', `subq1`.`title`)), '[]') AS `data`
FROM
@@ -70,22 +50,12 @@ SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'title', `subq1`.`
ORDER BY `table0`.`id`
LIMIT 100) AS `subq1`";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await MultipleResultQuery(mySqlQuery);
}
[TestMethod]
public async Task MultipleResultQueryWithVariables()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"query ($first: Int!) {
- getBooks(first: $first) {
- id
- title
- }
- }";
string mySqlQuery = @"
SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'title', `subq1`.`title`)), '[]') AS `data`
FROM
@@ -96,82 +66,13 @@ SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'title', `subq1`.`
ORDER BY `table0`.`id`
LIMIT 100) AS `subq1`";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController, new() { { "first", 100 } });
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await MultipleResultQueryWithVariables(mySqlQuery);
}
[TestMethod]
- public async Task MultipleResultJoinQuery()
+ public override async Task MultipleResultJoinQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- id
- title
- publisher_id
- publisher {
- id
- name
- }
- reviews(first: 100) {
- id
- content
- }
- authors(first: 100) {
- id
- name
- }
- }
- }";
- string mySqlQuery = @"
- SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq8`.`id`, 'title', `subq8`.`title`, 'publisher_id',
- `subq8`.`publisher_id`, 'publisher', JSON_EXTRACT(`subq8`.`publisher`, '$'), 'reviews',
- JSON_EXTRACT(`subq8`.`reviews`, '$'), 'authors', JSON_EXTRACT(`subq8`.`authors`, '$'))),
- '[]') AS `data`
- FROM (
- SELECT `table0`.`id` AS `id`,
- `table0`.`title` AS `title`,
- `table0`.`publisher_id` AS `publisher_id`,
- `table1_subq`.`data` AS `publisher`,
- `table2_subq`.`data` AS `reviews`,
- `table3_subq`.`data` AS `authors`
- FROM `books` AS `table0`
- LEFT OUTER JOIN LATERAL(SELECT JSON_OBJECT('id', `subq5`.`id`, 'name', `subq5`.`name`) AS `data` FROM (
- SELECT `table1`.`id` AS `id`,
- `table1`.`name` AS `name`
- FROM `publishers` AS `table1`
- WHERE `table0`.`publisher_id` = `table1`.`id`
- ORDER BY `table1`.`id` LIMIT 1
- ) AS `subq5`) AS `table1_subq` ON TRUE
- LEFT OUTER JOIN LATERAL(SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq6`.`id`, 'content',
- `subq6`.`content`)), '[]') AS `data` FROM (
- SELECT `table2`.`id` AS `id`,
- `table2`.`content` AS `content`
- FROM `reviews` AS `table2`
- WHERE `table0`.`id` = `table2`.`book_id`
- ORDER BY `table2`.`book_id`,
- `table2`.`id` LIMIT 100
- ) AS `subq6`) AS `table2_subq` ON TRUE
- LEFT OUTER JOIN LATERAL(SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq7`.`id`, 'name', `subq7`
- .`name`)), '[]') AS `data` FROM (
- SELECT `table3`.`id` AS `id`,
- `table3`.`name` AS `name`
- FROM `authors` AS `table3`
- INNER JOIN `book_author_link` AS `table4` ON `table4`.`author_id` = `table3`.`id`
- WHERE `table0`.`id` = `table4`.`book_id`
- ORDER BY `table3`.`id` LIMIT 100
- ) AS `subq7`) AS `table3_subq` ON TRUE
- WHERE 1 = 1
- ORDER BY `table0`.`id` LIMIT 100
- ) AS `subq8`
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.MultipleResultJoinQuery();
}
///
@@ -181,32 +82,18 @@ ORDER BY `table0`.`id` LIMIT 100
[TestMethod]
public async Task OneToOneJoinQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"query {
- getBooks {
- id
- website_placement {
- id
- price
- book {
- id
- }
- }
- }
- }";
-
string mySqlQuery = @"
- SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq11`.`id`, 'website_placement', `subq11`.`website_placement`)
- ), JSON_ARRAY()) AS `data`
+ SELECT JSON_OBJECT('id', `subq11`.`id`, 'websiteplacement', `subq11`.`websiteplacement`)
+ AS `data`
FROM (
SELECT `table0`.`id` AS `id`,
- `table1_subq`.`data` AS `website_placement`
+ `table1_subq`.`data` AS `websiteplacement`
FROM `books` AS `table0`
- LEFT OUTER JOIN LATERAL(SELECT JSON_OBJECT('id', `subq10`.`id`, 'price', `subq10`.`price`, 'book',
- `subq10`.`book`) AS `data` FROM (
+ LEFT OUTER JOIN LATERAL(SELECT JSON_OBJECT('id', `subq10`.`id`, 'price', `subq10`.`price`, 'books',
+ `subq10`.`books`) AS `data` FROM (
SELECT `table1`.`id` AS `id`,
`table1`.`price` AS `price`,
- `table2_subq`.`data` AS `book`
+ `table2_subq`.`data` AS `books`
FROM `book_website_placements` AS `table1`
LEFT OUTER JOIN LATERAL(SELECT JSON_OBJECT('id', `subq9`.`id`) AS `data` FROM (
SELECT `table2`.`id` AS `id`
@@ -217,15 +104,12 @@ ORDER BY `table2`.`id` LIMIT 1
WHERE `table0`.`id` = `table1`.`book_id`
ORDER BY `table1`.`id` LIMIT 1
) AS `subq10`) AS `table1_subq` ON TRUE
- WHERE 1 = 1
+ WHERE `table0`.`id` = 1
ORDER BY `table0`.`id` LIMIT 100
) AS `subq11`
";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await OneToOneJoinQuery(mySqlQuery);
}
///
@@ -234,87 +118,9 @@ ORDER BY `table0`.`id` LIMIT 100
///
///
[TestMethod]
- public async Task DeeplyNestedManyToOneJoinQuery()
+ public override async Task DeeplyNestedManyToOneJoinQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- title
- publisher {
- name
- books(first: 100) {
- title
- publisher {
- name
- books(first: 100) {
- title
- publisher {
- name
- }
- }
- }
- }
- }
- }
- }";
-
- string mySqlQuery = @"
- SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('title', `subq11`.`title`, 'publisher', JSON_EXTRACT(`subq11`.
- `publisher`, '$'))), '[]') AS `data`
- FROM (
- SELECT `table0`.`title` AS `title`,
- `table1_subq`.`data` AS `publisher`
- FROM `books` AS `table0`
- LEFT OUTER JOIN LATERAL(SELECT JSON_OBJECT('name', `subq10`.`name`, 'books', JSON_EXTRACT(`subq10`.
- `books`, '$')) AS `data` FROM (
- SELECT `table1`.`name` AS `name`,
- `table2_subq`.`data` AS `books`
- FROM `publishers` AS `table1`
- LEFT OUTER JOIN LATERAL(SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('title', `subq9`.`title`,
- 'publisher', JSON_EXTRACT(`subq9`.`publisher`, '$'))), '[]') AS `data`
- FROM (
- SELECT `table2`.`title` AS `title`,
- `table3_subq`.`data` AS `publisher`
- FROM `books` AS `table2`
- LEFT OUTER JOIN LATERAL(SELECT JSON_OBJECT('name', `subq8`.`name`, 'books',
- JSON_EXTRACT(`subq8`.`books`, '$')) AS `data` FROM (
- SELECT `table3`.`name` AS `name`,
- `table4_subq`.`data` AS `books`
- FROM `publishers` AS `table3`
- LEFT OUTER JOIN LATERAL(SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('title',
- `subq7`.`title`, 'publisher', JSON_EXTRACT(`subq7`.
- `publisher`, '$'))), '[]') AS `data` FROM (
- SELECT `table4`.`title` AS `title`,
- `table5_subq`.`data` AS `publisher`
- FROM `books` AS `table4`
- LEFT OUTER JOIN LATERAL(SELECT JSON_OBJECT('name', `subq6`.`name`)
- AS `data` FROM (
- SELECT `table5`.`name` AS `name`
- FROM `publishers` AS `table5`
- WHERE `table4`.`publisher_id` = `table5`.`id`
- ORDER BY `table5`.`id` LIMIT 1
- ) AS `subq6`) AS `table5_subq` ON TRUE
- WHERE `table3`.`id` = `table4`.`publisher_id`
- ORDER BY `table4`.`id` LIMIT 100
- ) AS `subq7`) AS `table4_subq` ON TRUE
- WHERE `table2`.`publisher_id` = `table3`.`id`
- ORDER BY `table3`.`id` LIMIT 1
- ) AS `subq8`) AS `table3_subq` ON TRUE
- WHERE `table1`.`id` = `table2`.`publisher_id`
- ORDER BY `table2`.`id` LIMIT 100
- ) AS `subq9`) AS `table2_subq` ON TRUE
- WHERE `table0`.`publisher_id` = `table1`.`id`
- ORDER BY `table1`.`id` LIMIT 1
- ) AS `subq10`) AS `table1_subq` ON TRUE
- WHERE 1 = 1
- ORDER BY `table0`.`id` LIMIT 100
- ) AS `subq11`
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.DeeplyNestedManyToOneJoinQuery();
}
///
@@ -323,78 +129,14 @@ ORDER BY `table0`.`id` LIMIT 100
///
///
[TestMethod]
- public async Task DeeplyNestedManyToManyJoinQuery()
+ public override async Task DeeplyNestedManyToManyJoinQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- title
- authors(first: 100) {
- name
- books(first: 100) {
- title
- authors(first: 100) {
- name
- }
- }
- }
- }
- }";
-
- string mySqlQuery = @"
- SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('title', `subq10`.`title`, 'authors', JSON_EXTRACT(`subq10`.
- `authors`, '$'))), '[]') AS `data`
- FROM (
- SELECT `table0`.`title` AS `title`,
- `table1_subq`.`data` AS `authors`
- FROM `books` AS `table0`
- LEFT OUTER JOIN LATERAL(SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('name', `subq9`.`name`, 'books',
- JSON_EXTRACT(`subq9`.`books`, '$'))), '[]') AS `data` FROM (
- SELECT `table1`.`name` AS `name`,
- `table2_subq`.`data` AS `books`
- FROM `authors` AS `table1`
- INNER JOIN `book_author_link` AS `table6` ON `table6`.`author_id` = `table1`.`id`
- LEFT OUTER JOIN LATERAL(SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('title', `subq8`.`title`,
- 'authors', JSON_EXTRACT(`subq8`.`authors`, '$'))), '[]') AS `data` FROM (
- SELECT `table2`.`title` AS `title`,
- `table3_subq`.`data` AS `authors`
- FROM `books` AS `table2`
- INNER JOIN `book_author_link` AS `table5` ON `table5`.`book_id` = `table2`.`id`
- LEFT OUTER JOIN LATERAL(SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('name', `subq7`.
- `name`)), '[]') AS `data` FROM (
- SELECT `table3`.`name` AS `name`
- FROM `authors` AS `table3`
- INNER JOIN `book_author_link` AS `table4` ON `table4`.`author_id` = `table3`.
- `id`
- WHERE `table2`.`id` = `table4`.`book_id`
- ORDER BY `table3`.`id` LIMIT 100
- ) AS `subq7`) AS `table3_subq` ON TRUE
- WHERE `table1`.`id` = `table5`.`author_id`
- ORDER BY `table2`.`id` LIMIT 100
- ) AS `subq8`) AS `table2_subq` ON TRUE
- WHERE `table0`.`id` = `table6`.`book_id`
- ORDER BY `table1`.`id` LIMIT 100
- ) AS `subq9`) AS `table1_subq` ON TRUE
- WHERE 1 = 1
- ORDER BY `table0`.`id` LIMIT 100
- ) AS `subq10`
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.DeeplyNestedManyToManyJoinQuery();
}
[TestMethod]
public async Task QueryWithSingleColumnPrimaryKey()
{
- string graphQLQueryName = "getBook";
- string graphQLQuery = @"{
- getBook(id: 2) {
- title
- }
- }";
string mySqlQuery = @"
SELECT JSON_OBJECT('title', `subq2`.`title`) AS `data`
FROM
@@ -405,21 +147,12 @@ ORDER BY `table0`.`id`
LIMIT 1) AS `subq2`
";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await QueryWithSingleColumnPrimaryKey(mySqlQuery);
}
[TestMethod]
public async Task QueryWithMultileColumnPrimaryKey()
{
- string graphQLQueryName = "getReview";
- string graphQLQuery = @"{
- getReview(id: 568, book_id: 1) {
- content
- }
- }";
string mySqlQuery = @"
SELECT JSON_OBJECT('content', `subq3`.`content`) AS `data`
FROM (
@@ -432,131 +165,31 @@ SELECT JSON_OBJECT('content', `subq3`.`content`) AS `data`
) AS `subq3`
";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await QueryWithMultileColumnPrimaryKey(mySqlQuery);
}
[TestMethod]
- public async Task QueryWithNullResult()
+ public override async Task QueryWithNullResult()
{
- string graphQLQueryName = "getBook";
- string graphQLQuery = @"{
- getBook(id: -9999) {
- title
- }
- }";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
-
- SqlTestHelper.PerformTestEqualJsonStrings("null", actual);
+ await base.QueryWithNullResult();
}
///
/// Test if first param successfully limits list quries
///
[TestMethod]
- public async Task TestFirstParamForListQueries()
+ public override async Task TestFirstParamForListQueries()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 1) {
- title
- publisher {
- name
- books(first: 3) {
- title
- }
- }
- }
- }";
-
- string mySqlQuery = @"
- SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('title', `subq5`.`title`, 'publisher', JSON_EXTRACT(`subq5`.
- `publisher`, '$'))), '[]') AS `data`
- FROM (
- SELECT `table0`.`title` AS `title`,
- `table1_subq`.`data` AS `publisher`
- FROM `books` AS `table0`
- LEFT OUTER JOIN LATERAL(SELECT JSON_OBJECT('name', `subq4`.`name`, 'books', JSON_EXTRACT(`subq4`.
- `books`, '$')) AS `data` FROM (
- SELECT `table1`.`name` AS `name`,
- `table2_subq`.`data` AS `books`
- FROM `publishers` AS `table1`
- LEFT OUTER JOIN LATERAL(SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('title', `subq3`.`title`)
- ), '[]') AS `data` FROM (
- SELECT `table2`.`title` AS `title`
- FROM `books` AS `table2`
- WHERE `table1`.`id` = `table2`.`publisher_id`
- ORDER BY `table2`.`id` LIMIT 3
- ) AS `subq3`) AS `table2_subq` ON TRUE
- WHERE `table0`.`publisher_id` = `table1`.`id`
- ORDER BY `table1`.`id` LIMIT 1
- ) AS `subq4`) AS `table1_subq` ON TRUE
- WHERE 1 = 1
- ORDER BY `table0`.`id` LIMIT 1
- ) AS `subq5`
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.TestFirstParamForListQueries();
}
///
/// Test if filter and filterOData param successfully filters the query results
///
[TestMethod]
- public async Task TestFilterAndFilterODataParamForListQueries()
+ public override async Task TestFilterAndFilterODataParamForListQueries()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(_filter: {id: {gte: 1} and: [{id: {lte: 4}}]}) {
- id
- publisher {
- books(first: 3, _filterOData: ""id ne 2"") {
- id
- }
- }
- }
- }";
-
- string mySqlQuery = @"
- SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT(""id"", `subq12`.`id`, ""publisher"", JSON_EXTRACT(`subq12`.
- `publisher`, '$'))), '[]') AS `data`
- FROM (
- SELECT `table0`.`id` AS `id`,
- `table1_subq`.`data` AS `publisher`
- FROM `books` AS `table0`
- LEFT OUTER JOIN LATERAL(SELECT JSON_OBJECT(""books"", JSON_EXTRACT(`subq11`.`books`, '$')) AS `data` FROM
- (
- SELECT `table2_subq`.`data` AS `books`
- FROM `publishers` AS `table1`
- LEFT OUTER JOIN LATERAL(SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT(""id"", `subq10`.`id`)),
- '[]') AS `data` FROM (
- SELECT `table2`.`id` AS `id`
- FROM `books` AS `table2`
- WHERE (id != 2)
- AND `table1`.`id` = `table2`.`publisher_id`
- ORDER BY `table2`.`id` LIMIT 3
- ) AS `subq10`) AS `table2_subq` ON TRUE
- WHERE `table0`.`publisher_id` = `table1`.`id`
- ORDER BY `table1`.`id` LIMIT 1
- ) AS `subq11`) AS `table1_subq` ON TRUE
- WHERE (
- (id >= 1)
- AND (id <= 4)
- )
- ORDER BY `table0`.`id` LIMIT 100
- ) AS `subq12`
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.TestFilterAndFilterODataParamForListQueries();
}
///
@@ -565,15 +198,6 @@ ORDER BY `table0`.`id` LIMIT 100
[TestMethod]
public async Task TestQueryingTypeWithNullableIntFields()
{
- string graphQLQueryName = "getMagazines";
- string graphQLQuery = @"{
- getMagazines{
- id
- title
- issue_number
- }
- }";
-
string mySqlQuery = @"
SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'title', `subq1`.`title`, 'issue_number',
`subq1`.`issue_number`)), JSON_ARRAY()) AS `data`
@@ -587,10 +211,7 @@ ORDER BY `table0`.`id` LIMIT 100
) AS `subq1`
";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestQueryingTypeWithNullableIntFields(mySqlQuery);
}
///
@@ -599,14 +220,6 @@ ORDER BY `table0`.`id` LIMIT 100
[TestMethod]
public async Task TestQueryingTypeWithNullableStringFields()
{
- string graphQLQueryName = "getWebsiteUsers";
- string graphQLQuery = @"{
- getWebsiteUsers{
- id
- username
- }
- }";
-
string mySqlQuery = @"
SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'username', `subq1`.`username`)), JSON_ARRAY()) AS `data`
FROM (
@@ -618,10 +231,7 @@ ORDER BY `table0`.`id` LIMIT 100
) AS `subq1`
";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestQueryingTypeWithNullableStringFields(mySqlQuery);
}
///
@@ -632,13 +242,6 @@ ORDER BY `table0`.`id` LIMIT 100
[TestMethod]
public async Task TestAliasSupportForGraphQLQueryFields()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 2) {
- book_id: id
- book_title: title
- }
- }";
string mySqlQuery = @"
SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('book_id', `subq1`.`book_id`, 'book_title', `subq1`.`book_title`)), '[]') AS `data`
FROM
@@ -649,10 +252,7 @@ SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('book_id', `subq1`.`book_id`, 'book_ti
ORDER BY `table0`.`id`
LIMIT 2) AS `subq1`";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.TestAliasSupportForGraphQLQueryFields(mySqlQuery);
}
///
@@ -663,13 +263,6 @@ ORDER BY `table0`.`id`
[TestMethod]
public async Task TestSupportForMixOfRawDbFieldFieldAndAlias()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 2) {
- book_id: id
- title
- }
- }";
string mySqlQuery = @"
SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('book_id', `subq1`.`book_id`, 'title', `subq1`.`title`)), '[]') AS `data`
FROM
@@ -680,25 +273,16 @@ SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('book_id', `subq1`.`book_id`, 'title',
ORDER BY `table0`.`id`
LIMIT 2) AS `subq1`";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestSupportForMixOfRawDbFieldFieldAndAlias(mySqlQuery);
}
///
/// Tests orderBy on a list query
///
+ [Ignore]
[TestMethod]
public async Task TestOrderByInListQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100 orderBy: {title: Desc}) {
- id
- title
- }
- }";
string mySqlQuery = @"
SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'title', `subq1`.`title`)), '[]') AS `data`
FROM
@@ -709,25 +293,16 @@ SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'title', `subq1`.`
ORDER BY `table0`.`title` DESC, `table0`.`id` ASC
LIMIT 100) AS `subq1`";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestOrderByInListQuery(mySqlQuery);
}
///
/// Use multiple order options and order an entity with a composite pk
///
+ [Ignore]
[TestMethod]
public async Task TestOrderByInListQueryOnCompPkType()
{
- string graphQLQueryName = "getReviews";
- string graphQLQuery = @"{
- getReviews(orderBy: {content: Asc id: Desc}) {
- id
- content
- }
- }";
string mySqlQuery = @"
SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'content', `subq1`.`content`)), '[]') AS `data`
FROM
@@ -738,10 +313,7 @@ SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'content', `subq1`
ORDER BY `table0`.`content` ASC, `table0`.`id` DESC, `table0`.`book_id` ASC
LIMIT 100) AS `subq1`";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestOrderByInListQueryOnCompPkType(mySqlQuery);
}
///
@@ -749,16 +321,10 @@ SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'content', `subq1`
/// meaning that null pk columns are included in the ORDER BY clause
/// as ASC by default while null non-pk columns are completely ignored
///
+ [Ignore]
[TestMethod]
public async Task TestNullFieldsInOrderByAreIgnored()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100 orderBy: {title: Desc id: null publisher_id: null}) {
- id
- title
- }
- }";
string mySqlQuery = @"
SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'title', `subq1`.`title`)), '[]') AS `data`
FROM
@@ -769,25 +335,16 @@ SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'title', `subq1`.`
ORDER BY `table0`.`title` DESC, `table0`.`id` ASC
LIMIT 100) AS `subq1`";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestNullFieldsInOrderByAreIgnored(mySqlQuery);
}
///
/// Tests that an orderBy with only null fields results in default pk sorting
///
+ [Ignore]
[TestMethod]
public async Task TestOrderByWithOnlyNullFieldsDefaultsToPkSorting()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100 orderBy: {title: null}) {
- id
- title
- }
- }";
string mySqlQuery = @"
SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'title', `subq1`.`title`)), '[]') AS `data`
FROM
@@ -798,10 +355,7 @@ SELECT COALESCE(JSON_ARRAYAGG(JSON_OBJECT('id', `subq1`.`id`, 'title', `subq1`.`
ORDER BY `table0`.`id` ASC
LIMIT 100) AS `subq1`";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(mySqlQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestOrderByWithOnlyNullFieldsDefaultsToPkSorting(mySqlQuery);
}
#endregion
@@ -809,33 +363,15 @@ ORDER BY `table0`.`id` ASC
#region Negative Tests
[TestMethod]
- public async Task TestInvalidFirstParamQuery()
+ public override async Task TestInvalidFirstParamQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: -1) {
- id
- title
- }
- }";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.BadRequest}");
+ await base.TestInvalidFilterParamQuery();
}
[TestMethod]
- public async Task TestInvalidFilterParamQuery()
+ public override async Task TestInvalidFilterParamQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(_filterOData: ""INVALID"") {
- id
- title
- }
- }";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.BadRequest}");
+ await base.TestInvalidFilterParamQuery();
}
#endregion
diff --git a/DataGateway.Service.Tests/SqlTests/PostgreSqlGQLFilterTests.cs b/DataGateway.Service.Tests/SqlTests/PostgreSqlGQLFilterTests.cs
index fb1cc170b6..ccfd8e02b8 100644
--- a/DataGateway.Service.Tests/SqlTests/PostgreSqlGQLFilterTests.cs
+++ b/DataGateway.Service.Tests/SqlTests/PostgreSqlGQLFilterTests.cs
@@ -29,7 +29,7 @@ public static async Task InitializeTestFixture(TestContext context)
_runtimeConfigPath,
_queryEngine,
_mutationEngine,
- _metadataStoreProvider,
+ graphQLMetadataProvider: null,
new DocumentCache(),
new Sha256DocumentHashProvider(),
_sqlMetadataProvider);
diff --git a/DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLMutationTests.cs b/DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLMutationTests.cs
index f16b4436b0..f5b8f98e84 100644
--- a/DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLMutationTests.cs
+++ b/DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLMutationTests.cs
@@ -1,7 +1,5 @@
-using System.Text.Json;
using System.Threading.Tasks;
using Azure.DataGateway.Service.Controllers;
-using Azure.DataGateway.Service.Exceptions;
using Azure.DataGateway.Service.Resolvers;
using Azure.DataGateway.Service.Services;
using HotChocolate.Language;
@@ -11,13 +9,10 @@ namespace Azure.DataGateway.Service.Tests.SqlTests
{
[TestClass, TestCategory(TestCategory.POSTGRESQL)]
- public class PostgreSqlGraphQLMutationTests : SqlTestBase
+ public class PostgreSqlGraphQLMutationTests : GraphQLMutationTestBase
{
#region Test Fixture Setup
- private static GraphQLService _graphQLService;
- private static GraphQLController _graphQLController;
-
///
/// Sets up test fixture for class, only to be run once per test run, as defined by
/// MSTest decorator.
@@ -33,7 +28,7 @@ public static async Task InitializeTestFixture(TestContext context)
_runtimeConfigPath,
_queryEngine,
_mutationEngine,
- _metadataStoreProvider,
+ graphQLMetadataProvider: null,
new DocumentCache(),
new Sha256DocumentHashProvider(),
_sqlMetadataProvider);
@@ -61,16 +56,6 @@ public async Task TestCleanup()
[TestMethod]
public async Task InsertMutation()
{
- string graphQLMutationName = "insertBook";
- string graphQLMutation = @"
- mutation {
- insertBook(title: ""My New Book"", publisher_id: 1234) {
- id
- title
- }
- }
- ";
-
string postgresQuery = @"
SELECT to_jsonb(subq) AS DATA
FROM
@@ -84,10 +69,7 @@ ORDER BY id
LIMIT 1) AS subq
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await InsertMutation(postgresQuery);
}
///
@@ -98,16 +80,6 @@ ORDER BY id
[TestMethod]
public async Task InsertMutationForConstantdefaultValue()
{
- string graphQLMutationName = "insertReview";
- string graphQLMutation = @"
- mutation {
- insertReview(book_id: 1) {
- id
- content
- }
- }
- ";
-
string postgresQuery = @"
SELECT to_jsonb(subq) AS DATA
FROM
@@ -121,10 +93,7 @@ ORDER BY id
LIMIT 1) AS subq
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await InsertMutationForConstantdefaultValue(postgresQuery);
}
///
@@ -135,16 +104,6 @@ ORDER BY id
[TestMethod]
public async Task UpdateMutation()
{
- string graphQLMutationName = "editBook";
- string graphQLMutation = @"
- mutation {
- editBook(id: 1, title: ""Even Better Title"", publisher_id: 2345) {
- title
- publisher_id
- }
- }
- ";
-
string postgresQuery = @"
SELECT to_jsonb(subq) AS DATA
FROM
@@ -156,10 +115,7 @@ ORDER BY id
LIMIT 1) AS subq
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await UpdateMutation(postgresQuery);
}
///
@@ -169,16 +125,6 @@ ORDER BY id
[TestMethod]
public async Task DeleteMutation()
{
- string graphQLMutationName = "deleteBook";
- string graphQLMutation = @"
- mutation {
- deleteBook(id: 1) {
- title
- publisher_id
- }
- }
- ";
-
string postgresQueryForResult = @"
SELECT to_jsonb(subq) AS DATA
FROM
@@ -190,13 +136,6 @@ ORDER BY id
LIMIT 1) AS subq
";
- // query the table before deletion is performed to see if what the mutation
- // returns is correct
- string expected = await GetDatabaseResultAsync(postgresQueryForResult);
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
-
string postgresQueryToVerifyDeletion = @"
SELECT to_jsonb(subq) AS DATA
FROM
@@ -205,10 +144,7 @@ FROM books AS table0
WHERE id = 1) AS subq
";
- string dbResponse = await GetDatabaseResultAsync(postgresQueryToVerifyDeletion);
-
- using JsonDocument result = JsonDocument.Parse(dbResponse);
- Assert.AreEqual(result.RootElement.GetProperty("count").GetInt64(), 0);
+ await DeleteMutation(postgresQueryForResult, postgresQueryToVerifyDeletion);
}
///
@@ -220,13 +156,6 @@ FROM books AS table0
[Ignore]
public async Task InsertMutationForNonGraphQLTypeTable()
{
- string graphQLMutationName = "addAuthorToBook";
- string graphQLMutation = @"
- mutation {
- addAuthorToBook(author_id: 123, book_id: 2)
- }
- ";
-
string postgresQuery = @"
SELECT to_jsonb(subq) AS DATA
FROM
@@ -236,11 +165,7 @@ FROM book_author_link AS table0
AND author_id = 123) AS subq
";
- await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string dbResponse = await GetDatabaseResultAsync(postgresQuery);
-
- using JsonDocument result = JsonDocument.Parse(dbResponse);
- Assert.AreEqual(result.RootElement.GetProperty("count").GetInt64(), 1);
+ await InsertMutationForNonGraphQLTypeTable(postgresQuery);
}
///
@@ -250,25 +175,12 @@ FROM book_author_link AS table0
[TestMethod]
public async Task NestedQueryingInMutation()
{
- string graphQLMutationName = "insertBook";
- string graphQLMutation = @"
- mutation {
- insertBook(title: ""My New Book"", publisher_id: 1234) {
- id
- title
- publisher {
- name
- }
- }
- }
- ";
-
string postgresQuery = @"
SELECT to_jsonb(subq3) AS DATA
FROM
(SELECT table0.id AS id,
table0.title AS title,
- table1_subq.data AS publisher
+ table1_subq.data AS publishers
FROM books AS table0
LEFT OUTER JOIN LATERAL
(SELECT to_jsonb(subq2) AS DATA
@@ -285,10 +197,7 @@ ORDER BY table0.id
LIMIT 1) AS subq3
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await NestedQueryingInMutation(postgresQuery);
}
///
@@ -297,17 +206,6 @@ ORDER BY table0.id
[TestMethod]
public async Task TestExplicitNullInsert()
{
- string graphQLMutationName = "insertMagazine";
- string graphQLMutation = @"
- mutation {
- insertMagazine(id: 800, title: ""New Magazine"", issue_number: null) {
- id
- title
- issue_number
- }
- }
- ";
-
string postgresQuery = @"
SELECT to_jsonb(subq) AS DATA
FROM
@@ -322,10 +220,7 @@ ORDER BY id
LIMIT 1) AS subq
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestExplicitNullInsert(postgresQuery);
}
///
@@ -334,17 +229,6 @@ ORDER BY id
[TestMethod]
public async Task TestImplicitNullInsert()
{
- string graphQLMutationName = "insertMagazine";
- string graphQLMutation = @"
- mutation {
- insertMagazine(id: 801, title: ""New Magazine 2"") {
- id
- title
- issue_number
- }
- }
- ";
-
string postgresQuery = @"
SELECT to_jsonb(subq) AS DATA
FROM
@@ -359,10 +243,7 @@ ORDER BY id
LIMIT 1) AS subq
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestImplicitNullInsert(postgresQuery);
}
///
@@ -371,16 +252,6 @@ ORDER BY id
[TestMethod]
public async Task TestUpdateColumnToNull()
{
- string graphQLMutationName = "updateMagazine";
- string graphQLMutation = @"
- mutation {
- updateMagazine(id: 1, issue_number: null) {
- id
- issue_number
- }
- }
- ";
-
string postgresQuery = @"
SELECT to_jsonb(subq) AS DATA
FROM
@@ -393,10 +264,7 @@ ORDER BY id
LIMIT 1) AS subq
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestUpdateColumnToNull(postgresQuery);
}
///
@@ -405,17 +273,6 @@ ORDER BY id
[TestMethod]
public async Task TestMissingColumnNotUpdatedToNull()
{
- string graphQLMutationName = "updateMagazine";
- string graphQLMutation = @"
- mutation {
- updateMagazine(id: 1, title: ""Newest Magazine"") {
- id
- title
- issue_number
- }
- }
- ";
-
string postgresQuery = @"
SELECT to_jsonb(subq) AS DATA
FROM
@@ -430,10 +287,31 @@ ORDER BY id
LIMIT 1) AS subq
";
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
+ await TestMissingColumnNotUpdatedToNull(postgresQuery);
+ }
+
+ ///
+ /// Do: Inserts new book and return its id and title with their aliases(arbitrarily set by user while making request)
+ /// Check: If book with the expected values of the new book is present in the database and
+ /// if the mutation query has returned the correct information with Aliases where provided.
+ ///
+ [TestMethod]
+ public async Task TestAliasSupportForGraphQLMutationQueryFields()
+ {
+ string postgresQuery = @"
+ SELECT to_jsonb(subq) AS DATA
+ FROM
+ (SELECT table0.id AS book_id,
+ table0.title AS book_title
+ FROM books AS table0
+ WHERE id = 5001
+ AND title = 'My New Book'
+ AND publisher_id = 1234
+ ORDER BY id
+ LIMIT 1) AS subq
+ ";
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestAliasSupportForGraphQLMutationQueryFields(postgresQuery);
}
#endregion
@@ -447,24 +325,6 @@ ORDER BY id
[TestMethod]
public async Task InsertWithInvalidForeignKey()
{
- string graphQLMutationName = "insertBook";
- string graphQLMutation = @"
- mutation {
- insertBook(title: ""My New Book"", publisher_id: -1) {
- id
- title
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
-
- SqlTestHelper.TestForErrorInGraphQLResponse(
- result.ToString(),
- message: PostgresDbExceptionParser.FK_VIOLATION_MESSAGE,
- statusCode: $"{DataGatewayException.SubStatusCodes.DatabaseOperationFailed}"
- );
-
string postgresQuery = @"
SELECT to_jsonb(subq) AS DATA
FROM
@@ -473,9 +333,7 @@ FROM books
WHERE publisher_id = -1 ) AS subq
";
- string dbResponse = await GetDatabaseResultAsync(postgresQuery);
- using JsonDocument dbResponseJson = JsonDocument.Parse(dbResponse);
- Assert.AreEqual(dbResponseJson.RootElement.GetProperty("count").GetInt64(), 0);
+ await InsertWithInvalidForeignKey(postgresQuery, PostgresDbExceptionParser.FK_VIOLATION_MESSAGE);
}
///
@@ -485,24 +343,6 @@ FROM books
[TestMethod]
public async Task UpdateWithInvalidForeignKey()
{
- string graphQLMutationName = "editBook";
- string graphQLMutation = @"
- mutation {
- editBook(id: 1, publisher_id: -1) {
- id
- title
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
-
- SqlTestHelper.TestForErrorInGraphQLResponse(
- result.ToString(),
- message: PostgresDbExceptionParser.FK_VIOLATION_MESSAGE,
- statusCode: $"{DataGatewayException.SubStatusCodes.DatabaseOperationFailed}"
- );
-
string postgresQuery = @"
SELECT to_jsonb(subq) AS DATA
FROM
@@ -511,9 +351,7 @@ FROM books
WHERE id = 1 AND publisher_id = -1 ) AS subq
";
- string dbResponse = await GetDatabaseResultAsync(postgresQuery);
- using JsonDocument dbResponseJson = JsonDocument.Parse(dbResponse);
- Assert.AreEqual(dbResponseJson.RootElement.GetProperty("count").GetInt64(), 0);
+ await UpdateWithInvalidForeignKey(postgresQuery, PostgresDbExceptionParser.FK_VIOLATION_MESSAGE);
}
///
@@ -521,20 +359,9 @@ FROM books
/// Check: check that GraphQL returns the appropriate message to the user
///
[TestMethod]
- public async Task UpdateWithNoNewValues()
+ public override async Task UpdateWithNoNewValues()
{
- string graphQLMutationName = "editBook";
- string graphQLMutation = @"
- mutation {
- editBook(id: 1) {
- id
- title
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.BadRequest}");
+ await base.UpdateWithNoNewValues();
}
///
@@ -542,20 +369,9 @@ public async Task UpdateWithNoNewValues()
/// Check: check that GraphQL returns an appropriate exception to the user
///
[TestMethod]
- public async Task UpdateWithInvalidIdentifier()
+ public override async Task UpdateWithInvalidIdentifier()
{
- string graphQLMutationName = "editBook";
- string graphQLMutation = @"
- mutation {
- editBook(id: -1, title: ""Even Better Title"") {
- id
- title
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.EntityNotFound}");
+ await base.UpdateWithInvalidIdentifier();
}
///
@@ -565,58 +381,7 @@ public async Task UpdateWithInvalidIdentifier()
[TestMethod]
public async Task TestViolatingOneToOneRelashionShip()
{
- string graphQLMutationName = "insertWebsitePlacement";
- string graphQLMutation = @"
- mutation {
- insertWebsitePlacement(book_id: 1, price: 25) {
- id
- }
- }
- ";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(
- result.ToString(),
- message: PostgresDbExceptionParser.UNQIUE_VIOLATION_MESSAGE,
- statusCode: $"{DataGatewayException.SubStatusCodes.DatabaseOperationFailed}"
- );
- }
-
- ///
- /// Do: Inserts new book and return its id and title with their aliases(arbitrarily set by user while making request)
- /// Check: If book with the expected values of the new book is present in the database and
- /// if the mutation query has returned the correct information with Aliases where provided.
- ///
- [TestMethod]
- public async Task TestAliasSupportForGraphQLMutationQueryFields()
- {
- string graphQLMutationName = "insertBook";
- string graphQLMutation = @"
- mutation {
- insertBook(title: ""My New Book"", publisher_id: 1234) {
- book_id: id
- book_title: title
- }
- }
- ";
-
- string postgresQuery = @"
- SELECT to_jsonb(subq) AS DATA
- FROM
- (SELECT table0.id AS book_id,
- table0.title AS book_title
- FROM books AS table0
- WHERE id = 5001
- AND title = 'My New Book'
- AND publisher_id = 1234
- ORDER BY id
- LIMIT 1) AS subq
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLMutation, graphQLMutationName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestViolatingOneToOneRelashionShip(PostgresDbExceptionParser.UNQIUE_VIOLATION_MESSAGE);
}
#endregion
}
diff --git a/DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLPaginationTests.cs b/DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLPaginationTests.cs
index b2621defbb..3f703b4b2d 100644
--- a/DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLPaginationTests.cs
+++ b/DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLPaginationTests.cs
@@ -28,7 +28,7 @@ public static async Task InitializeTestFixture(TestContext context)
_runtimeConfigPath,
_queryEngine,
_mutationEngine,
- _metadataStoreProvider,
+ graphQLMetadataProvider: null,
new DocumentCache(),
new Sha256DocumentHashProvider(),
_sqlMetadataProvider);
diff --git a/DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLQueryTests.cs b/DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLQueryTests.cs
index 487de07115..f7d199127a 100644
--- a/DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLQueryTests.cs
+++ b/DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLQueryTests.cs
@@ -1,8 +1,5 @@
-using System.Text.Json;
using System.Threading.Tasks;
-using Azure.DataGateway.Service.Configurations;
using Azure.DataGateway.Service.Controllers;
-using Azure.DataGateway.Service.Exceptions;
using Azure.DataGateway.Service.Services;
using HotChocolate.Language;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -11,13 +8,10 @@ namespace Azure.DataGateway.Service.Tests.SqlTests
{
[TestClass, TestCategory(TestCategory.POSTGRESQL)]
- public class PostgreSqlGraphQLQueryTests : SqlTestBase
+ public class PostgreSqlGraphQLQueryTests : GraphQLQueryTestBase
{
#region Test Fixture Setup
- private static GraphQLService _graphQLService;
- private static GraphQLController _graphQLController;
-
///
/// Sets up test fixture for class, only to be run once per test run, as defined by
/// MSTest decorator.
@@ -33,7 +27,7 @@ public static async Task InitializeTestFixture(TestContext context)
_runtimeConfigPath,
_queryEngine,
_mutationEngine,
- _metadataStoreProvider,
+ graphQLMetadataProvider: null,
new DocumentCache(),
new Sha256DocumentHashProvider(),
_sqlMetadataProvider);
@@ -43,120 +37,24 @@ public static async Task InitializeTestFixture(TestContext context)
#endregion
#region Tests
-
- [TestMethod]
- public void TestConfigIsValid()
- {
- IConfigValidator configValidator = new SqlConfigValidator(_metadataStoreProvider, _graphQLService, _sqlMetadataProvider);
- configValidator.ValidateConfig();
- }
-
[TestMethod]
public async Task MultipleResultQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- id
- title
- }
- }";
string postgresQuery = $"SELECT json_agg(to_jsonb(table0)) FROM (SELECT id, title FROM books ORDER BY id) as table0 LIMIT 100";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await MultipleResultQuery(postgresQuery);
}
[TestMethod]
public async Task MultipleResultQueryWithVariables()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"query ($first: Int!) {
- getBooks(first: $first) {
- id
- title
- }
- }";
string postgresQuery = $"SELECT json_agg(to_jsonb(table0)) FROM (SELECT id, title FROM books ORDER BY id) as table0 LIMIT 100";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController, new() { { "first", 100 } });
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await MultipleResultQueryWithVariables(postgresQuery);
}
[TestMethod]
- public async Task MultipleResultJoinQuery()
+ public override async Task MultipleResultJoinQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- id
- title
- publisher_id
- publisher {
- id
- name
- }
- reviews(first: 100) {
- id
- content
- }
- authors(first: 100) {
- id
- name
- }
- }
- }";
- string postgresQuery = @"
- SELECT COALESCE(jsonb_agg(to_jsonb(subq8)), '[]') AS data
- FROM
- (SELECT table0.id AS id,
- table0.title AS title,
- table0.publisher_id AS publisher_id,
- table1_subq.data AS publisher,
- table2_subq.data AS reviews,
- table3_subq.data AS authors
- FROM books AS table0
- LEFT OUTER JOIN LATERAL
- (SELECT to_jsonb(subq5) AS data
- FROM
- (SELECT table1.id AS id,
- table1.name AS name
- FROM publishers AS table1
- WHERE table0.publisher_id = table1.id
- ORDER BY id
- LIMIT 1) AS subq5) AS table1_subq ON TRUE
- LEFT OUTER JOIN LATERAL
- (SELECT COALESCE(jsonb_agg(to_jsonb(subq6)), '[]') AS data
- FROM
- (SELECT table2.id AS id,
- table2.content AS content
- FROM reviews AS table2
- WHERE table0.id = table2.book_id
- ORDER BY id
- LIMIT 100) AS subq6) AS table2_subq ON TRUE
- LEFT OUTER JOIN LATERAL
- (SELECT COALESCE(jsonb_agg(to_jsonb(subq7)), '[]') AS data
- FROM
- (SELECT table3.id AS id,
- table3.name AS name
- FROM authors AS table3
- INNER JOIN book_author_link AS table4 ON table4.author_id = table3.id
- WHERE table0.id = table4.book_id
- ORDER BY id
- LIMIT 100) AS subq7) AS table3_subq ON TRUE
- WHERE 1 = 1
- ORDER BY id
- LIMIT 100) AS subq8
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.MultipleResultJoinQuery();
}
///
@@ -166,53 +64,62 @@ ORDER BY id
[TestMethod]
public async Task OneToOneJoinQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"query {
- getBooks {
- id
- website_placement {
- id
- price
- book {
- id
- }
- }
- }
- }";
-
string postgresQuery = @"
- SELECT COALESCE(jsonb_agg(to_jsonb(subq11)), '[]') AS data
+SELECT
+ to_jsonb(""subq12"") AS ""data""
+FROM
+ (
+ SELECT
+ ""table0"".""id"" AS ""id"",
+ ""table1_subq"".""data"" AS ""websiteplacement""
+ FROM
+ ""public"".""books"" AS ""table0""
+ LEFT OUTER JOIN LATERAL(
+ SELECT
+ to_jsonb(""subq11"") AS ""data""
+ FROM
+ (
+ SELECT
+ ""table1"".""id"" AS ""id"",
+ ""table1"".""price"" AS ""price"",
+ ""table2_subq"".""data"" AS ""books""
+ FROM
+ ""public"".""book_website_placements"" AS ""table1""
+ LEFT OUTER JOIN LATERAL(
+ SELECT
+ to_jsonb(""subq10"") AS ""data""
FROM
- (SELECT table0.id AS id,
- table1_subq.data AS website_placement
- FROM books AS table0
- LEFT OUTER JOIN LATERAL
- (SELECT to_jsonb(subq10) AS data
- FROM
- (SELECT table1.id AS id,
- table1.price AS price,
- table2_subq.data AS book
- FROM book_website_placements AS table1
- LEFT OUTER JOIN LATERAL
- (SELECT to_jsonb(subq9) AS data
- FROM
- (SELECT table2.id AS id
- FROM books AS table2
- WHERE table1.book_id = table2.id
- ORDER BY table2.id
- LIMIT 1) AS subq9) AS table2_subq ON TRUE
- WHERE table0.id = table1.book_id
- ORDER BY table1.id
- LIMIT 1) AS subq10) AS table1_subq ON TRUE
- WHERE 1 = 1
- ORDER BY table0.id
- LIMIT 100) AS subq11
+ (
+ SELECT
+ ""table2"".""id"" AS ""id""
+ FROM
+ ""public"".""books"" AS ""table2""
+ WHERE
+ ""table1"".""book_id"" = ""table2"".""id""
+ ORDER BY
+ ""table2"".""id"" Asc
+ LIMIT
+ 1
+ ) AS ""subq10""
+ ) AS ""table2_subq"" ON TRUE
+ WHERE
+ ""table1"".""book_id"" = ""table0"".""id""
+ ORDER BY
+ ""table1"".""id"" Asc
+ LIMIT
+ 1
+ ) AS ""subq11""
+ ) AS ""table1_subq"" ON TRUE
+ WHERE
+ ""table0"".""id"" = 1
+ ORDER BY
+ ""table0"".""id"" Asc
+ LIMIT
+ 1
+ ) AS ""subq12""
";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await OneToOneJoinQuery(postgresQuery);
}
///
@@ -221,89 +128,9 @@ ORDER BY table0.id
///
///
[TestMethod]
- public async Task DeeplyNestedManyToOneJoinQuery()
+ public override async Task DeeplyNestedManyToOneJoinQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- title
- publisher {
- name
- books(first: 100) {
- title
- publisher {
- name
- books(first: 100) {
- title
- publisher {
- name
- }
- }
- }
- }
- }
- }
- }";
-
- string postgresQuery = @"
- SELECT COALESCE(jsonb_agg(to_jsonb(subq11)), '[]') AS data
- FROM
- (SELECT table0.title AS title,
- table1_subq.data AS publisher
- FROM books AS table0
- LEFT OUTER JOIN LATERAL
- (SELECT to_jsonb(subq10) AS data
- FROM
- (SELECT table1.name AS name,
- table2_subq.data AS books
- FROM publishers AS table1
- LEFT OUTER JOIN LATERAL
- (SELECT COALESCE(jsonb_agg(to_jsonb(subq9)), '[]') AS data
- FROM
- (SELECT table2.title AS title,
- table3_subq.data AS publisher
- FROM books AS table2
- LEFT OUTER JOIN LATERAL
- (SELECT to_jsonb(subq8) AS data
- FROM
- (SELECT table3.name AS name,
- table4_subq.data AS books
- FROM publishers AS table3
- LEFT OUTER JOIN LATERAL
- (SELECT COALESCE(jsonb_agg(to_jsonb(subq7)), '[]') AS data
- FROM
- (SELECT table4.title AS title,
- table5_subq.data AS publisher
- FROM books AS table4
- LEFT OUTER JOIN LATERAL
- (SELECT to_jsonb(subq6) AS data
- FROM
- (SELECT table5.name AS name
- FROM publishers AS table5
- WHERE table4.publisher_id = table5.id
- ORDER BY id
- LIMIT 1) AS subq6) AS table5_subq ON TRUE
- WHERE table3.id = table4.publisher_id
- ORDER BY id
- LIMIT 100) AS subq7) AS table4_subq ON TRUE
- WHERE table2.publisher_id = table3.id
- ORDER BY id
- LIMIT 1) AS subq8) AS table3_subq ON TRUE
- WHERE table1.id = table2.publisher_id
- ORDER BY id
- LIMIT 100) AS subq9) AS table2_subq ON TRUE
- WHERE table0.publisher_id = table1.id
- ORDER BY id
- LIMIT 1) AS subq10) AS table1_subq ON TRUE
- WHERE 1 = 1
- ORDER BY id
- LIMIT 100) AS subq11
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.DeeplyNestedManyToManyJoinQuery();
}
///
@@ -312,79 +139,14 @@ ORDER BY id
///
///
[TestMethod]
- public async Task DeeplyNestedManyToManyJoinQuery()
+ public override async Task DeeplyNestedManyToManyJoinQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- title
- authors(first: 100) {
- name
- books(first: 100) {
- title
- authors(first: 100) {
- name
- }
- }
- }
- }
- }";
-
- string postgresQuery = @"
- SELECT COALESCE(jsonb_agg(to_jsonb(subq10)), '[]') AS data
- FROM
- (SELECT table0.title AS title,
- table1_subq.data AS authors
- FROM books AS table0
- LEFT OUTER JOIN LATERAL
- (SELECT COALESCE(jsonb_agg(to_jsonb(subq9)), '[]') AS data
- FROM
- (SELECT table1.name AS name,
- table2_subq.data AS books
- FROM authors AS table1
- INNER JOIN book_author_link AS table6 ON table6.author_id = table1.id
- LEFT OUTER JOIN LATERAL
- (SELECT COALESCE(jsonb_agg(to_jsonb(subq8)), '[]') AS data
- FROM
- (SELECT table2.title AS title,
- table3_subq.data AS authors
- FROM books AS table2
- INNER JOIN book_author_link AS table5 ON table5.book_id = table2.id
- LEFT OUTER JOIN LATERAL
- (SELECT COALESCE(jsonb_agg(to_jsonb(subq7)), '[]') AS data
- FROM
- (SELECT table3.name AS name
- FROM authors AS table3
- INNER JOIN book_author_link AS table4 ON table4.author_id = table3.id
- WHERE table2.id = table4.book_id
- ORDER BY id
- LIMIT 100) AS subq7) AS table3_subq ON TRUE
- WHERE table1.id = table5.author_id
- ORDER BY id
- LIMIT 100) AS subq8) AS table2_subq ON TRUE
- WHERE table0.id = table6.book_id
- ORDER BY id
- LIMIT 100) AS subq9) AS table1_subq ON TRUE
- WHERE 1 = 1
- ORDER BY id
- LIMIT 100) AS subq10
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.DeeplyNestedManyToManyJoinQuery();
}
[TestMethod]
public async Task QueryWithSingleColumnPrimaryKey()
{
- string graphQLQueryName = "getBook";
- string graphQLQuery = @"{
- getBook(id: 2) {
- title
- }
- }";
string postgresQuery = @"
SELECT to_jsonb(subq) AS data
FROM (
@@ -396,21 +158,12 @@ LIMIT 1
) AS subq
";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await QueryWithSingleColumnPrimaryKey(postgresQuery);
}
[TestMethod]
public async Task QueryWithMultileColumnPrimaryKey()
{
- string graphQLQueryName = "getReview";
- string graphQLQuery = @"{
- getReview(id: 568, book_id: 1) {
- content
- }
- }";
string postgresQuery = @"
SELECT to_jsonb(subq) AS data
FROM (
@@ -422,131 +175,31 @@ LIMIT 1
) AS subq
";
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await QueryWithMultileColumnPrimaryKey(postgresQuery);
}
[TestMethod]
- public async Task QueryWithNullResult()
+ public override async Task QueryWithNullResult()
{
- string graphQLQueryName = "getBook";
- string graphQLQuery = @"{
- getBook(id: -9999) {
- title
- }
- }";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
-
- SqlTestHelper.PerformTestEqualJsonStrings("null", actual);
+ await base.QueryWithNullResult();
}
///
/// Test if first param successfully limits list quries
///
[TestMethod]
- public async Task TestFirstParamForListQueries()
+ public override async Task TestFirstParamForListQueries()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 1) {
- title
- publisher {
- name
- books(first: 3) {
- title
- }
- }
- }
- }";
-
- string postgresQuery = @"
- SELECT COALESCE(jsonb_agg(to_jsonb(subq5)), '[]') AS DATA
- FROM
- (SELECT table0.title AS title,
- table1_subq.data AS publisher
- FROM books AS table0
- LEFT OUTER JOIN LATERAL
- (SELECT to_jsonb(subq4) AS DATA
- FROM
- (SELECT table1.name AS name,
- table2_subq.data AS books
- FROM publishers AS table1
- LEFT OUTER JOIN LATERAL
- (SELECT COALESCE(jsonb_agg(to_jsonb(subq3)), '[]') AS DATA
- FROM
- (SELECT table2.title AS title
- FROM books AS table2
- WHERE table1.id = table2.publisher_id
- ORDER BY id
- LIMIT 3) AS subq3) AS table2_subq ON TRUE
- WHERE table0.publisher_id = table1.id
- ORDER BY id
- LIMIT 1) AS subq4) AS table1_subq ON TRUE
- WHERE 1 = 1
- ORDER BY id
- LIMIT 1) AS subq5
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.TestFirstParamForListQueries();
}
///
/// Test if filter and filterOData param successfully filters the query results
///
[TestMethod]
- public async Task TestFilterAndFilterODataParamForListQueries()
+ public override async Task TestFilterAndFilterODataParamForListQueries()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(_filter: {id: {gte: 1} and: [{id: {lte: 4}}]}) {
- id
- publisher {
- books(first: 3, _filterOData: ""id ne 2"") {
- id
- }
- }
- }
- }";
-
- string postgresQuery = @"
- SELECT COALESCE(jsonb_agg(to_jsonb(subq12)), '[]') AS data
- FROM
- (SELECT table0.id AS id,
- table1_subq.data AS publisher
- FROM books AS table0
- LEFT OUTER JOIN LATERAL
- (SELECT to_jsonb(subq11) AS data
- FROM
- (SELECT table2_subq.data AS books
- FROM publishers AS table1
- LEFT OUTER JOIN LATERAL
- (SELECT COALESCE(jsonb_agg(to_jsonb(subq10)), '[]') AS data
- FROM
- (SELECT table2.id AS id
- FROM books AS table2
- WHERE (id != 2)
- AND table1.id = table2.publisher_id
- ORDER BY table2.id
- LIMIT 3) AS subq10) AS table2_subq ON TRUE
- WHERE table0.publisher_id = table1.id
- ORDER BY table1.id
- LIMIT 1) AS subq11) AS table1_subq ON TRUE
- WHERE ((id >= 1)
- AND (id <= 4))
- ORDER BY table0.id
- LIMIT 100) AS subq12
- ";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await base.TestFilterAndFilterODataParamForListQueries();
}
///
@@ -555,21 +208,9 @@ ORDER BY table0.id
[TestMethod]
public async Task TestQueryingTypeWithNullableIntFields()
{
- string graphQLQueryName = "getMagazines";
- string graphQLQuery = @"{
- getMagazines{
- id
- title
- issue_number
- }
- }";
-
string postgresQuery = $"SELECT json_agg(to_jsonb(table0)) FROM (SELECT id, title, \"issue_number\" FROM foo.magazines ORDER BY id) as table0 LIMIT 100";
+ await TestQueryingTypeWithNullableIntFields(postgresQuery);
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
}
///
@@ -578,20 +219,8 @@ public async Task TestQueryingTypeWithNullableIntFields()
[TestMethod]
public async Task TestQueryingTypeWithNullableStringFields()
{
- string graphQLQueryName = "getWebsiteUsers";
- string graphQLQuery = @"{
- getWebsiteUsers{
- id
- username
- }
- }";
-
string postgresQuery = $"SELECT json_agg(to_jsonb(table0)) FROM (SELECT id, username FROM website_users ORDER BY id) as table0 LIMIT 100";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestQueryingTypeWithNullableStringFields(postgresQuery);
}
///
@@ -602,19 +231,21 @@ public async Task TestQueryingTypeWithNullableStringFields()
[TestMethod]
public async Task TestAliasSupportForGraphQLQueryFields()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- book_id: id
- book_title: title
- }
- }";
- string postgresQuery = $"SELECT json_agg(to_jsonb(table0)) FROM (SELECT id as book_id, title as book_title FROM books ORDER BY id) as table0 LIMIT 100";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ string postgresQuery = @"
+SELECT
+ json_agg(to_jsonb(table0))
+FROM
+ (
+ SELECT
+ id as book_id,
+ title as book_title
+ FROM
+ books
+ ORDER BY
+ id
+ LIMIT 2
+ ) as table0";
+ await TestAliasSupportForGraphQLQueryFields(postgresQuery);
}
///
@@ -625,61 +256,44 @@ public async Task TestAliasSupportForGraphQLQueryFields()
[TestMethod]
public async Task TestSupportForMixOfRawDbFieldFieldAndAlias()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100) {
- book_id: id
- title
- }
- }";
- string postgresQuery = $"SELECT json_agg(to_jsonb(table0)) FROM (SELECT id as book_id, title as title FROM books ORDER BY id) as table0 LIMIT 100";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ string postgresQuery = @"
+ SELECT
+ json_agg(to_jsonb(table0))
+ FROM
+ (
+ SELECT
+ id as book_id,
+ title as title
+ FROM
+ books
+ ORDER BY
+ id
+ LIMIT
+ 2
+ ) as table0";
+ await TestSupportForMixOfRawDbFieldFieldAndAlias(postgresQuery);
}
///
/// Tests orderBy on a list query
///
+ [Ignore]
[TestMethod]
public async Task TestOrderByInListQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100 orderBy: {title: Desc}) {
- id
- title
- }
- }";
string postgresQuery = $"SELECT json_agg(to_jsonb(table0)) FROM (SELECT id, title FROM books ORDER BY title DESC, id ASC) as table0 LIMIT 100";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestOrderByInListQuery(postgresQuery);
}
///
/// Use multiple order options and order an entity with a composite pk
///
+ [Ignore]
[TestMethod]
public async Task TestOrderByInListQueryOnCompPkType()
{
- string graphQLQueryName = "getReviews";
- string graphQLQuery = @"{
- getReviews(orderBy: {content: Asc id: Desc}) {
- id
- content
- }
- }";
string postgresQuery = $"SELECT json_agg(to_jsonb(table0)) FROM (SELECT id, content FROM reviews ORDER BY content ASC, id DESC, book_id ASC) as table0 LIMIT 100";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestOrderByInListQueryOnCompPkType(postgresQuery);
}
///
@@ -687,43 +301,23 @@ public async Task TestOrderByInListQueryOnCompPkType()
/// meaning that null pk columns are included in the ORDER BY clause
/// as ASC by default while null non-pk columns are completely ignored
///
+ [Ignore]
[TestMethod]
public async Task TestNullFieldsInOrderByAreIgnored()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100 orderBy: {title: Desc id: null publisher_id: null}) {
- id
- title
- }
- }";
string postgresQuery = $"SELECT json_agg(to_jsonb(table0)) FROM (SELECT id, title FROM books ORDER BY title DESC, id ASC) as table0 LIMIT 100";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestNullFieldsInOrderByAreIgnored(postgresQuery);
}
///
/// Tests that an orderBy with only null fields results in default pk sorting
///
+ [Ignore]
[TestMethod]
public async Task TestOrderByWithOnlyNullFieldsDefaultsToPkSorting()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: 100 orderBy: {title: null}) {
- id
- title
- }
- }";
string postgresQuery = $"SELECT json_agg(to_jsonb(table0)) FROM (SELECT id, title FROM books ORDER BY id ASC) as table0 LIMIT 100";
-
- string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- string expected = await GetDatabaseResultAsync(postgresQuery);
-
- SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
+ await TestOrderByWithOnlyNullFieldsDefaultsToPkSorting(postgresQuery);
}
#endregion
@@ -731,33 +325,15 @@ public async Task TestOrderByWithOnlyNullFieldsDefaultsToPkSorting()
#region Negative Tests
[TestMethod]
- public async Task TestInvalidFirstParamQuery()
+ public override async Task TestInvalidFirstParamQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(first: -1) {
- id
- title
- }
- }";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.BadRequest}");
+ await base.TestInvalidFirstParamQuery();
}
[TestMethod]
- public async Task TestInvalidFilterParamQuery()
+ public override async Task TestInvalidFilterParamQuery()
{
- string graphQLQueryName = "getBooks";
- string graphQLQuery = @"{
- getBooks(_filterOData: ""INVALID"") {
- id
- title
- }
- }";
-
- JsonElement result = await GetGraphQLControllerResultAsync(graphQLQuery, graphQLQueryName, _graphQLController);
- SqlTestHelper.TestForErrorInGraphQLResponse(result.ToString(), statusCode: $"{DataGatewayException.SubStatusCodes.BadRequest}");
+ await base.TestInvalidFilterParamQuery();
}
#endregion
diff --git a/DataGateway.Service.Tests/SqlTests/SqlTestBase.cs b/DataGateway.Service.Tests/SqlTests/SqlTestBase.cs
index 1ee498e2e6..1f123413c2 100644
--- a/DataGateway.Service.Tests/SqlTests/SqlTestBase.cs
+++ b/DataGateway.Service.Tests/SqlTests/SqlTestBase.cs
@@ -39,7 +39,6 @@ public abstract class SqlTestBase
protected static IQueryBuilder _queryBuilder;
protected static IQueryEngine _queryEngine;
protected static IMutationEngine _mutationEngine;
- protected static GraphQLFileMetadataProvider _metadataStoreProvider;
protected static Mock _authorizationService;
protected static Mock _httpContextAccessor;
protected static DbExceptionParserBase _dbExceptionParser;
@@ -94,7 +93,6 @@ protected static async Task InitializeTestFixture(TestContext context, string te
break;
}
- _metadataStoreProvider = new GraphQLFileMetadataProvider(_runtimeConfigPath);
// Setup AuthorizationService to always return Authorized.
_authorizationService = new Mock();
_authorizationService.Setup(x => x.AuthorizeAsync(
@@ -108,14 +106,12 @@ protected static async Task InitializeTestFixture(TestContext context, string te
_httpContextAccessor.Setup(x => x.HttpContext.User).Returns(new ClaimsPrincipal());
_queryEngine = new SqlQueryEngine(
- _metadataStoreProvider,
_queryExecutor,
_queryBuilder,
_sqlMetadataProvider);
_mutationEngine =
new SqlMutationEngine(
_queryEngine,
- _metadataStoreProvider,
_queryExecutor,
_queryBuilder,
_sqlMetadataProvider);
@@ -140,7 +136,6 @@ protected static DefaultHttpContext GetRequestHttpContext(
IHeaderDictionary headers = null,
string bodyData = null)
{
-
DefaultHttpContext httpContext;
if (headers is not null)
{
@@ -360,10 +355,16 @@ protected static void ConfigureRestController(
///
/// Variables to be included in the GraphQL request. If null, no variables property is included in the request, to pass an empty object provide an empty dictionary
/// string in JSON format
- protected static async Task GetGraphQLResultAsync(string graphQLQuery, string graphQLQueryName, GraphQLController graphQLController, Dictionary variables = null)
+ protected virtual async Task GetGraphQLResultAsync(string graphQLQuery, string graphQLQueryName, GraphQLController graphQLController, Dictionary variables = null, bool failOnErrors = true)
{
JsonElement graphQLResult = await GetGraphQLControllerResultAsync(graphQLQuery, graphQLQueryName, graphQLController, variables);
Console.WriteLine(graphQLResult.ToString());
+
+ if (failOnErrors && graphQLResult.TryGetProperty("errors", out JsonElement errors))
+ {
+ Assert.Fail(errors.GetRawText());
+ }
+
JsonElement graphQLResultData = graphQLResult.GetProperty("data").GetProperty(graphQLQueryName);
// JsonElement.ToString() prints null values as empty strings instead of "null"
@@ -372,13 +373,13 @@ protected static async Task GetGraphQLResultAsync(string graphQLQuery, s
///
/// Sends graphQL query through graphQL service, consisting of gql engine processing (resolvers, object serialization)
- /// returning the result as a JsonDocument
+ /// returning the result as a JsonElement - the root of the JsonDocument.
///
///
///
///
/// Variables to be included in the GraphQL request. If null, no variables property is included in the request, to pass an empty object provide an empty dictionary
- /// JsonDocument
+ /// JsonElement
protected static async Task GetGraphQLControllerResultAsync(string query, string graphQLQueryName, GraphQLController graphQLController, Dictionary variables = null)
{
string graphqlQueryJson = variables == null ?
diff --git a/DataGateway.Service.Tests/SqlTests/SqlTestHelper.cs b/DataGateway.Service.Tests/SqlTests/SqlTestHelper.cs
index fc46eb7295..fb3f272ffb 100644
--- a/DataGateway.Service.Tests/SqlTests/SqlTestHelper.cs
+++ b/DataGateway.Service.Tests/SqlTests/SqlTestHelper.cs
@@ -123,7 +123,7 @@ public static void TestForErrorInGraphQLResponse(string response, string message
Assert.IsTrue(response.Contains("\"errors\""), "No error was found where error is expected.");
- if (message != null)
+ if (message is not null)
{
Console.WriteLine(response);
Assert.IsTrue(response.Contains(message), $"Message \"{message}\" not found in error");
diff --git a/DataGateway.Service/Azure.DataGateway.Service.csproj b/DataGateway.Service/Azure.DataGateway.Service.csproj
index 86d5e9a629..e9c57ead4e 100644
--- a/DataGateway.Service/Azure.DataGateway.Service.csproj
+++ b/DataGateway.Service/Azure.DataGateway.Service.csproj
@@ -57,14 +57,14 @@
-
+
-
+
-
+
-
+
diff --git a/DataGateway.Service/Configurations/CosmosConfigValidator.cs b/DataGateway.Service/Configurations/CosmosConfigValidator.cs
deleted file mode 100644
index 795f20dea9..0000000000
--- a/DataGateway.Service/Configurations/CosmosConfigValidator.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Azure.DataGateway.Service.Configurations
-{
- public class CosmosConfigValidator : IConfigValidator
- {
- public void ValidateConfig()
- {
- // TODO add any necessary validation
- }
- }
-}
diff --git a/DataGateway.Service/Configurations/IConfigValidator.cs b/DataGateway.Service/Configurations/IConfigValidator.cs
index f18f8f9135..15f5d93ffe 100644
--- a/DataGateway.Service/Configurations/IConfigValidator.cs
+++ b/DataGateway.Service/Configurations/IConfigValidator.cs
@@ -2,13 +2,13 @@ namespace Azure.DataGateway.Service.Configurations
{
///
- /// Validates the application logic config
+ /// Validates the runtime config.
///
public interface IConfigValidator
{
///
- /// Validate the application logic of the resolved config both within the
- /// config itself and in relation to the graphQL schema
+ /// Validate the runtime config both within the
+ /// config itself and in relation to the schema if available.
///
void ValidateConfig();
}
diff --git a/DataGateway.Service/Configurations/RuntimeConfigValidator.cs b/DataGateway.Service/Configurations/RuntimeConfigValidator.cs
index 843212d768..bcfc0f1760 100644
--- a/DataGateway.Service/Configurations/RuntimeConfigValidator.cs
+++ b/DataGateway.Service/Configurations/RuntimeConfigValidator.cs
@@ -23,6 +23,11 @@ public RuntimeConfigValidator(RuntimeConfig config)
_runtimeConfig = config;
}
+ ///
+ /// The driver for validation of the runtime configuration file.
+ ///
+ ///
+ ///
public void ValidateConfig()
{
if (_runtimeConfig is null)
@@ -41,10 +46,12 @@ public void ValidateConfig()
throw new NotSupportedException($"The Connection String should be provided.");
}
- if (string.IsNullOrEmpty(_runtimeConfig.DataSource.ResolverConfigFile)
- || !File.Exists(_runtimeConfig.DataSource.ResolverConfigFile))
+ if (_runtimeConfig.DatabaseType.Equals(DatabaseType.cosmos) &&
+ ((_runtimeConfig.CosmosDb is null) ||
+ (string.IsNullOrWhiteSpace(_runtimeConfig.CosmosDb.ResolverConfigFile)) ||
+ (!File.Exists(_runtimeConfig.CosmosDb.ResolverConfigFile))))
{
- throw new NotSupportedException("The resolver-config-file should be provided with the runtime config and must exist in the current directory.");
+ throw new NotSupportedException("The resolver-config-file should be provided with the runtime config and must exist in the current directory when database type is cosmosdb.");
}
ValidateAuthenticationConfig();
diff --git a/DataGateway.Service/Configurations/SqlConfigValidatorExceptions.cs b/DataGateway.Service/Configurations/SqlConfigValidatorExceptions.cs
deleted file mode 100644
index 406773ac96..0000000000
--- a/DataGateway.Service/Configurations/SqlConfigValidatorExceptions.cs
+++ /dev/null
@@ -1,1199 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Azure.DataGateway.Config;
-using Azure.DataGateway.Service.Exceptions;
-using Azure.DataGateway.Service.Models;
-using Azure.DataGateway.Service.Services;
-using HotChocolate;
-using HotChocolate.Language;
-using HotChocolate.Types;
-
-namespace Azure.DataGateway.Service.Configurations
-{
- /// This portion of the class
- /// holds the members of the SqlConfigValidator and the functions
- /// which run its validation logic.
- /// All config/schema related exceptions are thrown here
- /// Each function checks for only one thing and throws only one exception.
- public partial class SqlConfigValidator : IConfigValidator
- {
- private ResolverConfig _resolverConfig;
- private ISchema? _schema;
- private ISqlMetadataProvider _sqlMetadataProvider;
- private Stack _configValidationStack;
- private Stack _schemaValidationStack;
- private Dictionary _queries;
- private Dictionary _mutations;
- private Dictionary _types;
- private bool _graphQLTypesAreValidated;
-
- ///
- /// Sets the config and schema for the validator
- ///
- public SqlConfigValidator(
- IGraphQLMetadataProvider metadataStoreProvider,
- GraphQLService graphQLService,
- ISqlMetadataProvider sqlMetadataProvider)
- {
- _configValidationStack = MakeConfigPosition(Enumerable.Empty());
- _schemaValidationStack = MakeSchemaPosition(Enumerable.Empty());
- _types = new();
- _mutations = new();
- _queries = new();
- _graphQLTypesAreValidated = false;
-
- _resolverConfig = metadataStoreProvider.GetResolvedConfig();
- _sqlMetadataProvider = sqlMetadataProvider;
- _schema = graphQLService.Schema;
-
- if (_schema != null)
- {
- foreach (IDefinitionNode node in _schema.ToDocument().Definitions)
- {
- if (node is ObjectTypeDefinitionNode objectTypeDef)
- {
- if (objectTypeDef.Name.ToString() == "Mutation")
- {
- _mutations = GetObjTypeDefFields(objectTypeDef);
- }
- else if (objectTypeDef.Name.Value == "Query")
- {
- _queries = GetObjTypeDefFields(objectTypeDef);
- }
- else
- {
- _types.Add(objectTypeDef.Name.ToString(), objectTypeDef);
- }
- }
- }
- }
- }
-
- ///
- /// Validate that config has a GraphQLTypes element
- ///
- private void ValidateConfigHasGraphQLTypes()
- {
- if (_resolverConfig.GraphQLTypes == null || _resolverConfig.GraphQLTypes.Count == 0)
- {
- throw new ConfigValidationException(
- $"Config must have a non empty \"GraphQLTypes\" element.",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that config has a MutationResolvers element
- /// if the GraphQL schema has a mutations
- ///
- private void ValidateConfigHasMutationResolvers()
- {
- if (_resolverConfig.MutationResolvers == null || _resolverConfig.MutationResolvers.Count == 0)
- {
- throw new ConfigValidationException(
- $"Config must have a non empty \"MutationResolvers\" element to resolve " +
- "GraphQL mutations.",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that the config has "MutationResolvers" element
- /// Called when there are no mutations in the schema
- ///
- private void ValidateNoMutationResolvers()
- {
- if (_resolverConfig.MutationResolvers != null)
- {
- throw new ConfigValidationException(
- "Config doesn't need a \"MutationResolvers\" element. No mutations in the schema.",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that the GraphQLType in the config match the types in the schema
- ///
- private void ValidateTypesMatchSchemaTypes(Dictionary types)
- {
- IEnumerable unmatchedConfigTypes = types.Keys.Except(_types.Keys);
- IEnumerable unmatchedSchemaTypes = _types.Keys.Except(types.Keys);
-
- if (unmatchedConfigTypes.Any() || unmatchedSchemaTypes.Any())
- {
- string unmatchedConfigTypesMessage =
- unmatchedConfigTypes.Any() ?
- $"Types [{string.Join(", ", unmatchedConfigTypes)}] are not matched in the schema. " :
- string.Empty;
-
- string unmatchedSchemaTypesMessage =
- unmatchedSchemaTypes.Any() ?
- $"Schema types [{string.Join(", ", unmatchedSchemaTypes)}] are not matched in the config." :
- string.Empty;
-
- throw new ConfigValidationException(
- $"Mismatch between types in the config and in the schema. " +
- unmatchedConfigTypesMessage +
- unmatchedSchemaTypesMessage,
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that config fields are matched to a schema field and that
- /// there is no non scalar schema field not matched to a config field
- ///
- private void ValidateConfigFieldsMatchSchemaFields(
- Dictionary configFields,
- Dictionary schemaFields)
- {
- IEnumerable unmatchedConfigFields = configFields.Keys.Except(schemaFields.Keys);
-
- // note that scalar fields can be matched to table columns so they don't
- // need to match a config field
- Dictionary nonScalarFields = GetNonScalarFields(schemaFields);
- IEnumerable unmatchedNonScalarSchemaFields = nonScalarFields.Keys.Except(configFields.Keys);
-
- if (unmatchedConfigFields.Any() || unmatchedNonScalarSchemaFields.Any())
- {
- string unmatchedConFieldsMessage =
- unmatchedConfigFields.Any() ?
- $"[{string.Join(", ", unmatchedConfigFields)}] fields don't match any field in the schema. "
- : string.Empty;
- string unmatchedSchFieldsMessage =
- unmatchedNonScalarSchemaFields.Any() ?
- $"[{string.Join(", ", unmatchedNonScalarSchemaFields)}] schema fields are not matched by any config fields."
- : string.Empty;
-
- throw new ConfigValidationException(
- "Mismatch between fields and the schema fields in " +
- PrettyPrintValidationStack(_schemaValidationStack) + ". " +
- unmatchedConFieldsMessage +
- unmatchedSchFieldsMessage,
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that the fields of a schema type no have invalid return types
- ///
- ///
- /// Nested list types and lists of *Connection types are considered invalid
- ///
- private void ValidateSchemaFieldsReturnTypes(Dictionary fieldDefinitions)
- {
- List nestedListFields = new();
- List listOfPgTypeFields = new();
-
- foreach (KeyValuePair nameFieldPair in fieldDefinitions)
- {
- string fieldName = nameFieldPair.Key;
- FieldDefinitionNode field = nameFieldPair.Value;
-
- if (IsNestedListType(field.Type))
- {
- nestedListFields.Add(fieldName);
- }
- else if (IsListOfPaginationType(field.Type))
- {
- listOfPgTypeFields.Add(fieldName);
- }
- }
-
- if (nestedListFields.Any() || listOfPgTypeFields.Any())
- {
- string nestedListMessage =
- nestedListFields.Any() ?
- $"Fields [{string.Join(", ", nestedListFields)}] must not have a nested " +
- "list as a return type. "
- : string.Empty;
-
- string listOfPgTypeMessage =
- listOfPgTypeFields.Any() ?
- $"Fields [{string.Join(", ", listOfPgTypeFields)}] must have a list of " +
- "*Connection types as a return type."
- : string.Empty;
-
- throw new ConfigValidationException(
- "Found fields with invalid return types. " +
- nestedListMessage +
- listOfPgTypeMessage,
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate pagination type has required fields
- ///
- private void ValidatePaginationTypeHasRequiredFields(
- Dictionary typeFields,
- List requiredFields)
- {
- IEnumerable missingRequiredFields = requiredFields.Except(typeFields.Keys);
- IEnumerable extraFields = typeFields.Keys.Except(requiredFields);
- if (missingRequiredFields.Any() || extraFields.Any())
- {
- throw new ConfigValidationException(
- $"Pagination type must have only [{string.Join(", ", requiredFields)}] fields.",
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate that the pagination required fields have no arguments
- ///
- private void ValidatePaginationFieldsHaveNoArguments(
- Dictionary typeFields,
- List paginationFieldNames)
- {
- List fieldsWithArguments = new();
- foreach (string fieldName in paginationFieldNames)
- {
- if (GetArgumentsFromField(typeFields[fieldName]).Count > 0)
- {
- fieldsWithArguments.Add(fieldName);
- }
- }
-
- if (fieldsWithArguments.Any())
- {
- throw new ConfigValidationException(
- $"[{string.Join(", ", fieldsWithArguments)}] field of a pagination type must not have arguments.",
- _schemaValidationStack);
- }
- }
-
- ///
- /// Validate the type of "items" field in a Pagination type
- ///
- private void ValidateItemsFieldType(FieldDefinitionNode itemsField)
- {
- ITypeNode itemsType = itemsField.Type;
- if (!IsListType(itemsType) ||
- !IsInnerTypeCustom(itemsType) ||
- IsNullableType(itemsType) ||
- AreListElementsNullable(itemsType) ||
- IsPaginationType(InnerType(itemsType)))
- {
- throw new ConfigValidationException(
- "\"items\" must return a non nullable list type of non nullable custom type " +
- "\"[CustomType!]!\" where CustomType is not a pagination type.",
- _schemaValidationStack);
- }
- }
-
- ///
- /// Validate the type of "endCursor" field in a Pagination type
- ///
- private void ValidateEndCursorFieldType(FieldDefinitionNode endCursorField)
- {
- ITypeNode endCursorFieldType = endCursorField.Type;
- if (IsListType(endCursorFieldType) ||
- InnerTypeStr(endCursorFieldType) != "String" ||
- endCursorFieldType.IsNonNullType())
- {
- throw new ConfigValidationException(
- "\"endCursor\" must return a nullable \"String\" type.",
- _schemaValidationStack);
- }
- }
-
- ///
- /// Validate the type of "hasNextPage" field in a Pagination type
- ///
- private void ValidateHasNextPageFieldType(FieldDefinitionNode hasNextPageField)
- {
- ITypeNode hasNextPageFieldType = hasNextPageField.Type;
- if (IsListType(hasNextPageFieldType) ||
- InnerTypeStr(hasNextPageFieldType) != "Boolean" ||
- IsNullableType(hasNextPageFieldType))
- {
- throw new ConfigValidationException(
- "\"hasNextPage\" must return a non nullable \"Boolean!\" type.",
- _schemaValidationStack);
- }
- }
-
- ///
- /// Validate pagination type has correct name
- ///
- private void ValidatePaginationTypeName(string paginationTypeName)
- {
- FieldDefinitionNode itemsField = GetTypeFields(paginationTypeName)["items"];
- string paginationUnderlyingType = InnerTypeStr(itemsField.Type);
- string expectedTypeName = $"{paginationUnderlyingType}Connection";
- if (paginationTypeName != expectedTypeName)
- {
- throw new ConfigValidationException(
- $"Pagination type on \"{paginationUnderlyingType}\" must be called \"{expectedTypeName}\".",
- _schemaValidationStack);
- }
- }
-
- ///
- /// Validate graphQLType has table
- ///
- private void ValidateGraphQLTypeHasTable(GraphQLType type)
- {
- if (string.IsNullOrEmpty(type.Table))
- {
- throw new ConfigValidationException(
- "This type must contain a non empty string \"Table\" element.",
- _configValidationStack);
- }
- }
-
- ///
- /// Validate that type does not share an underlying table with any other type
- ///
- private void ValidateGQLTypeTableIsUnique(GraphQLType type, Dictionary tableToType)
- {
- if (tableToType.ContainsKey(type.Table))
- {
- throw new ConfigValidationException(
- $"SystemType shares underlying table \"{type.Table}\" with other type " +
- $"\"{tableToType[type.Table]}\". All underlying type tables must be unique.",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate the scalar fields and table columns match one to one
- ///
- ///
- /// Each table column and scalar field should serve a purpouse
- /// So each table column should either:
- ///
- /// - match in name and type to a field
- /// - be part of the primary or foreign key
- ///
- /// Each scalar field should either:
- ///
- /// - match a table column in name and type
- /// - match a GraphQLType.Field
- ///
- ///
- private void ValidateTableColumnsMatchScalarFields(string tableName, string typeName, Stack tableColumnPosition)
- {
- TableDefinition table = GetTableWithName(typeName);
- Dictionary tableColumns = table.Columns;
- Dictionary scalarFields = GetScalarFields(GetTypeFields(typeName));
-
- IEnumerable unmatchedTableColumns = tableColumns.Keys
- .Except(scalarFields.Keys)
- .Except(GetPkAndFkColumns(table));
-
- IEnumerable unmatchedScalarFields = scalarFields.Keys
- .Except(tableColumns.Keys)
- .Except(GetConfigFieldsForGqlType(_types[typeName]));
-
- if (unmatchedTableColumns.Any() || unmatchedScalarFields.Any())
- {
- string unmatchedFieldsMessage =
- unmatchedScalarFields.Any() ?
- $"Fields [{string.Join(", ", unmatchedScalarFields)}] are neither matched to columns nor " +
- $"match to type fields in the config. " :
- string.Empty;
-
- string unmatchedColumnsMessage =
- unmatchedTableColumns.Any() ?
- $"Columns [{string.Join(", ", unmatchedTableColumns)}] are neither matched to fields nor " +
- $"serve as primary key or foreign key columns in table \"{tableName}\"." :
- string.Empty;
-
- throw new ConfigValidationException(
- "Mismatch between scalar fields and table columns in " +
- $"{PrettyPrintValidationStack(tableColumnPosition)}. " +
- unmatchedColumnsMessage +
- unmatchedFieldsMessage,
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate the scalar fields and table columns that match in name match in type
- ///
- private void ValidateTableColumnTypesMatchScalarFieldTypes(string typeName, Stack tableColumnPosition)
- {
- TableDefinition table = GetTableWithName(typeName);
- Dictionary tableColumns = table.Columns;
- Dictionary typeFields = GetTypeFields(typeName);
-
- IEnumerable matchedColumnAndFieldNames = tableColumns.Keys.Intersect(typeFields.Keys);
-
- List mismatchedFieldColumnTypeMessages = new();
-
- foreach (string matchedName in matchedColumnAndFieldNames)
- {
- Type columnType = tableColumns[matchedName].SystemType;
- ITypeNode fieldType = typeFields[matchedName].Type;
-
- if (!GraphQLTypeEqualsColumnType(fieldType, columnType))
- {
- mismatchedFieldColumnTypeMessages.Add(
- $"Column \"{matchedName}\" with type \"{columnType}\" doesn't match field " +
- $"\"{matchedName}\" with type \"{fieldType.ToString()}\".");
- }
- }
-
- if (mismatchedFieldColumnTypeMessages.Any())
- {
- throw new ConfigValidationException(
- "There are mismatched types between some type fields and some columns of the types's underlying table in " +
- $"{PrettyPrintValidationStack(tableColumnPosition)}. {string.Join(" ", mismatchedFieldColumnTypeMessages)}",
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate that the scalar fields that match table columns do
- /// not have arguments
- ///
- private void ValidateScalarFieldsMatchingTableColumnsHaveNoArgs(
- string typeName,
- Stack tableColumnsPosition
- )
- {
- Dictionary scalarFields = GetScalarFields(GetTypeFields(typeName));
- IEnumerable fieldsWithArgs =
- scalarFields.Keys.Where(fieldName => GetArgumentsFromField(scalarFields[fieldName]).Count > 0)
- .Intersect(GetTableWithName(typeName).Columns.Keys);
-
- if (fieldsWithArgs.Any())
- {
- throw new ConfigValidationException(
- $"Fields [{string.Join(", ", fieldsWithArgs)}] which match with table columns " +
- $"in {PrettyPrintValidationStack(tableColumnsPosition)} should not have any arguments.",
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate the nullability of scalar type fields which match table columns
- ///
- private void ValidateScalarFieldsMatchingTableColumnsNullability(
- string typeName,
- Stack tableColumnsPosition)
- {
- Dictionary scalarFields = GetScalarFields(GetTypeFields(typeName));
- IEnumerable nullableScalarFields =
- scalarFields.Keys.Where(fieldName => IsNullableType(scalarFields[fieldName].Type));
- IEnumerable notNullableScalarFields = scalarFields.Keys.Except(nullableScalarFields);
-
- TableDefinition table = GetTableWithName(typeName);
- IEnumerable nullableTableColumns =
- table.Columns.Keys.Where(colName => table.Columns[colName].IsNullable);
- IEnumerable notNullableTableColumns = table.Columns.Keys.Except(nullableTableColumns);
-
- IEnumerable shouldBeNullable = notNullableScalarFields.Intersect(nullableTableColumns);
- IEnumerable shouldBeNotNullable = nullableScalarFields.Intersect(notNullableTableColumns);
-
- if (shouldBeNullable.Any() || shouldBeNotNullable.Any())
- {
- string shouldBeNullableMessage = shouldBeNullable.Any() ?
- $"The fields [{string.Join(", ", shouldBeNullable)}] should be nullable. " :
- string.Empty;
- string shouldBeNotNullableMessage = shouldBeNotNullable.Any() ?
- $"The fields [{string.Join(", ", shouldBeNotNullable)}] should be not nullable." :
- string.Empty;
-
- throw new ConfigValidationException(
- $"Mismatch of field nullability with table columns in {PrettyPrintValidationStack(tableColumnsPosition)}." +
- shouldBeNullableMessage +
- shouldBeNotNullableMessage,
- _schemaValidationStack);
- }
- }
-
- ///
- /// Validate that type has no fields which return a custom type
- ///
- ///
- /// Called if config type has no fields
- ///
- private void ValidateNoFieldsWithInnerCustomType(string typeName, Dictionary fields)
- {
- IEnumerable fieldsWithCustomTypes = fields.Keys.Where(fieldName => IsInnerTypeCustom(fields[fieldName].Type));
-
- if (fieldsWithCustomTypes.Any())
- {
- throw new ConfigValidationException(
- $"SystemType \"{typeName}\" has no fields to resolve schema fields which return custom types [" +
- string.Join(", ", fieldsWithCustomTypes) + "].",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate if argument names match required arguments
- ///
- private void ValidateFieldArguments(
- IEnumerable fieldArgumentNames,
- IEnumerable? requiredArguments = null,
- IEnumerable? optionalArguments = null)
- {
- IEnumerable empty = Enumerable.Empty();
- IEnumerable missingArguments = requiredArguments?.Except(fieldArgumentNames) ?? empty;
- IEnumerable extraArguments = fieldArgumentNames.Except(requiredArguments ?? empty)
- .Except(optionalArguments ?? empty);
-
- if (missingArguments.Any() || extraArguments.Any())
- {
- string missingArgsMessage =
- missingArguments.Any() ?
- $"Missing [{string.Join(", ", missingArguments)}] arguments. "
- : string.Empty;
- string extraArgsMessage =
- extraArguments.Any() ?
- $"Arguments [{string.Join(", ", extraArguments)}] are not appropriate for this field."
- : string.Empty;
-
- throw new ConfigValidationException(
- $"Field has invalid arguments." +
- missingArgsMessage +
- extraArgsMessage,
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate that the argument type of the fields matches what what is expected
- ///
- private void ValidateFieldArgumentTypes(
- Dictionary fieldArguments,
- Dictionary> expectedArguments)
- {
- List mismatchMessages = new();
- foreach (KeyValuePair nameArgumentPair in fieldArguments)
- {
- string argName = nameArgumentPair.Key;
- InputValueDefinitionNode argument = nameArgumentPair.Value;
-
- if (!expectedArguments[argName].Contains(argument.Type.ToString()))
- {
- mismatchMessages.Add(
- $"Argument \"{argName}\" has unexpected type \"{argument.Type.ToString()}\". " +
- $"It's type can only be one of [{string.Join(", ", expectedArguments[argName])}].");
- }
- }
-
- if (mismatchMessages.Any())
- {
- throw new ConfigValidationException(
- "Unexpected arguments types found. " + string.Join(" ", mismatchMessages),
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate that the field has a valid relationship type
- ///
- private void ValidateRelationshipType(GraphQLField field, List validRelationshipTypes)
- {
- if (!validRelationshipTypes.Contains(field.RelationshipType))
- {
- throw new ConfigValidationException(
- $"{field.RelationshipType} is not a valid/supported relationship type.",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate the nullability of the return type of the field
- ///
- private void ValidateReturnTypeNullability(FieldDefinitionNode field, bool returnsNullable)
- {
- if (field.Type.IsNonNullType() == returnsNullable)
- {
- string label = returnsNullable ? "nullable" : "non nullable";
- throw new ConfigValidationException(
- $"The type returned from this field must be {label}.",
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate that field does return a pagination type
- ///
- private void ValidateReturnTypeNotPagination(GraphQLField field, FieldDefinitionNode fieldDefinition)
- {
- if (IsPaginationType(fieldDefinition.Type))
- {
- throw new ConfigValidationException(
- $"{field.RelationshipType} field must not return a pagination " +
- $"type \"{fieldDefinition.Type.ToString()}\".",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that the field returns a list of custom type
- ///
- private void ValidateFieldReturnsListOfCustomType(
- FieldDefinitionNode fieldDefinition,
- bool listNullabe = true,
- bool listElemsNullable = true)
- {
- ITypeNode type = fieldDefinition.Type;
- if (!IsListOfCustomType(type) ||
- listNullabe == type.IsNonNullType() ||
- listElemsNullable != AreListElementsNullable(type))
- {
- string listLabel = listNullabe ? "nullable" : "non nullable";
- string elemLabel = listElemsNullable ? "nullable" : "non nullable";
-
- throw new ConfigValidationException(
- $"Field must return a {listLabel} list of {elemLabel} custom type.",
- _schemaValidationStack);
- }
- }
-
- ///
- /// Validate that the field returns a custom type
- ///
- private void ValidateFieldReturnsCustomType(FieldDefinitionNode fieldDefinition, bool typeNullable = true)
- {
- ITypeNode type = fieldDefinition.Type;
- if (!IsCustomType(type) || typeNullable == type.IsNonNullType())
- {
- string typeLabel = typeNullable ? "nullable" : "non nullable";
- throw new ConfigValidationException(
- $"Field must return a {typeLabel} custom type.",
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Make sure the field has no association table
- ///
- private void ValidateNoAssociationTable(GraphQLField field)
- {
- if (!string.IsNullOrEmpty(field.AssociativeTable))
- {
- throw new ConfigValidationException(
- $"Cannot have Associative Table in {field.RelationshipType} field.",
- _configValidationStack);
- }
- }
-
- ///
- /// Make sure the field has an association table
- ///
- private void ValidateHasAssociationTable(GraphQLField field)
- {
- if (string.IsNullOrEmpty(field.AssociativeTable))
- {
- throw new ConfigValidationException(
- $"Must have a non empty string Associative Table in {field.RelationshipType} field.",
- _configValidationStack);
- }
- }
-
- ///
- /// Validates that field has only left foreign key
- ///
- private void ValidateHasOnlyLeftForeignKey(GraphQLField field)
- {
- if (!HasLeftForeignKey(field) || HasRightForeignKey(field))
- {
- throw new ConfigValidationException(
- $"{field.RelationshipType} field must have only left foreign key.",
- _configValidationStack);
- }
- }
-
- ///
- /// Validates that field has only right foreign key
- ///
- private void ValidateHasOnlyRightForeignKey(GraphQLField field)
- {
- if (HasLeftForeignKey(field) || !HasRightForeignKey(field))
- {
- throw new ConfigValidationException(
- $"{field.RelationshipType} field must have only right foreign key.",
- _configValidationStack);
- }
- }
-
- ///
- /// Validates that field has left foreign key or right foreign key
- ///
- private void ValidateHasLeftOrRightForeignKey(GraphQLField field)
- {
- if (!(HasLeftForeignKey(field) || HasRightForeignKey(field)))
- {
- throw new ConfigValidationException(
- $"{field.RelationshipType} field must have a left foreign key or right foreign key.",
- _configValidationStack);
- }
- }
-
- ///
- /// Validates that the field has both left and right foreign keys
- ///
- private void ValidateHasBothLeftAndRightFK(GraphQLField field)
- {
- if (!HasLeftForeignKey(field) || !HasRightForeignKey(field))
- {
- throw new ConfigValidationException(
- $"{field.RelationshipType} field must have both left and right foreign keys.",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that the left foreign key of the field is a foreign key of the
- /// table of the type that this field belongs to
- ///
- private void ValidateLeftForeignKey(GraphQLField field, string type)
- {
- string typeTable = GetTypeTable(type);
- if (!TableContainsForeignKey(type, field.LeftForeignKey))
- {
- throw new ConfigValidationException(
- $"Left foreign key in {field.RelationshipType} field, must be a foreign key " +
- $"of the table \"{typeTable}\", which is the underlying table of the type \"{type}\" " +
- "that contains this field.",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that the right foreign key of the field is a foreign key of the
- /// table of the type that this field returns
- ///
- private void ValidateRightForeignKey(GraphQLField field, string returnedType)
- {
- string returnedTypeTable = GetTypeTable(returnedType);
- if (!TableContainsForeignKey(returnedType, field.RightForeignKey))
- {
- throw new ConfigValidationException(
- $"Right foreign key in {field.RelationshipType} field, must be a foreign key " +
- $"of the table \"{returnedTypeTable}\", which is the underlying table of the type " +
- $"\"{returnedType}\" that this field returns.",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that the reference table of the right foreign key refers to type table
- ///
- private void ValidateRightFkRefTableIsTypeTable(ForeignKeyDefinition rightFk, string type)
- {
- string typeTable = GetTypeTable(type);
- if (rightFk.ReferencedTable != typeTable)
- {
- throw new ConfigValidationException(
- $"Right foreign key's referenced table \"{rightFk.ReferencedTable}\" does not " +
- $"refer to the type table \"{typeTable}\" of type \"{typeTable}\".",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that the reference table of the left foreign key refers to the returned type's table
- ///
- private void ValidateLeftFkRefTableIsReturnedTypeTable(ForeignKeyDefinition rightFk, string returnedType)
- {
- string returnedTypeTable = GetTypeTable(returnedType);
- if (rightFk.ReferencedTable != returnedTypeTable)
- {
- throw new ConfigValidationException(
- $"Left foreign key's referenced table \"{rightFk.ReferencedTable}\" does not refer " +
- $"to the type table \"{returnedTypeTable}\" of the returned type \"{returnedTypeTable}\".",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate the left and right foreign keys for many to many field
- ///
- private void ValidateLeftAndRightFkForM2MField(GraphQLField field)
- {
- if (!TableContainsForeignKey(field.AssociativeTable, field.LeftForeignKey) ||
- !TableContainsForeignKey(field.AssociativeTable, field.RightForeignKey))
- {
- throw new ConfigValidationException(
- $"Both the left and right foreign key in {field.RelationshipType} field " +
- $"must be foreign keys of the field's associative table \"{field.AssociativeTable}\".",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that Config.GraphQLTypes has already been validated
- ///
- private void ValidateGraphQLTypesIsValidated()
- {
- if (!IsGraphQLTypesValidated())
- {
- throw new NotSupportedException(
- "Current validation functions requires that the Config > GraphQLTypes is validated first.");
- }
- }
-
- ///
- /// Validate that none of the mutation resolver ids are missing
- ///
- private void ValidateNoMissingIds(IEnumerable ids)
- {
- int missingIdsCount = ids.Count(id => string.IsNullOrEmpty(id));
-
- if (missingIdsCount > 0)
- {
- throw new ConfigValidationException(
- $"{missingIdsCount} mutation ids missing. All mutation resolvers must " +
- "have a non empty string \"Id\" element.",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that all mutation resolver ids are unique
- ///
- private void ValidateNoDuplicateMutIds(IEnumerable ids)
- {
- IEnumerable duplicateIds = GetDuplicates(ids);
-
- if (duplicateIds.Any())
- {
- throw new ConfigValidationException(
- "All mutation resolver ids must be unique." +
- $"[{string.Join(", ", duplicateIds)}] ids appear multiple times.",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that mutation resolvers and mutations in the schema are matched one-to-one
- ///
- private void ValidateMutationResolversMatchSchema(IEnumerable ids)
- {
- IEnumerable unmatchedMutations = _mutations.Keys.Except(ids);
- IEnumerable extraIds = ids.Except(_mutations.Keys);
-
- if (unmatchedMutations.Any() || extraIds.Any())
- {
- string unmatchedMutationsMessage =
- unmatchedMutations.Any() ?
- $"[{string.Join(", ", unmatchedMutations)}] mutations in the GraphQL schema " +
- "do not have equivalent resolvers. "
- : string.Empty;
- string extraIdsMessage =
- extraIds.Any() ?
- $"Resolvers with ids [{string.Join(", ", extraIds)}] do not resolver any mutation."
- : string.Empty;
-
- throw new ConfigValidationException(
- $"Mismatch between mutation resolvers and GraphQL mutations. " +
- unmatchedMutationsMessage +
- extraIdsMessage,
- _configValidationStack);
- }
- }
-
- ///
- /// Validate mutaiton resolver has a "Table" element
- ///
- private void ValidateMutResolverHasTable(MutationResolver resolver)
- {
- if (string.IsNullOrEmpty(resolver.Table))
- {
- throw new ConfigValidationException(
- "Mutation resolver must have a non empty string \"Table\" element.",
- _configValidationStack);
- }
- }
-
- ///
- /// Check if the mutation resolver operation is a valid/supported for sql (pg and mssql)
- ///
- private void ValidateMutResolverOperation(Operation op, List supportedOperations)
- {
- if (!supportedOperations.Contains(op))
- {
- throw new ConfigValidationException(
- $"Mutation resolver operation type \"{op}\" is not valid for Sql. " +
- $"Only supported operations are [{string.Join(", ", supportedOperations)}].",
- _configValidationStack
- );
- }
- }
-
- ///
- /// Validate that the mutation does not return a list type
- ///
- private void ValidateMutReturnTypeIsNotListType(FieldDefinitionNode mutation)
- {
- if (IsListType(mutation.Type))
- {
- throw new ConfigValidationException(
- "Mutation must not return a list type.",
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate the return type of the mutation matches the mutation resolver table
- ///
- private void ValidateMutReturnTypeMatchesTable(string resolverTable, FieldDefinitionNode mutation)
- {
- if (resolverTable != GetTypeTable(InnerTypeStr(mutation.Type)))
- {
- throw new ConfigValidationException(
- $"Mutation return type {mutation.Type.ToString()} does not match the type " +
- $"associated with this mutation's resolver table \"{resolverTable}\".",
- _schemaValidationStack);
- }
- }
-
- ///
- /// Validate that all parameters of mutation match a colum in the mutation table
- ///
- private void ValidateMutArgsMatchTableColumns(
- string tableName,
- TableDefinition table,
- Dictionary mutArguments)
- {
- Dictionary arguments = mutArguments;
- IEnumerable nonColumnArgs = arguments.Keys.Except(table.Columns.Keys);
- if (nonColumnArgs.Any())
- {
- throw new ConfigValidationException(
- $"Arguments [{string.Join(", ", nonColumnArgs)}] are not valid columns of the table " +
- $"\"{tableName}\" associated with this mutation.",
- _schemaValidationStack);
- }
- }
-
- ///
- /// Validate mutation argument types match table column types
- ///
- private void ValidateMutArgTypesMatchTableColTypes(
- string tableName,
- TableDefinition table,
- Dictionary mutArguments)
- {
- List typeMismatchMessages = new();
- foreach (KeyValuePair nameArgPair in mutArguments)
- {
- string argName = nameArgPair.Key;
- InputValueDefinitionNode argument = nameArgPair.Value;
-
- ColumnDefinition matchedCol = table.Columns[argName];
-
- if (!GraphQLTypeEqualsColumnType(argument.Type, matchedCol.SystemType))
- {
- typeMismatchMessages.Add(
- $"Argument \"{argName}\" with type \"{InnerTypeStr(argument.Type)}\" does not match " +
- $"the type of \"{argName}\" in table \"{tableName}\" with type \"{matchedCol.SystemType}\"");
- }
- }
-
- if (typeMismatchMessages.Any())
- {
- throw new ConfigValidationException(
- $"SystemType mismatch between mutation arguments and columns of mutation table. " +
- string.Join(" ", typeMismatchMessages),
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate that the arguments of the insert mutation are properly set to nullable or not
- ///
- ///
- /// In the current implemetation, none of the arguments in insert mutations
- /// are nullable, this may change when the projects starts to provide nullable
- /// type support in the database.
- ///
- private void ValidateArgNullabilityInInsertMut(
- TableDefinition table,
- Dictionary mutArguments)
- {
- List shouldBeNullable = new();
- List shouldBeNonNullable = new();
- foreach (KeyValuePair nameArgPair in mutArguments)
- {
- string argName = nameArgPair.Key;
- InputValueDefinitionNode argument = nameArgPair.Value;
-
- bool isNullable = table.Columns[argName].IsNullable;
- if (isNullable && argument.Type.IsNonNullType())
- {
- shouldBeNullable.Add(argName);
- }
- else if (!isNullable && IsNullableType(argument.Type))
- {
- shouldBeNonNullable.Add(argName);
- }
- }
-
- if (shouldBeNullable.Any() || shouldBeNonNullable.Any())
- {
- string shouldBeNullableMsg =
- shouldBeNullable.Any() ?
- $"Arguments [{string.Join(", ", shouldBeNullable)}] must be nullable. "
- : string.Empty;
-
- string shouldBeNonNullableMsg =
- shouldBeNonNullable.Any() ?
- $"Arguments [{string.Join(", ", shouldBeNonNullable)}] must not be nullable."
- : string.Empty;
-
- throw new ConfigValidationException(
- $"Insert mutation arguments have incorrent nullability. " +
- shouldBeNullableMsg +
- shouldBeNonNullableMsg,
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate there insert mutation has the correct args
- ///
- ///
- /// In the current implemetation,
- /// all but autogenerated columns must be added as arguments
- ///
- private void ValidateInsertMutHasCorrectArgs(
- TableDefinition table,
- Dictionary mutArgs)
- {
- List requiredArguments = new();
- foreach (KeyValuePair nameColumnPair in table.Columns)
- {
- string columnName = nameColumnPair.Key;
- ColumnDefinition column = nameColumnPair.Value;
-
- if (!column.IsAutoGenerated)
- {
- requiredArguments.Add(columnName);
- }
- }
-
- ValidateFieldArguments(mutArgs.Keys, requiredArguments: requiredArguments);
- }
-
- ///
- /// Validate the update mutation has the correct args
- ///
- ///
- /// All but non pk autogenerated columns must be added as arguments
- ///
- private void ValidateUpdateMutHasCorrectArgs(
- TableDefinition table,
- Dictionary mutArgs)
- {
- List requiredArguments = new();
- foreach (KeyValuePair nameColumnPair in table.Columns)
- {
- string columnName = nameColumnPair.Key;
- ColumnDefinition column = nameColumnPair.Value;
-
- if (table.PrimaryKey.Contains(columnName) || !column.IsAutoGenerated)
- {
- requiredArguments.Add(columnName);
- }
- }
-
- ValidateFieldArguments(mutArgs.Keys, requiredArguments: requiredArguments);
- }
-
- ///
- /// Validate that the arguments of the update mutation are properly set to nullable or not
- ///
- ///
- /// In the current implemetation, only primary key arguments cannot be nullable
- ///
- private void ValidateArgNullabilityInUpdateMut(
- TableDefinition table,
- Dictionary mutArguments)
- {
- List shouldNotBeNullable = new();
- foreach (KeyValuePair nameArgPair in mutArguments)
- {
- string argName = nameArgPair.Key;
-
- if (table.PrimaryKey.Contains(argName))
- {
- shouldNotBeNullable.Add(argName);
- }
- }
-
- if (shouldNotBeNullable.Any())
- {
- throw new ConfigValidationException(
- $"The arguments [{string.Join(", ", shouldNotBeNullable)}] cannot be null in an " +
- "update mutation. All primary key arguments must be non nullable.",
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate that none of the provided field arguments are nullable
- ///
- private void ValidateFieldArgumentsAreNonNullable(Dictionary arguments)
- {
- IEnumerable nullableArgs = arguments.Keys.Where(argName => IsNullableType(arguments[argName].Type));
-
- if (nullableArgs.Any())
- {
- throw new ConfigValidationException(
- $"Field arguments [{string.Join(", ", nullableArgs)}] must not be nullable.",
- _schemaValidationStack
- );
- }
- }
-
- ///
- /// Validate there are no queries which return a type with a scalar inner type
- /// types with inner type scalar: String, String!, [String!]!
- ///
- private void ValidateNoScalarInnerTypeQueries(Dictionary queries)
- {
- IEnumerable queryNames = queries.Keys;
- IEnumerable scalarTypeQueries = queryNames.Where(name => IsScalarType(InnerType(queries[name].Type)));
-
- if (scalarTypeQueries.Any())
- {
- throw new ConfigValidationException(
- $"Query fields [{string.Join(", ", scalarTypeQueries)}] have invalid return types. " +
- "There is no support for queries returning scalar types or list of scalar types.",
- _schemaValidationStack
- );
- }
- }
- }
-}
diff --git a/DataGateway.Service/Configurations/SqlConfigValidatorMain.cs b/DataGateway.Service/Configurations/SqlConfigValidatorMain.cs
deleted file mode 100644
index 35357623dd..0000000000
--- a/DataGateway.Service/Configurations/SqlConfigValidatorMain.cs
+++ /dev/null
@@ -1,563 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Azure.DataGateway.Config;
-using Azure.DataGateway.Service.Models;
-using HotChocolate.Language;
-
-namespace Azure.DataGateway.Service.Configurations
-{
-
- /// This portion of the class
- /// contains the high level validation workflow.
- /// It doesn't access any of the private members
- /// or throw any exceptions.
-
- ///
- /// Validates the sql config and the graphql schema and
- /// and if the two match each other
- ///
- public partial class SqlConfigValidator : IConfigValidator
- {
- ///
- public void ValidateConfig()
- {
- System.Diagnostics.Stopwatch timer = System.Diagnostics.Stopwatch.StartNew();
-
- ValidateConfigHasGraphQLTypes();
- ValidateGraphQLTypes();
-
- if (SchemaHasMutations())
- {
- ValidateConfigHasMutationResolvers();
- ValidateMutationResolvers();
- }
- else
- {
- ValidateNoMutationResolvers();
- }
-
- ValidateQuerySchema();
-
- timer.Stop();
- Console.WriteLine($"Done validating GQL schema in {timer.ElapsedMilliseconds}ms.");
- }
-
- ///
- /// Validate GraphQL type fields
- ///
- private void ValidateGraphQLTypes()
- {
- ConfigStepInto("GraphQLTypes");
-
- Dictionary types = GetGraphQLTypes();
- Dictionary tableToType = new();
-
- ValidateTypesMatchSchemaTypes(types);
-
- // Field validation relies on valid pagination types so
- // this must be validated first
- ValidatePaginationTypes(types);
-
- foreach (KeyValuePair nameTypePair in types)
- {
- string typeName = nameTypePair.Key;
- GraphQLType type = nameTypePair.Value;
-
- ConfigStepInto(typeName);
- SchemaStepInto(typeName);
-
- if (!IsPaginationTypeName(typeName))
- {
- ValidateGraphQLTypeHasTable(type);
-
- ValidateGQLTypeTableIsUnique(type, tableToType);
- tableToType.Add(type.Table, typeName);
-
- ValidateGraphQLTypeTableColumnsMatchSchema(typeName, type.Table);
-
- Dictionary fieldDefinitions = GetTypeFields(typeName);
- ValidateSchemaFieldsReturnTypes(fieldDefinitions);
-
- if (!TypeHasFields(type))
- {
- ValidateNoFieldsWithInnerCustomType(typeName, fieldDefinitions);
- }
- else
- {
- ValidateGraphQLTypeFields(typeName, type);
- }
- }
-
- ConfigStepOutOf(typeName);
- SchemaStepOutOf(typeName);
- }
-
- ConfigStepOutOf("GraphQLTypes");
-
- SetGraphQLTypesValidated(true);
- }
-
- ///
- /// Validate pagination types
- ///
- private void ValidatePaginationTypes(Dictionary types)
- {
- foreach (string typeName in types.Keys)
- {
- ConfigStepInto(typeName);
- SchemaStepInto(typeName);
-
- if (IsPaginationTypeName(typeName))
- {
- ValidatePaginationTypeSchema(typeName);
- }
-
- ConfigStepOutOf(typeName);
- SchemaStepOutOf(typeName);
- }
- }
-
- ///
- /// Validate that pagination type has the right GraphQL schema
- ///
- private void ValidatePaginationTypeSchema(string typeName)
- {
- Dictionary fields = GetTypeFields(typeName);
-
- List paginationTypeRequiredFields = new() { "items", "endCursor", "hasNextPage" };
-
- ValidatePaginationTypeHasRequiredFields(fields, paginationTypeRequiredFields);
- ValidatePaginationFieldsHaveNoArguments(fields, paginationTypeRequiredFields);
-
- ValidateItemsFieldType(fields["items"]);
- ValidateEndCursorFieldType(fields["endCursor"]);
- ValidateHasNextPageFieldType(fields["hasNextPage"]);
-
- ValidatePaginationTypeName(typeName);
- }
-
- ///
- /// Validate that the scalar fields of the type match the table columns associated with the type
- ///
- ///
- /// Ignore scalar fields which match config type fields
- ///
- private void ValidateGraphQLTypeTableColumnsMatchSchema(
- string typeName,
- string typeTable)
- {
- string[] tableColumnsPath = new[] { "DatabaseSchema", "Tables", typeTable, "Columns" };
- ValidateTableColumnsMatchScalarFields(typeTable, typeName, MakeConfigPosition(tableColumnsPath));
- ValidateTableColumnTypesMatchScalarFieldTypes(typeName, MakeConfigPosition(tableColumnsPath));
- ValidateScalarFieldsMatchingTableColumnsHaveNoArgs(typeName, MakeConfigPosition(tableColumnsPath));
- ValidateScalarFieldsMatchingTableColumnsNullability(typeName, MakeConfigPosition(tableColumnsPath));
- }
-
- ///
- /// Validate GraphQLType fields
- ///
- private void ValidateGraphQLTypeFields(string typeName, GraphQLType type)
- {
- ConfigStepInto("Fields");
-
- Dictionary fieldDefinitions = GetTypeFields(typeName);
-
- ValidateConfigFieldsMatchSchemaFields(type.Fields, fieldDefinitions);
-
- foreach (KeyValuePair nameFieldPair in type.Fields)
- {
- string fieldName = nameFieldPair.Key;
- GraphQLField field = nameFieldPair.Value;
-
- ConfigStepInto(fieldName);
- SchemaStepInto(fieldName);
-
- FieldDefinitionNode fieldDefinition = fieldDefinitions[fieldName];
- ITypeNode fieldType = fieldDefinition.Type;
- string returnedType = InnerTypeStr(fieldType);
-
- List validRelationshipTypes = new()
- {
- GraphQLRelationshipType.OneToOne,
- GraphQLRelationshipType.ManyToMany,
- GraphQLRelationshipType.OneToMany,
- GraphQLRelationshipType.ManyToOne
- };
-
- ValidateRelationshipType(field, validRelationshipTypes);
-
- switch (field.RelationshipType)
- {
- case GraphQLRelationshipType.OneToOne:
- ValidateOneToOneField(field, fieldDefinition, typeName, returnedType);
- break;
- case GraphQLRelationshipType.OneToMany:
- ValidateOneToManyField(field, fieldDefinition, typeName, returnedType);
- break;
- case GraphQLRelationshipType.ManyToOne:
- ValidateManyToOneField(field, fieldDefinition, typeName, returnedType);
- break;
- case GraphQLRelationshipType.ManyToMany:
- ValidateManyToManyField(field, fieldDefinition, typeName, returnedType);
- break;
- }
-
- ConfigStepOutOf(fieldName);
- SchemaStepOutOf(fieldName);
- }
-
- ConfigStepOutOf("Fields");
- }
-
- ///
- /// Validate that pagination type field has the required arguments
- ///
- private void ValidatePaginationTypeFieldArguments(FieldDefinitionNode field)
- {
- Dictionary> requiredArguments = new()
- {
- ["first"] = new[] { "Int", "Int!" },
- ["after"] = new[] { "String" }
- };
-
- string returnedPaginationType = InnerTypeStr(field.Type);
- string itemsType = InnerTypeStr(GetTypeFields(returnedPaginationType)["items"].Type);
- Dictionary> optionalArguments = new()
- {
- ["_filter"] = new[] { $"{itemsType}FilterInput", $"{itemsType}FilterInput!" },
- ["orderBy"] = new[] { $"{itemsType}OrderByInput", $"{itemsType}OrderByInput!" },
- ["_filterOData"] = new[] { "String", "String!" }
- };
-
- Dictionary fieldArguments = GetArgumentsFromField(field);
-
- ValidateFieldArguments(
- fieldArguments.Keys,
- requiredArguments: requiredArguments.Keys,
- optionalArguments: optionalArguments.Keys);
- ValidateFieldArgumentTypes(
- fieldArguments,
- MergeDictionaries>(requiredArguments, optionalArguments));
- }
-
- ///
- /// Validate that list type field has the expected arguments
- ///
- private void ValidateListTypeFieldArguments(FieldDefinitionNode field)
- {
- string returnedType = InnerTypeStr(field.Type);
- Dictionary> optionalArguments = new()
- {
- ["first"] = new[] { "Int", "Int!" },
- ["_filter"] = new[] { $"{returnedType}FilterInput", $"{returnedType}FilterInput!" },
- ["orderBy"] = new[] { $"{returnedType}OrderByInput", $"{returnedType}OrderByInput!" },
- ["_filterOData"] = new[] { "String", "String!" }
- };
-
- Dictionary fieldArguments = GetArgumentsFromField(field);
-
- ValidateFieldArguments(fieldArguments.Keys, optionalArguments: optionalArguments.Keys);
- ValidateFieldArgumentTypes(fieldArguments, optionalArguments);
- }
-
- ///
- /// Validate that field doesn't have any arguments.
- ///
- private void ValidateNoFieldArguments(FieldDefinitionNode field)
- {
- Dictionary fieldArguments = GetArgumentsFromField(field);
- ValidateFieldArguments(fieldArguments.Keys, requiredArguments: Enumerable.Empty());
- }
-
- ///
- /// Validate field with One-To-One relationship to the type that owns it
- ///
- /// The type which owns the field
- /// The type returned by the field
- private void ValidateOneToOneField(GraphQLField field, FieldDefinitionNode fieldDefinition, string type, string returnedType)
- {
- bool hasLeftFk = HasLeftForeignKey(field);
- bool hasRightFk = HasRightForeignKey(field);
-
- ValidateReturnTypeNotPagination(field, fieldDefinition);
- ValidateFieldReturnsCustomType(fieldDefinition, typeNullable: !hasLeftFk);
- ValidateNoFieldArguments(fieldDefinition);
-
- ValidateNoAssociationTable(field);
- ValidateHasLeftOrRightForeignKey(field);
-
- if (hasLeftFk)
- {
- ValidateLeftForeignKey(field, type);
- ForeignKeyDefinition leftFk = GetFkFromTable(type, field.LeftForeignKey);
- ValidateLeftFkRefTableIsReturnedTypeTable(leftFk, returnedType);
- }
-
- if (hasRightFk)
- {
- ValidateRightForeignKey(field, returnedType);
- ForeignKeyDefinition rightFk = GetFkFromTable(returnedType, field.RightForeignKey);
- ValidateRightFkRefTableIsTypeTable(rightFk, type);
- }
- }
-
- ///
- /// Validate field with One-To-Many relationship to the type that owns it
- ///
- /// The type which owns the field
- /// The type returned by the field
- private void ValidateOneToManyField(GraphQLField field, FieldDefinitionNode fieldDefinition, string type, string returnedType)
- {
- if (IsPaginationType(fieldDefinition.Type))
- {
- ValidateReturnTypeNullability(fieldDefinition, returnsNullable: false);
- ValidatePaginationTypeFieldArguments(fieldDefinition);
- returnedType = InnerTypeStr(GetTypeFields(returnedType)["items"].Type);
- }
- else
- {
- ValidateFieldReturnsListOfCustomType(fieldDefinition, listNullabe: false, listElemsNullable: false);
- ValidateListTypeFieldArguments(fieldDefinition);
- }
-
- ValidateNoAssociationTable(field);
- ValidateHasOnlyRightForeignKey(field);
- ValidateRightForeignKey(field, returnedType);
-
- ForeignKeyDefinition rightFk = GetFkFromTable(returnedType, field.RightForeignKey);
- ValidateRightFkRefTableIsTypeTable(rightFk, type);
- }
-
- ///
- /// Validate field with Many-To-One relationship to the type that owns it
- ///
- /// The type which owns the field
- /// The type returned by the field
- private void ValidateManyToOneField(GraphQLField field, FieldDefinitionNode fieldDefinition, string type, string returnedType)
- {
- ValidateReturnTypeNotPagination(field, fieldDefinition);
- ValidateFieldReturnsCustomType(fieldDefinition, typeNullable: false);
- ValidateNoFieldArguments(fieldDefinition);
-
- ValidateNoAssociationTable(field);
- ValidateHasOnlyLeftForeignKey(field);
- ValidateLeftForeignKey(field, type);
-
- ForeignKeyDefinition leftFk = GetFkFromTable(type, field.LeftForeignKey);
- ValidateLeftFkRefTableIsReturnedTypeTable(leftFk, returnedType);
- }
-
- ///
- /// Validate field with Many-To-Many relationship to the type that owns it
- ///
- /// The type which owns the field
- /// The type returned by the field
- private void ValidateManyToManyField(GraphQLField field, FieldDefinitionNode fieldDefinition, string type, string returnedType)
- {
- if (IsPaginationType(fieldDefinition.Type))
- {
- ValidateReturnTypeNullability(fieldDefinition, returnsNullable: false);
- ValidatePaginationTypeFieldArguments(fieldDefinition);
- returnedType = InnerTypeStr(GetTypeFields(returnedType)["items"].Type);
- }
- else
- {
- ValidateFieldReturnsListOfCustomType(fieldDefinition, listNullabe: false, listElemsNullable: false);
- ValidateListTypeFieldArguments(fieldDefinition);
- }
-
- ValidateHasAssociationTable(field);
- ValidateHasBothLeftAndRightFK(field);
- ValidateLeftAndRightFkForM2MField(field);
-
- ForeignKeyDefinition rightFk = GetFkFromTable(field.AssociativeTable, field.RightForeignKey);
- ValidateRightFkRefTableIsTypeTable(rightFk, returnedType);
- ForeignKeyDefinition leftFk = GetFkFromTable(field.AssociativeTable, field.LeftForeignKey);
- ValidateLeftFkRefTableIsReturnedTypeTable(leftFk, type);
- }
-
- ///
- /// Validate mutation resolvers
- ///
- private void ValidateMutationResolvers()
- {
- ValidateGraphQLTypesIsValidated();
-
- ConfigStepInto("MutationResolvers");
- SchemaStepInto("Mutation");
-
- IEnumerable mutationResolverIds = GetMutationResolverIds();
-
- ValidateNoMissingIds(mutationResolverIds);
- ValidateNoDuplicateMutIds(mutationResolverIds);
- ValidateMutationResolversMatchSchema(mutationResolverIds);
-
- foreach (MutationResolver resolver in GetMutationResolvers())
- {
- ConfigStepInto($"Id = {resolver.Id}");
- SchemaStepInto(resolver.Id);
-
- ValidateMutResolverHasTable(resolver);
-
- // the rest of the mutation operations are only valid for cosmos
- List supportedOperations = new()
- {
- Operation.Insert,
- Operation.UpdateIncremental,
- Operation.Delete
- };
-
- ValidateMutResolverOperation(resolver.OperationType, supportedOperations);
-
- switch (resolver.OperationType)
- {
- case Operation.Insert:
- ValidateInsertMutationSchema(resolver);
- break;
- case Operation.Update:
- ValidateUpdateMutationSchema(resolver);
- break;
- case Operation.Delete:
- ValidateDeleteMutationSchema(resolver);
- break;
- }
-
- ConfigStepOutOf($"Id = {resolver.Id}");
- SchemaStepOutOf(resolver.Id);
- }
-
- ConfigStepOutOf("MutationResolvers");
- SchemaStepOutOf("Mutation");
- }
-
- ///
- /// Validate the schema of an insert mutation
- ///
- private void ValidateInsertMutationSchema(MutationResolver resolver)
- {
- FieldDefinitionNode mutation = GetMutation(resolver.Id);
- Dictionary mutArgs = GetArgumentsFromField(mutation);
- string type = _sqlMetadataProvider.EntityToDatabaseObject.FirstOrDefault(x => x.Value.Name == resolver.Table).Key;
- TableDefinition table = GetTableWithName(type);
-
- ValidateMutReturnTypeIsNotListType(mutation);
- if (IsCustomType(mutation.Type))
- {
- ValidateMutReturnTypeMatchesTable(resolver.Table, mutation);
- }
-
- ValidateMutArgsMatchTableColumns(resolver.Table, table, mutArgs);
- ValidateMutArgTypesMatchTableColTypes(resolver.Table, table, mutArgs);
-
- ValidateInsertMutHasCorrectArgs(table, mutArgs);
- ValidateArgNullabilityInInsertMut(table, mutArgs);
- ValidateReturnTypeNullability(mutation, returnsNullable: true);
- }
-
- ///
- /// Validate the schema of an update mutation
- ///
- private void ValidateUpdateMutationSchema(MutationResolver resolver)
- {
- FieldDefinitionNode mutation = GetMutation(resolver.Id);
- Dictionary mutArgs = GetArgumentsFromField(mutation);
- TableDefinition table = GetTableWithName(resolver.Table);
-
- ValidateMutReturnTypeIsNotListType(mutation);
- if (IsCustomType(mutation.Type))
- {
- ValidateMutReturnTypeMatchesTable(resolver.Table, mutation);
- }
-
- ValidateMutArgsMatchTableColumns(resolver.Table, table, mutArgs);
- ValidateMutArgTypesMatchTableColTypes(resolver.Table, table, mutArgs);
-
- ValidateUpdateMutHasCorrectArgs(table, mutArgs);
- ValidateArgNullabilityInUpdateMut(table, mutArgs);
- ValidateReturnTypeNullability(mutation, returnsNullable: true);
- }
-
- ///
- /// Validate the schema of a delete mutation
- ///
- private void ValidateDeleteMutationSchema(MutationResolver resolver)
- {
- FieldDefinitionNode mutation = GetMutation(resolver.Id);
- Dictionary mutArgs = GetArgumentsFromField(mutation);
- string type = _sqlMetadataProvider.EntityToDatabaseObject.FirstOrDefault(x => x.Value.Name == resolver.Table).Key;
- TableDefinition table = GetTableWithName(type);
-
- ValidateMutReturnTypeIsNotListType(mutation);
- if (IsCustomType(mutation.Type))
- {
- ValidateMutReturnTypeMatchesTable(resolver.Table, mutation);
- }
-
- ValidateFieldArguments(mutArgs.Keys, requiredArguments: table.PrimaryKey);
- ValidateMutArgTypesMatchTableColTypes(resolver.Table, table, mutArgs);
- ValidateFieldArgumentsAreNonNullable(mutArgs);
- ValidateReturnTypeNullability(mutation, returnsNullable: true);
- }
-
- ///
- /// Validate query schema
- ///
- private void ValidateQuerySchema()
- {
- ValidateGraphQLTypesIsValidated();
-
- SchemaStepInto("Query");
-
- Dictionary queries = GetQueries();
-
- ValidateSchemaFieldsReturnTypes(queries);
- ValidateNoScalarInnerTypeQueries(queries);
-
- foreach (KeyValuePair nameQueryPair in queries)
- {
- string queryName = nameQueryPair.Key;
- FieldDefinitionNode queryField = nameQueryPair.Value;
-
- SchemaStepInto(queryName);
-
- if (IsPaginationType(queryField.Type))
- {
- ValidateReturnTypeNullability(queryField, returnsNullable: false);
- ValidatePaginationTypeFieldArguments(queryField);
- }
- else if (IsListType(queryField.Type))
- {
- ValidateFieldReturnsListOfCustomType(queryField, listNullabe: false, listElemsNullable: false);
- ValidateListTypeFieldArguments(queryField);
- }
- else if (IsCustomType(queryField.Type))
- {
- ValidateReturnTypeNullability(queryField, returnsNullable: true);
- ValidateNonListCustomTypeQueryFieldArgs(queryField);
- }
-
- SchemaStepOutOf(queryName);
- }
-
- SchemaStepOutOf("Query");
- }
-
- ///
- /// Validate non list custom query field arguments
- ///
- ///
- /// This is a search by primary key query so the arguments should match
- /// the return type table primary key
- ///
- private void ValidateNonListCustomTypeQueryFieldArgs(FieldDefinitionNode queryField)
- {
- Dictionary arguments = GetArgumentsFromField(queryField);
-
- TableDefinition returnedTypeTable = GetTableWithName(InnerTypeStr(queryField.Type));
-
- ValidateFieldArguments(arguments.Keys, requiredArguments: returnedTypeTable.PrimaryKey);
- ValidateFieldArgumentsAreNonNullable(arguments);
- }
- }
-}
diff --git a/DataGateway.Service/Configurations/SqlConfigValidatorUtil.cs b/DataGateway.Service/Configurations/SqlConfigValidatorUtil.cs
deleted file mode 100644
index f1c02ef10b..0000000000
--- a/DataGateway.Service/Configurations/SqlConfigValidatorUtil.cs
+++ /dev/null
@@ -1,571 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Azure.DataGateway.Config;
-using Azure.DataGateway.Service.Models;
-using HotChocolate.Language;
-using HotChocolate.Types;
-
-namespace Azure.DataGateway.Service.Configurations
-{
-
- /// This portion of the class
- /// hold all function which do not directly do validation
- public partial class SqlConfigValidator : IConfigValidator
- {
- ///
- /// Make a stack for the a position in the config
- /// If no path is passed, make starting stack,
- /// add the path to the stack otherwise
- ///
- private static Stack MakeConfigPosition(IEnumerable path)
- {
- Stack configStack = new();
- configStack.Push("Config");
-
- foreach (string pathElement in path)
- {
- configStack.Push(pathElement);
- }
-
- return configStack;
- }
-
- ///
- /// Make a stack for the a position in the config
- /// If no path is passed, make starting stack,
- /// add the path to the stack otherwise
- ///
- private static Stack MakeSchemaPosition(IEnumerable path)
- {
- Stack schemaStack = new();
- schemaStack.Push("GQL Schema");
-
- foreach (string pathElement in path)
- {
- schemaStack.Push(pathElement);
- }
-
- return schemaStack;
- }
-
- ///
- /// Sets the validation status of the GraphQLTypes
- ///
- private void SetGraphQLTypesValidated(bool flag)
- {
- _graphQLTypesAreValidated = flag;
- }
-
- ///
- /// Gets the validation status of the GraphQLTypes
- ///
- private bool IsGraphQLTypesValidated()
- {
- return _graphQLTypesAreValidated;
- }
-
- ///
- /// Print the reversed validation stack since the validation stack
- /// contains the smallest context at the top and the largest at the bottom
- ///
- private static string PrettyPrintValidationStack(Stack validationStack)
- {
- string[] stackArray = validationStack.ToArray();
- Array.Reverse(stackArray);
- return string.Join(" > ", stackArray);
- }
-
- ///
- /// Move into path from the current position in config
- ///
- private void ConfigStepInto(string path)
- {
- _configValidationStack.Push(path);
- }
-
- ///
- /// Move out of path from the current postion in config
- ///
- ///
- /// If the last element in the config path is not equal to the
- /// the parameter path
- ///
- private void ConfigStepOutOf(string path)
- {
- string lastdPath = _configValidationStack.Peek();
- if (lastdPath != path)
- {
- throw new ArgumentException(
- $"Cannot step out of {path} because config is currently " +
- $"being validated at {PrettyPrintValidationStack(_configValidationStack)}");
- }
- else
- {
- _configValidationStack.Pop();
- }
- }
-
- ///
- /// Move into path from the current position in the GraphQL schema
- ///
- private void SchemaStepInto(string path)
- {
- _schemaValidationStack.Push(path);
- }
-
- ///
- /// Move out of path from the current postion in the GraphQL schema
- ///
- ///
- /// If the last element in the schema path is not equal to the
- /// the parameter path
- ///
- private void SchemaStepOutOf(string path)
- {
- string lastdPath = _schemaValidationStack.Peek();
- if (lastdPath != path)
- {
- throw new ArgumentException(
- $"Cannot step out of {path} because schema is currently " +
- $"being validated at {PrettyPrintValidationStack(_schemaValidationStack)}");
- }
- else
- {
- _schemaValidationStack.Pop();
- }
- }
-
- ///
- /// Gets fields from GraphQL type
- ///
- private Dictionary GetTypeFields(string typeName)
- {
- return GetObjTypeDefFields(_types[typeName]);
- }
-
- ///
- /// Get fields from a HotCholate ObjectTypeDefinitionNode
- ///
- private static Dictionary GetObjTypeDefFields(ObjectTypeDefinitionNode objectTypeDef)
- {
- Dictionary fields = new();
- foreach (FieldDefinitionNode field in objectTypeDef.Fields)
- {
- fields.Add(field.Name.Value, field);
- }
-
- return fields;
- }
-
- ///
- /// Gets graphql types from config
- ///
- private Dictionary GetGraphQLTypes()
- {
- return _resolverConfig.GraphQLTypes;
- }
-
- ///
- /// Get table definition from the entity name
- /// Expects valid entity name and a sql entity.
- ///
- ///
- /// If the given entity name does not exist in the schema.
- ///
- private TableDefinition GetTableWithName(string entityName)
- {
- return _sqlMetadataProvider.GetTableDefinition(entityName);
- }
-
- ///
- /// Check if the list has duplicates
- ///
- private static IEnumerable GetDuplicates(IEnumerable enumerable)
- {
- HashSet distinct = new(enumerable);
- List duplicates = new();
-
- foreach (string elem in enumerable)
- {
- if (distinct.Contains(elem))
- {
- distinct.Remove(elem);
- }
- else
- {
- duplicates.Add(elem);
- }
- }
-
- return duplicates.Distinct();
- }
-
- ///
- /// Checks if config type has fields
- ///
- private static bool TypeHasFields(GraphQLType type)
- {
- return type.Fields != null;
- }
-
- ///
- /// A more readable version of !type.IsNonNullType
- ///
- private static bool IsNullableType(ITypeNode type)
- {
- return !type.IsNonNullType();
- }
-
- ///
- /// Checks if the type is a nested list type
- /// e.g. [[Book]], [[[Book!]!]!]!
- ///
- private static bool IsNestedListType(ITypeNode type)
- {
- return IsListType(InnerType(type));
- }
-
- ///
- /// Checks if the type is a list of pagination type
- /// e.g.
- /// [BookConnection] -> true
- /// [[BookConnection]] -> false (list of lists, not list of pagination type)
- ///
- private bool IsListOfPaginationType(ITypeNode type)
- {
- return IsListType(type) && IsPaginationType(InnerType(type));
- }
-
- ///
- /// Checks if the given type name is the name of a pagination type
- ///
- private bool IsPaginationTypeName(string typeName)
- {
- if (_resolverConfig.GraphQLTypes.TryGetValue(typeName, out GraphQLType? type))
- {
- return type.IsPaginationType;
- }
-
- return false;
- }
-
- ///
- /// Returns if type is a pagination type or not
- ///
- private bool IsPaginationType(ITypeNode type)
- {
- return IsPaginationTypeName(type.NullableType().ToString());
- }
-
- ///
- /// Gets inner type from ITypeNode in string format
- ///
- private static string InnerTypeStr(ITypeNode type)
- {
- return InnerType(type).ToString();
- }
-
- ///
- /// Gets inner type from ITypeNode
- ///
- ///
- /// Go one level deep (if possible) while ignoring non nullability (!)
- /// e.g.
- /// Book, Book!, [Book], [Book!], [Book]!, [Book!]! -> Book
- /// [[Book]!]!, [[Book]]
- ///
- private static ITypeNode InnerType(ITypeNode type)
- {
- // ITypeNode.InnerType returns the same type if no inner type
- return type.NullableType().InnerType().NullableType();
- }
-
- ///
- /// Checks if ITypeNode is list type
- ///
- ///
- /// The build in IsListType function of ITypeNode will
- /// return false for [Book]! so that needs to be addressed
- /// with this custom function
- ///
- private static bool IsListType(ITypeNode type)
- {
- return type.NullableType().IsListType();
- }
-
- ///
- /// Checks if a ITypeNode is a custom type
- /// Checks if the nullable type is declared in the GQL Schema
- ///
- private bool IsCustomType(ITypeNode type)
- {
- return _types.ContainsKey(type.NullableType().ToString());
- }
-
- ///
- /// Checks if the ITypeNode is a list of custom types
- /// e.g.
- /// [Book!] -> true
- /// [BookConnection] -> true (pagination types also qualify as custom)
- ///
- public bool IsListOfCustomType(ITypeNode type)
- {
- return IsListType(type) && IsCustomType(InnerType(type));
- }
-
- ///
- /// Checks if the inner type of a given type is a custom type
- ///
- private bool IsInnerTypeCustom(ITypeNode type)
- {
- return IsCustomType(InnerType(type));
- }
-
- ///
- /// Check if list the elements of a list type are nullable
- /// e.g.
- /// Book -> false (not list)
- /// [Book] -> true
- /// [Book!] -> false
- ///
- private static bool AreListElementsNullable(ITypeNode type)
- {
- if (IsListType(type))
- {
- return IsNullableType(type.NullableType().InnerType());
- }
-
- return false;
- }
-
- ///
- /// Get arguments from field and return a dictionary in [argName, argument] format
- ///
- private static Dictionary GetArgumentsFromField(FieldDefinitionNode field)
- {
- Dictionary arguments = new();
-
- foreach (InputValueDefinitionNode node in field.Arguments)
- {
- arguments.Add(node.Name.ToString(), node);
- }
-
- return arguments;
- }
-
- ///
- /// Checks if ITypeNode is scalar type which means
- /// it is not a custom type nor a list type
- ///
- private bool IsScalarType(ITypeNode type)
- {
- return !IsCustomType(type) && !IsListType(type);
- }
-
- ///
- /// Returns the scalar fields from a dictionary of fields
- ///
- private Dictionary GetScalarFields(Dictionary fields)
- {
- Dictionary scalarFields = new();
- foreach (KeyValuePair nameFieldPair in fields)
- {
- string fieldName = nameFieldPair.Key;
- FieldDefinitionNode field = nameFieldPair.Value;
-
- if (IsScalarType(field.Type))
- {
- scalarFields.Add(fieldName, field);
- }
- }
-
- return scalarFields;
- }
-
- ///
- /// Returns the non scalar fields from a dictionary of fields
- ///
- ///
- /// Note that [String] is also considered non
- ///
- private Dictionary GetNonScalarFields(Dictionary fields)
- {
- Dictionary nonScalarFields = new();
- foreach (KeyValuePair nameFieldPair in fields)
- {
- string fieldName = nameFieldPair.Key;
- FieldDefinitionNode field = nameFieldPair.Value;
- if (!IsScalarType(field.Type))
- {
- nonScalarFields.Add(fieldName, field);
- }
- }
-
- return nonScalarFields;
- }
-
- ///
- /// Checks if a GraphQL type is equal to a ColumnType
- ///
- private static bool GraphQLTypeEqualsColumnType(ITypeNode gqlType, Type columnType)
- {
- return GetGraphQLTypeForColumnType(columnType) == gqlType.NullableType().ToString();
- }
-
- ///
- /// Get the GraphQL type equivalent from ColumnType
- ///
- private static string GetGraphQLTypeForColumnType(Type type)
- {
- switch (Type.GetTypeCode(type))
- {
- case TypeCode.String:
- return "String";
- case TypeCode.Int64:
- return "Int";
- default:
- throw new ArgumentException($"ColumnType {type} not handled by case. Please add a case resolving " +
- $"{type} to the appropriate GraphQL type");
- }
- }
-
- ///
- /// Get columns used in the primary key and foreign keys of the table
- ///
- private static IEnumerable GetPkAndFkColumns(TableDefinition table)
- {
- List columns = new();
-
- columns.AddRange(table.PrimaryKey);
-
- foreach (KeyValuePair nameFKPair in table.ForeignKeys)
- {
- ForeignKeyDefinition foreignKey = nameFKPair.Value;
- columns.AddRange(foreignKey.ReferencingColumns);
- }
-
- return columns;
- }
-
- ///
- /// Get the config GraphQLTypes.Fields for a graphql schema type
- ///
- private IEnumerable GetConfigFieldsForGqlType(ObjectTypeDefinitionNode type)
- {
- return _resolverConfig.GraphQLTypes[type.Name.Value].Fields.Keys;
- }
-
- ///
- /// Check that GraphQLType.Field has only a left foreign key
- ///
- private static bool HasLeftForeignKey(GraphQLField field)
- {
- return !string.IsNullOrEmpty(field.LeftForeignKey);
- }
-
- ///
- /// Check that GraphQLType.Field has only a right foreign key
- ///
- private static bool HasRightForeignKey(GraphQLField field)
- {
- return !string.IsNullOrEmpty(field.RightForeignKey);
- }
-
- ///
- /// Get the db table underlying the GraphQL type
- /// Assumes type is valid throws KeyNotFoundException otherwise
- ///
- private string GetTypeTable(string type)
- {
- return GetGraphQLTypes()[type].Table;
- }
-
- ///
- /// Whether a table contains a foreign key by the given name
- /// ArgumentException on invalid tableName
- ///
- private bool TableContainsForeignKey(string tableName, string foreignKeyName)
- {
- TableDefinition table = GetTableWithName(tableName);
-
- if (table.ForeignKeys == null)
- {
- return false;
- }
-
- return table.ForeignKeys.ContainsKey(foreignKeyName);
- }
-
- ///
- /// Gets a foreign key by entity name from the underlying table.
- ///
- ///
- private ForeignKeyDefinition GetFkFromTable(string entityName, string fkName)
- {
- return _sqlMetadataProvider.GetTableDefinition(entityName).ForeignKeys[fkName];
- }
-
- ///
- /// Gets mutation resolvers from config
- ///
- private List GetMutationResolvers()
- {
- return _resolverConfig.MutationResolvers;
- }
-
- ///
- /// Get mutation resolver ids
- /// May contain null for resolvers without ids
- ///
- private IEnumerable GetMutationResolverIds()
- {
- return GetMutationResolvers().Select(resolver => resolver.Id);
- }
-
- ///