Skip to content
169 changes: 169 additions & 0 deletions src/Service.Tests/GraphQLBuilder/GraphQLNamingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure.DataApiBuilder.Config.ObjectModel;
using Azure.DataApiBuilder.Service.GraphQLBuilder;
using Azure.DataApiBuilder.Service.Tests.GraphQLBuilder.Helpers;
using HotChocolate.Language;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Azure.DataApiBuilder.Service.Tests.GraphQLBuilder
{
/// <summary>
/// Unit tests for the pure naming helpers in <see cref="GraphQLNaming"/>.
/// </summary>
[TestClass]
public class GraphQLNamingTests
{
[DataTestMethod]
[DataRow("Book", "Book", DisplayName = "Valid name is unchanged")]
[DataRow("1Book", "Book", DisplayName = "Illegal leading digit is stripped")]
[DataRow("_Book", "Book", DisplayName = "Illegal leading underscore is stripped")]
[DataRow("Bo-ok", "Book", DisplayName = "Invalid symbol removed")]
public void SanitizeGraphQLName_SingleSegment(string input, string expected)
{
string[] segments = GraphQLNaming.SanitizeGraphQLName(input);
Assert.AreEqual(1, segments.Length);
Assert.AreEqual(expected, segments[0]);
}

[TestMethod]
public void SanitizeGraphQLName_RemovesSpacesAndInvalidSymbols()
{
// Spaces and non-alphanumeric symbols are removed, yielding a single segment.
string[] segments = GraphQLNaming.SanitizeGraphQLName("my table!");
CollectionAssert.AreEqual(new[] { "mytable" }, segments);
}

[DataTestMethod]
[DataRow("Book", false)]
[DataRow("book", false)]
[DataRow("1book", true)]
[DataRow("_book", true)]
public void ViolatesNamePrefixRequirements(string name, bool expected)
{
Assert.AreEqual(expected, GraphQLNaming.ViolatesNamePrefixRequirements(name));
}

[DataTestMethod]
[DataRow("Book", false)]
[DataRow("Book_1", false)]
[DataRow("Bo-ok", true)]
[DataRow("my table", true)]
public void ViolatesNameRequirements(string name, bool expected)
{
Assert.AreEqual(expected, GraphQLNaming.ViolatesNameRequirements(name));
Comment thread
aaronburtle marked this conversation as resolved.
}

[DataTestMethod]
[DataRow("__type", true)]
[DataRow("__schema", true)]
[DataRow("type", false)]
[DataRow("_type", false)]
public void IsIntrospectionField(string fieldName, bool expected)
{
Assert.AreEqual(expected, GraphQLNaming.IsIntrospectionField(fieldName));
}

[TestMethod]
public void GetDefinedSingularName_WithSingular_ReturnsSingular()
{
Entity entity = GraphQLTestHelpers.GenerateEntityWithSingularPlural("Book", "Books");
Assert.AreEqual("Book", GraphQLNaming.GetDefinedSingularName("Book", entity));
}

[TestMethod]
public void GetDefinedSingularName_WithoutSingular_Throws()
{
Entity entity = GraphQLTestHelpers.GenerateEmptyEntity();
Assert.ThrowsException<System.ArgumentException>(
() => GraphQLNaming.GetDefinedSingularName("Book", entity));
}

[TestMethod]
public void GetDefinedPluralName_WithPlural_ReturnsPlural()
{
Entity entity = GraphQLTestHelpers.GenerateEntityWithSingularPlural("Book", "Books");
Assert.AreEqual("Books", GraphQLNaming.GetDefinedPluralName("Book", entity));
}

[TestMethod]
public void GetDefinedPluralName_WithoutPlural_Throws()
{
Entity entity = GraphQLTestHelpers.GenerateEntityWithStringType("Book");
Assert.ThrowsException<System.ArgumentException>(
() => GraphQLNaming.GetDefinedPluralName("Book", entity));
}

[DataTestMethod]
[DataRow("Book", "book", DisplayName = "First char lowercased")]
[DataRow("my table", "mytable", DisplayName = "Spaces removed, first char lowercased")]
[DataRow("book", "book", DisplayName = "Already camel-case unchanged")]
public void FormatNameForField(string input, string expected)
{
Assert.AreEqual(expected, GraphQLNaming.FormatNameForField(input));
}

[TestMethod]
public void Pluralize_ReturnsConfiguredPluralName()
{
Entity entity = GraphQLTestHelpers.GenerateEntityWithSingularPlural("Book", "Books");
NameNode result = GraphQLNaming.Pluralize("Book", entity);
Assert.AreEqual("Books", result.Value);
}

[TestMethod]
public void ObjectTypeToEntityName_NoModelDirective_ReturnsTypeName()
{
ObjectTypeDefinitionNode node = ParseObjectType("type Book { id: Int }");
Assert.AreEqual("Book", GraphQLNaming.ObjectTypeToEntityName(node));
}

[TestMethod]
public void ObjectTypeToEntityName_ModelDirectiveWithName_ReturnsModelName()
{
ObjectTypeDefinitionNode node = ParseObjectType(@"type Book @model(name: ""TopLevelBook"") { id: Int }");
Assert.AreEqual("TopLevelBook", GraphQLNaming.ObjectTypeToEntityName(node));
}

[TestMethod]
public void ObjectTypeToEntityName_ModelDirectiveNoArguments_ReturnsTypeName()
{
ObjectTypeDefinitionNode node = ParseObjectType("type Book @model { id: Int }");
Assert.AreEqual("Book", GraphQLNaming.ObjectTypeToEntityName(node));
}

[TestMethod]
public void GenerateByPKQueryName_UsesSingularWithSuffix()
{
Entity entity = GraphQLTestHelpers.GenerateEntityWithSingularPlural("Book", "Books");
Assert.AreEqual("book_by_pk", GraphQLNaming.GenerateByPKQueryName("Book", entity));
}

[TestMethod]
public void GenerateListQueryName_UsesPlural()
{
Entity entity = GraphQLTestHelpers.GenerateEntityWithSingularPlural("Book", "Books");
Assert.AreEqual("books", GraphQLNaming.GenerateListQueryName("Book", entity));
}

[TestMethod]
public void GenerateStoredProcedureGraphQLFieldName_PrefixesExecute()
{
Entity entity = GraphQLTestHelpers.GenerateEntityWithStringType("GetBook");
Assert.AreEqual("executeGetBook", GraphQLNaming.GenerateStoredProcedureGraphQLFieldName("GetBook", entity));
}

[TestMethod]
public void GenerateLinkingNodeName_ConcatenatesWithPrefix()
{
Assert.AreEqual("linkingObjectBookAuthor", GraphQLNaming.GenerateLinkingNodeName("Book", "Author"));
}

private static ObjectTypeDefinitionNode ParseObjectType(string sdl)
{
DocumentNode document = Utf8GraphQLParser.Parse(sdl);
return (ObjectTypeDefinitionNode)document.Definitions[0];
}
}
}
158 changes: 158 additions & 0 deletions src/Service.Tests/GraphQLBuilder/GraphQLUtilsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;
using Azure.DataApiBuilder.Config.ObjectModel;
using Azure.DataApiBuilder.Service.Exceptions;
using Azure.DataApiBuilder.Service.GraphQLBuilder;
using HotChocolate.Language;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Azure.DataApiBuilder.Service.Tests.GraphQLBuilder
{
/// <summary>
/// Unit tests for the pure static helpers in <see cref="GraphQLUtils"/>.
/// </summary>
[TestClass]
public class GraphQLUtilsTests
{
[DataTestMethod]
[DataRow(DatabaseType.MSSQL, true)]
[DataRow(DatabaseType.MySQL, true)]
[DataRow(DatabaseType.DWSQL, true)]
[DataRow(DatabaseType.PostgreSQL, true)]
[DataRow(DatabaseType.CosmosDB_PostgreSQL, true)]
[DataRow(DatabaseType.CosmosDB_NoSQL, false)]
public void IsRelationalDb(DatabaseType databaseType, bool expected)
{
Assert.AreEqual(expected, GraphQLUtils.IsRelationalDb(databaseType));
}

[DataTestMethod]
[DataRow("String", true)]
[DataRow("Int", true)]
[DataRow("Boolean", true)]
[DataRow("ID", true)]
[DataRow("Book", false)]
[DataRow("CustomType", false)]
public void IsBuiltInType(string typeName, bool expected)
{
ITypeNode typeNode = new NamedTypeNode(new NameNode(typeName));
Assert.AreEqual(expected, GraphQLUtils.IsBuiltInType(typeNode));
}

[TestMethod]
public void IsModelType_WithModelDirective_ReturnsTrue()
{
ObjectTypeDefinitionNode node = ParseObjectType(@"type Book @model(name: ""Book"") { id: Int }");
Assert.IsTrue(GraphQLUtils.IsModelType(node));
}

[TestMethod]
public void IsModelType_WithoutModelDirective_ReturnsFalse()
{
ObjectTypeDefinitionNode node = ParseObjectType("type Book { id: Int }");
Assert.IsFalse(GraphQLUtils.IsModelType(node));
}

[TestMethod]
public void FindPrimaryKeyFields_CosmosNoSql_ReturnsIdAndPartitionKey()
{
ObjectTypeDefinitionNode node = ParseObjectType("type Book { title: String }");
List<FieldDefinitionNode> pkFields = GraphQLUtils.FindPrimaryKeyFields(node, DatabaseType.CosmosDB_NoSQL);

Assert.AreEqual(2, pkFields.Count);
CollectionAssert.AreEquivalent(
new[] { GraphQLUtils.DEFAULT_PRIMARY_KEY_NAME, GraphQLUtils.DEFAULT_PARTITION_KEY_NAME },
pkFields.ConvertAll(f => f.Name.Value));
}

[TestMethod]
public void FindPrimaryKeyFields_PrimaryKeyDirective_ReturnsDirectiveField()
{
ObjectTypeDefinitionNode node = ParseObjectType("type Book { book_id: Int @primaryKey title: String }");
List<FieldDefinitionNode> pkFields = GraphQLUtils.FindPrimaryKeyFields(node, DatabaseType.MSSQL);

Assert.AreEqual(1, pkFields.Count);
Assert.AreEqual("book_id", pkFields[0].Name.Value);
}

[TestMethod]
public void FindPrimaryKeyFields_NoDirective_FallsBackToIdField()
{
ObjectTypeDefinitionNode node = ParseObjectType("type Book { id: Int title: String }");
List<FieldDefinitionNode> pkFields = GraphQLUtils.FindPrimaryKeyFields(node, DatabaseType.MSSQL);

Assert.AreEqual(1, pkFields.Count);
Assert.AreEqual("id", pkFields[0].Name.Value);
}

[TestMethod]
public void FindPrimaryKeyFields_NoDirectiveNoIdField_Throws()
{
ObjectTypeDefinitionNode node = ParseObjectType("type Book { title: String }");
DataApiBuilderException ex = Assert.ThrowsException<DataApiBuilderException>(
() => GraphQLUtils.FindPrimaryKeyFields(node, DatabaseType.MSSQL));

Assert.AreEqual(DataApiBuilderException.SubStatusCodes.ErrorInInitialization, ex.SubStatusCode);
}

[TestMethod]
public void IsAutoGeneratedField_WithDirective_ReturnsTrue()
{
ObjectTypeDefinitionNode node = ParseObjectType("type Book { id: Int @autoGenerated }");
Assert.IsTrue(GraphQLUtils.IsAutoGeneratedField(node.Fields[0]));
}

[TestMethod]
public void IsAutoGeneratedField_WithoutDirective_ReturnsFalse()
{
ObjectTypeDefinitionNode node = ParseObjectType("type Book { id: Int }");
Assert.IsFalse(GraphQLUtils.IsAutoGeneratedField(node.Fields[0]));
}

[TestMethod]
public void CreateAuthorizationDirective_NullRoles_ReturnsFalse()
{
bool created = GraphQLUtils.CreateAuthorizationDirectiveIfNecessary(null, out DirectiveNode? directive);
Assert.IsFalse(created);
Assert.IsNull(directive);
}

[TestMethod]
public void CreateAuthorizationDirective_EmptyRoles_ReturnsFalse()
{
bool created = GraphQLUtils.CreateAuthorizationDirectiveIfNecessary(new List<string>(), out DirectiveNode? directive);
Assert.IsFalse(created);
Assert.IsNull(directive);
}

[TestMethod]
public void CreateAuthorizationDirective_ContainsAnonymous_ReturnsFalse()
{
bool created = GraphQLUtils.CreateAuthorizationDirectiveIfNecessary(
new[] { "role1", GraphQLUtils.SYSTEM_ROLE_ANONYMOUS }, out DirectiveNode? directive);
Assert.IsFalse(created);
Assert.IsNull(directive);
}

[TestMethod]
public void CreateAuthorizationDirective_WithRoles_ReturnsAuthorizeDirective()
{
bool created = GraphQLUtils.CreateAuthorizationDirectiveIfNecessary(
new[] { "role1", "role2" }, out DirectiveNode? directive);

Assert.IsTrue(created);
Assert.IsNotNull(directive);
Assert.AreEqual(GraphQLUtils.AUTHORIZE_DIRECTIVE, directive!.Name.Value);
Assert.AreEqual(1, directive.Arguments.Count);
Assert.AreEqual(GraphQLUtils.AUTHORIZE_DIRECTIVE_ARGUMENT_ROLES, directive.Arguments[0].Name.Value);
}

private static ObjectTypeDefinitionNode ParseObjectType(string sdl)
{
DocumentNode document = Utf8GraphQLParser.Parse(sdl);
return (ObjectTypeDefinitionNode)document.Definitions[0];
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation.
Comment thread
aaronburtle marked this conversation as resolved.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Text.Json;
using Azure.DataApiBuilder.Service.GraphQLBuilder;
using HotChocolate.Language;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Azure.DataApiBuilder.Service.Tests.GraphQLBuilder.Sql
{
/// <summary>
/// Unit tests for the pure helper methods on <see cref="GraphQLStoredProcedureBuilder"/> that
/// shape stored-procedure results and default result fields.
/// </summary>
[TestClass]
public class GraphQLStoredProcedureBuilderHelpersTests
{
[TestMethod]
public void FormatStoredProcedureResultAsJsonList_Null_ReturnsEmptyList()
{
List<JsonDocument> result = GraphQLStoredProcedureBuilder.FormatStoredProcedureResultAsJsonList(null);

Assert.IsNotNull(result);
Assert.AreEqual(0, result.Count);
}

[TestMethod]
public void FormatStoredProcedureResultAsJsonList_EmptyArray_ReturnsEmptyList()
{
using JsonDocument input = JsonDocument.Parse("[]");
List<JsonDocument> result = GraphQLStoredProcedureBuilder.FormatStoredProcedureResultAsJsonList(input);

Assert.AreEqual(0, result.Count);
}

[TestMethod]
public void FormatStoredProcedureResultAsJsonList_MultipleRows_ReturnsOneDocumentPerRow()
{
using JsonDocument input = JsonDocument.Parse(@"[{""id"":1,""title"":""A""},{""id"":2,""title"":""B""}]");
List<JsonDocument> result = GraphQLStoredProcedureBuilder.FormatStoredProcedureResultAsJsonList(input);

Assert.AreEqual(2, result.Count);
Assert.AreEqual(1, result[0].RootElement.GetProperty("id").GetInt32());
Assert.AreEqual("B", result[1].RootElement.GetProperty("title").GetString());
}

[TestMethod]
public void GetDefaultResultFieldForStoredProcedure_ReturnsResultStringField()
{
FieldDefinitionNode field = GraphQLStoredProcedureBuilder.GetDefaultResultFieldForStoredProcedure();

Assert.AreEqual("result", field.Name.Value);
Assert.AreEqual(0, field.Arguments.Count);
Assert.IsInstanceOfType(field.Type, typeof(NamedTypeNode));
Assert.AreEqual("String", ((NamedTypeNode)field.Type).Name.Value);
}
}
}
Loading