Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}

[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.
// 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