Skip to content

Commit

Permalink
Add test cases for External Arguments Default Mismatch rule.
Browse files Browse the repository at this point in the history
  • Loading branch information
danielreynolds1 committed Dec 17, 2024
1 parent 664a518 commit ad5d476
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using HotChocolate.Fusion.Events;
using HotChocolate.Skimmed;
using static HotChocolate.Fusion.Logging.LogEntryHelper;

namespace HotChocolate.Fusion.PreMergeValidation.Rules;

/// <summary>
/// This rule ensures that certain essential elements of a GraphQL schema, particularly built-in
/// scalars, directive arguments, and introspection types, cannot be marked as @inaccessible. These
/// types are fundamental to GraphQL. Making these elements inaccessible would break core GraphQL
/// functionality.
/// </summary>
/// <seealso href="https://graphql.github.io/composite-schemas-spec/draft/#sec-Disallowed-Inaccessible-Elements">
/// Specification
/// </seealso>
internal sealed class ExternalArgumentsDefaultMismatchRule
: IEventHandler<TypeEvent>
, IEventHandler<OutputFieldEvent>
, IEventHandler<FieldArgumentEvent>
, IEventHandler<DirectiveArgumentEvent>
{
public void Handle(TypeEvent @event, CompositionContext context)
{
var (type, schema) = @event;

// Built-in scalar types must be accessible.
if (type is ScalarTypeDefinition { IsSpecScalar: true } scalar
&& !ValidationHelper.IsAccessible(scalar))
{
context.Log.Write(DisallowedInaccessibleBuiltInScalar(scalar, schema));
}

// Introspection types must be accessible.
if (type.IsIntrospectionType && !ValidationHelper.IsAccessible(type))
{
context.Log.Write(DisallowedInaccessibleIntrospectionType(type, schema));
}
}

public void Handle(OutputFieldEvent @event, CompositionContext context)
{
var (field, type, schema) = @event;

// Introspection fields must be accessible.
if (type.IsIntrospectionType && !ValidationHelper.IsAccessible(field))
{
context.Log.Write(
DisallowedInaccessibleIntrospectionField(
field,
type.Name,
schema));
}
}

public void Handle(FieldArgumentEvent @event, CompositionContext context)
{
var (argument, field, type, schema) = @event;

// Introspection arguments must be accessible.
if (type.IsIntrospectionType && !ValidationHelper.IsAccessible(argument))
{
context.Log.Write(
DisallowedInaccessibleIntrospectionArgument(
argument,
field.Name,
type.Name,
schema));
}
}

public void Handle(DirectiveArgumentEvent @event, CompositionContext context)
{
var (argument, directive, schema) = @event;

// Built-in directive arguments must be accessible.
if (BuiltIns.IsBuiltInDirective(directive.Name) && !ValidationHelper.IsAccessible(argument))
{
context.Log.Write(
DisallowedInaccessibleDirectiveArgument(
argument,
directive.Name,
schema));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ internal static class WellKnownDirectiveNames
{
public const string External = "external";
public const string Inaccessible = "inaccessible";
public const string External = "external";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using HotChocolate.Fusion;
using HotChocolate.Fusion.Logging;
using HotChocolate.Fusion.PreMergeValidation;
using HotChocolate.Fusion.PreMergeValidation.Rules;
using HotChocolate.Skimmed.Serialization;

namespace HotChocolate.Composition.PreMergeValidation.Rules;

public sealed class ExternalArgumentsDefaultMismatchRuleTests
{
[Test]
[MethodDataSource(nameof(ValidExamplesData))]
public async Task Examples_Valid(string[] sdl)
{
// arrange
var log = new CompositionLog();
var context = new CompositionContext([.. sdl.Select(SchemaParser.Parse)], log);
var preMergeValidator = new PreMergeValidator([new ExternalArgumentsDefaultMismatchRule()]);

// act
var result = preMergeValidator.Validate(context);

// assert
await Assert.That(result.IsSuccess).IsTrue();
await Assert.That(log.IsEmpty).IsTrue();
}

[Test]
[MethodDataSource(nameof(InvalidExamplesData))]
public async Task Examples_Invalid(string[] sdl)
{
// arrange
var log = new CompositionLog();
var context = new CompositionContext([.. sdl.Select(SchemaParser.Parse)], log);
var preMergeValidator = new PreMergeValidator([new ExternalArgumentsDefaultMismatchRule()]);

// act
var result = preMergeValidator.Validate(context);

// assert
await Assert.That(result.IsFailure).IsTrue();
await Assert.That(log.Count()).IsEqualTo(1);
await Assert.That(log.First().Code).IsEqualTo("EXTERNAL_ARGUMENT_DEFAULT_MISMATCH");
await Assert.That(log.First().Severity).IsEqualTo(LogSeverity.Error);
}

public static IEnumerable<Func<string[]>> ValidExamplesData()
{
return
[
// Fields with the same arguments are mergeable.
() =>
[
"""
type Product {
name(language: String = "en"): String
}
""",
"""
type Product {
name(language: String = "en"): String @external
}
"""
],
];
}

public static IEnumerable<Func<string[]>> InvalidExamplesData()
{
return
[
// Fields are not mergeable if the default arguments do not match
() =>
[
"""
type Product {
name(language: String = "en"): String
}
""",
"""
type Product {
name(language: String = "de"): String @external
}
"""
],
() =>
[
"""
type Product {
name(language: String = "en"): String
}
""",
"""
type Product {
name(language: String): String @external
}
"""
]
];
}
}

0 comments on commit ad5d476

Please sign in to comment.