Skip to content
Draft
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
239 changes: 239 additions & 0 deletions src/HotChocolate/Core/test/Types.Analyzers.Tests/ConnectionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
namespace HotChocolate.Types;

public class ConnectionTests
{
[Fact]
public async Task Build_Schema_Should_Reuse_Single_Connection_When_Same_Type_Paged_Twice_In_One_Assembly()
{
// arrange
const string source =
"""
using System.Linq;
using HotChocolate.Types;

namespace Demo.SingleAssembly;

public sealed class Author
{
public int Id { get; set; }

public string Name { get; set; } = string.Empty;
}

[QueryType]
public static partial class Query
{
[UsePaging(InferConnectionNameFromField = false)]
public static IQueryable<Author> GetAuthors() => default!;

[UsePaging(InferConnectionNameFromField = false)]
public static IQueryable<Author> GetMoreAuthors() => default!;
}
""";

// act
var schema = await GeneratorTestServer.CreateSchemaAsync(
source,
disableDefaultSecurity: false);

// assert
schema.MatchSnapshot();
}

[Fact]
public async Task Build_Schema_Should_Reuse_Single_Connection_When_Same_Type_Paged_Across_Two_Assemblies()
{
// arrange
const string authorAssemblySource =
"""
namespace Demo.CrossAssembly;

public sealed class Author
{
public int Id { get; set; }

public string Name { get; set; } = string.Empty;
}
""";

const string assemblyOneSource =
"""
using System.Linq;
using HotChocolate.Types;
using Demo.CrossAssembly;

namespace Demo.CrossAssembly.One;

[QueryType]
public static partial class AssemblyOneQuery
{
[UsePaging(InferConnectionNameFromField = false)]
public static IQueryable<Author> GetAuthors() => default!;
}
""";

const string assemblyTwoSource =
"""
using System.Linq;
using HotChocolate.Types;
using Demo.CrossAssembly;

namespace Demo.CrossAssembly.Two;

[QueryType]
public static partial class AssemblyTwoQuery
{
[UsePaging(InferConnectionNameFromField = false)]
public static IQueryable<Author> GetMoreAuthors() => default!;
}
""";

var assemblies = new GeneratorAssembly[]
{
new(
"Demo.ConnectionAssemblyAuthor",
[authorAssemblySource],
References: [],
Register: false),
new(
"Demo.ConnectionAssemblyOne",
[assemblyOneSource],
References: ["Demo.ConnectionAssemblyAuthor"]),
new(
"Demo.ConnectionAssemblyTwo",
[assemblyTwoSource],
References: ["Demo.ConnectionAssemblyAuthor"])
};

// act
var schema = await GeneratorTestServer.CreateSchemaAsync(
assemblies,
disableDefaultSecurity: false);

// assert
schema.MatchSnapshot();
}

[Fact]
public async Task Build_Schema_Should_Reuse_Single_Connection_When_Static_And_NonStatic_Query_Page_Same_Type_In_One_Assembly()
{
// arrange
// A reflection-based (non-static) query type and a source-generated (static partial)
// query type page the same Author in the same assembly, so both fields should resolve
// to one shared "AuthorConnection".
const string source =
"""
using System.Linq;
using HotChocolate.Types;

namespace Demo.MixedSingleAssembly;

public sealed class Author
{
public int Id { get; set; }

public string Name { get; set; } = string.Empty;
}

[QueryType]
public class RuntimeQuery
{
[UsePaging(InferConnectionNameFromField = false)]
public IQueryable<Author> GetAuthors() => default!;
}

[QueryType]
public static partial class GeneratedQuery
{
[UsePaging(InferConnectionNameFromField = false)]
public static IQueryable<Author> GetMoreAuthors() => default!;
}
""";

// act
var schema = await GeneratorTestServer.CreateSchemaAsync(
source,
disableDefaultSecurity: false);

// assert
schema.MatchSnapshot();
}

[Fact]
public async Task Build_Schema_Should_Reuse_Single_Connection_When_Static_And_NonStatic_Query_Page_Same_Type_Across_Assemblies()
{
// arrange
// The shared Author lives in its own assembly. One assembly pages it from a
// reflection-based (non-static) query type, the other from a source-generated
// (static partial) query type, so both fields should resolve to one shared
// "AuthorConnection".
const string authorAssemblySource =
"""
namespace Demo.MixedCrossAssembly;

public sealed class Author
{
public int Id { get; set; }

public string Name { get; set; } = string.Empty;
}
""";

const string runtimeAssemblySource =
"""
using System.Linq;
using HotChocolate.Types;
using Demo.MixedCrossAssembly;

namespace Demo.MixedCrossAssembly.Runtime;

[QueryType]
public class RuntimeQuery
{
[UsePaging(InferConnectionNameFromField = false)]
public IQueryable<Author> GetAuthors() => default!;
}
""";

const string generatedAssemblySource =
"""
using System.Linq;
using HotChocolate.Types;
using Demo.MixedCrossAssembly;

namespace Demo.MixedCrossAssembly.Generated;

[QueryType]
public static partial class GeneratedQuery
{
[UsePaging(InferConnectionNameFromField = false)]
public static IQueryable<Author> GetMoreAuthors() => default!;
}
""";

var assemblies = new GeneratorAssembly[]
{
new(
"Demo.MixedAssemblyAuthor",
[authorAssemblySource],
References: [],
Register: false),
new(
"Demo.MixedAssemblyRuntime",
[runtimeAssemblySource],
References: ["Demo.MixedAssemblyAuthor"]),
new(
"Demo.MixedAssemblyGenerated",
[generatedAssemblySource],
References: ["Demo.MixedAssemblyAuthor"])
};

// act
var schema = await GeneratorTestServer.CreateSchemaAsync(
assemblies,
disableDefaultSecurity: false);

// assert
schema.MatchSnapshot();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using Basic.Reference.Assemblies;
using GreenDonut;
using GreenDonut.Data;
using HotChocolate.Data.Filters;
using HotChocolate.Execution;
using HotChocolate.Execution.Configuration;
using HotChocolate.Execution.Processing;
using HotChocolate.Features;
using HotChocolate.Language;
using HotChocolate.Language.Visitors;
using HotChocolate.Types.Pagination;
using Microsoft.AspNetCore.Builder;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;

namespace HotChocolate.Types;

/// <summary>
/// Provides the metadata references that mirror the HotChocolate package graph an
/// application using the source generator would compile against. The list is shared by
/// every in-memory compilation in this test project so that the generated registration
/// code, the HotChocolate runtime, and the test process all bind to the same loaded
/// assemblies (a requirement for type identity to hold across assembly load contexts).
/// </summary>
internal static class GeneratorReferences
{
/// <summary>
/// Gets the metadata references for an in-memory HotChocolate compilation. The list
/// combines the framework reference assemblies for the active target framework with
/// metadata references to the loaded HotChocolate assemblies.
/// </summary>
public static IReadOnlyList<PortableExecutableReference> All { get; } =
[
#if NET8_0
.. Net80.References.All,
#elif NET9_0
.. Net90.References.All,
#elif NET10_0
.. Net100.References.All,
#endif
// HotChocolate.Primitives
MetadataReference.CreateFromFile(typeof(ITypeSystemMember).Assembly.Location),

// HotChocolate.Execution
MetadataReference.CreateFromFile(typeof(RequestDelegate).Assembly.Location),

// HotChocolate.Execution.Abstractions
MetadataReference.CreateFromFile(typeof(RequestContext).Assembly.Location),

// HotChocolate.Execution.Processing
MetadataReference.CreateFromFile(typeof(HotChocolateExecutionSelectionExtensions).Assembly.Location),

// HotChocolate.Execution.Abstractions
MetadataReference.CreateFromFile(typeof(IRequestExecutorBuilder).Assembly.Location),

// HotChocolate.Execution.Operation.Abstractions
MetadataReference.CreateFromFile(typeof(ISelection).Assembly.Location),

// HotChocolate.Types
MetadataReference.CreateFromFile(typeof(ObjectTypeAttribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(QueryTypeAttribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Connection).Assembly.Location),
MetadataReference.CreateFromFile(typeof(PageConnection<>).Assembly.Location),

// HotChocolate.Types.Abstractions
MetadataReference.CreateFromFile(typeof(ISchemaDefinition).Assembly.Location),

// HotChocolate.Features
MetadataReference.CreateFromFile(typeof(IFeatureProvider).Assembly.Location),

// HotChocolate.Language
MetadataReference.CreateFromFile(typeof(OperationType).Assembly.Location),

// HotChocolate.Language.Utf8
MetadataReference.CreateFromFile(typeof(ParserOptions).Assembly.Location),

// HotChocolate.Language.Visitors
MetadataReference.CreateFromFile(typeof(SyntaxVisitor).Assembly.Location),

// HotChocolate.Abstractions
MetadataReference.CreateFromFile(typeof(ParentAttribute).Assembly.Location),

// HotChocolate.AspNetCore
MetadataReference.CreateFromFile(
typeof(HotChocolateAspNetCoreServiceCollectionExtensions).Assembly.Location),

// GreenDonut
MetadataReference.CreateFromFile(typeof(DataLoaderBase<,>).Assembly.Location),
MetadataReference.CreateFromFile(typeof(IDataLoader).Assembly.Location),

// GreenDonut.Data
MetadataReference.CreateFromFile(typeof(PagingArguments).Assembly.Location),
MetadataReference.CreateFromFile(typeof(IPredicateBuilder).Assembly.Location),
MetadataReference.CreateFromFile(typeof(DefaultPredicateBuilder).Assembly.Location),

// HotChocolate.Data
MetadataReference.CreateFromFile(typeof(IFilterContext).Assembly.Location),

// Microsoft.AspNetCore
MetadataReference.CreateFromFile(typeof(WebApplication).Assembly.Location),

// Microsoft.Extensions.DependencyInjection.Abstractions
MetadataReference.CreateFromFile(typeof(IServiceCollection).Assembly.Location),

// Microsoft.AspNetCore.Authorization
MetadataReference.CreateFromFile(typeof(Microsoft.AspNetCore.Authorization.AuthorizeAttribute).Assembly.Location),

// HotChocolate.Authorization
MetadataReference.CreateFromFile(typeof(Authorization.AuthorizeAttribute).Assembly.Location),

// HotChocolate.Types.OffsetPagination
MetadataReference.CreateFromFile(typeof(UseOffsetPagingAttribute).Assembly.Location)
];
}
Loading
Loading