diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationDirectiveNames.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationDirectiveNames.cs index eba74e40c74..f6e59acc3b0 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationDirectiveNames.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationDirectiveNames.cs @@ -6,6 +6,7 @@ internal static class FederationDirectiveNames public const string Requires = "requires"; public const string Provides = "provides"; public const string External = "external"; + public const string Extends = "extends"; public const string Link = "link"; public const string Shareable = "shareable"; public const string Inaccessible = "inaccessible"; diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationSchemaTransformer.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationSchemaTransformer.cs index 3b94a13a894..9fac12cbbc2 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationSchemaTransformer.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationSchemaTransformer.cs @@ -100,6 +100,7 @@ public static CompositionResult Transform(string federationSdl) RemoveExternalFields.Apply(schema); GenerateNodeLookup.Apply(schema); SourceExternalFieldMetadata.WriteMarker(schema); + RemoveEmptyQueryRoot.Apply(schema); return SchemaFormatter.FormatAsString(schema); } diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationV1DirectiveDefinitions.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationV1DirectiveDefinitions.cs new file mode 100644 index 00000000000..726705a63ae --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationV1DirectiveDefinitions.cs @@ -0,0 +1,59 @@ +using HotChocolate.Fusion.Definitions; +using HotChocolate.Types; +using HotChocolate.Types.Mutable; + +namespace HotChocolate.Fusion.ApolloFederation; + +internal static class FederationV1DirectiveDefinitions +{ + public static void Apply(MutableSchemaDefinition schema) + { + if (!schema.Types.TryGetType( + WellKnownTypeNames.FieldSelectionSet, + out var fieldSelectionSetType)) + { + return; + } + + Replace(schema, CreateKey(fieldSelectionSetType)); + Replace(schema, new RequiresMutableDirectiveDefinition(fieldSelectionSetType)); + Replace(schema, CreateExternal()); + Replace(schema, CreateExtends()); + } + + private static MutableDirectiveDefinition CreateKey( + MutableScalarTypeDefinition fieldSelectionSetType) + { + var definition = new MutableDirectiveDefinition(FederationDirectiveNames.Key) + { + IsRepeatable = true, + Locations = DirectiveLocation.Object | DirectiveLocation.Interface + }; + + definition.Arguments.Add( + new MutableInputFieldDefinition( + WellKnownArgumentNames.Fields, + new NonNullType(fieldSelectionSetType))); + return definition; + } + + private static MutableDirectiveDefinition CreateExternal() + => new(FederationDirectiveNames.External) + { + Locations = DirectiveLocation.FieldDefinition + }; + + private static MutableDirectiveDefinition CreateExtends() + => new(FederationDirectiveNames.Extends) + { + Locations = DirectiveLocation.Object | DirectiveLocation.Interface + }; + + private static void Replace( + MutableSchemaDefinition schema, + MutableDirectiveDefinition definition) + { + schema.DirectiveDefinitions.Remove(definition.Name); + schema.DirectiveDefinitions.Add(definition); + } +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationV1SchemaAnalyzer.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationV1SchemaAnalyzer.cs new file mode 100644 index 00000000000..ca50f558914 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/FederationV1SchemaAnalyzer.cs @@ -0,0 +1,203 @@ +using HotChocolate.Fusion.Logging; +using HotChocolate.Fusion.Logging.Contracts; +using HotChocolate.Types; +using HotChocolate.Types.Mutable; +using static HotChocolate.Fusion.ApolloFederation.Properties.FederationResources; + +namespace HotChocolate.Fusion.ApolloFederation; + +internal static class FederationV1SchemaAnalyzer +{ + private static readonly Dictionary s_signatures = new( + StringComparer.Ordinal) + { + [FederationDirectiveNames.Key] = new( + DirectiveLocation.Object | DirectiveLocation.Interface, + WellKnownArgumentNames.Fields, + "the required 'fields' argument only"), + [FederationDirectiveNames.Extends] = new( + DirectiveLocation.Object | DirectiveLocation.Interface, + null, + "no arguments"), + [FederationDirectiveNames.External] = new( + DirectiveLocation.FieldDefinition, + null, + "no arguments"), + [FederationDirectiveNames.Requires] = new( + DirectiveLocation.FieldDefinition, + WellKnownArgumentNames.Fields, + "the required 'fields' argument only"), + [FederationDirectiveNames.Provides] = new( + DirectiveLocation.FieldDefinition, + WellKnownArgumentNames.Fields, + "the required 'fields' argument only"), + [FederationDirectiveNames.Tag] = new( + DirectiveLocation.FieldDefinition + | DirectiveLocation.Object + | DirectiveLocation.Interface + | DirectiveLocation.Union + | DirectiveLocation.ArgumentDefinition + | DirectiveLocation.Scalar + | DirectiveLocation.Enum + | DirectiveLocation.EnumValue + | DirectiveLocation.InputObject + | DirectiveLocation.InputFieldDefinition, + "name", + "the required 'name' argument only") + }; + + public static void Validate( + MutableSchemaDefinition schema, + ICompositionLog log) + { + ValidateProvider(schema, schema, DirectiveLocation.Schema, "schema", log); + + foreach (var type in schema.Types) + { + ValidateProvider(schema, type, GetTypeLocation(type), type.Name, log); + + switch (type) + { + case MutableComplexTypeDefinition complexType: + foreach (var field in complexType.Fields) + { + ValidateProvider( + schema, + field, + DirectiveLocation.FieldDefinition, + field.Coordinate.ToString(), + log); + + foreach (var argument in field.Arguments) + { + ValidateProvider( + schema, + argument, + DirectiveLocation.ArgumentDefinition, + argument.Coordinate.ToString(), + log); + } + } + + break; + + case MutableInputObjectTypeDefinition inputObjectType: + foreach (var field in inputObjectType.Fields) + { + ValidateProvider( + schema, + field, + DirectiveLocation.InputFieldDefinition, + field.Coordinate.ToString(), + log); + } + + break; + + case MutableEnumTypeDefinition enumType: + foreach (var value in enumType.Values) + { + ValidateProvider( + schema, + value, + DirectiveLocation.EnumValue, + value.Coordinate.ToString(), + log); + } + + break; + } + } + + foreach (var directiveDefinition in schema.DirectiveDefinitions) + { + ValidateProvider( + schema, + directiveDefinition, + DirectiveLocation.DirectiveDefinition, + $"@{directiveDefinition.Name}", + log); + + foreach (var argument in directiveDefinition.Arguments) + { + ValidateProvider( + schema, + argument, + DirectiveLocation.ArgumentDefinition, + $"@{directiveDefinition.Name}({argument.Name}:)", + log); + } + } + } + + private static void ValidateProvider( + MutableSchemaDefinition schema, + IDirectivesProvider provider, + DirectiveLocation location, + string member, + ICompositionLog log) + { + foreach (var directive in provider.Directives) + { + if (!s_signatures.TryGetValue(directive.Name, out var signature)) + { + continue; + } + + if ((signature.Locations & location) == 0) + { + log.Write( + LogEntryBuilder.New() + .SetMessage( + FederationV1SchemaAnalyzer_InvalidDirectiveLocation, + schema.Name, + directive.Name, + member) + .SetCode(LogEntryCodes.FederationV1DirectiveNotSupported) + .SetSeverity(LogSeverity.Error) + .SetSchema(schema) + .SetTypeSystemMember(provider) + .Build()); + } + + var argumentsAreValid = signature.RequiredArgument is { } requiredArgument + ? directive.Arguments.Count == 1 + && directive.Arguments.ContainsName(requiredArgument) + : directive.Arguments.Count == 0; + + if (!argumentsAreValid) + { + log.Write( + LogEntryBuilder.New() + .SetMessage( + FederationV1SchemaAnalyzer_InvalidDirectiveArguments, + schema.Name, + directive.Name, + member, + signature.ExpectedArguments) + .SetCode(LogEntryCodes.FederationV1DirectiveNotSupported) + .SetSeverity(LogSeverity.Error) + .SetSchema(schema) + .SetTypeSystemMember(provider) + .Build()); + } + } + } + + private static DirectiveLocation GetTypeLocation(ITypeDefinition type) + => type switch + { + IObjectTypeDefinition => DirectiveLocation.Object, + IInterfaceTypeDefinition => DirectiveLocation.Interface, + IUnionTypeDefinition => DirectiveLocation.Union, + IScalarTypeDefinition => DirectiveLocation.Scalar, + IEnumTypeDefinition => DirectiveLocation.Enum, + IInputObjectTypeDefinition => DirectiveLocation.InputObject, + _ => 0 + }; + + private sealed record DirectiveSignature( + DirectiveLocation Locations, + string? RequiredArgument, + string ExpectedArguments); +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/Properties/FederationResources.Designer.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/Properties/FederationResources.Designer.cs index e9060eef903..33dcff9a39a 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/Properties/FederationResources.Designer.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/Properties/FederationResources.Designer.cs @@ -77,6 +77,24 @@ internal static string FederationSchemaAnalyzer_FederationV1NotSupported { } } + /// + /// Looks up a localized string similar to Apollo Federation v1 source schema '{0}' applies '@{1}' to '{2}' with invalid arguments. Expected {3}.. + /// + internal static string FederationV1SchemaAnalyzer_InvalidDirectiveArguments { + get { + return ResourceManager.GetString("FederationV1SchemaAnalyzer_InvalidDirectiveArguments", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Apollo Federation v1 source schema '{0}' applies '@{1}' at unsupported location '{2}'.. + /// + internal static string FederationV1SchemaAnalyzer_InvalidDirectiveLocation { + get { + return ResourceManager.GetString("FederationV1SchemaAnalyzer_InvalidDirectiveLocation", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed to parse federation SDL: {0}. /// diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/Properties/FederationResources.resx b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/Properties/FederationResources.resx index 8f0f053a869..90fc702bfe2 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/Properties/FederationResources.resx +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/Properties/FederationResources.resx @@ -24,7 +24,13 @@ The @{0} directive is not supported. - + + Apollo Federation v1 source schema '{0}' applies '@{1}' to '{2}' with invalid arguments. Expected {3}. + + + Apollo Federation v1 source schema '{0}' applies '@{1}' at unsupported location '{2}'. + + Failed to parse federation SDL: {0} diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/RemoveEmptyQueryRoot.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/RemoveEmptyQueryRoot.cs new file mode 100644 index 00000000000..27ed3e13fc6 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/RemoveEmptyQueryRoot.cs @@ -0,0 +1,20 @@ +using HotChocolate.Types.Mutable; + +namespace HotChocolate.Fusion.ApolloFederation; + +/// +/// Removes an empty query root from a mutable source schema. +/// +internal static class RemoveEmptyQueryRoot +{ + public static void Apply(MutableSchemaDefinition schema) + { + if (schema.QueryType is not { Fields.Count: 0 } emptyQueryRoot) + { + return; + } + + schema.Types.Remove(emptyQueryRoot); + schema.QueryType = null; + } +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/RemoveExternalFields.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/RemoveExternalFields.cs index 4ec4c3997ae..c3e11ffb0c6 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/RemoveExternalFields.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/RemoveExternalFields.cs @@ -223,7 +223,7 @@ private static bool IsReferencedByOutputField( return false; } - private static HashSet<(string TypeName, string FieldName)> CollectProvidesReferences( + internal static HashSet<(string TypeName, string FieldName)> CollectProvidesReferences( MutableSchemaDefinition schema) { var referenced = new HashSet<(string, string)>(); @@ -265,13 +265,19 @@ private static bool IsReferencedByOutputField( var namedType = field.Type.NamedType(); - if (!schema.Types.TryGetType( - namedType.Name, out var targetType)) + if (!schema.Types.TryGetType(namedType.Name, out var targetType) + || targetType is not MutableComplexTypeDefinition + and not MutableUnionTypeDefinition) { continue; } - CollectReferencedFields(selectionSet, targetType, schema, referenced); + CollectReferencedFields( + selectionSet, + targetType, + schema, + referenced, + includeInterfaceRuntimeFields: true); } } @@ -411,7 +417,12 @@ private static bool IsReferencedByOutputField( continue; } - CollectReferencedFields(selectionSet, complexType, schema, referenced); + CollectReferencedFields( + selectionSet, + complexType, + schema, + referenced, + includeInterfaceRuntimeFields: false); } } @@ -420,41 +431,73 @@ private static bool IsReferencedByOutputField( private static void CollectReferencedFields( SelectionSetNode selectionSet, - MutableComplexTypeDefinition currentType, + ITypeDefinition currentType, MutableSchemaDefinition schema, - HashSet<(string, string)> referenced) + HashSet<(string, string)> referenced, + bool includeInterfaceRuntimeFields) { foreach (var selection in selectionSet.Selections) { switch (selection) { - case FieldNode fieldNode: - referenced.Add((currentType.Name, fieldNode.Name.Value)); + case FieldNode fieldNode + when currentType is MutableComplexTypeDefinition complexType: + referenced.Add((complexType.Name, fieldNode.Name.Value)); + + if (includeInterfaceRuntimeFields + && complexType is MutableInterfaceTypeDefinition interfaceType) + { + foreach (var possibleType in schema.GetPossibleTypes(interfaceType)) + { + if (possibleType.Fields.ContainsName(fieldNode.Name.Value)) + { + referenced.Add((possibleType.Name, fieldNode.Name.Value)); + } + } + } if (fieldNode.SelectionSet?.Selections.Count > 0 - && currentType.Fields.TryGetField( + && complexType.Fields.TryGetField( fieldNode.Name.Value, out var nestedField)) { var nestedNamedType = nestedField.Type.NamedType(); - if (schema.Types.TryGetType( - nestedNamedType.Name, out var nestedType)) + if (schema.Types.TryGetType(nestedNamedType.Name, out var nestedType) + && nestedType is MutableComplexTypeDefinition + or MutableUnionTypeDefinition) { CollectReferencedFields( - fieldNode.SelectionSet, nestedType, schema, referenced); + fieldNode.SelectionSet, + nestedType, + schema, + referenced, + includeInterfaceRuntimeFields); } } break; - case InlineFragmentNode inlineFragment - when inlineFragment.TypeCondition is not null: + case InlineFragmentNode { TypeCondition: null } inlineFragment: + CollectReferencedFields( + inlineFragment.SelectionSet, + currentType, + schema, + referenced, + includeInterfaceRuntimeFields); + break; + + case InlineFragmentNode { TypeCondition: { } typeCondition } inlineFragment: if (schema.Types.TryGetType( - inlineFragment.TypeCondition.Name.Value, out var fragmentType)) + typeCondition.Name.Value, out var fragmentType) + && schema.GetPossibleTypes(currentType).Contains(fragmentType)) { CollectReferencedFields( - inlineFragment.SelectionSet, fragmentType, schema, referenced); + inlineFragment.SelectionSet, + fragmentType, + schema, + referenced, + includeInterfaceRuntimeFields); } break; diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/RemoveFederationInfrastructure.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/RemoveFederationInfrastructure.cs index 14e6d622b51..e7ecacc0d00 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/RemoveFederationInfrastructure.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/ApolloFederation/RemoveFederationInfrastructure.cs @@ -16,6 +16,7 @@ internal static class RemoveFederationInfrastructure FederationDirectiveNames.Requires, FederationDirectiveNames.Provides, FederationDirectiveNames.External, + FederationDirectiveNames.Extends, FederationDirectiveNames.Link, FederationDirectiveNames.Shareable, FederationDirectiveNames.Inaccessible, @@ -57,13 +58,21 @@ public static void Apply(MutableSchemaDefinition schema) } // Remove @link directives from schema. - var linkDirectives = schema.Directives[FederationDirectiveNames.Link].ToList(); - - foreach (var directive in linkDirectives) + foreach (var directive in schema.Directives[FederationDirectiveNames.Link].ToList()) { schema.Directives.Remove(directive); } + // Federation v1's @extends marker is represented by the source schema's type + // contribution after preprocessing and has no Composite Schema equivalent. + foreach (var type in schema.Types.OfType()) + { + foreach (var directive in type.Directives[FederationDirectiveNames.Extends].ToList()) + { + type.Directives.Remove(directive); + } + } + // Collect the type names still referenced after the federation directives and fields // above have been removed. Federation types such as FieldSet are exported vocabulary and // may be used by user-defined members (for example as a custom directive argument type). diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/CompositionHelper.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/CompositionHelper.cs index cea4685028b..bb75db58e50 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/CompositionHelper.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/CompositionHelper.cs @@ -90,45 +90,74 @@ await archive.GetSourceSchemaNamesAsync(cancellationToken), var satisfiabilityOptions = mergedCompositionSettings.Satisfiability.ToOptions(); var apolloFederationCompatibilityOptions = mergedCompositionSettings.ApolloFederationCompatibility.ToOptions(); + var runtimeSourceSchemaSettings = new List(sourceSchemas.Count); + var runtimeSettingsDocuments = new List(); + CompositionResult result; + using var bufferWriter = new PooledArrayWriter(); - foreach (var (sourceSchemaName, (_, sourceSchemaSettings)) in sourceSchemas) + try { - var schemaSettings = - sourceSchemaSettings.Deserialize(SettingsJsonSerializerContext.Default.SourceSchemaSettings)!; - - var sourceSchemaOptions = schemaSettings.ToOptions(); + foreach (var (sourceSchemaName, (_, sourceSchemaSettings)) in sourceSchemas) + { + if (!SourceSchemaSettingsReader.TryRead( + sourceSchemaName, + sourceSchemaSettings, + compositionLog, + out var settingsResult)) + { + return (ImmutableArray)[new("❌ Composition failed")]; + } + + var sourceSchemaOptions = settingsResult.Options; + + mergedCompositionSettings.Preprocessor?.MergeInto(sourceSchemaOptions.Preprocessor); + sourceSchemaOptionsMap.Add(sourceSchemaName, sourceSchemaOptions); + settingsResult.Settings.Satisfiability?.MergeInto(satisfiabilityOptions); + + if (settingsResult.RuntimeSettings is { } runtimeSettings) + { + runtimeSettingsDocuments.Add(runtimeSettings); + runtimeSourceSchemaSettings.Add(runtimeSettings.RootElement); + } + else + { + runtimeSourceSchemaSettings.Add(sourceSchemaSettings.RootElement); + } + } - mergedCompositionSettings.Preprocessor?.MergeInto(sourceSchemaOptions.Preprocessor); - sourceSchemaOptionsMap.Add(sourceSchemaName, sourceSchemaOptions); - schemaSettings.Satisfiability?.MergeInto(satisfiabilityOptions); - } + var schemaComposerOptions = new SchemaComposerOptions + { + SourceSchemas = sourceSchemaOptionsMap, + Merger = mergerOptions, + Satisfiability = satisfiabilityOptions, + ApolloFederationCompatibility = apolloFederationCompatibilityOptions + }; - var schemaComposerOptions = new SchemaComposerOptions - { - SourceSchemas = sourceSchemaOptionsMap, - Merger = mergerOptions, - Satisfiability = satisfiabilityOptions, - ApolloFederationCompatibility = apolloFederationCompatibilityOptions - }; + var schemaComposer = new SchemaComposer( + sourceSchemas.Select(s => s.Value.Item1), + schemaComposerOptions, + compositionLog); - var schemaComposer = new SchemaComposer( - sourceSchemas.Select(s => s.Value.Item1), - schemaComposerOptions, - compositionLog); + result = schemaComposer.Compose(); - var result = schemaComposer.Compose(); + if (result.IsFailure) + { + return result; + } - if (result.IsFailure) + new SettingsComposer().Compose( + bufferWriter, + [.. runtimeSourceSchemaSettings], + environment); + } + finally { - return result; + foreach (var runtimeSettingsDocument in runtimeSettingsDocuments) + { + runtimeSettingsDocument.Dispose(); + } } - using var bufferWriter = new PooledArrayWriter(); - new SettingsComposer().Compose( - bufferWriter, - sourceSchemas.Select(s => s.Value.Item2.RootElement).ToArray(), - environment); - var metadata = new ArchiveMetadata { SupportedGatewayFormats = [WellKnownVersions.LatestGatewayFormatVersion], diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/HotChocolate.Fusion.Composition.csproj b/src/HotChocolate/Fusion/src/Fusion.Composition/HotChocolate.Fusion.Composition.csproj index 33f1e51550e..59a93281e25 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/HotChocolate.Fusion.Composition.csproj +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/HotChocolate.Fusion.Composition.csproj @@ -13,6 +13,7 @@ + diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/Logging/LogEntryCodes.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/Logging/LogEntryCodes.cs index d2c090ee9d8..f5bb4ab54a4 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/Logging/LogEntryCodes.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/Logging/LogEntryCodes.cs @@ -2,6 +2,8 @@ namespace HotChocolate.Fusion.Logging; public static class LogEntryCodes { + public const string ConflictingApolloFederationVersion = + "CONFLICTING_APOLLO_FEDERATION_VERSION"; public const string ConflictingSourceSchemaName = "CONFLICTING_SOURCE_SCHEMA_NAME"; public const string CursorArgumentNotString = "CURSOR_ARGUMENT_NOT_STRING"; public const string CursorArgumentRequiresCursorField = "CURSOR_ARGUMENT_REQUIRES_CURSOR_FIELD"; @@ -27,6 +29,8 @@ public static class LogEntryCodes public const string ExternalUnused = "EXTERNAL_UNUSED"; public const string FederationDirectiveNotSupported = "FEDERATION_DIRECTIVE_NOT_SUPPORTED"; public const string FederationV1NotSupported = "FEDERATION_V1_NOT_SUPPORTED"; + public const string FederationV1DirectiveNotSupported = + "FEDERATION_V1_DIRECTIVE_NOT_SUPPORTED"; public const string FieldArgumentTypesNotMergeable = "FIELD_ARGUMENT_TYPES_NOT_MERGEABLE"; public const string FieldWithMissingRequiredArgument = "FIELD_WITH_MISSING_REQUIRED_ARGUMENT"; public const string ImplementedByInaccessible = "IMPLEMENTED_BY_INACCESSIBLE"; @@ -42,6 +46,8 @@ public static class LogEntryCodes public const string InterfaceObjectNoInterface = "INTERFACE_OBJECT_NO_INTERFACE"; public const string InvalidFieldSharing = "INVALID_FIELD_SHARING"; public const string InvalidGraphQL = "INVALID_GRAPHQL"; + public const string InvalidApolloFederationSupportSettings = + "INVALID_APOLLO_FEDERATION_SUPPORT_SETTINGS"; public const string InvalidNodeResolution = "INVALID_NODE_RESOLUTION"; public const string InvalidProjectedFieldSharing = "INVALID_PROJECTED_FIELD_SHARING"; public const string InvalidShareableUsage = "INVALID_SHAREABLE_USAGE"; diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/Options/SourceSchemaOptions.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/Options/SourceSchemaOptions.cs index 8090c914d3a..5737b3529f3 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/Options/SourceSchemaOptions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/Options/SourceSchemaOptions.cs @@ -23,4 +23,6 @@ public sealed class SourceSchemaOptions /// The severity used when a field is deprecated but the implemented interface field is not. /// public LogSeverity InvalidFieldDeprecationSeverity { get; set; } = LogSeverity.Warning; + + internal bool IsApolloFederationV1 { get; set; } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/Properties/CompositionResources.Designer.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/Properties/CompositionResources.Designer.cs index e8e974937d1..15375ae10a1 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/Properties/CompositionResources.Designer.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/Properties/CompositionResources.Designer.cs @@ -1843,6 +1843,51 @@ internal static string ShareableMutableDirectiveDefinition_Description { } } + /// + /// Looks up a localized string similar to The source schema setting '{0}' for source schema '{1}' must be an object containing only the 'version' property.. + /// + internal static string SourceSchemaSettingsReader_InvalidApolloFederationSupportShape { + get { + return ResourceManager.GetString("SourceSchemaSettingsReader_InvalidApolloFederationSupportShape", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The source schema setting '{0}' for source schema '{1}' must be an object, but found '{2}'.. + /// + internal static string SourceSchemaSettingsReader_SettingMustBeObject { + get { + return ResourceManager.GetString("SourceSchemaSettingsReader_SettingMustBeObject", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The source schema setting '{0}' for source schema '{1}' must be a string, but found '{2}'.. + /// + internal static string SourceSchemaSettingsReader_SettingMustBeString { + get { + return ResourceManager.GetString("SourceSchemaSettingsReader_SettingMustBeString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The source schema setting '{0}' for source schema '{1}' has unsupported version '{2}'. The supported version is '1.0'.. + /// + internal static string SourceSchemaSettingsReader_UnsupportedApolloFederationVersion { + get { + return ResourceManager.GetString("SourceSchemaSettingsReader_UnsupportedApolloFederationVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Source schema '{0}' cannot enable Apollo Federation v1 support because it links to the Apollo Federation specification.. + /// + internal static string SourceSchemaParser_ConflictingApolloFederationVersion { + get { + return ResourceManager.GetString("SourceSchemaParser_ConflictingApolloFederationVersion", resourceCulture); + } + } + /// /// Looks up a localized string similar to The @specifiedBy directive must have a 'url' argument of type String.. /// diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/Properties/CompositionResources.resx b/src/HotChocolate/Fusion/src/Fusion.Composition/Properties/CompositionResources.resx index 49cd583038b..aa5841cf8e5 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/Properties/CompositionResources.resx +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/Properties/CompositionResources.resx @@ -54,6 +54,21 @@ Source schema validation failed. + + The source schema setting '{0}' for source schema '{1}' must be an object containing only the 'version' property. + + + The source schema setting '{0}' for source schema '{1}' must be an object, but found '{2}'. + + + The source schema setting '{0}' for source schema '{1}' must be a string, but found '{2}'. + + + The source schema setting '{0}' for source schema '{1}' has unsupported version '{2}'. The supported version is '1.0'. + + + Source schema '{0}' cannot enable Apollo Federation v1 support because it links to the Apollo Federation specification. + The @external directive indicates that a field is recognized by the current source schema but is not directly contributed (resolved) by it. diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/SchemaComposer.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/SchemaComposer.cs index 47640daedfd..ee685cdd54c 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/SchemaComposer.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/SchemaComposer.cs @@ -69,7 +69,8 @@ public CompositionResult Compose() schema, _log, options?.Parser, - options?.InvalidFieldDeprecationSeverity ?? LogSeverity.Warning).Parse(); + options?.InvalidFieldDeprecationSeverity ?? LogSeverity.Warning, + options?.IsApolloFederationV1 ?? false).Parse(); }).Combine(); if (parsingResult.IsFailure) @@ -92,7 +93,8 @@ public CompositionResult Compose() _log, options?.Version, options?.Preprocessor, - options?.InvalidFieldDeprecationSeverity ?? LogSeverity.Warning) + options?.InvalidFieldDeprecationSeverity ?? LogSeverity.Warning, + options?.IsApolloFederationV1 ?? false) .Preprocess(); }).Combine(); diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/Settings/SourceSchemaSettingsReader.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/Settings/SourceSchemaSettingsReader.cs new file mode 100644 index 00000000000..0d9f31f9aac --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/Settings/SourceSchemaSettingsReader.cs @@ -0,0 +1,205 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using HotChocolate.Buffers; +using HotChocolate.Fusion.Logging; +using HotChocolate.Fusion.Logging.Contracts; +using HotChocolate.Fusion.Options; +using static HotChocolate.Fusion.Properties.CompositionResources; + +namespace HotChocolate.Fusion; + +internal static class SourceSchemaSettingsReader +{ + private const string ExtensionsPath = "$.extensions"; + private const string ChilliCreamPath = "$.extensions.chillicream"; + private const string ApolloFederationSupportPath = + "$.extensions.chillicream.apolloFederationSupport"; + private const string ApolloFederationVersionPath = + "$.extensions.chillicream.apolloFederationSupport.version"; + + public static bool TryRead( + string sourceSchemaName, + JsonDocument sourceSchemaSettings, + ICompositionLog compositionLog, + out SourceSchemaSettingsReadResult result) + { + ArgumentException.ThrowIfNullOrEmpty(sourceSchemaName); + ArgumentNullException.ThrowIfNull(sourceSchemaSettings); + ArgumentNullException.ThrowIfNull(compositionLog); + + result = default; + + var root = sourceSchemaSettings.RootElement; + + if (root.ValueKind is not JsonValueKind.Object) + { + WriteMustBeObject(compositionLog, sourceSchemaName, "$", root.ValueKind); + return false; + } + + var schemaSettings = + sourceSchemaSettings.Deserialize(SettingsJsonSerializerContext.Default.SourceSchemaSettings)!; + var options = schemaSettings.ToOptions(); + + if (!root.TryGetProperty("extensions", out var extensions)) + { + result = new(schemaSettings, options, null); + return true; + } + + if (extensions.ValueKind is not JsonValueKind.Object) + { + WriteMustBeObject( + compositionLog, + sourceSchemaName, + ExtensionsPath, + extensions.ValueKind); + return false; + } + + if (!extensions.TryGetProperty("chillicream", out var chilliCream)) + { + result = new(schemaSettings, options, null); + return true; + } + + if (chilliCream.ValueKind is not JsonValueKind.Object) + { + WriteMustBeObject( + compositionLog, + sourceSchemaName, + ChilliCreamPath, + chilliCream.ValueKind); + return false; + } + + if (!chilliCream.TryGetProperty( + "apolloFederationSupport", + out var apolloFederationSupport)) + { + result = new(schemaSettings, options, null); + return true; + } + + if (apolloFederationSupport.ValueKind is not JsonValueKind.Object) + { + WriteMustBeObject( + compositionLog, + sourceSchemaName, + ApolloFederationSupportPath, + apolloFederationSupport.ValueKind); + return false; + } + + using var properties = apolloFederationSupport.EnumerateObject(); + + if (!properties.MoveNext() + || !properties.Current.NameEquals("version") + || properties.MoveNext()) + { + compositionLog.Write( + LogEntryBuilder.New() + .SetMessage( + SourceSchemaSettingsReader_InvalidApolloFederationSupportShape, + ApolloFederationSupportPath, + sourceSchemaName) + .SetCode(LogEntryCodes.InvalidApolloFederationSupportSettings) + .SetSeverity(LogSeverity.Error) + .Build()); + return false; + } + + var version = apolloFederationSupport.GetProperty("version"); + + if (version.ValueKind is not JsonValueKind.String) + { + compositionLog.Write( + LogEntryBuilder.New() + .SetMessage( + SourceSchemaSettingsReader_SettingMustBeString, + ApolloFederationVersionPath, + sourceSchemaName, + version.ValueKind) + .SetCode(LogEntryCodes.InvalidApolloFederationSupportSettings) + .SetSeverity(LogSeverity.Error) + .Build()); + return false; + } + + var versionValue = version.GetString()!; + + if (!versionValue.Equals("1.0", StringComparison.Ordinal)) + { + compositionLog.Write( + LogEntryBuilder.New() + .SetMessage( + SourceSchemaSettingsReader_UnsupportedApolloFederationVersion, + ApolloFederationVersionPath, + sourceSchemaName, + versionValue) + .SetCode(LogEntryCodes.InvalidApolloFederationSupportSettings) + .SetSeverity(LogSeverity.Error) + .Build()); + return false; + } + + options.IsApolloFederationV1 = true; + result = new( + schemaSettings, + options, + RemoveApolloFederationSupport(sourceSchemaSettings)); + return true; + } + + private static JsonDocument RemoveApolloFederationSupport(JsonDocument sourceSchemaSettings) + { + var root = JsonNode.Parse(sourceSchemaSettings.RootElement.GetRawText())!.AsObject(); + var extensions = root["extensions"]!.AsObject(); + var chilliCream = extensions["chillicream"]!.AsObject(); + + chilliCream.Remove("apolloFederationSupport"); + + if (chilliCream.Count == 0) + { + extensions.Remove("chillicream"); + } + + if (extensions.Count == 0) + { + root.Remove("extensions"); + } + + using var buffer = new PooledArrayWriter(); + + using (var writer = new Utf8JsonWriter(buffer)) + { + root.WriteTo(writer); + writer.Flush(); + } + + return JsonDocument.Parse(buffer.WrittenMemory.ToArray()); + } + + private static void WriteMustBeObject( + ICompositionLog compositionLog, + string sourceSchemaName, + string path, + JsonValueKind actualKind) + { + compositionLog.Write( + LogEntryBuilder.New() + .SetMessage( + SourceSchemaSettingsReader_SettingMustBeObject, + path, + sourceSchemaName, + actualKind) + .SetCode(LogEntryCodes.InvalidApolloFederationSupportSettings) + .SetSeverity(LogSeverity.Error) + .Build()); + } +} + +internal readonly record struct SourceSchemaSettingsReadResult( + SourceSchemaSettings Settings, + SourceSchemaOptions Options, + JsonDocument? RuntimeSettings); diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaMerger.InterfaceObject.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaMerger.InterfaceObject.cs index 82dc4990985..e6a1f444c3b 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaMerger.InterfaceObject.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaMerger.InterfaceObject.cs @@ -79,7 +79,7 @@ private void ProjectInterfaceObjectFields(MutableSchemaDefinition mergedSchema) var owner = ResolveEffectiveOwner(objectType, fieldName, standInsByName); if (owner is null - || !owner.Value.ContributingInterface.Fields.TryGetField(fieldName, out var contractField)) + || !owner.Value.Contributions[0].Interface.Fields.TryGetField(fieldName, out var contractField)) { continue; } @@ -90,14 +90,38 @@ private void ProjectInterfaceObjectFields(MutableSchemaDefinition mergedSchema) Type = contractField.Type }; - foreach (var ownerSchema in owner.Value.Schemas) + foreach (var contribution in owner.Value.Contributions) { + var schemaName = _schemaConstantNames[contribution.Schema.Name]; + projected.Directives.Add( new Directive( _fusionDirectiveDefinitions[DirectiveNames.FusionField], new ArgumentAssignment( ArgumentNames.Schema, - new EnumValueNode(_schemaConstantNames[ownerSchema.Name])))); + new EnumValueNode(schemaName)))); + } + + foreach (var contribution in owner.Value.Contributions) + { + var schemaName = _schemaConstantNames[contribution.Schema.Name]; + + if (!contribution.Interface.Fields.TryGetField(fieldName, out var contributingField)) + { + continue; + } + + foreach (var directive in contributingField.Directives[DirectiveNames.FusionRequires]) + { + if (directive.Arguments[ArgumentNames.Schema] is EnumValueNode schema + && schema.Value == schemaName) + { + projected.Directives.Add( + new Directive( + directive.Definition, + directive.Arguments.AsEnumerable())); + } + } } projected.DeclaringMember = objectType; @@ -154,12 +178,12 @@ private void ProjectInterfaceObjectFields(MutableSchemaDefinition mergedSchema) && other.Interface.Implements.ContainsName(c.Interface.Name))) .ToList(); - var schemas = minimal - .Select(c => c.StandIn.Schema) - .Distinct() + var contributions = minimal + .Select(c => new EffectiveOwnerContribution(c.Interface, c.StandIn.Schema)) + .DistinctBy(c => c.Schema.Name, StringComparer.Ordinal) .ToArray(); - return new EffectiveOwner(minimal[0].Interface, schemas); + return new EffectiveOwner(contributions); } /// @@ -403,7 +427,7 @@ private static bool AddEdge(Dictionary> edges, string ty { if (!edges.TryGetValue(type, out var interfaces)) { - interfaces = new HashSet(StringComparer.Ordinal); + interfaces = [with(StringComparer.Ordinal)]; edges[type] = interfaces; } @@ -415,7 +439,9 @@ private readonly record struct StandInInfo( MutableSchemaDefinition Schema, HashSet KeyFieldNames); - private readonly record struct EffectiveOwner( - MutableInterfaceTypeDefinition ContributingInterface, - MutableSchemaDefinition[] Schemas); + private readonly record struct EffectiveOwner(EffectiveOwnerContribution[] Contributions); + + private readonly record struct EffectiveOwnerContribution( + MutableInterfaceTypeDefinition Interface, + MutableSchemaDefinition Schema); } diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaParser.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaParser.cs index 0958e8ee9a4..1c58e88131c 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaParser.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaParser.cs @@ -8,8 +8,11 @@ using HotChocolate.Logging; using HotChocolate.Types.Mutable; using HotChocolate.Types.Mutable.Serialization; +using FusionLogEntryBuilder = HotChocolate.Fusion.Logging.LogEntryBuilder; +using FusionLogEntryCodes = HotChocolate.Fusion.Logging.LogEntryCodes; using LogEntryHelper = HotChocolate.Fusion.Logging.LogEntryHelper; using LogSeverity = HotChocolate.Fusion.Logging.LogSeverity; +using static HotChocolate.Fusion.Properties.CompositionResources; namespace HotChocolate.Fusion; @@ -17,7 +20,8 @@ internal sealed class SourceSchemaParser( SourceSchemaText sourceSchemaText, ICompositionLog log, SourceSchemaParserOptions? options = null, - LogSeverity invalidFieldDeprecationSeverity = LogSeverity.Warning) + LogSeverity invalidFieldDeprecationSeverity = LogSeverity.Warning, + bool isApolloFederationV1 = false) { private static readonly SchemaValidator s_schemaValidator = new(); private readonly SourceSchemaParserOptions _options = options ?? new SourceSchemaParserOptions(); @@ -28,13 +32,19 @@ public CompositionResult Parse() schema.AddBuiltInFusionTypes(); schema.AddBuiltInFusionDirectives(); + if (isApolloFederationV1) + { + FederationV1DirectiveDefinitions.Apply(schema); + } + // Apollo Federation's @requires directive has no Fusion-native definition (the Fusion // equivalent @require differs in name and argument shape). Register it before parsing // federation source schemas so an applied @requires binds to a real definition instead // of a missing one; preprocessing then rewrites it to @require and removes the // definition. A non-federation schema does not get the definition, so an applied // @requires is reported as an unknown directive, steering authors to @require. - if (IsFederationSourceText(sourceSchemaText) + if (!isApolloFederationV1 + && IsFederationSourceText(sourceSchemaText) && schema.Types.TryGetType( WellKnownTypeNames.FieldSelectionSet, out var fieldSelectionSetType)) { @@ -79,6 +89,25 @@ public CompositionResult Parse() } } + if (isApolloFederationV1 + && FederationSchemaTransformer.IsFederationSchema(schema)) + { + log.Write( + FusionLogEntryBuilder.New() + .SetMessage( + SourceSchemaParser_ConflictingApolloFederationVersion, + schema.Name) + .SetCode(FusionLogEntryCodes.ConflictingApolloFederationVersion) + .SetSeverity(LogSeverity.Error) + .SetSchema(schema) + .Build()); + } + + if (isApolloFederationV1) + { + FederationV1SchemaAnalyzer.Validate(schema, log); + } + // Schema validation. if (_options.EnableSchemaValidation && !log.HasErrors) { diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaPreprocessor.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaPreprocessor.cs index 92412554275..e2a8055c083 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaPreprocessor.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaPreprocessor.cs @@ -27,7 +27,8 @@ internal sealed partial class SourceSchemaPreprocessor( ICompositionLog log, Version? sourceSchemaVersion = null, SourceSchemaPreprocessorOptions? options = null, - LogSeverity invalidFieldDeprecationSeverity = LogSeverity.Warning) + LogSeverity invalidFieldDeprecationSeverity = LogSeverity.Warning, + bool isApolloFederationV1 = false) { private readonly SourceSchemaPreprocessorOptions _options = options ?? new SourceSchemaPreprocessorOptions(); @@ -37,8 +38,9 @@ public CompositionResult Preprocess() SourceExternalFieldMetadata.CaptureMarker(schema); - var isFederationSchema = FederationSchemaTransformer.IsFederationSchema(schema) - && FederationSchemaAnalyzer.Validate(schema, log); + var isFederationSchema = isApolloFederationV1 + || (FederationSchemaTransformer.IsFederationSchema(schema) + && FederationSchemaAnalyzer.Validate(schema, log)); if (isFederationSchema) { @@ -75,11 +77,18 @@ public CompositionResult Preprocess() } // We need to run this after keys have been inferred, so we do not attempt to mark them as @shareable. - if (fusionV1CompatibilityMode || _options.InferShareable) + if (isApolloFederationV1 + || fusionV1CompatibilityMode + || _options.InferShareable) { ApplyShareableDirectives(); } + if (isFederationSchema) + { + RemoveEmptyQueryRoot.Apply(schema); + } + // Additional schema validation will catch issues introduced during preprocessing. It runs // while @external fields are still present so that the validator sees the source schema as // authored (modulo the earlier transforms); the external fields are stripped afterwards and @@ -97,6 +106,7 @@ public CompositionResult Preprocess() if (isFederationSchema) { RemoveExternalFields.Apply(schema); + RemoveEmptyQueryRoot.Apply(schema); } return log.HasErrors @@ -374,13 +384,16 @@ private void ApplyShareableDirectives() continue; } - if (field.Directives.ContainsName(WellKnownDirectiveNames.Internal) || field.Directives.ContainsName(Inaccessible)) + if (field.Directives.ContainsName(WellKnownDirectiveNames.Internal) + || field.Directives.ContainsName(WellKnownDirectiveNames.External) + || field.Directives.ContainsName(Inaccessible)) { continue; } if (!otherType.Fields.TryGetField(field.Name, out var otherField) || otherField.Directives.ContainsName(WellKnownDirectiveNames.Internal) + || otherField.Directives.ContainsName(WellKnownDirectiveNames.External) || otherField.Directives.ContainsName(Inaccessible)) { continue; diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaValidationRules/ExternalUnusedRule.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaValidationRules/ExternalUnusedRule.cs index 18ec3f0140b..7bac263ed40 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaValidationRules/ExternalUnusedRule.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaValidationRules/ExternalUnusedRule.cs @@ -55,6 +55,14 @@ public void Handle(OutputFieldEvent @event, CompositionContext context) } } + if (!isUsed + && schema.Features.Get()?.Kind == "ApolloFederation" + && RemoveExternalFields.CollectProvidesReferences(schema) + .Contains((type.Name, field.Name))) + { + isUsed = true; + } + // An @external field is also considered used when it is referenced by a // @require field-selection map on the same source schema. This shares the // exact collector used to preserve those externals during preprocessing. diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaValidationRules/ProvidesFieldsMissingExternalRule.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaValidationRules/ProvidesFieldsMissingExternalRule.cs index 8eb310f0021..6aaa6b36db3 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaValidationRules/ProvidesFieldsMissingExternalRule.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaValidationRules/ProvidesFieldsMissingExternalRule.cs @@ -1,6 +1,10 @@ +using HotChocolate.Fusion.ApolloFederation; using HotChocolate.Fusion.Events; using HotChocolate.Fusion.Events.Contracts; using HotChocolate.Fusion.Extensions; +using HotChocolate.Language; +using HotChocolate.Types; +using HotChocolate.Types.Mutable; using static HotChocolate.Fusion.Logging.LogEntryHelper; namespace HotChocolate.Fusion.SourceSchemaValidationRules; @@ -27,19 +31,120 @@ public void Handle(OutputFieldEvent @event, CompositionContext context) { var (field, _, schema) = @event; - if (field.ProvidesInfo is { } providesInfo) + if (field.ProvidesInfo is not { } providesInfo) { - foreach (var providedField in providesInfo.Fields) + return; + } + + if (schema.Features.Get()?.Kind == "ApolloFederation" + && providesInfo.SelectionSet is { } selectionSet) + { + ValidateApolloFederationSelections( + selectionSet, + field.Type.AsTypeDefinition(), + hasExternalInParents: false, + providesInfo.Directive, + field, + schema, + context); + return; + } + + foreach (var providedField in providesInfo.Fields) + { + if (!providedField.IsExternal) + { + context.Log.Write( + ProvidesFieldsMissingExternal( + providedField, + providesInfo.Directive, + field, + schema)); + } + } + } + + private static void ValidateApolloFederationSelections( + SelectionSetNode selectionSet, + ITypeDefinition type, + bool hasExternalInParents, + Directive providesDirective, + MutableOutputFieldDefinition providingField, + MutableSchemaDefinition schema, + CompositionContext context) + { + foreach (var selection in selectionSet.Selections) + { + switch (selection) { - if (!providedField.IsExternal) - { - context.Log.Write( - ProvidesFieldsMissingExternal( - providedField, - providesInfo.Directive, - field, - schema)); - } + case FieldNode fieldNode + when type is MutableComplexTypeDefinition complexType + && complexType.Fields.TryGetField( + fieldNode.Name.Value, + out var providedField): + if (fieldNode.SelectionSet is null) + { + if (!hasExternalInParents && !providedField.IsExternal) + { + context.Log.Write( + ProvidesFieldsMissingExternal( + providedField, + providesDirective, + providingField, + schema)); + } + + break; + } + + var hasExternalInPath = hasExternalInParents || providedField.IsExternal; + + if (!hasExternalInPath + && complexType is MutableInterfaceTypeDefinition interfaceType) + { + foreach (var possibleType in schema.GetPossibleTypes(interfaceType)) + { + if (possibleType.Fields.TryGetField( + providedField.Name, + out var possibleField) + && possibleField.IsExternal) + { + hasExternalInPath = true; + break; + } + } + } + + ValidateApolloFederationSelections( + fieldNode.SelectionSet, + providedField.Type.AsTypeDefinition(), + hasExternalInPath, + providesDirective, + providingField, + schema, + context); + break; + + case InlineFragmentNode inlineFragment: + var fragmentType = type; + + if (inlineFragment.TypeCondition is { } typeCondition + && schema.Types.TryGetType( + typeCondition.Name.Value, + out var resolvedType)) + { + fragmentType = resolvedType; + } + + ValidateApolloFederationSelections( + inlineFragment.SelectionSet, + fragmentType, + hasExternalInParents, + providesDirective, + providingField, + schema, + context); + break; } } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaValidationRules/ProvidesOnNonCompositeFieldRule.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaValidationRules/ProvidesOnNonCompositeFieldRule.cs index 2f3b0923072..079cc39d60d 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaValidationRules/ProvidesOnNonCompositeFieldRule.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/SourceSchemaValidationRules/ProvidesOnNonCompositeFieldRule.cs @@ -9,8 +9,8 @@ namespace HotChocolate.Fusion.SourceSchemaValidationRules; /// /// The @provides directive allows a field to “provide” additional nested fields on the -/// composite type it returns. If a field’s base type is not an object or interface type (e.g., -/// String, Int, Boolean, Enum, Union, or an Input type), it cannot hold nested fields for +/// composite type it returns. If a field’s base type is not an object, interface, or union type +/// (e.g., String, Int, Boolean, Enum, or an input type), it cannot hold nested fields for /// @provides to select. Consequently, attaching @provides to such a field is /// invalid and raises a PROVIDES_ON_NON_COMPOSITE_FIELD error. /// @@ -27,7 +27,8 @@ public void Handle(OutputFieldEvent @event, CompositionContext context) { var fieldType = field.Type.AsTypeDefinition(); - if (fieldType is not MutableComplexTypeDefinition) + if (fieldType is not MutableComplexTypeDefinition + and not MutableUnionTypeDefinition) { context.Log.Write(ProvidesOnNonCompositeField(field, schema)); } diff --git a/src/HotChocolate/Fusion/src/Fusion.Composition/SyntaxWalkers/SelectionSetFieldsExtractor.cs b/src/HotChocolate/Fusion/src/Fusion.Composition/SyntaxWalkers/SelectionSetFieldsExtractor.cs index f7a90075975..40af61173ef 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Composition/SyntaxWalkers/SelectionSetFieldsExtractor.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Composition/SyntaxWalkers/SelectionSetFieldsExtractor.cs @@ -31,6 +31,11 @@ protected override ISyntaxVisitorAction Enter( { var type = context.TypeContext.Peek(); + if (type is MutableUnionTypeDefinition) + { + return Skip; + } + if (type is MutableComplexTypeDefinition complexType) { if (complexType.Fields.TryGetField(node.Name.Value, out var field)) diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution.Types/Completion/CompositeSchemaContext.cs b/src/HotChocolate/Fusion/src/Fusion.Execution.Types/Completion/CompositeSchemaContext.cs index 1177cdbe8d4..3136677b03d 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution.Types/Completion/CompositeSchemaContext.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution.Types/Completion/CompositeSchemaContext.cs @@ -14,7 +14,9 @@ internal sealed class CompositeSchemaBuilderContext : ICompositeSchemaBuilderCon #pragma warning disable IDE0052 // WIP private readonly DocumentNode _document; #pragma warning restore IDE0052 - private readonly Dictionary _compositeTypes = new(SyntaxComparer.BySyntax); + // A source type structure can be resolved against a differently named composite type. + // Both values are therefore part of the cache identity. + private readonly Dictionary> _compositeTypes = [with(StringComparer.Ordinal)]; private readonly Dictionary _typeDefinitionLookup; private ImmutableDictionary _typeDefinitionNodeLookup; private readonly Dictionary _directiveDefinitionLookup; @@ -138,10 +140,16 @@ public IType GetType(ITypeNode typeStructure, string? typeName = null) { typeName ??= typeStructure.NamedType().Name.Value; - if (!_compositeTypes.TryGetValue(typeStructure, out var type)) + if (!_compositeTypes.TryGetValue(typeName, out var typeLookup)) + { + typeLookup = [with(SyntaxComparer.BySyntax)]; + _compositeTypes.Add(typeName, typeLookup); + } + + if (!typeLookup.TryGetValue(typeStructure, out var type)) { type = CreateType(typeStructure, typeName); - _compositeTypes[typeStructure] = type; + typeLookup.Add(typeStructure, type); } return type; diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClient.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClient.cs index 9839830cc65..4d1b74a3307 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClient.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Clients/HttpSourceSchemaClient.cs @@ -37,7 +37,7 @@ public sealed class HttpSourceSchemaClient : ISourceSchemaClient /// The transport configuration for this source schema. /// /// Whether the GraphQL operation kind is annotated onto outgoing subgraph HTTP requests via - /// , so that delegating handlers (such as the + /// , so that delegating handlers (such as the /// RequestDeduplicationHandler) can consume it. false by default because materializing the /// request options bag allocates per request. /// diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/LargeResultSelectionSet.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/LargeResultSelectionSet.cs index 85a14ccb246..8895553979e 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/LargeResultSelectionSet.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/LargeResultSelectionSet.cs @@ -12,8 +12,9 @@ internal sealed class LargeResultSelectionSet : ResultSelectionSet internal LargeResultSelectionSet( ResultSelection[] selections, ResultFragment[] fragments, - string[] allResponseNames) - : base(fragments, allResponseNames) + string[] allResponseNames, + SourceResponseNameMapping[]? sourceResponseNameMappings) + : base(fragments, allResponseNames, sourceResponseNameMappings) { _selections = selections; _childLookup = new Dictionary(selections.Length, StringComparer.Ordinal); diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/ResultSelectionSet.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/ResultSelectionSet.cs index 12d0daa58bf..9b10477d855 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/ResultSelectionSet.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/ResultSelectionSet.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; using HotChocolate.Fusion.Types; +using HotChocolate.Fusion.Text.Json; using HotChocolate.Language; using HotChocolate.Types; @@ -11,9 +12,11 @@ namespace HotChocolate.Fusion.Execution.Nodes; /// internal abstract class ResultSelectionSet( ResultFragment[] fragments, - string[] allResponseNames) + string[] allResponseNames, + SourceResponseNameMapping[]? sourceResponseNameMappings) { private const int SmallThreshold = 8; + private const string ResponseNameDirective = "fusion__responseName"; /// /// The pre-computed union of ALL response names at this level, @@ -22,6 +25,10 @@ internal abstract class ResultSelectionSet( /// public ReadOnlySpan ResponseNames => allResponseNames; + public bool HasSourceResponseNameMappings { get; } = + sourceResponseNameMappings is not null + || fragments.Any(static fragment => fragment.Body.HasSourceResponseNameMappings); + /// /// Gets a value indicating whether the objects described by this selection set are opaque /// interface values produced by an @interfaceObject stand-in. Such values initialize @@ -59,6 +66,34 @@ internal abstract class ResultSelectionSet( ? selectionSet : TryGetFragmentChild(responseName, objectType); + public bool TryMapSourceResponseName( + SourceResultProperty property, + out SourceResponseNameMapping mapping) + { + if (sourceResponseNameMappings is not null) + { + for (var i = 0; i < sourceResponseNameMappings.Length; i++) + { + if (property.NameEquals(sourceResponseNameMappings[i].SourceResponseName)) + { + mapping = sourceResponseNameMappings[i]; + return true; + } + } + } + + for (var i = 0; i < fragments.Length; i++) + { + if (fragments[i].Body.TryMapSourceResponseName(property, out mapping)) + { + return true; + } + } + + mapping = default; + return false; + } + /// /// Tries to find a child in direct selections. Implemented by subclasses /// (linear scan for small sets, dictionary for large sets). @@ -111,9 +146,39 @@ public SelectionSetNode ToSelectionSetNode() foreach (var selection in DirectSelections) { - selections.Add(new FieldNode( - selection.ResponseName, - selectionSet: selection.Child?.ToSelectionSetNode())); + SourceResponseNameMapping? mapping = null; + + if (sourceResponseNameMappings is not null) + { + for (var i = 0; i < sourceResponseNameMappings.Length; i++) + { + if (sourceResponseNameMappings[i].ResponseName.Equals( + selection.ResponseName, + StringComparison.Ordinal)) + { + mapping = sourceResponseNameMappings[i]; + break; + } + } + } + + if (mapping is { } sourceMapping) + { + selections.Add(new FieldNode( + new NameNode(sourceMapping.FieldName), + new NameNode(sourceMapping.SourceResponseName), + [new DirectiveNode( + ResponseNameDirective, + [new ArgumentNode("name", sourceMapping.ResponseName)])], + [], + selectionSet: selection.Child?.ToSelectionSetNode())); + } + else + { + selections.Add(new FieldNode( + selection.ResponseName, + selectionSet: selection.Child?.ToSelectionSetNode())); + } } foreach (var fragment in fragments) @@ -162,10 +227,19 @@ public static ResultSelectionSet Create( ISchemaDefinition? schema = null, ITypeDefinition? parentType = null, string? sourceSchemaName = null) + => Create(selectionSet, schema, parentType, sourceSchemaName, sourceAliases: null); + + public static ResultSelectionSet Create( + SelectionSetNode selectionSet, + ISchemaDefinition? schema, + ITypeDefinition? parentType, + string? sourceSchemaName, + IReadOnlyDictionary? sourceAliases) { var directSelections = new List(); var fragments = new List(); var allResponseNames = new HashSet(StringComparer.Ordinal); + List? sourceResponseNameMappings = null; foreach (var selection in selectionSet.Selections) { @@ -175,11 +249,27 @@ public static ResultSelectionSet Create( var name = field.Alias?.Value ?? field.Name.Value; allResponseNames.Add(name); + if (sourceAliases is not null + && sourceAliases.TryGetValue(field, out var sourceAlias)) + { + sourceResponseNameMappings ??= []; + sourceResponseNameMappings.Add( + new SourceResponseNameMapping( + field.Name.Value, + sourceAlias, + name)); + } + ResultSelectionSet? child = null; if (field.SelectionSet is { } childSet) { var fieldType = ResolveFieldType(parentType, field.Name.Value); - child = Create(childSet, schema, fieldType, sourceSchemaName); + child = Create( + childSet, + schema, + fieldType, + sourceSchemaName, + sourceAliases); if (IsOpaqueStandIn(fieldType, sourceSchemaName)) { @@ -203,7 +293,8 @@ public static ResultSelectionSet Create( inlineFragment.SelectionSet, schema, typeCondition ?? parentType, - sourceSchemaName); + sourceSchemaName, + sourceAliases); fragments.Add(new ResultFragment(typeCondition, body)); @@ -227,13 +318,128 @@ public static ResultSelectionSet Create( return new SmallResultSelectionSet( selectionsArray, fragmentsArray, - responseNamesArray); + responseNamesArray, + sourceResponseNameMappings?.ToArray()); } return new LargeResultSelectionSet( selectionsArray, fragmentsArray, - responseNamesArray); + responseNamesArray, + sourceResponseNameMappings?.ToArray()); + } + + public static ResultSelectionSet CreateFromPlan( + SelectionSetNode selectionSet, + ISchemaDefinition? schema = null, + ITypeDefinition? parentType = null, + string? sourceSchemaName = null) + { + Dictionary? sourceAliases = null; + var normalized = NormalizePlanSelectionSet(selectionSet, ref sourceAliases); + return Create(normalized, schema, parentType, sourceSchemaName, sourceAliases); + } + + private static SelectionSetNode NormalizePlanSelectionSet( + SelectionSetNode selectionSet, + ref Dictionary? sourceAliases) + { + var selections = new ISelectionNode[selectionSet.Selections.Count]; + + for (var i = 0; i < selectionSet.Selections.Count; i++) + { + switch (selectionSet.Selections[i]) + { + case FieldNode field: + var child = field.SelectionSet is null + ? null + : NormalizePlanSelectionSet(field.SelectionSet, ref sourceAliases); + var responseName = GetMappedResponseName(field); + var normalized = new FieldNode( + field.Location, + field.Name, + responseName is null + || responseName.Equals(field.Name.Value, StringComparison.Ordinal) + ? null + : new NameNode(responseName), + RemoveResponseNameDirective(field.Directives), + field.Arguments, + child); + + if (field.Alias is not null && responseName is not null) + { + sourceAliases ??= new Dictionary( + ReferenceEqualityComparer.Instance); + sourceAliases.Add(normalized, field.Alias.Value); + } + + selections[i] = normalized; + break; + + case InlineFragmentNode inlineFragment: + selections[i] = inlineFragment.WithSelectionSet( + NormalizePlanSelectionSet( + inlineFragment.SelectionSet, + ref sourceAliases)); + break; + + default: + selections[i] = selectionSet.Selections[i]; + break; + } + } + + return new SelectionSetNode(selections); + } + + private static string? GetMappedResponseName(FieldNode field) + { + foreach (var directive in field.Directives) + { + if (directive.Name.Value.Equals(ResponseNameDirective, StringComparison.Ordinal) + && directive.Arguments is + [ + { + Name.Value: "name", + Value: StringValueNode responseName + } + ]) + { + return responseName.Value; + } + } + + return null; + } + + private static IReadOnlyList RemoveResponseNameDirective( + IReadOnlyList directives) + { + if (directives.Count == 0) + { + return directives; + } + + List? filtered = null; + + for (var i = 0; i < directives.Count; i++) + { + var directive = directives[i]; + if (directive.Name.Value.Equals(ResponseNameDirective, StringComparison.Ordinal)) + { + filtered ??= [with(directives.Count - 1)]; + for (var j = 0; j < i; j++) + { + filtered.Add(directives[j]); + } + } + else + { + filtered?.Add(directive); + } + } + + return filtered ?? directives; } private static ITypeDefinition? ResolveFieldType(ITypeDefinition? parentType, string fieldName) diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/Serialization/JsonOperationPlanParser.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/Serialization/JsonOperationPlanParser.cs index 7b2eb0b5a46..5eeb0780989 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/Serialization/JsonOperationPlanParser.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/Serialization/JsonOperationPlanParser.cs @@ -555,7 +555,12 @@ private static ParsedOperationNodeInfo ParseOperationNodeInfo( Target = target ?? SelectionPath.Root, Requirements = requirements?.ToArray() ?? [], ForwardedVariables = forwardedVariables ?? [], - ResultSelectionSet = ResultSelectionSet.Create(resultSelectionSet!, schema, parentType, schemaName), + ResultSelectionSet = + ResultSelectionSet.CreateFromPlan( + resultSelectionSet!, + schema, + parentType, + schemaName), Dependencies = dependencies, ParentDependencies = parentDependencies, BatchingGroupId = batchingGroupId, @@ -565,7 +570,7 @@ private static ParsedOperationNodeInfo ParseOperationNodeInfo( }; } - private static ParsedOperationNodeInfo ParseApolloOperationNodeInfo( + private static ParsedApolloOperationNodeInfo ParseApolloOperationNodeInfo( JsonElement nodeElement, int id, FusionSchemaDefinition schema) { var (schemaName, opSource, source, requirements, forwardedVariables, @@ -590,7 +595,12 @@ private static ParsedOperationNodeInfo ParseApolloOperationNodeInfo( Target = target ?? SelectionPath.Root, Requirements = requirements?.ToArray() ?? [], ForwardedVariables = forwardedVariables ?? [], - ResultSelectionSet = ResultSelectionSet.Create(resultSelectionSet!, schema, parentType, schemaName), + ResultSelectionSet = + ResultSelectionSet.CreateFromPlan( + resultSelectionSet!, + schema, + parentType, + schemaName), Dependencies = dependencies, ParentDependencies = parentDependencies, BatchingGroupId = batchingGroupId, @@ -624,7 +634,7 @@ private static ParsedEventStreamNodeInfo ParseEventStreamNodeInfo( FieldName = fieldName, Source = source, Target = target, - ResultSelectionSet = ResultSelectionSet.Create(resultSelectionSet, schema), + ResultSelectionSet = ResultSelectionSet.CreateFromPlan(resultSelectionSet, schema), EventStreamSource = eventStreamSource, Message = message, Dependencies = dependencies, @@ -706,7 +716,12 @@ private static ParsedOperationNodeInfo ParseOperationBatchNodeInfo( Targets = targets, Requirements = requirements?.ToArray() ?? [], ForwardedVariables = forwardedVariables ?? [], - ResultSelectionSet = ResultSelectionSet.Create(resultSelectionSet!, schema, parentType, schemaName), + ResultSelectionSet = + ResultSelectionSet.CreateFromPlan( + resultSelectionSet!, + schema, + parentType, + schemaName), Dependencies = dependencies, ParentDependencies = parentDependencies, BatchingGroupId = batchingGroupId, diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/SmallResultSelectionSet.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/SmallResultSelectionSet.cs index 021bb4cfc7a..009164da50a 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/SmallResultSelectionSet.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/SmallResultSelectionSet.cs @@ -7,8 +7,9 @@ namespace HotChocolate.Fusion.Execution.Nodes; internal sealed class SmallResultSelectionSet( ResultSelection[] selections, ResultFragment[] fragments, - string[] allResponseNames) - : ResultSelectionSet(fragments, allResponseNames) + string[] allResponseNames, + SourceResponseNameMapping[]? sourceResponseNameMappings) + : ResultSelectionSet(fragments, allResponseNames, sourceResponseNameMappings) { protected override ReadOnlySpan DirectSelections => selections; diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/SourceResponseNameMapping.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/SourceResponseNameMapping.cs new file mode 100644 index 00000000000..f6980379ab3 --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/SourceResponseNameMapping.cs @@ -0,0 +1,17 @@ +using System.Text; + +namespace HotChocolate.Fusion.Execution.Nodes; + +internal readonly struct SourceResponseNameMapping( + string fieldName, + string sourceResponseName, + string responseName) +{ + public string FieldName { get; } = fieldName; + + public string SourceResponseName { get; } = sourceResponseName; + + public string ResponseName { get; } = responseName; + + public byte[] ResponseNameUtf8 { get; } = Encoding.UTF8.GetBytes(responseName); +} diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/OperationPlanContext.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/OperationPlanContext.cs index bfcdae45bd6..e02d067b22e 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/OperationPlanContext.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/OperationPlanContext.cs @@ -771,7 +771,7 @@ internal OperationResult Complete(bool reusable = false, bool retainMemoryForDef var operationResult = new OperationResult( new OperationResultData( resultDocument, - resultDocument.Data.IsNullOrInvalidated, + resultDocument.Data.IsNullOrInvalidated || resultDocument.Data.IsNullMarker, resultDocument, retainMemoryForDefer ? null : resultDocument), _resultStore.Errors?.ToImmutableList()); diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/OperationPlanExecutor.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/OperationPlanExecutor.cs index 08c8be313be..eff87c2ec82 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/OperationPlanExecutor.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/OperationPlanExecutor.cs @@ -800,9 +800,21 @@ private static bool TryCreateIncrementalResults( } element = next; + + if (element.IsNullMarker) + { + for (var j = i + 1; j < selectionPath.Length; j++) + { + if (selectionPath[j].Kind is SelectionPathSegmentKind.Field) + { + incrementalResults = []; + return false; + } + } + } } - if (element.ValueKind is JsonValueKind.Array) + if (element.ValueKind is JsonValueKind.Array && !element.IsNullMarker) { var builder = ImmutableList.CreateBuilder(); var length = element.GetArrayLength(); @@ -846,7 +858,7 @@ private static OperationResultData CreateIncrementalData( // the IncrementalObjectResult is a non-owning view over it. => new( document, - isValueNull: false, + isValueNull: element.IsNullMarker, new DeferredPayloadDataFormatter(element), memoryHolder: null); diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs index fd5c67714f3..a1d8fd23ab3 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs @@ -580,6 +580,7 @@ public void FinalizePocketedErrors() lock (_lock) { _valueCompletion.FinalizePocketedErrors(_result.Data); + _valueCompletion.FinalizeInaccessibleRuntimeTypes(_result.Data); } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/ValueCompletion.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/ValueCompletion.cs index c3ae408fb79..a8a0b2e3297 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/ValueCompletion.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/ValueCompletion.cs @@ -87,56 +87,136 @@ public bool BuildResult( var objectContext = target.GetObjectContext(); - foreach (var property in source.EnumerateObject()) + if (resultSelectionSet.HasSourceResponseNameMappings) { - if (!objectContext.TryGetProperty(property.NameSpan, out var resultField, out var selection)) + foreach (var property in source.EnumerateObject()) { - continue; - } + CompositeResultElement resultField; + Selection selection; + string sourceResponseName; - var propertyValue = property.Value; - var propertyValueKind = propertyValue.ValueKind; + if (resultSelectionSet.TryMapSourceResponseName( + property, + out var responseNameMapping)) + { + if (!objectContext.TryGetProperty( + responseNameMapping.ResponseNameUtf8, + out var mappedResultField, + out var mappedSelection)) + { + continue; + } + + resultField = mappedResultField; + selection = mappedSelection; + sourceResponseName = responseNameMapping.SourceResponseName; + } + else + { + if (!objectContext.TryGetProperty( + property.NameSpan, + out resultField, + out selection)) + { + continue; + } + + sourceResponseName = selection.ResponseName; + } - // Fast path: when there are no errors and the source value is a - // scalar (string, number, bool) we can set it directly without - // going through the full TryCompleteValue type-dispatch chain. - if (errorTrie is null && propertyValueKind.IsScalarValue()) - { - if (propertyValueKind is JsonValueKind.String && selection.IsEnumValue) + var propertyValue = property.Value; + var propertyValueKind = propertyValue.ValueKind; + + if (errorTrie is null && propertyValueKind.IsScalarValue()) { - CompleteEnumValue(propertyValue, resultField, selection); + if (propertyValueKind is JsonValueKind.String && selection.IsEnumValue) + { + CompleteEnumValue(propertyValue, resultField, selection); + continue; + } + + resultField.SetLeafValue(propertyValue); continue; } - resultField.SetLeafValue(propertyValue); - continue; - } + ErrorTrie? errorTrieForResponseName = null; + errorTrie?.TryGetValue(sourceResponseName, out errorTrieForResponseName); - ErrorTrie? errorTrieForResponseName = null; - errorTrie?.TryGetValue(selection.ResponseName, out errorTrieForResponseName); + var childSet = resultSelectionSet.TryGetChild(selection.ResponseName); + if (!TryCompleteValue( + propertyValue, + propertyValueKind, + resultField, + errorTrieForResponseName, + selection, + selection.Type, + 0, + childSet) + && _errorHandlingMode is ErrorHandlingMode.Propagate) + { + var didPropagateToRoot = PropagateNullValues(resultField); + if (didPropagateToRoot) + { + return false; + } - var childSet = resultSelectionSet.TryGetChild(selection.ResponseName); - if (!TryCompleteValue( - propertyValue, - propertyValueKind, - resultField, - errorTrieForResponseName, - selection, - selection.Type, - 0, - childSet)) + return ApplyPocketedErrors(target); + } + } + } + else + { + foreach (var property in source.EnumerateObject()) { - switch (_errorHandlingMode) + if (!objectContext.TryGetProperty(property.NameSpan, out var resultField, out var selection)) { - case ErrorHandlingMode.Propagate: - var didPropagateToRoot = PropagateNullValues(resultField); - if (didPropagateToRoot) - { - return false; - } + continue; + } - return ApplyPocketedErrors(target); + var propertyValue = property.Value; + var propertyValueKind = propertyValue.ValueKind; + // Fast path: when there are no errors and the source value is a + // scalar (string, number, bool) we can set it directly without + // going through the full TryCompleteValue type-dispatch chain. + if (errorTrie is null && propertyValueKind.IsScalarValue()) + { + if (propertyValueKind is JsonValueKind.String && selection.IsEnumValue) + { + CompleteEnumValue(propertyValue, resultField, selection); + continue; + } + + resultField.SetLeafValue(propertyValue); + continue; + } + + ErrorTrie? errorTrieForResponseName = null; + errorTrie?.TryGetValue(selection.ResponseName, out errorTrieForResponseName); + + var childSet = resultSelectionSet.TryGetChild(selection.ResponseName); + if (!TryCompleteValue( + propertyValue, + propertyValueKind, + resultField, + errorTrieForResponseName, + selection, + selection.Type, + 0, + childSet)) + { + switch (_errorHandlingMode) + { + case ErrorHandlingMode.Propagate: + var didPropagateToRoot = PropagateNullValues(resultField); + if (didPropagateToRoot) + { + return false; + } + + return ApplyPocketedErrors(target); + + } } } } @@ -181,6 +261,13 @@ private void TryUpgradeOpaqueTarget(CompositeResultElement target, SourceResultE } var concreteType = _schema.Types.GetType(typeName.AssertString()); + + if (!parentSelection.IsInternal + && concreteType is IInaccessibleProvider { IsInaccessible: true }) + { + _store.Result.RequireNullMarkerFinalization(); + } + var concreteSelectionSet = parentSelection.GetSelectionSet(concreteType)!; _store.Result.UpgradeObject(target, concreteSelectionSet); } @@ -328,6 +415,62 @@ public void FinalizePocketedErrors(CompositeResultElement resultData) } } + public void FinalizeInaccessibleRuntimeTypes(CompositeResultElement resultData) + { + if (!_store.Result.RequiresNullMarkerFinalization) + { + return; + } + + Visit(resultData, isPublic: true); + _store.Result.CompleteNullMarkerFinalization(); + + static void Visit(CompositeResultElement current, bool isPublic) + { + if (!isPublic || current.IsNullOrInvalidated || current.IsNullMarker) + { + return; + } + + switch (current.ValueKind) + { + case JsonValueKind.Object: + if (isPublic + && current.Type?.NamedType().IsAbstractType() == true + && current.SelectionSet?.Type is IInaccessibleProvider { IsInaccessible: true }) + { + SetNullMarker(current); + return; + } + + foreach (var property in current.EnumerateObject()) + { + Visit(property.Value, !property.Value.IsInternal); + } + break; + + case JsonValueKind.Array: + foreach (var element in current.EnumerateArray()) + { + Visit(element, isPublic); + } + break; + } + } + } + + private static void SetNullMarker(CompositeResultElement result) + { + var current = result; + + while (!current.IsRoot && !current.IsNullable) + { + current = current.Parent; + } + + current.SetNullMarker(); + } + /// /// Invalidates the current result and its parents, /// until reaching a parent that can be set to null. @@ -976,46 +1119,119 @@ private bool TryCompleteObjectValue( var objectContext = target.GetObjectContext(); - foreach (var property in source.EnumerateObject()) + if (resultSelectionSet is { HasSourceResponseNameMappings: true }) { - if (!objectContext.TryGetProperty(property.NameSpan, out var targetProperty, out var selection)) + foreach (var property in source.EnumerateObject()) { - continue; - } + CompositeResultElement targetProperty; + Selection selection; + string sourceResponseName; - var propertyValue = property.Value; - var propertyValueKind = propertyValue.ValueKind; + if (resultSelectionSet.TryMapSourceResponseName( + property, + out var responseNameMapping)) + { + if (!objectContext.TryGetProperty( + responseNameMapping.ResponseNameUtf8, + out var mappedTargetProperty, + out var mappedSelection)) + { + continue; + } + + targetProperty = mappedTargetProperty; + selection = mappedSelection; + sourceResponseName = responseNameMapping.SourceResponseName; + } + else + { + if (!objectContext.TryGetProperty( + property.NameSpan, + out targetProperty, + out selection)) + { + continue; + } + + sourceResponseName = selection.ResponseName; + } - // Fast path: when there are no errors and the source value is a - // scalar (string, number, bool) we can set it directly without - // going through the full TryCompleteValue type-dispatch chain. - if (errorTrie is null && propertyValueKind.IsScalarValue()) - { - if (propertyValueKind is JsonValueKind.String && selection.IsEnumValue) + var propertyValue = property.Value; + var propertyValueKind = propertyValue.ValueKind; + + if (errorTrie is null && propertyValueKind.IsScalarValue()) { - CompleteEnumValue(propertyValue, targetProperty, selection); + if (propertyValueKind is JsonValueKind.String && selection.IsEnumValue) + { + CompleteEnumValue(propertyValue, targetProperty, selection); + continue; + } + + targetProperty.SetLeafValue(propertyValue); continue; } - targetProperty.SetLeafValue(propertyValue); - continue; + ErrorTrie? errorTrieForResponseName = null; + errorTrie?.TryGetValue(sourceResponseName, out errorTrieForResponseName); + + var childSet = resultSelectionSet.TryGetChild(selection.ResponseName, objectType); + if (!TryCompleteValue( + propertyValue, + propertyValueKind, + targetProperty, + errorTrieForResponseName, + selection, + selection.Type, + depth, + childSet)) + { + return false; + } } + } + else + { + foreach (var property in source.EnumerateObject()) + { + if (!objectContext.TryGetProperty(property.NameSpan, out var targetProperty, out var selection)) + { + continue; + } - ErrorTrie? errorTrieForResponseName = null; - errorTrie?.TryGetValue(selection.ResponseName, out errorTrieForResponseName); + var propertyValue = property.Value; + var propertyValueKind = propertyValue.ValueKind; - var childSet = resultSelectionSet?.TryGetChild(selection.ResponseName, objectType); - if (!TryCompleteValue( - propertyValue, - propertyValueKind, - targetProperty, - errorTrieForResponseName, - selection, - selection.Type, - depth, - childSet)) - { - return false; + // Fast path: when there are no errors and the source value is a + // scalar (string, number, bool) we can set it directly without + // going through the full TryCompleteValue type-dispatch chain. + if (errorTrie is null && propertyValueKind.IsScalarValue()) + { + if (propertyValueKind is JsonValueKind.String && selection.IsEnumValue) + { + CompleteEnumValue(propertyValue, targetProperty, selection); + continue; + } + + targetProperty.SetLeafValue(propertyValue); + continue; + } + + ErrorTrie? errorTrieForResponseName = null; + errorTrie?.TryGetValue(selection.ResponseName, out errorTrieForResponseName); + + var childSet = resultSelectionSet?.TryGetChild(selection.ResponseName, objectType); + if (!TryCompleteValue( + propertyValue, + propertyValueKind, + targetProperty, + errorTrieForResponseName, + selection, + selection.Type, + depth, + childSet)) + { + return false; + } } } @@ -1032,12 +1248,20 @@ private bool TryCompleteAbstractValue( ResultSelectionSet? resultSelectionSet) { var isOpaque = resultSelectionSet?.ProducesOpaqueElements ?? false; + var objectType = GetType(type, source, isOpaque); + + if (!selection.IsInternal + && objectType is IInaccessibleProvider { IsInaccessible: true }) + { + _store.Result.RequireNullMarkerFinalization(); + } + return TryCompleteObjectValue( source, target, errorTrie, selection, - GetType(type, source, isOpaque), + objectType, depth, resultSelectionSet); } diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/OperationPlanner.BuildExecutionTree.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/OperationPlanner.BuildExecutionTree.cs index 4d3a3be14e8..065cda0694d 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/OperationPlanner.BuildExecutionTree.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/OperationPlanner.BuildExecutionTree.cs @@ -587,16 +587,36 @@ private static ExecutionNode CreateOperationExecutionNode( var requiresFileUpload = requiresUpload && DoVariablesContainUploadScalar(operationStep.Definition.VariableDefinitions, schema); - var operation = RemoveInternalDirectives(operationStep.Definition); + var sourceRewrite = operationStep.SchemaName is null + ? SourceFieldTypeMismatchRewriter.RewriteDynamic( + operationStep.Definition, + schema.GetOperationType(operationStep.Definition.Operation), + schema) + : SourceFieldTypeMismatchRewriter.Rewrite( + operationStep.Definition, + schema.GetOperationType(operationStep.Definition.Operation), + operationStep.SchemaName, + schema); + var operation = RemoveInternalDirectives(sourceRewrite.Operation); var operationSource = operation.ToSourceText(); var selectionSetNode = GetSelectionSetNodeFromPath(operationStep.Definition, operationStep.Source); - selectionSetNode = PruneNonValueTypeChildren(selectionSetNode, operationStep.Type, schema, operationStep.SchemaName); + var sourceAliasSelectionSets = CreateSourceAliasSelectionSetLookup( + selectionSetNode, + sourceRewrite.Aliases); + selectionSetNode = PruneNonValueTypeChildren( + selectionSetNode, + operationStep.Type, + schema, + operationStep.SchemaName, + sourceRewrite.Aliases, + sourceAliasSelectionSets); var resultSelectionSet = ResultSelectionSet.Create( selectionSetNode, schema, operationStep.Type, - operationStep.SchemaName); + operationStep.SchemaName, + sourceRewrite.Aliases); // Only synthetic internal key lookups resolve through _entities. Real public // root-field lookups, for example the composed node field, stay native even @@ -661,7 +681,13 @@ private static EventStreamExecutionNode CreateEventStreamExecutionNode( ?? throw new InvalidOperationException("The operation step does not carry event-stream metadata."); var selectionSetNode = GetSelectionSetNodeFromPath(operationStep.Definition, operationStep.Source); - selectionSetNode = PruneNonValueTypeChildren(selectionSetNode, operationStep.Type, schema, operationStep.SchemaName); + selectionSetNode = PruneNonValueTypeChildren( + selectionSetNode, + operationStep.Type, + schema, + operationStep.SchemaName, + sourceAliases: null, + sourceAliasSelectionSets: null); var resultSelectionSet = ResultSelectionSet.Create( selectionSetNode, schema, @@ -1867,12 +1893,11 @@ private static SelectionSetNode PruneNonValueTypeChildren( SelectionSetNode selectionSet, ITypeDefinition parentType, FusionSchemaDefinition schema, - string? sourceSchemaName) + string? sourceSchemaName, + Dictionary? sourceAliases, + HashSet? sourceAliasSelectionSets) { - if (parentType is not IComplexTypeDefinition complexType) - { - return selectionSet; - } + var complexType = parentType as IComplexTypeDefinition; var changed = false; var selections = new ISelectionNode[selectionSet.Selections.Count]; @@ -1885,19 +1910,31 @@ private static SelectionSetNode PruneNonValueTypeChildren( { case FieldNode field when field.SelectionSet is not null: { - if (complexType.Fields.TryGetField(field.Name.Value, out var fieldDef)) + if (complexType is not null + && complexType.Fields.TryGetField(field.Name.Value, out var fieldDef)) { var fieldNamedType = fieldDef.Type.NamedType(); if (fieldNamedType is FusionComplexTypeDefinition { IsValueType: true } valueType) { var pruned = PruneNonValueTypeChildren( - field.SelectionSet, valueType, schema, sourceSchemaName); + field.SelectionSet, + valueType, + schema, + sourceSchemaName, + sourceAliases, + sourceAliasSelectionSets); if (!ReferenceEquals(pruned, field.SelectionSet)) { - selections[i] = new FieldNode( - field.Name, field.Alias, field.Directives, field.Arguments, pruned); + var updated = new FieldNode( + field.Name, + field.Alias, + field.Directives, + field.Arguments, + pruned); + CopySourceAlias(field, updated, sourceAliases); + selections[i] = updated; changed = true; continue; } @@ -1909,21 +1946,34 @@ private static SelectionSetNode PruneNonValueTypeChildren( // reaches an @interfaceObject stand-in, however, the path to that field // must be kept so the result selection set can carry the opacity marker; // recurse to preserve it instead of stripping the whole subtree. - if (fieldNamedType is FusionComplexTypeDefinition complexFieldType - && SubtreeContainsOpaqueStandIn( - field.SelectionSet, complexFieldType, schema, sourceSchemaName)) + if (sourceAliasSelectionSets?.Contains(field.SelectionSet) == true + || (fieldNamedType is FusionComplexTypeDefinition complexFieldType + && SubtreeContainsOpaqueStandIn( + field.SelectionSet, + complexFieldType, + schema, + sourceSchemaName))) { var pruned = PruneNonValueTypeChildren( - field.SelectionSet, complexFieldType, schema, sourceSchemaName); - - selections[i] = new FieldNode( + field.SelectionSet, + fieldNamedType, + schema, + sourceSchemaName, + sourceAliases, + sourceAliasSelectionSets); + + var updated = new FieldNode( field.Name, field.Alias, field.Directives, field.Arguments, pruned); + CopySourceAlias(field, updated, sourceAliases); + selections[i] = updated; changed = true; continue; } - selections[i] = new FieldNode( + var stripped = new FieldNode( field.Name, field.Alias, field.Directives, field.Arguments, null); + CopySourceAlias(field, stripped, sourceAliases); + selections[i] = stripped; changed = true; continue; } @@ -1948,7 +1998,12 @@ private static SelectionSetNode PruneNonValueTypeChildren( : parentType; var pruned = PruneNonValueTypeChildren( - inlineFragment.SelectionSet, fragmentType, schema, sourceSchemaName); + inlineFragment.SelectionSet, + fragmentType, + schema, + sourceSchemaName, + sourceAliases, + sourceAliasSelectionSets); if (!ReferenceEquals(pruned, inlineFragment.SelectionSet)) { @@ -1971,7 +2026,62 @@ private static SelectionSetNode PruneNonValueTypeChildren( } } - return changed ? new SelectionSetNode(selections) : selectionSet; + return changed ? selectionSet.WithSelections(selections) : selectionSet; + } + + private static HashSet? CreateSourceAliasSelectionSetLookup( + SelectionSetNode selectionSet, + Dictionary? sourceAliases) + { + if (sourceAliases is null) + { + return null; + } + + var selectionSets = new HashSet(ReferenceEqualityComparer.Instance); + Collect(selectionSet); + return selectionSets; + + bool Collect(SelectionSetNode current) + { + var containsAlias = false; + + foreach (var selection in current.Selections) + { + switch (selection) + { + case FieldNode field: + containsAlias |= sourceAliases.ContainsKey(field); + if (field.SelectionSet is not null) + { + containsAlias |= Collect(field.SelectionSet); + } + break; + + case InlineFragmentNode inlineFragment: + containsAlias |= Collect(inlineFragment.SelectionSet); + break; + } + } + + if (containsAlias) + { + selectionSets.Add(current); + } + + return containsAlias; + } + } + + private static void CopySourceAlias( + FieldNode source, + FieldNode target, + Dictionary? sourceAliases) + { + if (sourceAliases is not null && sourceAliases.TryGetValue(source, out var alias)) + { + sourceAliases.TryAdd(target, alias); + } } private static bool IsOpaqueInterfaceObjectStandIn(ITypeDefinition namedType, string? sourceSchemaName) diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/OperationPlanner.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/OperationPlanner.cs index d7ecb214ee1..7cf693b100a 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/OperationPlanner.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/OperationPlanner.cs @@ -1044,7 +1044,21 @@ private void PlanLookupSelections( backlog, workItem.Conditions, workItem.AllowSourceSchemaReentry, - workItem.SourceSchemaNodePolicy); + workItem.SourceSchemaNodePolicy, + out var unresolvedRequirements); + + // A self-cyclic lookup can proceed only when an existing step supplies its key. + // If requirement inlining leaves the identical selection unresolved, this direct + // branch made no progress. The parent-path alternatives were enqueued separately. + if (unresolvedRequirements is not null + && PlanQueue.IsSelfCyclicLookup(workItem, lookup) + && SyntaxComparer.BySyntax.Equals( + unresolvedRequirements, + workItem.SelectionSet.Node)) + { + return; + } + PlanSelections( workItem, current, @@ -1229,9 +1243,10 @@ private PlanNode InlineLookupRequirements( Lookup lookup, int lookupStepDepth, Backlog backlog, - ExecutionNodeCondition[]? conditions = null, - bool allowSourceSchemaReentry = false, - SourceSchemaNodePlanningPolicy? sourceSchemaNodePolicy = null) + ExecutionNodeCondition[]? conditions, + bool allowSourceSchemaReentry, + SourceSchemaNodePlanningPolicy? sourceSchemaNodePolicy, + out SelectionSetNode? unresolvedRequirements) { var processed = new HashSet(); var lookupStepId = current.Steps.NextId(); @@ -1290,6 +1305,24 @@ private PlanNode InlineLookupRequirements( continue; } + var relativePath = workItemSelectionSet.Path.RelativeTo(step.Target); + + // A lookup key can already be present in this step because an ancestor @provides + // scope selected the concrete runtime field. Match the actual operation path so + // sibling abstract branches that share a selection-set id cannot satisfy each + // other's requirements. + if (ContainsSelectionsAtPath( + step.Definition.SelectionSet, + relativePath, + selectionSet)) + { + steps = steps.SetItem( + stepIndex, + step with { Dependents = step.Dependents.Add(lookupStepId) }); + selectionSet = null; + break; + } + var input = new SelectionSetPartitionerInput { SchemaName = schemaName, @@ -1394,6 +1427,8 @@ private PlanNode InlineLookupRequirements( } } + unresolvedRequirements = selectionSet; + // if we have still selections left we need to add them to the backlog. A nested // object/list leftover is re-rooted at its own entity type so a requirement that // crosses an entity boundary resolves via that entity's key lookup. @@ -1430,6 +1465,118 @@ private PlanNode InlineLookupRequirements( }; } + internal static bool ContainsSelectionsAtPath( + SelectionSetNode current, + SelectionPath path, + SelectionSetNode required) + => ContainsSelectionsAtPath(current, path, pathIndex: 0, required); + + private static bool ContainsSelectionsAtPath( + SelectionSetNode current, + SelectionPath path, + int pathIndex, + SelectionSetNode required) + { + if (pathIndex == path.Length) + { + return ContainsAllSelections(required, current); + } + + var segment = path[pathIndex]; + + foreach (var selection in current.Selections) + { + if (selection is InlineFragmentNode + { + TypeCondition: null, + Directives.Count: 0 + } wrapper + && ContainsSelectionsAtPath(wrapper.SelectionSet, path, pathIndex, required)) + { + return true; + } + + switch (segment.Kind) + { + case SelectionPathSegmentKind.Field + when selection is FieldNode + { + SelectionSet: { } child, + Directives.Count: 0 + } field && (field.Alias?.Value ?? field.Name.Value).Equals(segment.Name, StringComparison.Ordinal): + if (ContainsSelectionsAtPath(child, path, pathIndex + 1, required)) + { + return true; + } + break; + + case SelectionPathSegmentKind.InlineFragment + when selection is InlineFragmentNode + { + TypeCondition.Name.Value: var typeName, + Directives.Count: 0 + } fragment && typeName.Equals(segment.Name, StringComparison.Ordinal): + if (ContainsSelectionsAtPath( + fragment.SelectionSet, + path, + pathIndex + 1, + required)) + { + return true; + } + break; + } + } + + return false; + } + + private static bool ContainsAllSelections( + SelectionSetNode required, + SelectionSetNode existing) + { + foreach (var requiredSelection in required.Selections) + { + var isCovered = requiredSelection switch + { + FieldNode requiredField => existing.Selections + .OfType() + .Any(existingField => FieldContains(requiredField, existingField)), + InlineFragmentNode requiredFragment => existing.Selections + .OfType() + .Any(existingFragment => FragmentContains(requiredFragment, existingFragment)), + _ => false + }; + + if (!isCovered) + { + return false; + } + } + + return true; + } + + private static bool FieldContains(FieldNode required, FieldNode existing) + { + if (!SyntaxComparer.BySyntax.Equals(required.Name, existing.Name) + || !SyntaxComparer.BySyntax.Equals(required.Alias, existing.Alias) + || !required.Arguments.SequenceEqual(existing.Arguments, SyntaxComparer.BySyntax) + || !required.Directives.SequenceEqual(existing.Directives, SyntaxComparer.BySyntax)) + { + return false; + } + + return required.SelectionSet is null + || (existing.SelectionSet is not null + && ContainsAllSelections(required.SelectionSet, existing.SelectionSet)); + } + + private static bool FragmentContains(InlineFragmentNode required, InlineFragmentNode existing) + => SyntaxComparer.BySyntax.Equals(required.TypeCondition, existing.TypeCondition) + && required.Directives.SequenceEqual(existing.Directives, SyntaxComparer.BySyntax) + && ContainsAllSelections(required.SelectionSet, existing.SelectionSet); + /// /// Tries to inline the field that has a requirement into its original intended plan step /// by resolving the requirements from non-dependant siblings or from parents. @@ -1619,7 +1766,9 @@ private void PlanFieldWithRequirement( workItem.EstimatedDepth, backlog, workItem.Conditions, - sourceSchemaNodePolicy: workItem.SourceSchemaNodePolicy); + allowSourceSchemaReentry: false, + workItem.SourceSchemaNodePolicy, + out _); backlog = current.Backlog; if (current.Steps.ById(stepConsumer.StepId) is not OperationPlanStep updatedCurrentStep) @@ -3172,8 +3321,7 @@ private RequirementAliasRegistry( ImmutableArray<(RequirementFieldSelectionKey Key, string Alias)> entries) => _entries = entries; - public static RequirementAliasRegistry Empty { get; } = - new(ImmutableArray<(RequirementFieldSelectionKey Key, string Alias)>.Empty); + public static RequirementAliasRegistry Empty { get; } = new([]); public RequirementAliasRegistry GetOrAdd(FieldNode field, out string alias) { diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/PlanQueue.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/PlanQueue.cs index bbb5d218fd0..d41a9495d5d 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/PlanQueue.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/PlanQueue.cs @@ -251,7 +251,7 @@ private void EnqueueParentPathLookupPlanNodes( /// requires as input. Such a lookup cannot make progress on its own; the key must be supplied /// from the parent path instead. /// - private static bool IsSelfCyclicLookup(OperationWorkItem workItem, Lookup lookup) + internal static bool IsSelfCyclicLookup(OperationWorkItem workItem, Lookup lookup) { var requested = workItem.SelectionSet.Node.Selections; @@ -300,6 +300,12 @@ private bool TryEnqueueConcreteTypeLookupPlanNodes( { var enqueued = false; var hasCoveringAbstractLookup = false; + var possibleTypes = schema.GetPossibleTypes(type, includeInaccessible: true); + + if (possibleTypes.IsEmpty) + { + return false; + } // Phase 1: we try to find a single schema that can resolve all concrete types as // this would allow us to batch all requests to these into a single GraphQL batch request. @@ -329,7 +335,7 @@ private bool TryEnqueueConcreteTypeLookupPlanNodes( // for each concrete type that implements the abstract type, // find a lookup in the target schema. - foreach (var possibleType in schema.GetPossibleTypes(type, includeInaccessible: true)) + foreach (var possibleType in possibleTypes) { if (!schema.TryGetBestDirectLookup(possibleType, fromSchemas, toSchema, out var concreteLookup)) { @@ -389,7 +395,7 @@ private bool TryEnqueueConcreteTypeLookupPlanNodes( string? topSchema = null; double topCost = 0; - foreach (var possibleType in schema.GetPossibleTypes(type, includeInaccessible: true)) + foreach (var possibleType in possibleTypes) { // rewrite the selection set to target the concrete type with a // fragment path so the executor can match the runtime type. diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/SourceFieldTypeMismatchRewriter.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/SourceFieldTypeMismatchRewriter.cs new file mode 100644 index 00000000000..243ee5900ac --- /dev/null +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/SourceFieldTypeMismatchRewriter.cs @@ -0,0 +1,540 @@ +using HotChocolate.Fusion.Types; +using HotChocolate.Fusion.Types.Metadata; +using HotChocolate.Language; +using HotChocolate.Types; + +namespace HotChocolate.Fusion.Planning; + +internal static class SourceFieldTypeMismatchRewriter +{ + public static SourceFieldTypeMismatchRewriteResult RewriteDynamic( + OperationDefinitionNode operation, + ITypeDefinition rootType, + FusionSchemaDefinition schema) + { + var schemaNames = CollectRootSourceSchemaNames(operation.SelectionSet, rootType, schema); + HashSet? aliasedFields = null; + + foreach (var schemaName in schemaNames) + { + var result = Rewrite(operation, rootType, schemaName, schema); + if (result.Aliases is null) + { + continue; + } + + aliasedFields ??= new HashSet(ReferenceEqualityComparer.Instance); + aliasedFields.UnionWith(result.Aliases.Keys); + } + + if (aliasedFields is null) + { + return new SourceFieldTypeMismatchRewriteResult(operation, null); + } + + var aliases = CreateAliases(operation.SelectionSet, aliasedFields); + var selectionSet = RewriteSelectionSet(operation.SelectionSet, aliases); + return new SourceFieldTypeMismatchRewriteResult( + operation.WithSelectionSet(selectionSet), + aliases); + } + + public static SourceFieldTypeMismatchRewriteResult Rewrite( + OperationDefinitionNode operation, + ITypeDefinition rootType, + string schemaName, + FusionSchemaDefinition schema) + { + var context = new Context(operation, schemaName, schema); + var root = new SelectionSetContext(operation.SelectionSet, rootType); + + if (!context.HasSourceTypeDifference(root)) + { + return new SourceFieldTypeMismatchRewriteResult(operation, null); + } + + context.Analyze([root]); + + if (context.Aliases is null) + { + return new SourceFieldTypeMismatchRewriteResult(operation, null); + } + + var selectionSet = RewriteSelectionSet(operation.SelectionSet, context.Aliases); + return new SourceFieldTypeMismatchRewriteResult( + operation.WithSelectionSet(selectionSet), + context.Aliases); + } + + private static SortedSet CollectRootSourceSchemaNames( + SelectionSetNode selectionSet, + ITypeDefinition rootType, + FusionSchemaDefinition schema) + { + var schemaNames = new SortedSet(StringComparer.Ordinal); + Collect(selectionSet, rootType); + return schemaNames; + + void Collect(SelectionSetNode currentSelectionSet, ITypeDefinition parentType) + { + foreach (var selection in currentSelectionSet.Selections) + { + switch (selection) + { + case FieldNode field + when parentType is FusionComplexTypeDefinition complexType + && complexType.Fields.TryGetField( + field.Name.Value, + allowInaccessibleFields: true, + out var fieldDefinition): + foreach (var source in fieldDefinition.Sources) + { + schemaNames.Add(source.SchemaName); + } + break; + + case InlineFragmentNode inlineFragment: + var fragmentType = inlineFragment.TypeCondition is { } typeCondition + && schema.Types.TryGetType( + typeCondition.Name.Value, + allowInaccessibleFields: true, + out var conditionType) + ? conditionType + : parentType; + Collect(inlineFragment.SelectionSet, fragmentType); + break; + } + } + } + } + + private static Dictionary CreateAliases( + SelectionSetNode selectionSet, + HashSet aliasedFields) + { + var reservedResponseNames = new HashSet(StringComparer.Ordinal); + CollectResponseNames(selectionSet, reservedResponseNames); + + var aliases = new Dictionary(ReferenceEqualityComparer.Instance); + var nextAlias = 0; + AddAliases(selectionSet); + return aliases; + + void AddAliases(SelectionSetNode currentSelectionSet) + { + foreach (var selection in currentSelectionSet.Selections) + { + switch (selection) + { + case FieldNode field: + if (aliasedFields.Contains(field)) + { + string alias; + do + { + alias = $"fusion__field_{++nextAlias}"; + } + while (!reservedResponseNames.Add(alias)); + + aliases.Add(field, alias); + } + + if (field.SelectionSet is not null) + { + AddAliases(field.SelectionSet); + } + break; + + case InlineFragmentNode inlineFragment: + AddAliases(inlineFragment.SelectionSet); + break; + } + } + } + } + + private static void CollectResponseNames( + SelectionSetNode selectionSet, + HashSet responseNames) + { + foreach (var selection in selectionSet.Selections) + { + switch (selection) + { + case FieldNode field: + responseNames.Add(field.Alias?.Value ?? field.Name.Value); + if (field.SelectionSet is not null) + { + CollectResponseNames(field.SelectionSet, responseNames); + } + break; + + case InlineFragmentNode inlineFragment: + CollectResponseNames(inlineFragment.SelectionSet, responseNames); + break; + } + } + } + + private static SelectionSetNode RewriteSelectionSet( + SelectionSetNode selectionSet, + IReadOnlyDictionary aliases) + { + List? rewritten = null; + + for (var i = 0; i < selectionSet.Selections.Count; i++) + { + var selection = selectionSet.Selections[i]; + var updated = selection; + + switch (selection) + { + case FieldNode field: + { + var child = field.SelectionSet is null + ? null + : RewriteSelectionSet(field.SelectionSet, aliases); + + if (aliases.TryGetValue(field, out var alias)) + { + updated = new FieldNode( + field.Location, + field.Name, + new NameNode(alias), + field.Directives, + field.Arguments, + child); + } + else if (!ReferenceEquals(child, field.SelectionSet)) + { + updated = field.WithSelectionSet(child); + } + + break; + } + + case InlineFragmentNode inlineFragment: + { + var child = RewriteSelectionSet(inlineFragment.SelectionSet, aliases); + if (!ReferenceEquals(child, inlineFragment.SelectionSet)) + { + updated = inlineFragment.WithSelectionSet(child); + } + + break; + } + } + + if (!ReferenceEquals(updated, selection) && rewritten is null) + { + rewritten = [with(selectionSet.Selections.Count)]; + for (var j = 0; j < i; j++) + { + rewritten.Add(selectionSet.Selections[j]); + } + } + + rewritten?.Add(updated); + } + + return rewritten is null ? selectionSet : selectionSet.WithSelections(rewritten); + } + + private static bool HasSameResponseShape( + SourceOutputField firstSource, + SourceOutputField secondSource) + { + var first = firstSource.Type; + var second = secondSource.Type; + + while (true) + { + if (first is NonNullType || second is NonNullType) + { + if (first is not NonNullType || second is not NonNullType) + { + return false; + } + + first = first.InnerType(); + second = second.InnerType(); + continue; + } + + if (first is ListType || second is ListType) + { + if (first is not ListType || second is not ListType) + { + return false; + } + + first = first.InnerType(); + second = second.InnerType(); + continue; + } + + if (first.IsCompositeType() && second.IsCompositeType()) + { + return true; + } + + var firstTypeName = firstSource.SourceTypeName ?? first.NamedType().Name; + var secondTypeName = secondSource.SourceTypeName ?? second.NamedType().Name; + return firstTypeName.Equals(secondTypeName, StringComparison.Ordinal); + } + } + + private ref struct Context( + OperationDefinitionNode operation, + string schemaName, + FusionSchemaDefinition schema) + { + private HashSet? _reservedResponseNames; + private int _nextAlias; + + public Dictionary? Aliases { get; private set; } + + public bool HasSourceTypeDifference(SelectionSetContext selectionSetContext) + { + foreach (var selection in selectionSetContext.SelectionSet.Selections) + { + switch (selection) + { + case FieldNode field when TryGetSourceField( + selectionSetContext.ParentType, + field.Name.Value, + out var fieldDefinition, + out var source): + if (source.SourceTypeName is not null + || !fieldDefinition.Type.Equals(source.Type, TypeComparison.Structural)) + { + return true; + } + + if (field.SelectionSet is not null + && source.Type.NamedType() is ITypeDefinition childType + && HasSourceTypeDifference( + new SelectionSetContext(field.SelectionSet, childType))) + { + return true; + } + + break; + + case InlineFragmentNode inlineFragment: + var parentType = ResolveTypeCondition( + inlineFragment, + selectionSetContext.ParentType); + + if (HasSourceTypeDifference( + new SelectionSetContext(inlineFragment.SelectionSet, parentType))) + { + return true; + } + + break; + } + } + + return false; + } + + public void Analyze(IReadOnlyList selectionSets) + { + Dictionary>? fields = null; + + for (var i = 0; i < selectionSets.Count; i++) + { + CollectFields(selectionSets[i], ref fields); + } + + if (fields is null) + { + return; + } + + foreach (var occurrences in fields.Values) + { + AnalyzeOccurrences(occurrences); + } + } + + private void AnalyzeOccurrences(List occurrences) + { + var first = occurrences[0]; + List? compatibleChildren = null; + + AddChild(first, ref compatibleChildren); + + for (var i = 1; i < occurrences.Count; i++) + { + var current = occurrences[i]; + + if (HasSameResponseShape(first.Source, current.Source)) + { + AddChild(current, ref compatibleChildren); + continue; + } + + AddAlias(current.Field); + + if (current.Field.SelectionSet is not null + && current.Source.Type.NamedType() is ITypeDefinition childType) + { + Analyze([new SelectionSetContext(current.Field.SelectionSet, childType)]); + } + } + + if (compatibleChildren is not null) + { + Analyze(compatibleChildren); + } + } + + private static void AddChild( + FieldOccurrence occurrence, + ref List? children) + { + if (occurrence.Field.SelectionSet is not null + && occurrence.Source.Type.NamedType() is ITypeDefinition childType) + { + children ??= []; + children.Add(new SelectionSetContext(occurrence.Field.SelectionSet, childType)); + } + } + + private void CollectFields( + SelectionSetContext selectionSetContext, + ref Dictionary>? fields) + { + foreach (var selection in selectionSetContext.SelectionSet.Selections) + { + switch (selection) + { + case FieldNode field when TryGetSourceField( + selectionSetContext.ParentType, + field.Name.Value, + out _, + out var source): + var responseName = field.Alias?.Value ?? field.Name.Value; + fields ??= new Dictionary>(StringComparer.Ordinal); + + if (!fields.TryGetValue(responseName, out var occurrences)) + { + occurrences = []; + fields.Add(responseName, occurrences); + } + + occurrences.Add(new FieldOccurrence(field, source)); + break; + + case InlineFragmentNode inlineFragment: + var parentType = ResolveTypeCondition( + inlineFragment, + selectionSetContext.ParentType); + + CollectFields( + new SelectionSetContext(inlineFragment.SelectionSet, parentType), + ref fields); + break; + } + } + } + + private bool TryGetSourceField( + ITypeDefinition parentType, + string fieldName, + out FusionOutputFieldDefinition field, + out SourceOutputField source) + { + if (parentType is FusionComplexTypeDefinition complexType + && complexType.Fields.TryGetField( + fieldName, + allowInaccessibleFields: true, + out var resolvedField) + && resolvedField.Sources.TryGetMember(schemaName, out var resolvedSource)) + { + field = resolvedField; + source = resolvedSource; + return true; + } + + field = null!; + source = null!; + return false; + } + + private ITypeDefinition ResolveTypeCondition( + InlineFragmentNode inlineFragment, + ITypeDefinition parentType) + => inlineFragment.TypeCondition is { } typeCondition + && schema.Types.TryGetType( + typeCondition.Name.Value, + allowInaccessibleFields: true, + out var conditionType) + ? conditionType + : parentType; + + private void AddAlias(FieldNode field) + { + Aliases ??= new Dictionary(ReferenceEqualityComparer.Instance); + + if (Aliases.ContainsKey(field)) + { + return; + } + + _reservedResponseNames ??= CollectResponseNames(); + + string alias; + do + { + alias = $"fusion__field_{++_nextAlias}"; + } + while (!_reservedResponseNames.Add(alias)); + + Aliases.Add(field, alias); + } + + private HashSet CollectResponseNames() + { + var names = new HashSet(StringComparer.Ordinal); + + Collect(operation.SelectionSet); + return names; + + void Collect(SelectionSetNode selectionSet) + { + foreach (var selection in selectionSet.Selections) + { + switch (selection) + { + case FieldNode field: + names.Add(field.Alias?.Value ?? field.Name.Value); + if (field.SelectionSet is not null) + { + Collect(field.SelectionSet); + } + break; + + case InlineFragmentNode inlineFragment: + Collect(inlineFragment.SelectionSet); + break; + } + } + } + } + } + + private readonly record struct SelectionSetContext( + SelectionSetNode SelectionSet, + ITypeDefinition ParentType); + + private readonly record struct FieldOccurrence( + FieldNode Field, + SourceOutputField Source); +} + +internal readonly record struct SourceFieldTypeMismatchRewriteResult( + OperationDefinitionNode Operation, + Dictionary? Aliases); diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.DbRow.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.DbRow.cs index 90119a024c4..1a0699df9f5 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.DbRow.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.DbRow.cs @@ -18,11 +18,14 @@ internal readonly struct DbRow internal const int SizeOffset = 8; internal const int LocationOrRowsOffset = 12; internal const int SourceAndTypeOffset = 16; + internal const int FlagsShift = 17; + internal const int FlagsBitCount = 8; + internal const int FlagsMask = 0xFF; // 29 bits parent cursor value + 3 reserved private readonly int _parent; - // 15 bits OperationReferenceId + 2 bits OperationReferenceType + 7 bits Flags + 8 reserved + // 15 bits OperationReferenceId + 2 bits OperationReferenceType + 8 bits Flags + 7 reserved private readonly int _selectionAndFlags; // 1 bit HasComplexChildren (sign) + 31 bits SizeOrLength @@ -52,8 +55,8 @@ public DbRow( Debug.Assert(parentRow is >= 0 and <= 0x1FFFFFFF); // 29 bits (cursor value) Debug.Assert(operationReferenceId is >= 0 and <= 0x7FFF); // 15 bits Debug.Assert(numberOfRows is >= 0 and <= 0x1FFFFFFF); // 29 bits - Debug.Assert((byte)flags <= 127); // 7 bits (0x7F) Debug.Assert((byte)operationReferenceType <= 3); // 2 bits + Debug.Assert((int)flags is >= 0 and <= FlagsMask); Debug.Assert(Unsafe.SizeOf() == Size); var locationOrRows = location != 0 ? location : numberOfRows; @@ -61,7 +64,7 @@ public DbRow( _parent = parentRow & 0x1FFFFFFF; _selectionAndFlags = operationReferenceId | ((int)operationReferenceType << 15) - | ((int)flags << 17); + | (((int)flags & FlagsMask) << FlagsShift); _sizeOrLengthUnion = sizeOrLength; _locationOrRows = locationOrRows & 0x1FFFFFFF; _sourceAndType = (sourceDocumentId & 0x7FFF) | (((int)tokenType & 0x0F) << 15); @@ -146,9 +149,13 @@ public OperationReferenceType OperationReferenceType /// Element metadata flags. /// /// - /// 7 bits = 128 combinations + /// 8 bits = 256 combinations /// - public ElementFlags Flags => (ElementFlags)((_selectionAndFlags >> 17) & 0x7F); + public ElementFlags Flags + => (ElementFlags)((_selectionAndFlags >>> FlagsShift) & FlagsMask); + + public bool IsNullMarker + => (Flags & ElementFlags.NullMarker) is ElementFlags.NullMarker; /// /// True for primitive JSON values (strings, numbers, booleans, null). @@ -173,6 +180,7 @@ internal enum ElementFlags : byte IsRoot = 8, IsInternal = 16, IsExcluded = 32, - IsEnumValue = 64 + IsEnumValue = 64, + NullMarker = 128 } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.MetaDb.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.MetaDb.cs index 12f0d7e6a4c..3c8c1ae2a03 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.MetaDb.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.MetaDb.cs @@ -102,7 +102,7 @@ internal Cursor AppendEmptyProperty(int parentRow, int selectionId, ElementFlags { Debug.Assert(parentRow is >= 0 and <= 0x1FFFFFFF); Debug.Assert(selectionId is >= 0 and <= 0x7FFF); - Debug.Assert((byte)flags <= 127); + Debug.Assert((int)flags is >= 0 and <= DbRow.FlagsMask); var (chunk, byteOffset, cursor) = ReserveRow(); @@ -117,7 +117,7 @@ internal Cursor AppendEmptyProperty(int parentRow, int selectionId, ElementFlags ref Unsafe.Add(ref row, 4), selectionId | ((int)OperationReferenceType.Selection << 15) - | ((int)flags << 17)); + | (((int)flags & DbRow.FlagsMask) << DbRow.FlagsShift)); // ints 2..3 must be zero (int 4 is written directly below) Unsafe.InitBlockUnaligned(ref Unsafe.Add(ref row, 8), 0, 8); @@ -136,7 +136,7 @@ internal Cursor AppendEmptyPropertyWithNullValue(int parentRow, int selectionId, { Debug.Assert(parentRow is >= 0 and <= 0x1FFFFFFF); Debug.Assert(selectionId is >= 0 and <= 0x7FFF); - Debug.Assert((byte)flags <= 127); + Debug.Assert((int)flags is >= 0 and <= DbRow.FlagsMask); var next = _next; var byteOffset = next.ByteOffset; @@ -156,7 +156,7 @@ internal Cursor AppendEmptyPropertyWithNullValue(int parentRow, int selectionId, ref Unsafe.Add(ref row0, 4), selectionId | ((int)OperationReferenceType.Selection << 15) - | ((int)flags << 17)); + | (((int)flags & DbRow.FlagsMask) << DbRow.FlagsShift)); Unsafe.InitBlockUnaligned(ref Unsafe.Add(ref row0, 8), 0, 8); // int 4: PropertyName token Unsafe.WriteUnaligned( @@ -184,7 +184,7 @@ internal Cursor AppendStartObject(int parentRow, int selectionSetId, int propert Debug.Assert(parentRow is >= 0 and <= 0x1FFFFFFF); Debug.Assert(selectionSetId is >= 0 and <= 0x7FFF); Debug.Assert(propertyCount is >= 0 and <= 0x0FFFFFFF); // room for (count*2)+1 in 29 bits - Debug.Assert((byte)flags <= 127); + Debug.Assert((int)flags is >= 0 and <= DbRow.FlagsMask); var (chunk, byteOffset, cursor) = ReserveRow(); @@ -199,7 +199,7 @@ internal Cursor AppendStartObject(int parentRow, int selectionSetId, int propert ref Unsafe.Add(ref row, 4), selectionSetId | ((int)OperationReferenceType.SelectionSet << 15) - | ((int)flags << 17)); + | (((int)flags & DbRow.FlagsMask) << DbRow.FlagsShift)); // int 2: sizeOrLength = property count Unsafe.WriteUnaligned(ref Unsafe.Add(ref row, 8), propertyCount); @@ -221,7 +221,7 @@ internal Cursor AppendStartArray(int parentRow, int length, ElementFlags flags) { Debug.Assert(parentRow is >= 0 and <= 0x1FFFFFFF); Debug.Assert(length is >= 0 and <= int.MaxValue); - Debug.Assert((byte)flags <= 127); + Debug.Assert((int)flags is >= 0 and <= DbRow.FlagsMask); var (chunk, byteOffset, cursor) = ReserveRow(); @@ -234,7 +234,7 @@ internal Cursor AppendStartArray(int parentRow, int length, ElementFlags flags) // int 1: flags only (no OpRefId / no OpRefType for arrays) Unsafe.WriteUnaligned( ref Unsafe.Add(ref row, 4), - (int)flags << 17); + ((int)flags & DbRow.FlagsMask) << DbRow.FlagsShift); // int 2: sizeOrLength = length Unsafe.WriteUnaligned(ref Unsafe.Add(ref row, 8), length); @@ -423,6 +423,7 @@ internal readonly void ReplacePreserveParent( ElementFlags flags = ElementFlags.None) { AssertValidCursor(cursor); + Debug.Assert((int)flags is >= 0 and <= DbRow.FlagsMask); ref var row = ref RowRef(cursor); @@ -431,7 +432,7 @@ internal readonly void ReplacePreserveParent( ref Unsafe.Add(ref row, DbRow.SelectionAndFlagsOffset), operationReferenceId | ((int)operationReferenceType << 15) - | ((int)flags << 17)); + | (((int)flags & DbRow.FlagsMask) << DbRow.FlagsShift)); // int 2: SizeOrLength (full 32 bits; preserves the sign bit / UnknownSize sentinel) Unsafe.WriteUnaligned(ref Unsafe.Add(ref row, DbRow.SizeOffset), sizeOrLength); @@ -543,21 +544,22 @@ internal readonly ElementFlags GetFlags(Cursor cursor) AssertValidCursor(cursor); var selectionAndFlags = Unsafe.ReadUnaligned(ref Unsafe.Add(ref RowRef(cursor), DbRow.SelectionAndFlagsOffset)); - return (ElementFlags)((selectionAndFlags >> 17) & 0x7F); + return (ElementFlags)((selectionAndFlags >>> DbRow.FlagsShift) & DbRow.FlagsMask); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal readonly void SetFlags(Cursor cursor, ElementFlags flags) { AssertValidCursor(cursor); - Debug.Assert((byte)flags <= 127, "Flags value exceeds 7-bit limit"); - + Debug.Assert( + (int)flags is >= 0 and <= DbRow.FlagsMask, + $"Flags value exceeds {DbRow.FlagsBitCount}-bit limit"); var fieldSpan = RowSpan(cursor)[DbRow.SelectionAndFlagsOffset..]; var currentValue = MemoryMarshal.Read(fieldSpan); - // Clear bits 17..23 (7-bit Flags region) then OR new flags in. - var clearedValue = (int)((uint)currentValue & ~(0x7Fu << 17)); - var newValue = clearedValue | ((int)flags << 17); + var clearedValue = currentValue & ~(DbRow.FlagsMask << DbRow.FlagsShift); + var newValue = clearedValue + | (((int)flags & DbRow.FlagsMask) << DbRow.FlagsShift); MemoryMarshal.Write(fieldSpan, newValue); } diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.WriteTo.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.WriteTo.cs index bbf8dba0ddd..9cc2e7fa027 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.WriteTo.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.WriteTo.cs @@ -8,8 +8,16 @@ public sealed partial class CompositeResultDocument : IRawJsonFormatter { public void WriteDataTo(JsonWriter jsonWriter) { - var formatter = new RawJsonFormatter(this, jsonWriter); - formatter.Write(); + if (HasNullMarkers) + { + var formatter = new NullMarkerRawJsonFormatter(this, jsonWriter); + formatter.Write(); + } + else + { + var formatter = new RawJsonFormatter(this, jsonWriter); + formatter.Write(); + } } internal readonly ref struct RawJsonFormatter(CompositeResultDocument document, JsonWriter writer) @@ -178,4 +186,176 @@ private void WriteArray(Cursor start, DbRow startRow) writer.WriteEndArray(); } } + + internal readonly ref struct NullMarkerRawJsonFormatter( + CompositeResultDocument document, + JsonWriter writer) + { + public void Write() + { + var root = Cursor.CreateZero(); + var row = document._metaDb.Get(root); + + if (row.IsNullMarker + || row.TokenType is ElementTokenType.Null + || (ElementFlags.Invalidated & row.Flags) is ElementFlags.Invalidated) + { + writer.WriteNullValue(); + } + else + { + WriteObject(root, row); + } + } + + public void WriteValue(Cursor cursor, DbRow row) + { + if (row.IsNullMarker) + { + writer.WriteNullValue(); + return; + } + + var tokenType = row.TokenType; + + // The null marker belongs to this logical value/reference. Resolve only after + // checking it so another path that references the same object remains unaffected. + if (tokenType is ElementTokenType.Reference) + { + cursor = new Cursor(row.Location); + row = document._metaDb.Get(cursor); + tokenType = row.TokenType; + } + + Debug.Assert(tokenType is not ElementTokenType.Reference); + Debug.Assert(tokenType is not ElementTokenType.EndObject); + Debug.Assert(tokenType is not ElementTokenType.EndArray); + var isSourceResult = (ElementFlags.SourceResult & row.Flags) is ElementFlags.SourceResult; + + switch (tokenType) + { + case ElementTokenType.StartObject: + if (isSourceResult) + { + WriteSourceValue(row); + } + else + { + WriteObject(cursor, row); + } + break; + + case ElementTokenType.StartArray: + if (isSourceResult) + { + WriteSourceValue(row); + } + else + { + WriteArray(cursor, row); + } + break; + + case ElementTokenType.None: + case ElementTokenType.Null: + writer.WriteNullValue(); + break; + + case ElementTokenType.True: + writer.WriteBooleanValue(true); + break; + + case ElementTokenType.False: + writer.WriteBooleanValue(false); + break; + + case ElementTokenType.String: + if (isSourceResult) + { + document._sources[row.SourceDocumentId] + .WriteRawStringValueTo(writer, row.Location, row.SizeOrLength); + } + else + { + writer.WriteStringValue(document.ReadRawValue(row), skipEscaping: true); + } + break; + + case ElementTokenType.Number: + if (isSourceResult) + { + document._sources[row.SourceDocumentId] + .WriteRawNumberValueTo(writer, row.Location, row.SizeOrLength); + } + else + { + writer.WriteNumberValue(document.ReadRawValue(row)); + } + break; + + default: + throw new NotSupportedException(); + } + } + + private void WriteSourceValue(DbRow row) + { + var sourceDocument = document._sources[row.SourceDocumentId]; + var sourceCursor = SourceResultDocument.Cursor.From(row.Location, row.SizeOrLength); + var formatter = new SourceResultDocument.RawJsonFormatter(sourceDocument, writer); + formatter.WriteValue(sourceCursor); + } + + private void WriteObject(Cursor start, DbRow startRow) + { + Debug.Assert(startRow.TokenType is ElementTokenType.StartObject); + + var current = start + 1; + var end = start + startRow.NumberOfRows; + + writer.WriteStartObject(); + + while (current < end) + { + var row = document._metaDb.Get(current); + Debug.Assert(row.TokenType is ElementTokenType.PropertyName); + + if ((ElementFlags.IsInternal & row.Flags) is ElementFlags.IsInternal + || (ElementFlags.IsExcluded & row.Flags) is ElementFlags.IsExcluded) + { + current += 2; + continue; + } + + writer.WritePropertyName(document.ReadRawValue(row)); + + current++; + row = document._metaDb.Get(current); + WriteValue(current, row); + + current++; + } + + writer.WriteEndObject(); + } + + private void WriteArray(Cursor start, DbRow startRow) + { + Debug.Assert(startRow.TokenType is ElementTokenType.StartArray); + + var current = start + 1; + var end = start + startRow.NumberOfRows; + + writer.WriteStartArray(); + + while (current < end) + { + var row = document._metaDb.Get(current); + WriteValue(current, row); + current++; + } + + writer.WriteEndArray(); + } + } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.cs index d7360c90df8..a5ff8d3c404 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultDocument.cs @@ -17,6 +17,7 @@ public sealed partial class CompositeResultDocument : IDisposable private readonly ulong _deferFlags; private readonly PathSegmentLocalPool? _pathPool; internal MetaDb _metaDb; + private NullMarkerState _nullMarkerState; private int _disposed; internal CompositeResultDocument( @@ -285,6 +286,55 @@ internal bool IsNullOrInvalidated(Cursor current) return false; } + internal bool RequiresNullMarkerFinalization + => (_nullMarkerState & NullMarkerState.RequiresFinalization) + is NullMarkerState.RequiresFinalization; + + internal bool HasNullMarkers + => (_nullMarkerState & NullMarkerState.HasMarkers) is NullMarkerState.HasMarkers; + + internal bool IsNullMarker(Cursor current) + { + ObjectDisposedException.ThrowIf(_disposed != 0, this); + + return (_metaDb.GetFlags(current) & ElementFlags.NullMarker) is ElementFlags.NullMarker; + } + + internal void RequireNullMarkerFinalization() + { + ObjectDisposedException.ThrowIf(_disposed != 0, this); + + _nullMarkerState |= NullMarkerState.RequiresFinalization; + } + + internal void CompleteNullMarkerFinalization() + { + ObjectDisposedException.ThrowIf(_disposed != 0, this); + + _nullMarkerState &= ~NullMarkerState.RequiresFinalization; + } + + internal void SetNullMarker(Cursor current) + { + ObjectDisposedException.ThrowIf(_disposed != 0, this); + + var flags = _metaDb.GetFlags(current); + if ((flags & ElementFlags.NullMarker) is not ElementFlags.NullMarker) + { + _metaDb.SetFlags(current, flags | ElementFlags.NullMarker); + } + + _nullMarkerState |= NullMarkerState.HasMarkers; + } + + [Flags] + private enum NullMarkerState : byte + { + None = 0, + RequiresFinalization = 1, + HasMarkers = 2 + } + internal bool IsInternalProperty(Cursor current) { ObjectDisposedException.ThrowIf(_disposed != 0, this); diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultElement.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultElement.cs index cbabc736099..ddc1602c2f1 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultElement.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultElement.cs @@ -43,9 +43,17 @@ internal void WriteTo(JsonWriter jsonWriter) { CheckValidInstance(); - var formatter = new RawJsonFormatter(_parent, jsonWriter); var row = _parent._metaDb.Get(_cursor); - formatter.WriteValue(_cursor, row); + if (_parent.HasNullMarkers) + { + var formatter = new NullMarkerRawJsonFormatter(_parent, jsonWriter); + formatter.WriteValue(_cursor, row); + } + else + { + var formatter = new RawJsonFormatter(_parent, jsonWriter); + formatter.WriteValue(_cursor, row); + } } /// @@ -188,6 +196,21 @@ public bool IsNullOrInvalidated } } + internal bool IsRoot => _cursor.IsZero; + + internal bool IsNullMarker + { + get + { + if (_parent is null) + { + return false; + } + + return _parent.IsNullMarker(_cursor); + } + } + /// /// Gets the compact path to this element within the result document. /// @@ -1011,6 +1034,13 @@ internal void SetNullValue() _parent.AssignNullValue(this); } + internal void SetNullMarker() + { + CheckValidInstance(); + + _parent.SetNullMarker(_cursor); + } + /// public override string ToString() { diff --git a/src/HotChocolate/Fusion/src/Fusion.Utilities/Validators/SelectionSetValidator.cs b/src/HotChocolate/Fusion/src/Fusion.Utilities/Validators/SelectionSetValidator.cs index 49d6f9b07b5..e568480ed46 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Utilities/Validators/SelectionSetValidator.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Utilities/Validators/SelectionSetValidator.cs @@ -39,6 +39,17 @@ protected override ISyntaxVisitorAction Enter( var type = context.TypeContext.Peek(); + if (type is IUnionTypeDefinition unionType) + { + context.Errors.Add( + string.Format( + SelectionSetValidator_FieldDoesNotExistOnType, + node.Name.Value, + unionType.Name)); + + return Skip; + } + if (type is IComplexTypeDefinition complexType) { if (complexType.Fields.TryGetField(node.Name.Value, out var field)) diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/DeferTests.cs b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/DeferTests.cs index daa03927e6b..0b54b607bd2 100644 --- a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/DeferTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/DeferTests.cs @@ -537,6 +537,202 @@ ... @defer(label: "email") { await MatchSnapshotAsync(gateway, request, result, stableStream: true); } + [Fact] + public async Task Defer_Should_NotEmitDescendantData_When_InaccessibleRuntimeTypeNullsPendingAncestor() + { + // arrange + using var server = CreateSourceSchema( + "A", + """ + type Query { + entity: Entity + } + + type Entity { + id: ID! + wrapper: Wrapper + } + + type Wrapper { + visible: String + value: Foo! @returns(types: ["Baz"]) + } + + interface Foo { + name: String + } + + type Baz implements Foo @inaccessible { + name: String + } + + type Qux implements Foo { + name: String + } + """); + + using var gateway = await CreateCompositeSchemaAsync([("A", server)]); + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + var request = new OperationRequest( + """ + query { + entity { + id + wrapper { + visible + ... @defer(label: "hidden") { + value { + __typename + name + } + } + } + } + } + """); + + // act + using var result = await client.PostAsync( + request, + new Uri("http://localhost:5000/graphql"), + TestContext.Current.CancellationToken); + + // assert + var rawBody = await result.HttpResponseMessage.Content.ReadAsStringAsync( + TestContext.Current.CancellationToken); + rawBody.MatchSnapshot(); + } + + [Fact] + public async Task Defer_Should_EmitSingleNullPatch_When_InaccessibleRuntimeTypeNullsPendingList() + { + // arrange + using var server = CreateSourceSchema( + "A", + """ + type Query { + entities: [Entity!] + } + + type Entity { + id: ID! + value: Foo! @returns(types: ["Baz"]) + } + + interface Foo { + name: String + } + + type Baz implements Foo @inaccessible { + name: String + } + + type Qux implements Foo { + name: String + } + """); + + using var gateway = await CreateCompositeSchemaAsync([("A", server)]); + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + var request = new OperationRequest( + """ + query { + entities { + id + ... @defer(label: "hidden") { + value { + __typename + name + } + } + } + } + """); + + // act + using var result = await client.PostAsync( + request, + new Uri("http://localhost:5000/graphql"), + TestContext.Current.CancellationToken); + + // assert + var rawBody = await result.HttpResponseMessage.Content.ReadAsStringAsync( + TestContext.Current.CancellationToken); + rawBody.MatchSnapshot(); + } + + [Fact] + public async Task Defer_Should_SuppressDescendantPatch_When_NullMarkerBubblesAbovePendingPath() + { + // arrange + using var server = CreateSourceSchema( + "A", + """ + type Query { + deep: Deep + } + + type Deep { + parent: Parent + } + + type Parent { + visible: String + child: Child! + } + + type Child { + visible: String + value: Foo! @returns(types: ["Baz"]) + } + + interface Foo { + name: String + } + + type Baz implements Foo @inaccessible { + name: String + } + + type Qux implements Foo { + name: String + } + """); + + using var gateway = await CreateCompositeSchemaAsync([("A", server)]); + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + var request = new OperationRequest( + """ + query { + deep { + parent { + visible + child { + visible + ... @defer(label: "hidden") { + value { + __typename + name + } + } + } + } + } + } + """); + + // act + using var result = await client.PostAsync( + request, + new Uri("http://localhost:5000/graphql"), + TestContext.Current.CancellationToken); + + // assert + var rawBody = await result.HttpResponseMessage.Content.ReadAsStringAsync( + TestContext.Current.CancellationToken); + rawBody.MatchSnapshot(); + } + [Fact(Skip = "Requires validation of @skip/@include interaction with @defer at the planning level")] public async Task Defer_With_Skip_Directive_Should_Skip_Deferred_Fragment() { diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/ConditionalTests.NodeField_Skip_On_Interface_Selection_Type_Refinement_With_Same_Unskipped_Selection.yaml b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/ConditionalTests.NodeField_Skip_On_Interface_Selection_Type_Refinement_With_Same_Unskipped_Selection.yaml index 0eb9b0c7e87..90c443d4021 100644 --- a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/ConditionalTests.NodeField_Skip_On_Interface_Selection_Type_Refinement_With_Same_Unskipped_Selection.yaml +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/ConditionalTests.NodeField_Skip_On_Interface_Selection_Type_Refinement_With_Same_Unskipped_Selection.yaml @@ -61,24 +61,17 @@ sourceSchemas: - request: accept: application/graphql-response+json; charset=utf-8, application/json; charset=utf-8, application/jsonl; charset=utf-8, text/event-stream; charset=utf-8 document: | - query testQuery_ff22954e_3($skip: Boolean!) { + query testQuery_ff22954e_3 { node(id: "RGlzY3Vzc2lvbjox") { __typename ... on Discussion { __typename - author @skip(if: $skip) { - id - } author { id } } } } - variables: | - { - "skip": true - } response: results: - | @@ -176,22 +169,17 @@ operationPlan: type: Operation schema: A operation: | - query testQuery_ff22954e_3($skip: Boolean!) { + query testQuery_ff22954e_3 { node(id: "RGlzY3Vzc2lvbjox") { __typename ... on Discussion { __typename - author @skip(if: $skip) { - id - } author { id } } } } - forwardedVariables: - - skip dependencies: - id: 1 - id: 4 diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DeferTests.Defer_Should_EmitSingleNullPatch_When_InaccessibleRuntimeTypeNullsPendingList.snap b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DeferTests.Defer_Should_EmitSingleNullPatch_When_InaccessibleRuntimeTypeNullsPendingList.snap new file mode 100644 index 00000000000..444e50f171f --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DeferTests.Defer_Should_EmitSingleNullPatch_When_InaccessibleRuntimeTypeNullsPendingList.snap @@ -0,0 +1,2 @@ +{"data":{"entities":[{"id":"RW50aXR5OjE="},{"id":"RW50aXR5OjI="},{"id":"RW50aXR5OjM="}]},"extensions":{"fusion":{"operationPlan":{"id":"8612cb18e0daf0c45f0f98402db1eaed1bda0ca8ef95ccb43dcdc739af21263b","operation":{"kind":"Query","document":"{\n entities {\n id\n }\n}","id":"3c3f68c3b721c3ce21c1686d5b8c88fc","hash":"3c3f68c3b721c3ce21c1686d5b8c88fc","shortHash":"3c3f68c3"},"searchSpace":1,"expandedNodes":1,"nodes":[{"id":1,"type":"Operation","schema":"A","operation":{"name":"Op_3c3f68c3_1","kind":"Query","document":"query Op_3c3f68c3_1 {\n entities {\n id\n }\n}","hash":"f027dd8edcfdd33871b0d5d5a0ccc7f015296855e6d641605a2ebb4e06e21611","shortHash":"f027dd8e"},"resultSelectionSet":"{ entities }"}],"deliveryGroups":[{"id":0,"path":"$.entities","label":"hidden"}],"incrementalPlans":[{"deliveryGroupIds":[0],"parentNodeId":1,"operation":{"kind":"Query","document":"{\n entities {\n value {\n __typename\n name\n }\n }\n}","id":"3c3f68c3b721c3ce21c1686d5b8c88fc#defer_0","hash":"3c3f68c3b721c3ce21c1686d5b8c88fc#defer_0","shortHash":"3c3f68c3"},"nodes":[{"id":1,"type":"Operation","schema":"A","operation":{"name":"Op_defer_1","kind":"Query","document":"query Op_defer_1 {\n entities {\n value {\n __typename\n name\n }\n }\n}","hash":"0ee5291aa9892194dc0eab7f8e44189fd2ee71618f0476fc2194f1f3c59704c2","shortHash":"0ee5291a"},"resultSelectionSet":"{ entities }"}]}]}}},"pending":[{"id":"0","path":["entities"],"label":"hidden"}],"hasNext":true} +{"incremental":[{"id":"0","data":null}],"completed":[{"id":"0"}],"hasNext":false} diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DeferTests.Defer_Should_NotEmitDescendantData_When_InaccessibleRuntimeTypeNullsPendingAncestor.snap b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DeferTests.Defer_Should_NotEmitDescendantData_When_InaccessibleRuntimeTypeNullsPendingAncestor.snap new file mode 100644 index 00000000000..3f9f32f6fe8 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DeferTests.Defer_Should_NotEmitDescendantData_When_InaccessibleRuntimeTypeNullsPendingAncestor.snap @@ -0,0 +1,2 @@ +{"data":{"entity":{"id":"RW50aXR5OjE=","wrapper":{"visible":"Wrapper: V3JhcHBlcjoy"}}},"extensions":{"fusion":{"operationPlan":{"id":"7d619ee751a9a07318e4074cb3789cc494d2e36a06f23e31b852aeaca5b9b794","operation":{"kind":"Query","document":"{\n entity {\n id\n wrapper {\n visible\n }\n }\n}","id":"342bd0f2202b2347e1ab4137fce22287","hash":"342bd0f2202b2347e1ab4137fce22287","shortHash":"342bd0f2"},"searchSpace":1,"expandedNodes":1,"nodes":[{"id":1,"type":"Operation","schema":"A","operation":{"name":"Op_342bd0f2_1","kind":"Query","document":"query Op_342bd0f2_1 {\n entity {\n id\n wrapper {\n visible\n }\n }\n}","hash":"bbe7f9771da2b35c614cc00a89cb9716bd26f966b50e9ade3a19e199ca6bf410","shortHash":"bbe7f977"},"resultSelectionSet":"{ entity }"}],"deliveryGroups":[{"id":0,"path":"$.entity.wrapper","label":"hidden"}],"incrementalPlans":[{"deliveryGroupIds":[0],"parentNodeId":1,"operation":{"kind":"Query","document":"{\n entity {\n wrapper {\n value {\n __typename\n name\n }\n }\n }\n}","id":"342bd0f2202b2347e1ab4137fce22287#defer_0","hash":"342bd0f2202b2347e1ab4137fce22287#defer_0","shortHash":"342bd0f2"},"nodes":[{"id":1,"type":"Operation","schema":"A","operation":{"name":"Op_defer_1","kind":"Query","document":"query Op_defer_1 {\n entity {\n wrapper {\n value {\n __typename\n name\n }\n }\n }\n}","hash":"0ef9395c0a4e3fc15bc53af23b1c1189f72aaa0c4a7a1a9ba8829048f23eed59","shortHash":"0ef9395c"},"resultSelectionSet":"{ entity }"}]}]}}},"pending":[{"id":"0","path":["entity","wrapper"],"label":"hidden"}],"hasNext":true} +{"incremental":[{"id":"0","data":null}],"completed":[{"id":"0"}],"hasNext":false} diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DeferTests.Defer_Should_SuppressDescendantPatch_When_NullMarkerBubblesAbovePendingPath.snap b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DeferTests.Defer_Should_SuppressDescendantPatch_When_NullMarkerBubblesAbovePendingPath.snap new file mode 100644 index 00000000000..d49451ffa7e --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DeferTests.Defer_Should_SuppressDescendantPatch_When_NullMarkerBubblesAbovePendingPath.snap @@ -0,0 +1,2 @@ +{"data":{"deep":{"parent":{"visible":"Parent: UGFyZW50OjI=","child":{"visible":"Child: Q2hpbGQ6Mw=="}}}},"extensions":{"fusion":{"operationPlan":{"id":"b6049711a5c1ecb83d1388e64ce3f535819cc0071ca8425900b615876616135f","operation":{"kind":"Query","document":"{\n deep {\n parent {\n visible\n child {\n visible\n }\n }\n }\n}","id":"d84127a7df8bc15ea21afd904131e5cd","hash":"d84127a7df8bc15ea21afd904131e5cd","shortHash":"d84127a7"},"searchSpace":1,"expandedNodes":1,"nodes":[{"id":1,"type":"Operation","schema":"A","operation":{"name":"Op_d84127a7_1","kind":"Query","document":"query Op_d84127a7_1 {\n deep {\n parent {\n visible\n child {\n visible\n }\n }\n }\n}","hash":"07b25d9cc2e3499154c9b0db7acae1a25d502b80f3633fbbea15bdcde4e3554c","shortHash":"07b25d9c"},"resultSelectionSet":"{ deep }"}],"deliveryGroups":[{"id":0,"path":"$.deep.parent.child","label":"hidden"}],"incrementalPlans":[{"deliveryGroupIds":[0],"parentNodeId":1,"operation":{"kind":"Query","document":"{\n deep {\n parent {\n child {\n value {\n __typename\n name\n }\n }\n }\n }\n}","id":"d84127a7df8bc15ea21afd904131e5cd#defer_0","hash":"d84127a7df8bc15ea21afd904131e5cd#defer_0","shortHash":"d84127a7"},"nodes":[{"id":1,"type":"Operation","schema":"A","operation":{"name":"Op_defer_1","kind":"Query","document":"query Op_defer_1 {\n deep {\n parent {\n child {\n value {\n __typename\n name\n }\n }\n }\n }\n}","hash":"bb4706d0801118f46c703d5c7d8a848d839b1a610e1d60101eeae5ce8c3917c3","shortHash":"bb4706d0"},"resultSelectionSet":"{ deep }"}]}]}}},"pending":[{"id":"0","path":["deep","parent","child"],"label":"hidden"}],"hasNext":true} +{"completed":[{"id":"0"}],"hasNext":false} diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/GlobalObjectIdentificationTests.Node_Field_Selections_On_Interface_And_Concrete_Type_Both_Have_Same_Dependency.yaml b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/GlobalObjectIdentificationTests.Node_Field_Selections_On_Interface_And_Concrete_Type_Both_Have_Same_Dependency.yaml index a1d9c15a508..ddf6382706b 100644 --- a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/GlobalObjectIdentificationTests.Node_Field_Selections_On_Interface_And_Concrete_Type_Both_Have_Same_Dependency.yaml +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/GlobalObjectIdentificationTests.Node_Field_Selections_On_Interface_And_Concrete_Type_Both_Have_Same_Dependency.yaml @@ -76,9 +76,6 @@ sourceSchemas: author { id } - author { - id - } } } } @@ -219,9 +216,6 @@ operationPlan: author { id } - author { - id - } } } } diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/ApolloFederationV1SourceSettingsTests.cs b/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/ApolloFederationV1SourceSettingsTests.cs new file mode 100644 index 00000000000..ec5928100c2 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/ApolloFederationV1SourceSettingsTests.cs @@ -0,0 +1,360 @@ +using HotChocolate.Fusion.Logging; +using HotChocolate.Fusion.Options; +using HotChocolate.Fusion.Results; +using HotChocolate.Language; +using HotChocolate.Types.Mutable; + +namespace HotChocolate.Fusion.ApolloFederation; + +public sealed class ApolloFederationV1SourceSettingsTests +{ + [Fact] + public void Compose_Should_TransformRawFederationV1_When_ExplicitlyConfigured() + { + // act + var (result, log) = Compose( + [new SourceSchemaText("Products", FederationV1Schema)], + ["Products"]); + + // assert + Assert.True(result.IsSuccess, FormatFailure(result, log)); + Assert.Equal( + "ApolloFederation", + GetConnectorKind(result.Value, "PRODUCTS")); + var document = result.Value.ToSyntaxNode(); + var transformedSchema = new DocumentNode( + document.Definitions.Where( + definition => definition is SchemaDefinitionNode + || definition is INamedSyntaxNode named + && (!named.Name.Value.StartsWith("fusion__", StringComparison.Ordinal) + || named.Name.Value == "fusion__Schema")) + .ToArray()); + + transformedSchema.ToString().MatchInlineSnapshot( + """ + schema + @fusion__execution( + nodeResolution: GATEWAY + shareableFieldRuntimeTypeRouting: SOURCE_LOCAL + ) { + query: Query + } + + type Query @fusion__type(schema: PRODUCTS) { + product: ProductV1 @fusion__field(schema: PRODUCTS) + } + + type ProductV1 + @fusion__type(schema: PRODUCTS) + @fusion__lookup( + schema: PRODUCTS + key: "id" + field: "fusion__lookup_productV1ById(id: ID!): ProductV1" + map: ["id"] + path: null + internal: true + ) { + displayName: String @fusion__field(schema: PRODUCTS) + id: ID! @fusion__field(schema: PRODUCTS, sourceExternal: true) + } + + "The fusion__Schema enum is a generated type used within an execution schema document to refer to a source schema in a type-safe manner." + enum fusion__Schema { + PRODUCTS @fusion__schema_metadata(name: "Products", kind: "ApolloFederation") + } + """); + } + + [Fact] + public void Compose_Should_RejectRawFederationV1_When_NotConfigured() + { + // act + var (result, _) = Compose( + [new SourceSchemaText("Products", FederationV1Schema)], + []); + + // assert + Assert.True(result.IsFailure); + } + + [Fact] + public void Compose_Should_RejectV2KeyArgument_When_ExplicitV1IsConfigured() + { + // arrange + var schema = FederationV1Schema.Replace( + "@key(fields: \"id\")", + "@key(fields: \"id\", resolvable: false)", + StringComparison.Ordinal); + + // act + var (result, _) = Compose( + [new SourceSchemaText("Products", schema)], + ["Products"]); + + // assert + Assert.True(result.IsFailure); + } + + [Theory] + [InlineData( + "id: ID! @external", + "id: ID! @external(reason: \"legacy\")")] + [InlineData( + "type ProductV1 @key(fields: \"id\") @extends", + "type ProductV1 @key(fields: \"id\") @extends @external")] + public void Compose_Should_RejectV2ExternalApplication_When_ExplicitV1IsConfigured( + string current, + string replacement) + { + // arrange + var schema = FederationV1Schema.Replace( + current, + replacement, + StringComparison.Ordinal); + + // act + var (result, _) = Compose( + [new SourceSchemaText("Products", schema)], + ["Products"]); + + // assert + Assert.True(result.IsFailure); + } + + [Fact] + public void Parse_Should_RegisterRawProvides_When_ExplicitV1IsConfigured() + { + // arrange + var log = new CompositionLog(); + var parser = new SourceSchemaParser( + new SourceSchemaText( + "Products", + """ + type Query { + product: Product @provides(fields: "sku") + } + + type Product { + sku: String @external + } + """), + log, + isApolloFederationV1: true); + + // act + var result = parser.Parse(); + + // assert + Assert.True(result.IsSuccess, FormatFailure(result, log)); + var query = Assert.IsType(result.Value.QueryType); + Assert.True(query.Fields["product"].Directives.ContainsName("provides")); + } + + [Fact] + public void Compose_Should_RejectInvalidTagArguments_When_ExplicitV1IsConfigured() + { + // arrange + var schema = (FederationV1TagDefinition + FederationV1Schema).Replace( + "displayName: String", + "displayName: String @tag(name: \"public\", unexpected: \"value\")", + StringComparison.Ordinal); + + // act + var (result, _) = Compose( + [new SourceSchemaText("Products", schema)], + ["Products"]); + + // assert + Assert.True(result.IsFailure); + } + + [Fact] + public void Compose_Should_RejectTagOnSchema_When_ExplicitV1IsConfigured() + { + // arrange + const string schema = + """ + extend schema @tag(name: "public") + + """ + FederationV1TagDefinition + FederationV1Schema; + + // act + var (result, _) = Compose( + [new SourceSchemaText("Products", schema)], + ["Products"]); + + // assert + Assert.True(result.IsFailure); + } + + [Fact] + public void Compose_Should_RejectExplicitV1_When_FederationLinkIsPresent() + { + // act + var (result, log) = Compose( + [new SourceSchemaText("Products", FederationV2Schema)], + ["Products"]); + + // assert + Assert.True(result.IsFailure); + log.Select(entry => entry.ToString()).MatchInlineSnapshots( + [ + """ + { + "message": "Source schema 'Products' cannot enable Apollo Federation v1 support because it links to the Apollo Federation specification.", + "code": "CONFLICTING_APOLLO_FEDERATION_VERSION", + "severity": "Error", + "schema": "Products", + "extensions": {} + } + """ + ]); + } + + [Fact] + public void Compose_Should_AllowExplicitV1_When_UnrelatedLinkIsPresent() + { + // arrange + const string schema = + """ + extend schema @link(url: "https://specs.example.com/custom/v1.0") + + directive @link(url: String!) repeatable on SCHEMA + + """ + FederationV1Schema; + + // act + var (result, log) = Compose( + [new SourceSchemaText("Products", schema)], + ["Products"]); + + // assert + Assert.True(result.IsSuccess, FormatFailure(result, log)); + } + + [Fact] + public void Compose_Should_SupportMixedFederationVersions_When_OnlyV1SourceIsConfigured() + { + // act + var (result, log) = Compose( + [ + new SourceSchemaText("Products", FederationV1Schema), + new SourceSchemaText("Reviews", FederationV2Schema) + ], + ["Products"]); + + // assert + Assert.True(result.IsSuccess, FormatFailure(result, log)); + Assert.Equal("ApolloFederation", GetConnectorKind(result.Value, "PRODUCTS")); + Assert.Equal("ApolloFederation", GetConnectorKind(result.Value, "REVIEWS")); + } + + [Fact] + public void Compose_Should_LeaveFederationV2BehaviorUnchanged_When_V1IsNotConfigured() + { + // act + var (result, log) = Compose( + [new SourceSchemaText("Reviews", FederationV2Schema)], + []); + + // assert + Assert.True(result.IsSuccess, FormatFailure(result, log)); + Assert.Equal("ApolloFederation", GetConnectorKind(result.Value, "REVIEWS")); + } + + private static ( + CompositionResult Result, + CompositionLog Log) Compose( + SourceSchemaText[] sourceSchemas, + string[] federationV1SourceNames) + { + var log = new CompositionLog(); + var options = new SchemaComposerOptions(); + + foreach (var sourceName in federationV1SourceNames) + { + options.SourceSchemas[sourceName] = new SourceSchemaOptions + { + IsApolloFederationV1 = true + }; + } + + var result = new SchemaComposer(sourceSchemas, options, log).Compose(); + return (result, log); + } + + private static string? FormatFailure( + CompositionResult result, + CompositionLog log) + => result.IsSuccess + ? null + : string.Join( + Environment.NewLine, + result.Errors.Select(error => error.Message).Concat(log.Select(entry => entry.Message))); + + private static string GetConnectorKind( + MutableSchemaDefinition mergedSchema, + string valueName) + { + var schemaEnum = Assert.IsAssignableFrom( + mergedSchema.Types["fusion__Schema"]); + Assert.True(schemaEnum.Values.TryGetValue(valueName, out var enumValue)); + var directive = Assert.IsType( + enumValue.Directives.FirstOrDefault("fusion__schema_metadata")); + Assert.True(directive.Arguments.TryGetValue("kind", out var kindValue)); + return Assert.IsType(kindValue).Value; + } + + private const string FederationV1Schema = + """ + scalar _Any + scalar _FieldSet + + type _Service { + sdl: String + } + + union _Entity = ProductV1 + + type Query { + product: ProductV1 + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type ProductV1 @key(fields: "id") @extends { + id: ID! @external + displayName: String + } + """; + + private const string FederationV1TagDefinition = + """ + directive @tag(name: String!) repeatable on + | FIELD_DEFINITION + | OBJECT + | INTERFACE + | UNION + | ARGUMENT_DEFINITION + | SCALAR + | ENUM + | ENUM_VALUE + | INPUT_OBJECT + | INPUT_FIELD_DEFINITION + + """; + + private const string FederationV2Schema = + """ + extend schema + @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) + + type Query { + review: Review + } + + type Review @key(fields: "id") { + id: ID! + } + """; +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/EmptyFederationQueryRootTests.cs b/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/EmptyFederationQueryRootTests.cs new file mode 100644 index 00000000000..ba648db1631 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/EmptyFederationQueryRootTests.cs @@ -0,0 +1,188 @@ +using HotChocolate.Serialization; +using HotChocolate.Types.Mutable; +using HotChocolate.Types.Mutable.Serialization; + +namespace HotChocolate.Fusion.ApolloFederation; + +public sealed class EmptyFederationQueryRootTests +{ + [Fact] + public void Transform_Should_RemoveSyntheticQuery_When_MutationOnlySchemaHasNoKey() + { + TransformAndDescribe( + """ + schema @link(url: "https://specs.apollo.dev/federation/v2.3") { + query: Query + mutation: Mutation + } + + type Query { + _service: _Service! + } + + type Mutation { + ping: Boolean + } + + type _Service { + sdl: String + } + + directive @link(url: String!) repeatable on SCHEMA + """).MatchInlineSnapshot( + """ + Query root: + Query fields: + Query description: + Query directives: + Mutation root: Mutation + Has Value type: False + Has Query type: False + Query field is lookup: False + """); + } + + [Fact] + public void Transform_Should_RemoveSyntheticQuery_When_ValueOnlySchemaHasNoKey() + { + TransformAndDescribe( + """ + schema @link(url: "https://specs.apollo.dev/federation/v2.3") { + query: Query + } + + type Query { + _service: _Service! + } + + type Value { + name: String + } + + type _Service { + sdl: String + } + + directive @link(url: String!) repeatable on SCHEMA + """).MatchInlineSnapshot( + """ + Query root: + Query fields: + Query description: + Query directives: + Mutation root: + Has Value type: True + Has Query type: False + Query field is lookup: False + """); + } + + [Fact] + public void Transform_Should_GenerateLookupQuery_When_TransportQueryHasResolvableKey() + { + TransformAndDescribe( + """" + schema @link(url: "https://specs.apollo.dev/federation/v2.3") { + query: Query + } + + """Transport query""" + type Query @audit { + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Product @key(fields: "id") { + id: ID! + } + + type _Service { + sdl: String + } + + union _Entity = Product + + scalar _Any + scalar FieldSet + + directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + directive @audit on OBJECT + directive @link(url: String!) repeatable on SCHEMA + """").MatchInlineSnapshot( + """ + Query root: Query + Query fields: fusion__lookup_productById + Query description: Transport query + Query directives: audit + Mutation root: + Has Value type: False + Has Query type: True + Query field is lookup: True + """); + } + + [Fact] + public void Transform_Should_RetainQuery_When_QueryHasRealField() + { + TransformAndDescribe( + """ + schema @link(url: "https://specs.apollo.dev/federation/v2.3") { + query: Query + } + + type Query { + ping: Boolean + _service: _Service! + } + + type _Service { + sdl: String + } + + directive @link(url: String!) repeatable on SCHEMA + """).MatchInlineSnapshot( + """ + Query root: Query + Query fields: ping + Query description: + Query directives: + Mutation root: + Has Value type: False + Has Query type: True + Query field is lookup: False + """); + } + + private static string TransformAndDescribe(string sourceSchema) + { + var result = FederationSchemaTransformer.Transform(sourceSchema); + Assert.True(result.IsSuccess); + + var schema = SchemaParser.Parse(result.Value); + return Describe(schema); + } + + private static string Describe(MutableSchemaDefinition schema) + { + var queryType = schema.QueryType; + var queryFields = queryType is null + ? "" + : string.Join(", ", queryType.Fields.AsEnumerable().Select(field => field.Name)); + var queryDirectives = queryType is null || queryType.Directives.Count == 0 + ? "" + : string.Join(", ", queryType.Directives.AsEnumerable().Select(directive => directive.Name)); + var queryFieldIsLookup = queryType?.Fields.Count == 1 + && queryType.Fields[0].Directives.ContainsName("lookup"); + + return $""" + Query root: {queryType?.Name ?? ""} + Query fields: {queryFields} + Query description: {queryType?.Description ?? ""} + Query directives: {queryDirectives} + Mutation root: {schema.MutationType?.Name ?? ""} + Has Value type: {schema.Types.ContainsName("Value")} + Has Query type: {schema.Types.ContainsName("Query")} + Query field is lookup: {queryFieldIsLookup} + """; + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/FederationSchemaTransformerTests.cs b/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/FederationSchemaTransformerTests.cs index 22d3ca3ed68..dc33f5582df 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/FederationSchemaTransformerTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/FederationSchemaTransformerTests.cs @@ -371,6 +371,67 @@ directive @link(url: String! import: [String!]) repeatable on SCHEMA .MatchMarkdownSnapshot(); } + [Fact] + public void Transform_Should_PreserveOnlyConditionedUnionProvidesFields() + { + // arrange + const string federationSdl = + """ + schema @link( + url: "https://specs.apollo.dev/federation/v2.6" + import: ["@external", "@key", "@provides", "@shareable"]) { + query: Query + } + + type Query { + media: [Media] @shareable @provides(fields: "... on Book { title }") + wrapper: Wrapper @provides(fields: "media { ... on Book { subtitle } }") + _service: _Service! + _entities(representations: [_Any!]!): [_Entity]! + } + + type Wrapper { + media: Media + } + + union Media = Book | Movie + + type Book @key(fields: "id") { + id: ID! + subtitle: String @external + title: String @external + } + + type Movie @key(fields: "id") { + id: ID! + subtitle: String @external + title: String @external + } + + type _Service { sdl: String! } + + union _Entity = Book | Movie + + scalar FieldSet + scalar _Any + + directive @external on FIELD_DEFINITION + directive @key(fields: FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + directive @link(url: String! import: [String!]) repeatable on SCHEMA + directive @provides(fields: FieldSet!) on FIELD_DEFINITION + directive @shareable repeatable on OBJECT | FIELD_DEFINITION + """; + + // act + var result = FederationSchemaTransformer.Transform(federationSdl); + + // assert + Assert.True(result.IsSuccess); + Snapshot.Create() + .Add(result.Value, "Transformed SDL", "graphql") + .MatchMarkdownSnapshot(); + } + [Fact] public void Transform_ExternalDirective() { diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/__snapshots__/FederationSchemaTransformerTests.Transform_Should_PreserveOnlyConditionedUnionProvidesFields.md b/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/__snapshots__/FederationSchemaTransformerTests.Transform_Should_PreserveOnlyConditionedUnionProvidesFields.md new file mode 100644 index 00000000000..5d0e84c4577 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.ApolloFederation.Tests/__snapshots__/FederationSchemaTransformerTests.Transform_Should_PreserveOnlyConditionedUnionProvidesFields.md @@ -0,0 +1,30 @@ +# Transform_Should_PreserveOnlyConditionedUnionProvidesFields + +```graphql +schema { + query: Query +} + +type Query { + fusion__lookup_bookById(id: ID!): Book @internal @lookup + fusion__lookup_movieById(id: ID!): Movie @internal @lookup + media: [Media] @shareable @provides(fields: "... on Book { title }") + wrapper: Wrapper @provides(fields: "media { ... on Book { subtitle } }") +} + +type Book @key(fields: "id") { + id: ID! + subtitle: String @external + title: String @external +} + +type Movie @key(fields: "id") { + id: ID! +} + +type Wrapper { + media: Media +} + +union Media = Book | Movie +``` diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/ApolloFederation/ApolloFederationProvidesFieldsMissingExternalRuleTests.cs b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/ApolloFederation/ApolloFederationProvidesFieldsMissingExternalRuleTests.cs new file mode 100644 index 00000000000..b24afacf2dd --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/ApolloFederation/ApolloFederationProvidesFieldsMissingExternalRuleTests.cs @@ -0,0 +1,192 @@ +using HotChocolate.Fusion.SourceSchemaValidationRules; + +namespace HotChocolate.Fusion.ApolloFederation; + +public sealed class ApolloFederationProvidesFieldsMissingExternalRuleTests : RuleTestBase +{ + protected override object Rule { get; } = new ProvidesFieldsMissingExternalRule(); + + [Fact] + public void Validate_Should_Succeed_When_LocalFieldTraversesIntoAbstractProvidedLeaf() + { + AssertValid( + [ + """ + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: ["@external", "@provides"]) + + type Query { + book: Book @provides(fields: "animals { ... on Dog { name } }") + } + + type Book { + animals: [Animal] + } + + interface Animal { + id: ID! + } + + type Dog implements Animal { + id: ID! + name: String @external + } + """ + ]); + } + + [Fact] + public void Validate_Should_Succeed_When_InterfacePathHasExternalRuntimeField() + { + AssertValid( + [ + """ + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: ["@external", "@provides"]) + + type Query { + media: Media @provides(fields: "animals { id name }") + } + + interface Media { + animals: [Animal] + } + + type Book implements Media { + animals: [Animal] @external + } + + interface Animal { + id: ID! + name: String + } + + type Dog implements Animal { + id: ID! @external + name: String @external + } + + type Cat implements Animal { + id: ID! @external + name: String @external + } + """ + ]); + } + + [Fact] + public void Validate_Should_Succeed_When_UnionMemberLeafIsExternal() + { + AssertValid( + [ + """ + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: ["@external", "@provides"]) + + type Query { + media: [Media] @provides(fields: "... on Book { title }") + } + + union Media = Book | Movie + + type Book { + title: String @external + } + + type Movie { + title: String + } + """ + ]); + } + + [Fact] + public void Validate_Should_Fail_When_UnionMemberLeafIsNotExternal() + { + AssertInvalid( + [ + """ + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: ["@provides"]) + + type Query { + media: [Media] @provides(fields: "... on Book { title }") + } + + union Media = Book | Movie + + type Book { + title: String + } + + type Movie { + title: String + } + """ + ], + [ + """ + { + "message": "The @provides directive on field 'Query.media' in schema 'A' references field 'Book.title', which must be marked as external.", + "code": "PROVIDES_FIELDS_MISSING_EXTERNAL", + "severity": "Error", + "coordinate": "Query.media", + "member": "provides", + "schema": "A", + "extensions": {} + } + """ + ]); + } + + [Fact] + public void Validate_Should_Fail_When_NestedLeafHasNoExternalPath() + { + AssertInvalid( + [ + """ + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: ["@provides"]) + + type Query { + order: Order + } + + type Order { + buyer: User @provides(fields: "info { address }") + } + + type User { + info: UserInfo + } + + type UserInfo { + address: String + } + """ + ], + [ + """ + { + "message": "The @provides directive on field 'Order.buyer' in schema 'A' references field 'UserInfo.address', which must be marked as external.", + "code": "PROVIDES_FIELDS_MISSING_EXTERNAL", + "severity": "Error", + "coordinate": "Order.buyer", + "member": "provides", + "schema": "A", + "extensions": {} + } + """ + ]); + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/CompositionHelperTests.cs b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/CompositionHelperTests.cs new file mode 100644 index 00000000000..433171e8e14 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/CompositionHelperTests.cs @@ -0,0 +1,148 @@ +using System.Text.Json; +using HotChocolate.Fusion.Logging; +using HotChocolate.Fusion.Packaging; + +namespace HotChocolate.Fusion; + +public sealed class CompositionHelperTests +{ + [Fact] + public async Task ComposeAsync_Should_RetainSourceExtensionAndRemoveRuntimeExtension_When_V1SupportIsConsumed() + { + // arrange + using var sourceSettings = JsonDocument.Parse( + """ + { + "name": "Products", + "preprocessor": { + "inferKeysFromLookups": false + }, + "extensions": { + "chillicream": { + "apolloFederationSupport": { + "version": "1.0" + }, + "sibling": { + "enabled": true + } + }, + "vendor": { + "mode": "test" + } + } + } + """); + var sourceSchemas = new Dictionary + { + ["Products"] = + ( + new SourceSchemaText( + "Products", + """ + scalar _Any + scalar _FieldSet + + type _Service { + sdl: String + } + + union _Entity = Product + + type Query { + product: Product + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + } + + type Product @key(fields: "id") { + id: ID! + } + """), + sourceSettings) + }; + var stream = new MemoryStream(); + var log = new CompositionLog(); + + // act + using (var archive = FusionArchive.Create(stream, leaveOpen: true)) + { + var result = await CompositionHelper.ComposeAsync( + log, + sourceSchemas, + archive, + "Development", + compositionSettings: null, + legacyArchive: null, + TestContext.Current.CancellationToken); + + Assert.True( + result.IsSuccess, + string.Join(Environment.NewLine, log.Select(entry => entry.Message))); + } + + stream.Position = 0; + + using var readArchive = FusionArchive.Open(stream, leaveOpen: true); + using var sourceConfiguration = Assert.IsType( + await readArchive.TryGetSourceSchemaConfigurationAsync( + "Products", + TestContext.Current.CancellationToken)); + using var gatewayConfiguration = Assert.IsType( + await readArchive.TryGetGatewayConfigurationAsync( + WellKnownVersions.LatestGatewayFormatVersion, + TestContext.Current.CancellationToken)); + + // assert + var snapshot = new + { + ArchivedSourceSettings = sourceConfiguration.Settings.RootElement, + RuntimeGatewaySettings = gatewayConfiguration.Settings.RootElement + }; + + JsonSerializer.Serialize( + snapshot, + new JsonSerializerOptions { WriteIndented = true }).MatchInlineSnapshot( + """ + { + "ArchivedSourceSettings": { + "name": "Products", + "preprocessor": { + "inferKeysFromLookups": false + }, + "extensions": { + "chillicream": { + "apolloFederationSupport": { + "version": "1.0" + }, + "sibling": { + "enabled": true + } + }, + "vendor": { + "mode": "test" + } + } + }, + "RuntimeGatewaySettings": { + "sourceSchemas": { + "Products": { + "preprocessor": { + "inferKeysFromLookups": false + }, + "extensions": { + "chillicream": { + "sibling": { + "enabled": true + } + }, + "vendor": { + "mode": "test" + } + } + } + } + } + } + """); + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SchemaComposerTests.cs b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SchemaComposerTests.cs index 7989fcf1d0c..a153e600449 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SchemaComposerTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SchemaComposerTests.cs @@ -6,46 +6,30 @@ namespace HotChocolate.Fusion; public sealed class SchemaComposerTests { - [Theory] - [InlineData(NodeResolution.Gateway)] - [InlineData(NodeResolution.SourceSchema)] - public void Compose_Should_EmitExecutionMetadata(NodeResolution nodeResolution) + [Fact] + public void Compose_Should_EmitExecutionMetadataDefinitions() { - var options = new SchemaComposerOptions - { - Merger = - { - EnableGlobalObjectIdentification = nodeResolution is NodeResolution.SourceSchema, - NodeResolution = nodeResolution - } - }; + // arrange var composer = new SchemaComposer( [new SourceSchemaText("A", "type Query { ping: String }")], - options, + new SchemaComposerOptions(), new CompositionLog()); + // act var result = composer.Compose(); + // assert Assert.True(result.IsSuccess); var document = result.Value.ToSyntaxNode(); var executionMetadataDefinitions = new DocumentNode( - [ - document.Definitions.Single( - definition => definition is EnumTypeDefinitionNode - { - Name.Value: "fusion__NodeResolution" - }), - document.Definitions.Single( - definition => definition is EnumTypeDefinitionNode - { - Name.Value: "fusion__ShareableFieldRuntimeTypeRouting" - }), - document.Definitions.Single( - definition => definition is DirectiveDefinitionNode - { - Name.Value: "fusion__execution" - }) - ]); + document.Definitions + .Where(definition => definition is EnumTypeDefinitionNode + { + Name.Value: "fusion__NodeResolution" + or "fusion__ShareableFieldRuntimeTypeRouting" + } + or DirectiveDefinitionNode { Name.Value: "fusion__execution" }) + .ToArray()); executionMetadataDefinitions.ToString().MatchInlineSnapshot( """ @@ -64,7 +48,33 @@ directive @fusion__execution( shareableFieldRuntimeTypeRouting: fusion__ShareableFieldRuntimeTypeRouting! = SOURCE_LOCAL ) on SCHEMA """); + } + + [Theory] + [InlineData(NodeResolution.Gateway)] + [InlineData(NodeResolution.SourceSchema)] + public void Compose_Should_EmitNodeResolution(NodeResolution nodeResolution) + { + // arrange + var options = new SchemaComposerOptions + { + Merger = + { + EnableGlobalObjectIdentification = nodeResolution is NodeResolution.SourceSchema, + NodeResolution = nodeResolution + } + }; + var composer = new SchemaComposer( + [new SourceSchemaText("A", "type Query { ping: String }")], + options, + new CompositionLog()); + + // act + var result = composer.Compose(); + // assert + Assert.True(result.IsSuccess); + var document = result.Value.ToSyntaxNode(); var schemaDefinition = document.Definitions .OfType() .Single(); @@ -88,12 +98,12 @@ nodeResolution is NodeResolution.Gateway } [Theory] - [InlineData(ShareableFieldRuntimeTypeRouting.SourceLocal, "SOURCE_LOCAL")] - [InlineData(ShareableFieldRuntimeTypeRouting.CommonRuntimeTypes, "COMMON_RUNTIME_TYPES")] + [InlineData(ShareableFieldRuntimeTypeRouting.SourceLocal)] + [InlineData(ShareableFieldRuntimeTypeRouting.CommonRuntimeTypes)] public void Compose_Should_EmitShareableFieldRuntimeTypeRouting( - ShareableFieldRuntimeTypeRouting routing, - string expectedValue) + ShareableFieldRuntimeTypeRouting routing) { + // arrange var options = new SchemaComposerOptions { ApolloFederationCompatibility = { ShareableFieldRuntimeTypeRouting = routing } @@ -103,27 +113,38 @@ [new SourceSchemaText("A", "type Query { ping: String }")], options, new CompositionLog()); + // act var result = composer.Compose(); + // assert Assert.True(result.IsSuccess); var document = result.Value.ToSyntaxNode(); - var enumType = Assert.Single( - document.Definitions.OfType(), - definition => definition.Name.Value == "fusion__ShareableFieldRuntimeTypeRouting"); - Assert.Equal( - ["SOURCE_LOCAL", "COMMON_RUNTIME_TYPES"], - enumType.Values.Select(value => value.Name.Value)); - var schemaDefinition = Assert.Single(document.Definitions.OfType()); - var directive = Assert.Single( - schemaDefinition.Directives, - application => application.Name.Value == "fusion__execution"); - Assert.Equal(expectedValue, Assert.IsType( - directive.Arguments[1].Value).Value); + var schemaDefinition = document.Definitions + .OfType() + .Single(); + var executionApplication = schemaDefinition.Directives.Single( + directive => directive.Name.Value == "fusion__execution"); + + executionApplication.ToString().MatchInlineSnapshot( + routing is ShareableFieldRuntimeTypeRouting.SourceLocal + ? """ + @fusion__execution( + nodeResolution: GATEWAY + shareableFieldRuntimeTypeRouting: SOURCE_LOCAL + ) + """ + : """ + @fusion__execution( + nodeResolution: GATEWAY + shareableFieldRuntimeTypeRouting: COMMON_RUNTIME_TYPES + ) + """); } [Fact] - public void Compose_Should_Fail_When_SourceSchemaNodeResolutionIsDisabled() + public void Compose_Should_Fail_When_GlobalObjectIdentificationIsDisabledForSourceSchemaNodeResolution() { + // arrange var log = new CompositionLog(); var options = new SchemaComposerOptions { @@ -134,20 +155,28 @@ [new SourceSchemaText("A", "type Query { ping: String }")], options, log); + // act var result = composer.Compose(); + // assert Assert.True(result.IsFailure); - var entry = Assert.Single(log); - Assert.Equal(LogEntryCodes.InvalidNodeResolution, entry.Code); - Assert.Equal(LogSeverity.Error, entry.Severity); - Assert.Equal( - "Source-schema node resolution requires global object identification to be enabled.", - entry.Message); + log.Select(e => e.ToString()).MatchInlineSnapshots( + [ + """ + { + "message": "Source-schema node resolution requires global object identification to be enabled.", + "code": "INVALID_NODE_RESOLUTION", + "severity": "Error", + "extensions": {} + } + """ + ]); } [Fact] public void Compose_Should_Fail_When_NodeResolutionIsInvalid() { + // arrange var log = new CompositionLog(); var options = new SchemaComposerOptions { @@ -158,17 +187,28 @@ [new SourceSchemaText("A", "type Query { ping: String }")], options, log); + // act var result = composer.Compose(); + // assert Assert.True(result.IsFailure); - var entry = Assert.Single(log); - Assert.Equal(LogEntryCodes.InvalidNodeResolution, entry.Code); - Assert.Equal("The node resolution mode '42' is invalid.", entry.Message); + log.Select(e => e.ToString()).MatchInlineSnapshots( + [ + """ + { + "message": "The node resolution mode '42' is invalid.", + "code": "INVALID_NODE_RESOLUTION", + "severity": "Error", + "extensions": {} + } + """ + ]); } [Fact] public void Compose_Should_Fail_When_ShareableFieldRuntimeTypeRoutingIsInvalid() { + // arrange var log = new CompositionLog(); var options = new SchemaComposerOptions { @@ -182,14 +222,22 @@ [new SourceSchemaText("A", "type Query { ping: String }")], options, log); + // act var result = composer.Compose(); + // assert Assert.True(result.IsFailure); - var entry = Assert.Single(log); - Assert.Equal(LogEntryCodes.InvalidShareableFieldRuntimeTypeRouting, entry.Code); - Assert.Equal( - "The shareable field runtime type routing mode '42' is invalid.", - entry.Message); + log.Select(e => e.ToString()).MatchInlineSnapshots( + [ + """ + { + "message": "The shareable field runtime type routing mode '42' is invalid.", + "code": "INVALID_SHAREABLE_FIELD_RUNTIME_TYPE_ROUTING", + "severity": "Error", + "extensions": {} + } + """ + ]); } [Fact] @@ -290,8 +338,42 @@ type Query { var result = schemaComposer.Compose(); // assert + // The schema validator reports the same HCV0011 warning once per validation pass, so the + // log carries two identical entries. Assert.True(result.IsSuccess); - Assert.Contains(log, e => e.Code == "HCV0011" && e.Severity == LogSeverity.Warning); + log.Select(e => e.ToString()).MatchInlineSnapshots( + [ + """ + { + "message": "The field 'User.name' must not be deprecated without the corresponding field in the interface 'Node' being deprecated. (Schema: 'A')", + "code": "HCV0011", + "severity": "Warning", + "coordinate": "User.name", + "member": "name", + "schema": "A", + "extensions": { + "field": "name", + "implementedField": "name", + "specifiedBy": "https://spec.graphql.org/September2025/#sec-Objects.Type-Validation" + } + } + """, + """ + { + "message": "The field 'User.name' must not be deprecated without the corresponding field in the interface 'Node' being deprecated. (Schema: 'A')", + "code": "HCV0011", + "severity": "Warning", + "coordinate": "User.name", + "member": "name", + "schema": "A", + "extensions": { + "field": "name", + "implementedField": "name", + "specifiedBy": "https://spec.graphql.org/September2025/#sec-Objects.Type-Validation" + } + } + """ + ]); } [Fact] @@ -339,7 +421,24 @@ type Query { // assert Assert.True(result.IsFailure); - Assert.Contains(log, e => e.Code == "HCV0011" && e.Severity == LogSeverity.Error); + log.Select(e => e.ToString()).MatchInlineSnapshots( + [ + """ + { + "message": "The field 'User.name' must not be deprecated without the corresponding field in the interface 'Node' being deprecated. (Schema: 'A')", + "code": "HCV0011", + "severity": "Error", + "coordinate": "User.name", + "member": "name", + "schema": "A", + "extensions": { + "field": "name", + "implementedField": "name", + "specifiedBy": "https://spec.graphql.org/September2025/#sec-Objects.Type-Validation" + } + } + """ + ]); } [Fact] @@ -394,11 +493,27 @@ type User { // assert Assert.True(result.IsFailure); - Assert.Contains(log, e => e.Code == LogEntryCodes.UnsatisfiableQueryPath); + // Two distinct unsatisfiable paths are reported; order them so the snapshot is stable + // regardless of the order the validator emits them in. + log.Select(e => e.Message) + .Order(StringComparer.Ordinal) + .MatchInlineSnapshots( + [ + """ + Unable to access the field 'User.membershipStatus'. + Unable to transition between schemas 'A' and 'B' for access to field 'B:User.membershipStatus'. + No lookups found for type 'User' in schema 'B'. + """, + """ + Unable to access the field 'User.name'. + Unable to transition between schemas 'B' and 'A' for access to field 'A:User.name'. + No lookups found for type 'User' in schema 'A'. + """ + ]); } [Fact] - public void Compose_LookupFieldWithoutArguments_FailsWithLookupMustHaveArgumentsError() + public void Compose_Should_FailWithLookupMustHaveArgumentsError_When_LookupFieldHasNoArguments() { // arrange var log = new CompositionLog(); @@ -425,16 +540,24 @@ type Product { // assert Assert.True(result.IsFailure); - var entry = Assert.Single(log); - Assert.Equal(LogEntryCodes.LookupMustHaveArguments, entry.Code); - Assert.Equal(LogSeverity.Error, entry.Severity); - Assert.Equal( - "The lookup field 'Query.product' in schema 'A' must declare at least one argument.", - entry.Message); + log.Select(e => e.ToString()).MatchInlineSnapshots( + [ + """ + { + "message": "The lookup field 'Query.product' in schema 'A' must declare at least one argument.", + "code": "LOOKUP_MUST_HAVE_ARGUMENTS", + "severity": "Error", + "coordinate": "Query.product", + "member": "product", + "schema": "A", + "extensions": {} + } + """ + ]); } [Fact] - public void Compose_OrphanedTypeAfterTagExclusion_DoesNotProduceShareableError() + public void Compose_Should_NotProduceShareableError_When_TagExclusionOrphansType() { // arrange // Schema A's Product is only reachable via Mutation. When Mutation is removed by @@ -499,12 +622,50 @@ type Product { // assert Assert.True(result.IsSuccess); - Assert.False(result.Value.Types.ContainsName("Mutation")); - Assert.True(result.Value.Types.ContainsName("Product")); + result.Value.MatchInlineSnapshot( + """ + schema { + query: Query + } + + type Query @fusion__type(schema: A) @fusion__type(schema: B) { + book(id: ID! @fusion__inputField(schema: A)): Book @fusion__field(schema: A) + productById(id: ID! @fusion__inputField(schema: B)): Product + @fusion__field(schema: B) + } + + type Book + @fusion__type(schema: A) + @fusion__lookup( + schema: A + key: "id" + field: "book(id: ID!): Book" + map: ["id"] + path: null + internal: false + ) { + id: ID! @fusion__field(schema: A) + title: String! @fusion__field(schema: A) + } + + type Product + @fusion__type(schema: B) + @fusion__lookup( + schema: B + key: "id" + field: "productById(id: ID!): Product" + map: ["id"] + path: null + internal: false + ) { + id: ID! @fusion__field(schema: B) + name: String! @fusion__field(schema: B) + } + """); } [Fact] - public void Compose_WithExtensions_AppliesExtensions() + public void Compose_Should_ApplyExtensions_When_ExtensionsAreProvided() { // arrange var schemaComposer = new SchemaComposer( diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaMerger.InterfaceObject.Tests.cs b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaMerger.InterfaceObject.Tests.cs index ee4861dc4f1..53a9531bd21 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaMerger.InterfaceObject.Tests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaMerger.InterfaceObject.Tests.cs @@ -163,6 +163,173 @@ interface Media """); } + // The stand-in's @require argument is converted into source-specific execution metadata on the + // interface contract. A default projected onto an implementing object must retain the effective + // owner's requirement without exposing generated arguments in the public field signature. + [Fact] + public void Merge_Should_ProjectEffectiveOwnerRequirement_When_StandInFieldIsInherited() + { + AssertMatches( + [ + """ + # Schema A + interface NodeWithName @key(fields: "id") { + id: ID! + name: String + } + + type User implements NodeWithName @key(fields: "id") { + id: ID! + name: String + } + """, + """ + # Schema B + type NodeWithName @interfaceObject @key(fields: "id") { + id: ID! + name: String @external + username(name: String @require(field: "name")): String + } + """ + ], + """ + type User implements NodeWithName + @fusion__type(schema: A) + @fusion__implements(schema: A, interface: "NodeWithName") { + id: ID! @fusion__field(schema: A) + name: String @fusion__field(schema: A) + username: String + @fusion__field(schema: B) + @fusion__requires( + schema: B + requirements: "name" + field: "username(name: String): String" + map: ["name"] + ) + } + + interface NodeWithName + @fusion__type(schema: A) + @fusion__type(schema: B) + @fusion__interfaceObject(schema: B) { + id: ID! @fusion__field(schema: A) @fusion__field(schema: B) + name: String + @fusion__field(schema: A) + @fusion__field(schema: B, partial: true) + username: String + @fusion__field(schema: B) + @fusion__requires( + schema: B + requirements: "name" + field: "username(name: String): String" + map: ["name"] + ) + } + """); + } + + // Two unrelated stand-ins can share the same projected field while requiring different values. + // Each projected requirement must come from the interface contract associated with its owning + // schema rather than from whichever contributing interface happened to be considered first. + [Fact] + public void Merge_Should_ProjectEachOwnerRequirement_When_UnrelatedStandInsShareField() + { + AssertMatches( + [ + """ + # Schema A + interface I1 @key(fields: "id") { + id: ID! + x: String + } + + interface I2 @key(fields: "id") { + id: ID! + y: String + } + + type User implements I1 & I2 @key(fields: "id") { + id: ID! + x: String + y: String + } + """, + """ + # Schema B + type I1 @interfaceObject @key(fields: "id") { + id: ID! + x: String @external + foo(x: String @require(field: "x")): String @shareable + } + """, + """ + # Schema C + type I2 @interfaceObject @key(fields: "id") { + id: ID! + y: String @external + foo(y: String @require(field: "y")): String @shareable + } + """ + ], + """ + type User implements I1 & I2 + @fusion__type(schema: A) + @fusion__implements(schema: A, interface: "I1") + @fusion__implements(schema: A, interface: "I2") { + foo: String + @fusion__field(schema: B) + @fusion__field(schema: C) + @fusion__requires( + schema: B + requirements: "x" + field: "foo(x: String): String" + map: ["x"] + ) + @fusion__requires( + schema: C + requirements: "y" + field: "foo(y: String): String" + map: ["y"] + ) + id: ID! @fusion__field(schema: A) + x: String @fusion__field(schema: A) + y: String @fusion__field(schema: A) + } + + interface I1 + @fusion__type(schema: A) + @fusion__type(schema: B) + @fusion__interfaceObject(schema: B) { + foo: String + @fusion__field(schema: B) + @fusion__requires( + schema: B + requirements: "x" + field: "foo(x: String): String" + map: ["x"] + ) + id: ID! @fusion__field(schema: A) @fusion__field(schema: B) + x: String @fusion__field(schema: A) @fusion__field(schema: B, partial: true) + } + + interface I2 + @fusion__type(schema: A) + @fusion__type(schema: C) + @fusion__interfaceObject(schema: C) { + foo: String + @fusion__field(schema: C) + @fusion__requires( + schema: C + requirements: "y" + field: "foo(y: String): String" + map: ["y"] + ) + id: ID! @fusion__field(schema: A) @fusion__field(schema: C) + y: String @fusion__field(schema: A) @fusion__field(schema: C, partial: true) + } + """); + } + // Source schema A declares that "PhysicalProduct" implements "Product", while source schema B // declares that "Chair" implements "PhysicalProduct" and never mentions "Product". The composite // "Chair" must implement both through the transitively closed implements relation. diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaMerger.OutputField.Tests.cs b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaMerger.OutputField.Tests.cs index 6c5619ef52b..de596acdba5 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaMerger.OutputField.Tests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaMerger.OutputField.Tests.cs @@ -308,6 +308,55 @@ type User @fusion__type(schema: A) { """); } + [Fact] + public void Merge_Should_PreserveConditionedProvides_When_FieldReturnsUnion() + { + AssertMatches( + [ + """ + type Query { + media: [Media] @provides(fields: "... on Book { title }") + } + + union Media = Book | Movie + + type Book { + id: ID! + title: String @external + } + + type Movie { + id: ID! + title: String + } + """ + ], + """ + schema { + query: Query + } + + type Query @fusion__type(schema: A) { + media: [Media] @fusion__field(schema: A, provides: "... on Book { title }") + } + + type Book @fusion__type(schema: A) { + id: ID! @fusion__field(schema: A) + title: String @fusion__field(schema: A, partial: true) + } + + type Movie @fusion__type(schema: A) { + id: ID! @fusion__field(schema: A) + title: String @fusion__field(schema: A) + } + + union Media + @fusion__type(schema: A) + @fusion__unionMember(schema: A, member: "Book") + @fusion__unionMember(schema: A, member: "Movie") = Book | Movie + """); + } + // Even if an output field is only @deprecated in one source schema, the composite output field // is marked as @deprecated. [Fact] diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaPreprocessorTests.cs b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaPreprocessorTests.cs index 0b47614c05e..938fc3e2ef0 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaPreprocessorTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaPreprocessorTests.cs @@ -940,4 +940,78 @@ type Product { var productAdded = subscriptionType.Fields["productAdded"]; Assert.False(productAdded.Directives.ContainsName(WellKnownDirectiveNames.Shareable)); } + + [Theory] + [InlineData(true, false)] + [InlineData(false, true)] + public void Preprocess_Should_NotInferShareable_When_EitherDuplicateFieldIsExternal( + bool currentFieldIsExternal, + bool otherFieldIsExternal) + { + // arrange + var currentField = currentFieldIsExternal + ? "sku: String @external" + : "sku: String"; + var currentQueryField = currentFieldIsExternal + ? "product: Product @provides(fields: \"sku\")" + : "product: Product"; + var otherField = otherFieldIsExternal + ? "sku: String @external" + : "sku: String"; + var otherQueryField = otherFieldIsExternal + ? "product: Product @provides(fields: \"sku\")" + : "product: Product"; + var log = new CompositionLog(); + var currentSchema = new SourceSchemaParser( + new SourceSchemaText( + "A", + $$""" + type Query { + {{currentQueryField}} + } + + type Product { + {{currentField}} + } + """), + log, + isApolloFederationV1: true).Parse().Value; + var otherSchema = new SourceSchemaParser( + new SourceSchemaText( + "B", + $$""" + type Query { + {{otherQueryField}} + } + + type Product { + {{otherField}} + } + """), + log).Parse().Value; + var schemas = ImmutableSortedSet.Create( + new SchemaByNameComparer(), + currentSchema, + otherSchema); + var preprocessor = new SourceSchemaPreprocessor( + currentSchema, + schemas, + log, + isApolloFederationV1: true); + + // act + var result = preprocessor.Preprocess(); + + // assert + Assert.True(result.IsSuccess); + var product = Assert.IsType( + currentSchema.Types["Product"]); + string[] expectedDirectives = currentFieldIsExternal ? ["external"] : []; + Assert.Equal( + expectedDirectives, + product.Fields["sku"].Directives + .AsEnumerable() + .Select(directive => directive.Name) + .ToArray()); + } } diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaSettingsReaderTests.cs b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaSettingsReaderTests.cs new file mode 100644 index 00000000000..f606aeb0726 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaSettingsReaderTests.cs @@ -0,0 +1,242 @@ +using System.Text.Json; +using HotChocolate.Fusion.Logging; + +namespace HotChocolate.Fusion; + +public sealed class SourceSchemaSettingsReaderTests +{ + private static readonly JsonSerializerOptions s_indentedJson = new() + { + WriteIndented = true + }; + + [Fact] + public void TryRead_Should_ClassifyAndRemoveSupportFromRuntimeSettings_When_Version1SettingIsExact() + { + // arrange + using var settings = JsonDocument.Parse( + """ + { + "name": "Products", + "extensions": { + "chillicream": { + "apolloFederationSupport": { + "version": "1.0" + }, + "other": { + "enabled": true + } + }, + "vendor": { + "mode": "test" + } + } + } + """); + var log = new CompositionLog(); + + // act + var success = SourceSchemaSettingsReader.TryRead( + "Products", + settings, + log, + out var result); + + // assert + Assert.True(success); + Assert.True(result.Options.IsApolloFederationV1); + Assert.True(log.IsEmpty); + using var runtimeSettings = Assert.IsType(result.RuntimeSettings); + + var snapshot = new + { + OriginalSettings = settings.RootElement, + RuntimeSettings = runtimeSettings.RootElement + }; + + JsonSerializer.Serialize(snapshot, s_indentedJson).MatchInlineSnapshot( + """ + { + "OriginalSettings": { + "name": "Products", + "extensions": { + "chillicream": { + "apolloFederationSupport": { + "version": "1.0" + }, + "other": { + "enabled": true + } + }, + "vendor": { + "mode": "test" + } + } + }, + "RuntimeSettings": { + "name": "Products", + "extensions": { + "chillicream": { + "other": { + "enabled": true + } + }, + "vendor": { + "mode": "test" + } + } + } + } + """); + } + + [Fact] + public void TryRead_Should_PruneEmptyAncestors_When_SupportIsOnlyExtension() + { + // arrange + using var settings = JsonDocument.Parse( + """ + { + "name": "Products", + "extensions": { + "chillicream": { + "apolloFederationSupport": { + "version": "1.0" + } + } + } + } + """); + var log = new CompositionLog(); + + // act + var success = SourceSchemaSettingsReader.TryRead( + "Products", + settings, + log, + out var result); + + // assert + Assert.True(success); + Assert.True(result.Options.IsApolloFederationV1); + Assert.True(log.IsEmpty); + using var runtimeSettings = Assert.IsType(result.RuntimeSettings); + Assert.Equal("{\"name\":\"Products\"}", runtimeSettings.RootElement.GetRawText()); + } + + [Fact] + public void TryRead_Should_LeaveBehaviorUnchanged_When_SupportIsAbsent() + { + // arrange + using var settings = JsonDocument.Parse( + """ + { + "name": "Products", + "extensions": { + "vendor": { + "mode": "test" + } + } + } + """); + var log = new CompositionLog(); + + // act + var success = SourceSchemaSettingsReader.TryRead( + "Products", + settings, + log, + out var result); + + // assert + Assert.True(success); + Assert.False(result.Options.IsApolloFederationV1); + Assert.Null(result.RuntimeSettings); + Assert.True(log.IsEmpty); + } + + [Theory] + [InlineData( + "[]", + "The source schema setting '$' for source schema 'Products' must be an object, but found 'Array'.")] + [InlineData( + "{\"name\":\"Products\",\"extensions\":null}", + "The source schema setting '$.extensions' for source schema 'Products' must be an object, but found 'Null'.")] + [InlineData( + "{\"name\":\"Products\",\"extensions\":{\"chillicream\":[]}}", + "The source schema setting '$.extensions.chillicream' for source schema 'Products' must be an object, but found 'Array'.")] + [InlineData( + "{\"name\":\"Products\",\"extensions\":{\"chillicream\":{\"apolloFederationSupport\":true}}}", + "The source schema setting '$.extensions.chillicream.apolloFederationSupport' for source schema 'Products' must be an object, but found 'True'.")] + public void TryRead_Should_RejectWrongContainerKind_When_ExtensionPathIsInvalid( + string json, + string expectedMessage) + { + // arrange + using var settings = JsonDocument.Parse(json); + var log = new CompositionLog(); + + // act + var success = SourceSchemaSettingsReader.TryRead( + "Products", + settings, + log, + out _); + + // assert + Assert.False(success); + var entry = Assert.Single(log); + Assert.Equal(LogEntryCodes.InvalidApolloFederationSupportSettings, entry.Code); + Assert.Equal(LogSeverity.Error, entry.Severity); + Assert.Equal(expectedMessage, entry.Message); + } + + [Theory] + [InlineData( + "{}", + "The source schema setting '$.extensions.chillicream.apolloFederationSupport' for source schema 'Products' must be an object containing only the 'version' property.")] + [InlineData( + "{\"version\":\"1.0\",\"enabled\":true}", + "The source schema setting '$.extensions.chillicream.apolloFederationSupport' for source schema 'Products' must be an object containing only the 'version' property.")] + [InlineData( + "{\"version\":null}", + "The source schema setting '$.extensions.chillicream.apolloFederationSupport.version' for source schema 'Products' must be a string, but found 'Null'.")] + [InlineData( + "{\"version\":1}", + "The source schema setting '$.extensions.chillicream.apolloFederationSupport.version' for source schema 'Products' must be a string, but found 'Number'.")] + [InlineData( + "{\"version\":\"2.0\"}", + "The source schema setting '$.extensions.chillicream.apolloFederationSupport.version' for source schema 'Products' has unsupported version '2.0'. The supported version is '1.0'.")] + public void TryRead_Should_RejectInvalidVersionSetting_When_SupportShapeIsInvalid( + string supportJson, + string expectedMessage) + { + // arrange + using var settings = JsonDocument.Parse( + $$""" + { + "name": "Products", + "extensions": { + "chillicream": { + "apolloFederationSupport": {{supportJson}} + } + } + } + """); + var log = new CompositionLog(); + + // act + var success = SourceSchemaSettingsReader.TryRead( + "Products", + settings, + log, + out _); + + // assert + Assert.False(success); + var entry = Assert.Single(log); + Assert.Equal(LogEntryCodes.InvalidApolloFederationSupportSettings, entry.Code); + Assert.Equal(LogSeverity.Error, entry.Severity); + Assert.Equal(expectedMessage, entry.Message); + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaValidationRules/ProvidesInvalidFieldsRuleTests.cs b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaValidationRules/ProvidesInvalidFieldsRuleTests.cs index 11a517c84b5..d39dd9082d5 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaValidationRules/ProvidesInvalidFieldsRuleTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaValidationRules/ProvidesInvalidFieldsRuleTests.cs @@ -171,4 +171,88 @@ type Info { """ ]); } + + [Fact] + public void Validate_Should_Fail_When_ProvidesSelectsDirectUnionField() + { + AssertInvalid( + [ + """ + type Query { + media: Media @provides(fields: "title") + } + + union Media = Book | Movie + + type Book { + title: String @external + } + + type Movie { + title: String + } + """ + ], + [ + """ + { + "message": "The @provides directive on field 'Query.media' in schema 'A' specifies an invalid field selection.", + "code": "PROVIDES_INVALID_FIELDS", + "severity": "Error", + "coordinate": "Query.media", + "member": "provides", + "schema": "A", + "extensions": { + "errors": [ + "The field 'title' does not exist on the type 'Media'." + ] + } + } + """ + ]); + } + + [Fact] + public void Validate_Should_Fail_When_ProvidesFragmentTypeIsNotUnionMember() + { + AssertInvalid( + [ + """ + type Query { + media: Media @provides(fields: "... on Article { title }") + } + + union Media = Book | Movie + + type Book { + title: String + } + + type Movie { + title: String + } + + type Article { + title: String @external + } + """ + ], + [ + """ + { + "message": "The @provides directive on field 'Query.media' in schema 'A' specifies an invalid field selection.", + "code": "PROVIDES_INVALID_FIELDS", + "severity": "Error", + "coordinate": "Query.media", + "member": "provides", + "schema": "A", + "extensions": { + "errors": [ + "The type 'Article' is not a possible type of type 'Media'." + ] + } + } + """ + ]); + } } diff --git a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaValidationRules/ProvidesOnNonCompositeFieldRuleTests.cs b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaValidationRules/ProvidesOnNonCompositeFieldRuleTests.cs index 7c0b337a6dc..63c9eb4ba1b 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaValidationRules/ProvidesOnNonCompositeFieldRuleTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Composition.Tests/SourceSchemaValidationRules/ProvidesOnNonCompositeFieldRuleTests.cs @@ -30,6 +30,32 @@ type User { ]); } + [Theory] + [InlineData("Media")] + [InlineData("[Media]")] + [InlineData("[Media!]!")] + public void Validate_Should_Succeed_When_FieldReturnsUnion(string mediaType) + { + AssertValid( + [ + $$""" + type Query { + media: {{mediaType}} @provides(fields: "... on Book { title }") + } + + union Media = Book | Movie + + type Book { + title: String @external + } + + type Movie { + title: String + } + """ + ]); + } + // In this example, "email" has a scalar base type (String). Because scalars do not expose // sub-fields, attaching @provides to "email" triggers a PROVIDES_ON_NON_COMPOSITE_FIELD error. [Fact] diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/ChildTypeMismatchSupplementalTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/ChildTypeMismatchSupplementalTests.cs new file mode 100644 index 00000000000..7a3225c4571 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/ChildTypeMismatchSupplementalTests.cs @@ -0,0 +1,37 @@ +using HotChocolate.Execution; +using HotChocolate.Fusion.Suites; +using HotChocolate.Fusion.Suites.ChildTypeMismatch.A; +using HotChocolate.Fusion.Suites.ChildTypeMismatch.B; + +namespace HotChocolate.Fusion.AdditionalCoverage; + +public sealed class ChildTypeMismatchSupplementalTests +{ + [Fact] + [Trait("Category", "Supplemental")] + public async Task Operation_Should_AliasOnlyConflictingSourceField_When_SourceTypesDiffer() + { + var capture = new SubgraphRequestCapture(); + await using var gateway = await FusionGatewayBuilder + .ComposeOfficialV2Async( + capture, + (ASubgraph.Name, ASubgraph.BuildAsync), + (BSubgraph.Name, BSubgraph.BuildAsync)); + var testCase = AuditFixture.GetOfficialV2Case( + "child-type-mismatch/000"); + + var result = await gateway.Executor.ExecuteAsync( + OperationRequestBuilder.New().SetDocument(testCase.Query).Build(), + TestContext.Current.CancellationToken); + AuditAssertions.Assert(result.ToJson(), testCase.ExpectedData, expectsErrors: false); + + capture.ForSubgraph(BSubgraph.Name).Select(static request => request.Body) + .MatchInlineSnapshot( + """ + [ + "{\"query\":\"query Op_56dcc647_1 {\\n accounts {\\n __typename\\n ... on User {\\n id\\n name\\n }\\n ... on Admin {\\n fusion__field_1: id\\n name\\n }\\n }\\n}\"}", + "{\"query\":\"query($representations: [_Any!]!) {\\n _entities(representations: $representations) {\\n ... on User {\\n name\\n }\\n }\\n}\",\"variables\":{\"representations\":[{\"__typename\":\"User\",\"id\":\"u1\"}]}}" + ] + """); + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/ComplexEntityCallSupplementalTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/ComplexEntityCallSupplementalTests.cs new file mode 100644 index 00000000000..0ed54842727 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/ComplexEntityCallSupplementalTests.cs @@ -0,0 +1,58 @@ +using HotChocolate.Fusion.Suites.ComplexEntityCall.Link; +using HotChocolate.Fusion.Suites.ComplexEntityCall.List; +using HotChocolate.Fusion.Suites.ComplexEntityCall.Price; +using HotChocolate.Fusion.Suites.ComplexEntityCall.Products; + +namespace HotChocolate.Fusion.AdditionalCoverage; + +public sealed class ComplexEntityCallSupplementalTests : ComplianceTestBase +{ + protected override Task BuildGatewayAsync() + => FusionGatewayBuilder.ComposeAsync( + (LinkSubgraph.Name, LinkSubgraph.BuildAsync), + (ListSubgraph.Name, ListSubgraph.BuildAsync), + (PriceSubgraph.Name, PriceSubgraph.BuildAsync), + (ProductsSubgraph.Name, ProductsSubgraph.BuildAsync)); + + [Fact] + [Trait("Category", "Supplemental")] + public Task TopProducts_Should_ResolveNestedCategoryKey_When_Queried() => RunAsync( + query: """ + query { + topProducts { + products { + id + category { + mainProduct { id } + id + tag + } + } + } + } + """, + expectedData: """ + { + "topProducts": { + "products": [ + { + "id": "1", + "category": { + "mainProduct": { "id": "1" }, + "id": "c1", + "tag": "t1" + } + }, + { + "id": "2", + "category": { + "mainProduct": { "id": "2" }, + "id": "c2", + "tag": "t2" + } + } + ] + } + } + """); +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/CorruptedSupergraphNodeIdSupplementalTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/CorruptedSupergraphNodeIdSupplementalTests.cs new file mode 100644 index 00000000000..ec1b10fe893 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/CorruptedSupergraphNodeIdSupplementalTests.cs @@ -0,0 +1,31 @@ +using HotChocolate.Fusion.Suites; +using HotChocolate.Fusion.Suites.CorruptedSupergraphNodeId.A; +using HotChocolate.Fusion.Suites.CorruptedSupergraphNodeId.B; + +namespace HotChocolate.Fusion.AdditionalCoverage; + +public sealed class CorruptedSupergraphNodeIdSupplementalTests +{ + [Fact] + [Trait("Category", "Supplemental")] + public async Task Gateway_Should_NotCallSource_When_NodeSelectionCannotBePlanned() + { + var testCase = AuditFixture.GetOfficialV2Case("corrupted-supergraph-node-id/005"); + var capture = new SubgraphRequestCapture(); + await using var gateway = await FusionGatewayBuilder + .ComposeOfficialV2Async( + capture, + (SubgraphASubgraph.Name, SubgraphASubgraph.BuildAsync), + (SubgraphBSubgraph.Name, SubgraphBSubgraph.BuildAsync)); + using var timeout = CancellationTokenSource.CreateLinkedTokenSource( + TestContext.Current.CancellationToken); + timeout.CancelAfter(TimeSpan.FromSeconds(2)); + + await ComplianceTestBase.ExecuteAndAssertAsync( + gateway, + testCase, + timeout.Token); + + Assert.Empty(capture.Requests); + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/InterfaceObjectIndirectExtensionSupplementalTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/InterfaceObjectIndirectExtensionSupplementalTests.cs new file mode 100644 index 00000000000..12f995be502 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/InterfaceObjectIndirectExtensionSupplementalTests.cs @@ -0,0 +1,48 @@ +using HotChocolate.Fusion.Suites.InterfaceObjectIndirectExtension.A; +using HotChocolate.Fusion.Suites.InterfaceObjectIndirectExtension.B; + +namespace HotChocolate.Fusion.AdditionalCoverage; + +public sealed class InterfaceObjectIndirectExtensionSupplementalTests +{ + [Fact] + [Trait("Category", "Supplemental")] + public async Task EntityRequests_Should_Resolve_When_UpstreamUsesApolloFallbacks() + { + await using var a = await ASubgraph.BuildAsync(); + await using var b = await BSubgraph.BuildAsync(); + using var aClient = a.CreateClient(); + using var bClient = b.CreateClient(); + using var aResponse = await aClient.PostAsync( + "/graphql", + new StringContent( + """ + {"query":"query($representations: [_Any!]!) { _entities(representations: $representations) { ... on Media { __typename ... on Video { duration } } } }","variables":{"representations":[{"__typename":"Media","id":"1"}]}} + """, + System.Text.Encoding.UTF8, + "application/json"), + TestContext.Current.CancellationToken); + using var bResponse = await bClient.PostAsync( + "/graphql", + new StringContent( + """ + {"query":"query($representations: [_Any!]!) { _entities(representations: $representations) { ... on Video { authorName } } }","variables":{"representations":[{"__typename":"Video","id":"1"}]}} + """, + System.Text.Encoding.UTF8, + "application/json"), + TestContext.Current.CancellationToken); + + ("a:\n" + + await aResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken) + + "\n\nb:\n" + + await bResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)) + .MatchInlineSnapshot( + """ + a: + {"data":{"_entities":[{"__typename":"Video","duration":100}]}} + + b: + {"data":{"_entities":[{"authorName":"John Doe"}]}} + """); + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/NonResolvableInterfaceObjectSupplementalTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/NonResolvableInterfaceObjectSupplementalTests.cs new file mode 100644 index 00000000000..3e161446d3a --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/NonResolvableInterfaceObjectSupplementalTests.cs @@ -0,0 +1,30 @@ +using HotChocolate.Execution; +using HotChocolate.Fusion.Suites; +using HotChocolate.Fusion.Suites.NonResolvableInterfaceObject.A; +using HotChocolate.Fusion.Suites.NonResolvableInterfaceObject.B; + +namespace HotChocolate.Fusion.AdditionalCoverage; + +public sealed class NonResolvableInterfaceObjectSupplementalTests +{ + [Fact] + [Trait("Category", "Supplemental")] + public async Task Gateway_Should_NotCallSource_When_InterfaceObjectIsNotResolvable() + { + var testCase = AuditFixture.GetOfficialV2Case("non-resolvable-interface-object/001"); + var capture = new SubgraphRequestCapture(); + await using var gateway = await FusionGatewayBuilder + .ComposeOfficialV2Async( + capture, + (ASubgraph.Name, ASubgraph.BuildAsync), + (BSubgraph.Name, BSubgraph.BuildAsync)); + + var result = await gateway.Executor.ExecuteAsync( + testCase.Query, + TestContext.Current.CancellationToken); + var json = result.ToJson(withIndentations: false); + + AuditAssertions.Assert(json, expectedDataJson: "null", expectsErrors: true); + Assert.Empty(capture.Requests); + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/PartialUnionComplexSupplementalTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/PartialUnionComplexSupplementalTests.cs new file mode 100644 index 00000000000..bef385ac106 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/PartialUnionComplexSupplementalTests.cs @@ -0,0 +1,53 @@ +using HotChocolate.Fusion.Options; +using HotChocolate.Fusion.Suites.PartialUnionComplex.A; +using HotChocolate.Fusion.Suites.PartialUnionComplex.B; + +namespace HotChocolate.Fusion.AdditionalCoverage; + +public sealed class PartialUnionComplexSupplementalTests : ComplianceTestBase +{ + protected override Task BuildGatewayAsync() + => FusionGatewayBuilder.ComposeAsync( + new ApolloFederationCompatibilityOptions + { + ShareableFieldRuntimeTypeRouting = + ShareableFieldRuntimeTypeRouting.CommonRuntimeTypes + }, + (ASubgraph.Name, ASubgraph.BuildAsync), + (BSubgraph.Name, BSubgraph.BuildAsync)); + + [Fact] + [Trait("Category", "Supplemental")] + public Task SharedActions_Should_UseCommonRuntimeTypes_When_RootHasMultipleProviders() + => RunAsync( + query: """ + query { + sharedActions { + __typename + ... on Common { + label + } + ... on OnlyA { + a + } + ... on OnlyB { + b + } + } + } + """, + expectedData: """ + { + "sharedActions": [ + { + "__typename": "Common", + "label": "common label" + }, + { + "__typename": "OnlyA", + "a": null + } + ] + } + """); +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/ProvidesOnInterfaceSupplementalTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/ProvidesOnInterfaceSupplementalTests.cs new file mode 100644 index 00000000000..0a8e5f456f4 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/ProvidesOnInterfaceSupplementalTests.cs @@ -0,0 +1,42 @@ +using HotChocolate.Execution; +using HotChocolate.Fusion.Suites; +using HotChocolate.Fusion.Suites.ProvidesOnInterface.SubgraphA; +using HotChocolate.Fusion.Suites.ProvidesOnInterface.SubgraphB; +using HotChocolate.Fusion.Suites.ProvidesOnInterface.SubgraphC; + +namespace HotChocolate.Fusion.AdditionalCoverage; + +public sealed class ProvidesOnInterfaceSupplementalTests +{ + [Fact] + [Trait("Category", "Supplemental")] + public async Task ProvidedInterfacePath_Should_RouteUnprovidedFieldToOwningSubgraph() + { + var capture = new SubgraphRequestCapture(); + await using var gateway = await FusionGatewayBuilder + .ComposeOfficialV2Async( + capture, + (SubgraphASubgraph.Name, SubgraphASubgraph.BuildAsync), + (SubgraphBSubgraph.Name, SubgraphBSubgraph.BuildAsync), + (SubgraphCSubgraph.Name, SubgraphCSubgraph.BuildAsync)); + var testCase = AuditFixture.GetOfficialV2Case( + "provides-on-interface/001"); + + await gateway.Executor.ExecuteAsync( + OperationRequestBuilder.New().SetDocument(testCase.Query).Build(), + TestContext.Current.CancellationToken); + + string.Join( + "\n\n", + capture.Requests.Select( + static request => $"{request.SubgraphName}:\n{request.Body}")) + .MatchInlineSnapshot( + """ + b: + {"query":"query Op_3e0174e7_1 {\n media {\n __typename\n id\n ... on Book {\n animals {\n __typename\n ... on Cat {\n id\n }\n ... on Dog {\n id\n }\n ... on Cat {\n name\n }\n ... on Dog {\n name\n }\n }\n }\n }\n}"} + + c: + {"query":"query($representations: [_Any!]!) {\n _entities(representations: $representations) {\n ... on Cat {\n age\n }\n }\n}","variables":{"representations":[{"__typename":"Cat","id":"a2"}]}} + """); + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/RequiresWithFragmentsSupplementalTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/RequiresWithFragmentsSupplementalTests.cs new file mode 100644 index 00000000000..95386ba76df --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/AdditionalCoverage/RequiresWithFragmentsSupplementalTests.cs @@ -0,0 +1,29 @@ +using HotChocolate.Fusion.Suites; +using HotChocolate.Fusion.Suites.RequiresWithFragments.A; +using HotChocolate.Fusion.Suites.RequiresWithFragments.B; + +namespace HotChocolate.Fusion.AdditionalCoverage; + +public sealed class RequiresWithFragmentsSupplementalTests : ComplianceTestBase +{ + protected override Task BuildGatewayAsync() + => FusionGatewayBuilder.ComposeOfficialV2Async( + (ASubgraph.Name, ASubgraph.BuildAsync), + (BSubgraph.Name, BSubgraph.BuildAsync)); + + [Fact] + [Trait("Category", "Supplemental")] + public Task Execute_Should_NotMaskPublicAncestor_When_InaccessibleAbstractValueIsInternalOnly() + => RunAsync( + """ + { + b { + requirer + } + } + """, + """ + {"b":{"requirer":"b1-foo_requirer"}} + """, + expectsErrors: false); +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/HotChocolate.Fusion.Connectors.ApolloFederation.Compliance.Tests.csproj b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/HotChocolate.Fusion.Connectors.ApolloFederation.Compliance.Tests.csproj index d36830934e9..ce7d3ed8b6c 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/HotChocolate.Fusion.Connectors.ApolloFederation.Compliance.Tests.csproj +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/HotChocolate.Fusion.Connectors.ApolloFederation.Compliance.Tests.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditAssertions.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditAssertions.cs index f90d4d0631b..b2a7812eec6 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditAssertions.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditAssertions.cs @@ -6,7 +6,9 @@ namespace HotChocolate.Fusion; /// /// Assertions for federation-gateway-audit test cases. Matches the audit runner /// contract from graphql-hive/federation-gateway-audit/src/test.ts: deep-compare -/// the data payload and assert presence-of-errors independently. +/// the data payload and assert presence-of-errors independently. Execution timeout +/// errors are rejected because the official runner aborts the request before Fusion's +/// execution timeout and therefore cannot observe them as a compliant GraphQL response. /// internal static class AuditAssertions { @@ -34,6 +36,12 @@ public static void Assert( var actual = JsonNode.Parse(actualJson) ?? throw new InvalidOperationException("Gateway response JSON parsed to null."); + if (ContainsExecutionTimeout(actual["errors"])) + { + Xunit.Assert.Fail( + "A Fusion execution timeout cannot satisfy an official audit expectation."); + } + var actualData = actual["data"]; var expectedData = expectedDataJson is null ? null @@ -77,5 +85,25 @@ Data payload did not match. } } + private static bool ContainsExecutionTimeout(JsonNode? errors) + { + if (errors is not JsonArray errorArray) + { + return false; + } + + foreach (var error in errorArray) + { + if (error?["extensions"]?["code"] is JsonValue codeValue + && codeValue.TryGetValue(out var code) + && string.Equals(code, ErrorCodes.Execution.Timeout, StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + private static readonly JsonSerializerOptions s_indented = new() { WriteIndented = true }; } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditAssertionsTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditAssertionsTests.cs new file mode 100644 index 00000000000..31ff53ab888 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditAssertionsTests.cs @@ -0,0 +1,51 @@ +using Xunit.Sdk; + +namespace HotChocolate.Fusion; + +public sealed class AuditAssertionsTests +{ + [Fact] + public void Assert_Should_RejectExecutionTimeout_When_ErrorsAreExpected() + { + var response = $$""" + { + "data": null, + "errors": [ + { + "message": "The request exceeded the configured timeout.", + "extensions": { + "code": "{{ErrorCodes.Execution.Timeout}}" + } + } + ] + } + """; + + var exception = Assert.Throws( + () => AuditAssertions.Assert(response, expectedDataJson: null, expectsErrors: true)); + + Assert.Equal( + "A Fusion execution timeout cannot satisfy an official audit expectation.", + exception.Message); + } + + [Fact] + public void Assert_Should_AcceptNonTimeoutError_When_ErrorsAreExpected() + { + const string response = """ + { + "data": null, + "errors": [ + { + "message": "The operation cannot be planned.", + "extensions": { + "code": "HC0010" + } + } + ] + } + """; + + AuditAssertions.Assert(response, expectedDataJson: null, expectsErrors: true); + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditFixture.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditFixture.cs index fb3de84235f..bb87e44c91b 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditFixture.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditFixture.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Text.Json; namespace HotChocolate.Fusion; @@ -13,8 +14,89 @@ internal static class AuditFixture { private const string SuitesPath = "Suites"; private const string ReferencePath = "Reference"; + private const string OfficialV1ManifestResource = + "HotChocolate.Fusion.OfficialAudit.v1-manifest.json"; + private const string OfficialV2ManifestResource = + "HotChocolate.Fusion.OfficialAudit.v2-manifest.json"; private static readonly Assembly s_assembly = typeof(AuditFixture).Assembly; private static readonly string s_assemblyName = s_assembly.GetName().Name!; + private static readonly Lazy s_officialV1Manifest = + new(LoadOfficialV1Manifest); + private static readonly Lazy s_officialV2Manifest = + new(LoadOfficialV2Manifest); + + public static TheoryData GetOfficialV1CaseIds() + { + var suite = GetOfficialV1Suite(); + var cases = new TheoryData(); + + foreach (var testCase in suite.Cases) + { + cases.Add(testCase.Id); + } + + return cases; + } + + public static AuditTestCase GetOfficialV1Case(string caseId) + { + ArgumentException.ThrowIfNullOrEmpty(caseId); + + return GetOfficialV1Suite().Cases.Single(testCase => testCase.Id == caseId); + } + + public static IReadOnlyList GetOfficialV1SourceSchemas() + => GetOfficialV1Suite().Sources; + + public static OfficialV1Manifest GetOfficialV1Manifest() + => s_officialV1Manifest.Value; + + public static OfficialV1SuiteAttribute GetOfficialV1SuiteAttribute() + => typeof(TSuite) + .GetCustomAttribute() + ?? throw new InvalidOperationException( + $"Test class '{typeof(TSuite).FullName}' has no OfficialV1SuiteAttribute."); + + public static TheoryData GetOfficialV2CaseIds() + { + var suite = GetOfficialV2Suite(); + var cases = new TheoryData(); + + foreach (var testCase in suite.Cases) + { + cases.Add(testCase.Id); + } + + return cases; + } + + public static AuditTestCase GetOfficialV2Case(string caseId) + { + ArgumentException.ThrowIfNullOrEmpty(caseId); + + return GetOfficialV2Suite().Cases.Single(testCase => testCase.Id == caseId); + } + + public static AuditTestCase GetOfficialV2Case(string caseId) + { + ArgumentException.ThrowIfNullOrEmpty(caseId); + + return s_officialV2Manifest.Value.Suites + .SelectMany(suite => suite.Cases) + .Single(testCase => testCase.Id == caseId); + } + + public static IReadOnlyList GetOfficialV2SourceSchemas() + => GetOfficialV2Suite().Sources; + + public static OfficialV2Manifest GetOfficialV2Manifest() + => s_officialV2Manifest.Value; + + public static OfficialV2SuiteAttribute GetOfficialV2SuiteAttribute() + => typeof(TSuite) + .GetCustomAttribute() + ?? throw new InvalidOperationException( + $"Test class '{typeof(TSuite).FullName}' has no OfficialV2SuiteAttribute."); /// /// Reads an embedded reference text file for the specified audit suite. @@ -55,4 +137,175 @@ private static string BuildResourceName(string suiteName, string relativePath) var normalized = relativePath.Replace('/', '.').Replace('\\', '.'); return $"{s_assemblyName}.{SuitesPath}.{suiteName}.{ReferencePath}.{normalized}"; } + + internal static OfficialAuditSuite GetOfficialV1Suite() + { + var suiteId = GetOfficialV1SuiteAttribute().Id; + + return s_officialV1Manifest.Value.Suites.Single(suite => suite.Id == suiteId); + } + + internal static OfficialAuditSuite GetOfficialV2Suite() + { + var suiteId = GetOfficialV2SuiteAttribute().Id; + + return s_officialV2Manifest.Value.Suites.Single(suite => suite.Id == suiteId); + } + + private static OfficialV1Manifest LoadOfficialV1Manifest() + { + using var document = LoadManifest(OfficialV1ManifestResource); + var root = document.RootElement; + + return new OfficialV1Manifest( + root.GetProperty("revision").GetString()!, + root.GetProperty("suiteCount").GetInt32(), + root.GetProperty("caseCount").GetInt32(), + LoadSuites(root, readV1Sources: true)); + } + + private static OfficialV2Manifest LoadOfficialV2Manifest() + { + using var document = LoadManifest(OfficialV2ManifestResource); + var root = document.RootElement; + var excludedV1DependentSuites = new List(); + + foreach (var excludedElement in root + .GetProperty("excludedV1DependentSuites") + .EnumerateArray()) + { + excludedV1DependentSuites.Add(new OfficialV1DependentSuite( + excludedElement.GetProperty("id").GetString()!, + excludedElement.GetProperty("caseCount").GetInt32(), + excludedElement + .GetProperty("v1Sources") + .EnumerateArray() + .Select(source => source.GetString()!) + .ToArray())); + } + + return new OfficialV2Manifest( + root.GetProperty("revision").GetString()!, + root.GetProperty("suiteCount").GetInt32(), + root.GetProperty("caseCount").GetInt32(), + LoadSuites(root, readV1Sources: false), + excludedV1DependentSuites); + } + + private static JsonDocument LoadManifest(string resourceName) + { + using var stream = s_assembly.GetManifestResourceStream(resourceName) + ?? throw new FileNotFoundException( + $"Embedded official audit manifest '{resourceName}' was not found.", + resourceName); + + return JsonDocument.Parse(stream); + } + + private static IReadOnlyList LoadSuites( + JsonElement root, + bool readV1Sources) + { + var suites = new List(); + + foreach (var suiteElement in root.GetProperty("suites").EnumerateArray()) + { + var suiteId = suiteElement.GetProperty("id").GetString()!; + var cases = new List(); + var sources = new List(); + var fixtureModules = new List(); + IReadOnlyList v1Sources = readV1Sources + ? suiteElement + .GetProperty("v1Sources") + .EnumerateArray() + .Select(source => source.GetString()!) + .ToArray() + : []; + + foreach (var caseElement in suiteElement.GetProperty("cases").EnumerateArray()) + { + var variables = caseElement.GetProperty("variables"); + var expectedData = caseElement.GetProperty("expectedData"); + var expectsErrors = caseElement.GetProperty("expectsErrors"); + + cases.Add(new AuditTestCase( + caseElement.GetProperty("id").GetString()!, + caseElement.GetProperty("query").GetString()!, + variables.ValueKind is JsonValueKind.Null + ? null + : variables.GetRawText(), + caseElement.GetProperty("hasExpectedData").GetBoolean(), + expectedData.ValueKind is JsonValueKind.Null + ? null + : expectedData.GetRawText(), + caseElement.GetProperty("hasExpectedErrors").GetBoolean(), + expectsErrors.ValueKind is JsonValueKind.Null + ? null + : expectsErrors.GetBoolean())); + } + + foreach (var sourceElement in suiteElement.GetProperty("sources").EnumerateArray()) + { + sources.Add(new OfficialSourceSchema( + sourceElement.GetProperty("name").GetString()!, + sourceElement.GetProperty("rawSdl").GetString()!, + sourceElement.GetProperty("serviceSdl").GetString()!, + sourceElement.TryGetProperty("settings", out var settings) + ? settings.GetRawText() + : null)); + } + + foreach (var moduleElement in suiteElement + .GetProperty("fixtureModules") + .EnumerateArray()) + { + fixtureModules.Add(new OfficialFixtureModule( + moduleElement.GetProperty("path").GetString()!, + moduleElement.GetProperty("sha256").GetString()!, + moduleElement.GetProperty("source").GetString()!)); + } + + suites.Add(new OfficialAuditSuite( + suiteId, + cases, + sources, + fixtureModules, + v1Sources)); + } + + return suites; + } } + +internal sealed record OfficialV1Manifest( + string Revision, + int SuiteCount, + int CaseCount, + IReadOnlyList Suites); + +internal sealed record OfficialV2Manifest( + string Revision, + int SuiteCount, + int CaseCount, + IReadOnlyList Suites, + IReadOnlyList ExcludedV1DependentSuites); + +internal sealed record OfficialAuditSuite( + string Id, + IReadOnlyList Cases, + IReadOnlyList Sources, + IReadOnlyList FixtureModules, + IReadOnlyList V1Sources); + +internal sealed record OfficialSourceSchema( + string Name, + string RawSdl, + string ServiceSdl, + string? Settings); + +internal sealed record OfficialFixtureModule(string Path, string Sha256, string Source); + +internal sealed record OfficialV1DependentSuite( + string Id, + int CaseCount, + IReadOnlyList V1Sources); diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditTestCase.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditTestCase.cs index a2c06db9cba..b8594d5aa88 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditTestCase.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/AuditTestCase.cs @@ -4,16 +4,28 @@ namespace HotChocolate.Fusion; /// Describes a single federation-gateway-audit test case: /// a GraphQL query and its expected response (data and/or errors). /// +/// The stable official suite and ordered case identifier. /// The GraphQL operation to execute against the gateway. +/// The optional JSON object containing operation variables. +/// +/// Indicates whether the upstream test explicitly declares a data member. +/// /// /// The expected JSON payload of the data field, or if the /// assertion should not compare the data payload. /// +/// +/// Indicates whether the upstream test explicitly declares an errors member. +/// /// /// If not , the test asserts whether an errors array is /// present on the response. /// public sealed record AuditTestCase( + string Id, string Query, + string? Variables, + bool HasExpectedData, string? ExpectedData, + bool HasExpectedErrors, bool? ExpectsErrors); diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/ComplianceTestBase.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/ComplianceTestBase.cs index fbb91d67bc3..00efeae3d61 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/ComplianceTestBase.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/ComplianceTestBase.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Text.Json; using HotChocolate.Execution; namespace HotChocolate.Fusion; @@ -46,6 +47,43 @@ protected async Task RunAsync( AuditAssertions.Assert(json, expectedData, expectsErrors); } + protected async Task RunAsync(AuditTestCase testCase) + { + ArgumentNullException.ThrowIfNull(testCase); + + _gateway ??= await BuildGatewayAsync(); + await ExecuteAndAssertAsync( + _gateway, + testCase, + TestContext.Current.CancellationToken); + } + + internal static async Task ExecuteAndAssertAsync( + FusionGateway gateway, + AuditTestCase testCase, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(gateway); + ArgumentNullException.ThrowIfNull(testCase); + + var request = OperationRequestBuilder.New().SetDocument(testCase.Query); + + if (testCase.Variables is not null) + { + request.SetVariableValues(ParseVariables(testCase.Variables)); + } + + var result = await gateway.Executor.ExecuteAsync( + request.Build(), + cancellationToken); + var json = result.ToJson(withIndentations: false); + + AuditAssertions.Assert( + json, + testCase.HasExpectedData ? testCase.ExpectedData : null, + testCase.HasExpectedErrors ? testCase.ExpectsErrors : null); + } + /// public ValueTask InitializeAsync() => ValueTask.CompletedTask; @@ -58,4 +96,33 @@ public async ValueTask DisposeAsync() _gateway = null; } } + + private static IReadOnlyDictionary ParseVariables(string json) + { + using var document = JsonDocument.Parse(json); + + if (document.RootElement.ValueKind is not JsonValueKind.Object) + { + throw new InvalidOperationException("Official audit variables must be a JSON object."); + } + + return (IReadOnlyDictionary)ParseValue(document.RootElement)!; + } + + private static object? ParseValue(JsonElement element) + => element.ValueKind switch + { + JsonValueKind.Object => element + .EnumerateObject() + .ToDictionary(property => property.Name, property => ParseValue(property.Value)), + JsonValueKind.Array => element.EnumerateArray().Select(ParseValue).ToArray(), + JsonValueKind.String => element.GetString(), + JsonValueKind.Number when element.TryGetInt64(out var value) => value, + JsonValueKind.Number => element.GetDouble(), + JsonValueKind.True => true, + JsonValueKind.False => false, + JsonValueKind.Null => null, + _ => throw new InvalidOperationException( + $"Unsupported official audit variable value kind '{element.ValueKind}'.") + }; } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/FusionGatewayBuilder.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/FusionGatewayBuilder.cs index d8a033e00c2..9fcd6316de2 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/FusionGatewayBuilder.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/FusionGatewayBuilder.cs @@ -145,7 +145,60 @@ public static Task ComposeAsync( /// Determines how the gateway resolves the Query.node field. /// /// Named subgraph factories. - public static async Task ComposeAsync( + public static Task ComposeAsync( + SubgraphRequestCapture? capture, + IReadOnlyDictionary? sourceSchemaSettings, + NodeResolution nodeResolution, + bool allowNonResolvableInterfaceObjects, + ShareableFieldRuntimeTypeRouting shareableFieldRuntimeTypeRouting, + params (string Name, Func> Factory)[] subgraphs) + => ComposeAsync( + officialSourceSchemas: null, + capture, + sourceSchemaSettings, + nodeResolution, + allowNonResolvableInterfaceObjects, + shareableFieldRuntimeTypeRouting, + subgraphs); + + public static Task ComposeOfficialV2Async( + params (string Name, Func> Factory)[] subgraphs) + => ComposeOfficialV2Async(capture: null, subgraphs); + + public static Task ComposeOfficialV1Async( + params (string Name, Func> Factory)[] subgraphs) + => ComposeOfficialV1Async(capture: null, subgraphs); + + public static Task ComposeOfficialV1Async( + SubgraphRequestCapture? capture, + params (string Name, Func> Factory)[] subgraphs) + => ComposeAsync( + AuditFixture.GetOfficialV1SourceSchemas(), + capture, + sourceSchemaSettings: null, + NodeResolution.Gateway, + allowNonResolvableInterfaceObjects: false, + ShareableFieldRuntimeTypeRouting.SourceLocal, + subgraphs); + + public static Task ComposeOfficialV2Async( + SubgraphRequestCapture? capture, + params (string Name, Func> Factory)[] subgraphs) + { + var suite = AuditFixture.GetOfficialV2SuiteAttribute(); + + return ComposeAsync( + AuditFixture.GetOfficialV2SourceSchemas(), + capture, + sourceSchemaSettings: null, + suite.NodeResolution, + suite.AllowNonResolvableInterfaceObjects, + suite.ShareableFieldRuntimeTypeRouting, + subgraphs); + } + + private static async Task ComposeAsync( + IReadOnlyList? officialSourceSchemas, SubgraphRequestCapture? capture, IReadOnlyDictionary? sourceSchemaSettings, NodeResolution nodeResolution, @@ -162,14 +215,27 @@ public static async Task ComposeAsync( nameof(subgraphs)); } + if (officialSourceSchemas is not null) + { + var officialSourceNames = officialSourceSchemas.Select(source => source.Name); + var localSourceNames = subgraphs.Select(subgraph => subgraph.Name); + + if (!officialSourceNames.SequenceEqual(localSourceNames, StringComparer.Ordinal)) + { + throw new InvalidOperationException( + "Local subgraph names and order must exactly match the official audit manifest."); + } + } + var hosts = new List(subgraphs.Length); var sourceSchemaTexts = new List(subgraphs.Length); var subgraphInfos = new List(subgraphs.Length); try { - foreach (var (name, factory) in subgraphs) + for (var i = 0; i < subgraphs.Length; i++) { + var (name, factory) = subgraphs[i]; var host = await factory().ConfigureAwait(false); if (!string.Equals(host.Name, name, StringComparison.Ordinal)) @@ -180,7 +246,12 @@ public static async Task ComposeAsync( hosts.Add(host); - var info = await BuildSubgraphInfoAsync(host).ConfigureAwait(false); + var info = officialSourceSchemas is null + ? await BuildSubgraphInfoAsync(host).ConfigureAwait(false) + : new SubgraphInfo( + name, + officialSourceSchemas[i].ServiceSdl, + SubgraphAddress(name)); sourceSchemaTexts.Add(new SourceSchemaText(name, info.SourceSchemaSdl)); subgraphInfos.Add(info); @@ -188,11 +259,15 @@ public static async Task ComposeAsync( var schemaDocument = ComposeSchema( sourceSchemaTexts, + officialSourceSchemas, nodeResolution != NodeResolution.Gateway, nodeResolution, allowNonResolvableInterfaceObjects, shareableFieldRuntimeTypeRouting); - var settings = BuildGatewaySettings(subgraphInfos, sourceSchemaSettings); + var settings = BuildGatewaySettings( + subgraphInfos, + sourceSchemaSettings, + disableBatching: officialSourceSchemas is not null); var gatewayServices = new ServiceCollection(); gatewayServices.AddSingleton( @@ -271,6 +346,7 @@ private static async Task FetchSubgraphSdlAsync(SubgraphHost host) private static DocumentNode ComposeSchema( IReadOnlyList sourceSchemas, + IReadOnlyList? officialSourceSchemas, bool enableGlobalObjectIdentification, NodeResolution nodeResolution, bool allowNonResolvableInterfaceObjects, @@ -289,15 +365,36 @@ private static DocumentNode ComposeSchema( // '@key' directive and prevents the composer from re-introducing // list-typed '@key' directives for nested list keys, which the // Composite Schema Spec disallows at type level. - foreach (var sourceSchema in sourceSchemas) + for (var i = 0; i < sourceSchemas.Count; i++) { - options.SourceSchemas[sourceSchema.Name] = new SourceSchemaOptions + var sourceSchema = sourceSchemas[i]; + var sourceSchemaOptions = new SourceSchemaOptions(); + + if (officialSourceSchemas?[i].Settings is { } settingsJson) { - Preprocessor = new SourceSchemaPreprocessorOptions + using var settings = JsonDocument.Parse(settingsJson); + + if (!SourceSchemaSettingsReader.TryRead( + sourceSchema.Name, + settings, + compositionLog, + out var settingsResult)) { - InferKeysFromLookups = false + throw new XunitException( + string.Join( + Environment.NewLine, + compositionLog.Select(entry => entry.Message))); } + + sourceSchemaOptions = settingsResult.Options; + settingsResult.RuntimeSettings?.Dispose(); + } + + sourceSchemaOptions.Preprocessor = new SourceSchemaPreprocessorOptions + { + InferKeysFromLookups = false }; + options.SourceSchemas[sourceSchema.Name] = sourceSchemaOptions; } if (enableGlobalObjectIdentification) @@ -360,7 +457,8 @@ private static DocumentNode ComposeSchema( private static JsonDocumentOwner BuildGatewaySettings( IReadOnlyList subgraphs, - IReadOnlyDictionary? sourceSchemaSettings) + IReadOnlyDictionary? sourceSchemaSettings, + bool disableBatching) { var buffer = new ArrayBufferWriter(); @@ -376,6 +474,17 @@ private static JsonDocumentOwner BuildGatewaySettings( writer.WriteStartObject("transports"); writer.WriteStartObject("http"); writer.WriteString("url", subgraph.BaseAddress.ToString()); + + if (disableBatching) + { + writer.WriteStartObject("capabilities"); + writer.WriteStartObject("batching"); + writer.WriteBoolean("variableBatching", false); + writer.WriteBoolean("requestBatching", false); + writer.WriteEndObject(); + writer.WriteEndObject(); + } + writer.WriteEndObject(); writer.WriteEndObject(); diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/OfficialV1ComplianceTestBase.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/OfficialV1ComplianceTestBase.cs new file mode 100644 index 00000000000..6cc56856d55 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/OfficialV1ComplianceTestBase.cs @@ -0,0 +1,22 @@ +namespace HotChocolate.Fusion; + +public abstract class OfficialV1ComplianceTestBase : ComplianceTestBase + where TSuite : OfficialV1ComplianceTestBase +{ + public static TheoryData Cases => AuditFixture.GetOfficialV1CaseIds(); + + protected Task RunOfficialV1CaseAsync(string caseId) + => OfficialAuditSuiteRun.AssertCaseAsync( + caseId, + BuildGatewayAsync, + static () => AuditFixture.GetOfficialV1Suite()); + + protected Task ComposeOfficialV1Async( + params (string Name, Func> Factory)[] subgraphs) + => FusionGatewayBuilder.ComposeOfficialV1Async(subgraphs); + + protected Task ComposeOfficialV1Async( + SubgraphRequestCapture capture, + params (string Name, Func> Factory)[] subgraphs) + => FusionGatewayBuilder.ComposeOfficialV1Async(capture, subgraphs); +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/OfficialV1SuiteAttribute.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/OfficialV1SuiteAttribute.cs new file mode 100644 index 00000000000..41f40e29cb2 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/OfficialV1SuiteAttribute.cs @@ -0,0 +1,7 @@ +namespace HotChocolate.Fusion; + +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public sealed class OfficialV1SuiteAttribute(string id) : Attribute +{ + public string Id { get; } = id; +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/OfficialV2ComplianceTestBase.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/OfficialV2ComplianceTestBase.cs new file mode 100644 index 00000000000..8260b753a81 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/OfficialV2ComplianceTestBase.cs @@ -0,0 +1,107 @@ +using System.Runtime.ExceptionServices; + +namespace HotChocolate.Fusion; + +public abstract class OfficialV2ComplianceTestBase : ComplianceTestBase + where TSuite : OfficialV2ComplianceTestBase +{ + public static TheoryData Cases => AuditFixture.GetOfficialV2CaseIds(); + + protected Task RunOfficialV2CaseAsync(string caseId) + => OfficialAuditSuiteRun.AssertCaseAsync( + caseId, + BuildGatewayAsync, + static () => AuditFixture.GetOfficialV2Suite()); + + protected Task ComposeOfficialV2Async( + params (string Name, Func> Factory)[] subgraphs) + => FusionGatewayBuilder.ComposeOfficialV2Async(subgraphs); + + protected Task ComposeOfficialV2Async( + SubgraphRequestCapture capture, + params (string Name, Func> Factory)[] subgraphs) + => FusionGatewayBuilder.ComposeOfficialV2Async(capture, subgraphs); +} + +internal static class OfficialAuditSuiteRun +{ + private static readonly TimeSpan s_caseTimeout = TimeSpan.FromSeconds(10); + private static readonly object s_sync = new(); + private static Lazy>>? s_run; + + public static async Task AssertCaseAsync( + string caseId, + Func> buildGatewayAsync, + Func getSuite) + { + var run = Volatile.Read(ref s_run); + + if (run is null) + { + lock (s_sync) + { + var cancellationToken = TestContext.Current.CancellationToken; + run = s_run ??= new Lazy>>( + () => ExecuteSuiteAsync(buildGatewayAsync, getSuite(), cancellationToken), + LazyThreadSafetyMode.ExecutionAndPublication); + } + } + + var outcomes = await run.Value.ConfigureAwait(false); + + if (outcomes[caseId] is { } error) + { + error.Throw(); + } + } + + private static async Task> ExecuteSuiteAsync( + Func> buildGatewayAsync, + OfficialAuditSuite suite, + CancellationToken cancellationToken) + { + var outcomes = new Dictionary(StringComparer.Ordinal); + FusionGateway gateway; + + try + { + gateway = await buildGatewayAsync().ConfigureAwait(false); + } + catch (Exception exception) + { + var error = ExceptionDispatchInfo.Capture(exception); + + foreach (var testCase in suite.Cases) + { + outcomes.Add(testCase.Id, error); + } + + return outcomes; + } + + await using (gateway.ConfigureAwait(false)) + { + foreach (var testCase in suite.Cases) + { + try + { + using var timeout = CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken); + timeout.CancelAfter(s_caseTimeout); + + await ComplianceTestBase.ExecuteAndAssertAsync( + gateway, + testCase, + timeout.Token).ConfigureAwait(false); + outcomes.Add(testCase.Id, null); + } + catch (Exception exception) + { + outcomes.Add(testCase.Id, ExceptionDispatchInfo.Capture(exception)); + } + } + } + + return outcomes; + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/OfficialV2SuiteAttribute.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/OfficialV2SuiteAttribute.cs new file mode 100644 index 00000000000..3b462aad6f2 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/OfficialV2SuiteAttribute.cs @@ -0,0 +1,14 @@ +namespace HotChocolate.Fusion; + +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public sealed class OfficialV2SuiteAttribute(string id) : Attribute +{ + public string Id { get; } = id; + + public NodeResolution NodeResolution { get; set; } = NodeResolution.Gateway; + + public bool AllowNonResolvableInterfaceObjects { get; set; } + + public ShareableFieldRuntimeTypeRouting ShareableFieldRuntimeTypeRouting { get; set; } = + ShareableFieldRuntimeTypeRouting.SourceLocal; +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/SubgraphEndpointExtensions.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/SubgraphEndpointExtensions.cs index a4eb755b74b..b24d1c643f1 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/SubgraphEndpointExtensions.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Infrastructure/SubgraphEndpointExtensions.cs @@ -17,10 +17,15 @@ internal static class SubgraphEndpointExtensions /// required. /// /// The subgraph web application. - public static void MapSubgraph(this WebApplication app) + public static void MapSubgraph(this WebApplication app, bool enableBatching = false) { ArgumentNullException.ThrowIfNull(app); - app.MapGraphQL().WithOptions(o => o.Batching = AllowedBatching.All); + var endpoint = app.MapGraphQL(); + + if (enableBatching) + { + endpoint.WithOptions(o => o.Batching = AllowedBatching.All); + } } } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/OfficialAuditParityTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/OfficialAuditParityTests.cs new file mode 100644 index 00000000000..0ecd9445641 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/OfficialAuditParityTests.cs @@ -0,0 +1,267 @@ +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; + +namespace HotChocolate.Fusion; + +public sealed class OfficialAuditParityTests +{ + [Fact] + [Trait("Category", "OfficialV1Parity")] + public void V1Manifest_Should_DefineExactInventory_When_Loaded() + { + using var manifestStream = typeof(OfficialAuditParityTests).Assembly.GetManifestResourceStream( + "HotChocolate.Fusion.OfficialAudit.v1-manifest.json") + ?? throw new InvalidOperationException("The official V1 manifest resource was not found."); + Assert.Equal( + "0be75d74affd9ae17520458e82678aa43afe042b7fc849191902db946a0094e2", + Convert.ToHexString(SHA256.HashData(manifestStream)).ToLowerInvariant()); + + var manifest = AuditFixture.GetOfficialV1Manifest(); + var caseIds = manifest.Suites.SelectMany(suite => suite.Cases).Select(c => c.Id).ToArray(); + + Assert.Equal("f59c05e3f48f4a4f8e8a731d67a1b71a9788a96f", manifest.Revision); + Assert.Equal(4, manifest.SuiteCount); + Assert.Equal(27, manifest.CaseCount); + Assert.Equal(manifest.SuiteCount, manifest.Suites.Count); + Assert.Equal(manifest.CaseCount, caseIds.Length); + Assert.Equal(caseIds.Length, caseIds.Distinct(StringComparer.Ordinal).Count()); + + foreach (var suite in manifest.Suites) + { + Assert.Equal( + Enumerable.Range(0, suite.Cases.Count) + .Select(index => $"{suite.Id}/{index:000}"), + suite.Cases.Select(testCase => testCase.Id)); + Assert.Equal( + suite.Sources.Count, + suite.Sources.Select(source => source.Name).Distinct(StringComparer.Ordinal).Count()); + + foreach (var source in suite.Sources) + { + Assert.False(string.IsNullOrWhiteSpace(source.RawSdl)); + Assert.False(string.IsNullOrWhiteSpace(source.ServiceSdl)); + AssertV1SourceSettings( + source, + suite.V1Sources.Contains(source.Name, StringComparer.Ordinal)); + } + + foreach (var testCase in suite.Cases) + { + if (!testCase.HasExpectedData) + { + Assert.Null(testCase.ExpectedData); + } + + Assert.Equal(testCase.HasExpectedErrors, testCase.ExpectsErrors is not null); + } + + foreach (var module in suite.FixtureModules) + { + var hash = Convert.ToHexString( + SHA256.HashData(Encoding.UTF8.GetBytes(module.Source))) + .ToLowerInvariant(); + + Assert.Equal(module.Sha256, hash); + } + } + + Assert.Collection( + manifest.Suites, + suite => AssertV1Suite(suite, "abstract-types", 18, "users"), + suite => AssertV1Suite(suite, "fed1-external-extends", 4, "a", "b"), + suite => AssertV1Suite(suite, "fed1-external-extends-resolvable", 1, "a", "b"), + suite => AssertV1Suite(suite, "fed1-external-extension", 4, "a", "b")); + } + + [Fact] + [Trait("Category", "OfficialV1Parity")] + public void V1Port_Should_CoverEverySuiteExactlyOnce_When_Discovered() + { + var manifest = AuditFixture.GetOfficialV1Manifest(); + var suites = typeof(OfficialAuditParityTests) + .Assembly + .GetTypes() + .Select(type => new + { + Type = type, + Suite = type.GetCustomAttribute() + }) + .Where(item => item.Suite is not null) + .ToArray(); + + Assert.Equal(suites.Length, suites.Select(item => item.Suite!.Id).Distinct().Count()); + Assert.Equal( + manifest.Suites.Select(suite => suite.Id).Order(StringComparer.Ordinal), + suites.Select(item => item.Suite!.Id).Order(StringComparer.Ordinal)); + + foreach (var item in suites) + { + var officialTestMethods = item.Type + .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(method => method.GetCustomAttributes(inherit: false) + .Any(attribute => attribute is FactAttribute)) + .ToArray(); + + var testMethod = Assert.Single(officialTestMethods); + Assert.IsType( + testMethod.GetCustomAttributes(inherit: false) + .Single(attribute => attribute is FactAttribute)); + } + } + + [Fact] + [Trait("Category", "OfficialV2Parity")] + public void Manifest_Should_DefineExactInventory_When_Loaded() + { + using var manifestStream = typeof(OfficialAuditParityTests).Assembly.GetManifestResourceStream( + "HotChocolate.Fusion.OfficialAudit.v2-manifest.json") + ?? throw new InvalidOperationException("The official V2 manifest resource was not found."); + Assert.Equal( + "146ef9f96f2f78ec35b613b67376b2b1741d41946e5f32efd4d0dc9062e3ae29", + Convert.ToHexString(SHA256.HashData(manifestStream)).ToLowerInvariant()); + + var manifest = AuditFixture.GetOfficialV2Manifest(); + var caseIds = manifest.Suites.SelectMany(suite => suite.Cases).Select(c => c.Id).ToArray(); + + Assert.Equal("f59c05e3f48f4a4f8e8a731d67a1b71a9788a96f", manifest.Revision); + Assert.Equal(42, manifest.SuiteCount); + Assert.Equal(172, manifest.CaseCount); + Assert.Equal(manifest.SuiteCount, manifest.Suites.Count); + Assert.Equal(manifest.CaseCount, caseIds.Length); + Assert.Equal(caseIds.Length, caseIds.Distinct(StringComparer.Ordinal).Count()); + + foreach (var suite in manifest.Suites) + { + Assert.Equal( + Enumerable.Range(0, suite.Cases.Count) + .Select(index => $"{suite.Id}/{index:000}"), + suite.Cases.Select(testCase => testCase.Id)); + Assert.Equal( + suite.Sources.Count, + suite.Sources.Select(source => source.Name).Distinct(StringComparer.Ordinal).Count()); + + foreach (var source in suite.Sources) + { + Assert.False(string.IsNullOrWhiteSpace(source.RawSdl)); + Assert.False(string.IsNullOrWhiteSpace(source.ServiceSdl)); + } + + foreach (var testCase in suite.Cases) + { + if (!testCase.HasExpectedData) + { + Assert.Null(testCase.ExpectedData); + } + + Assert.Equal(testCase.HasExpectedErrors, testCase.ExpectsErrors is not null); + } + + foreach (var module in suite.FixtureModules) + { + var hash = Convert.ToHexString( + SHA256.HashData(Encoding.UTF8.GetBytes(module.Source))) + .ToLowerInvariant(); + + Assert.Equal(module.Sha256, hash); + } + } + + Assert.Collection( + manifest.ExcludedV1DependentSuites, + suite => AssertV1Suite(suite, "abstract-types", 18, "users"), + suite => AssertV1Suite(suite, "fed1-external-extends", 4, "a", "b"), + suite => AssertV1Suite(suite, "fed1-external-extends-resolvable", 1, "a", "b"), + suite => AssertV1Suite(suite, "fed1-external-extension", 4, "a", "b")); + Assert.Equal(27, manifest.ExcludedV1DependentSuites.Sum(suite => suite.CaseCount)); + } + + [Fact] + [Trait("Category", "OfficialV2Parity")] + public void Port_Should_CoverEverySuiteExactlyOnce_When_Discovered() + { + var manifest = AuditFixture.GetOfficialV2Manifest(); + var suiteIds = typeof(OfficialAuditParityTests) + .Assembly + .GetTypes() + .Select(type => new + { + Type = type, + Suite = type.GetCustomAttribute() + }) + .Where(item => item.Suite is not null) + .ToArray(); + + Assert.Equal(suiteIds.Length, suiteIds.Select(item => item.Suite!.Id).Distinct().Count()); + Assert.Equal( + manifest.Suites.Select(suite => suite.Id).Order(StringComparer.Ordinal), + suiteIds.Select(item => item.Suite!.Id).Order(StringComparer.Ordinal)); + + foreach (var item in suiteIds) + { + var officialTestMethods = item.Type + .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(method => method.GetCustomAttributes(inherit: false) + .Any(attribute => attribute is FactAttribute)) + .ToArray(); + + var testMethod = Assert.Single(officialTestMethods); + Assert.IsType( + testMethod.GetCustomAttributes(inherit: false) + .Single(attribute => attribute is FactAttribute)); + } + } + + private static void AssertV1Suite( + OfficialV1DependentSuite suite, + string id, + int caseCount, + params string[] sources) + { + Assert.Equal(id, suite.Id); + Assert.Equal(caseCount, suite.CaseCount); + Assert.Equal(sources, suite.V1Sources); + } + + private static void AssertV1Suite( + OfficialAuditSuite suite, + string id, + int caseCount, + params string[] sources) + { + Assert.Equal(id, suite.Id); + Assert.Equal(caseCount, suite.Cases.Count); + Assert.Equal(sources, suite.V1Sources); + } + + private static void AssertV1SourceSettings( + OfficialSourceSchema source, + bool isFederationV1) + { + using var document = JsonDocument.Parse(source.Settings!); + var root = document.RootElement; + + if (!isFederationV1) + { + Assert.Collection( + root.EnumerateObject(), + property => + { + Assert.Equal("name", property.Name); + Assert.Equal(source.Name, property.Value.GetString()); + }); + return; + } + + Assert.Equal(source.Name, root.GetProperty("name").GetString()); + var extensions = root.GetProperty("extensions"); + Assert.Equal(2, root.EnumerateObject().Count()); + Assert.Single(extensions.EnumerateObject()); + var chillicream = extensions.GetProperty("chillicream"); + Assert.Single(chillicream.EnumerateObject()); + var support = chillicream.GetProperty("apolloFederationSupport"); + Assert.Single(support.EnumerateObject()); + Assert.Equal("1.0", support.GetProperty("version").GetString()); + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/generate-v2-manifest.mjs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/generate-v2-manifest.mjs new file mode 100644 index 00000000000..b5582ee43be --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/generate-v2-manifest.mjs @@ -0,0 +1,194 @@ +import { execFileSync, spawn } from "node:child_process"; +import { createHash } from "node:crypto"; +import { readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const expectedRevision = "f59c05e3f48f4a4f8e8a731d67a1b71a9788a96f"; +const scriptDirectory = dirname(fileURLToPath(import.meta.url)); +const auditDirectory = process.argv[2] + ? resolve(process.argv[2]) + : resolve(scriptDirectory, "../../../../../../../../federation-gateway-audit"); +const outputPath = resolve(scriptDirectory, "v2-manifest.json"); +const deterministicRuntime = pathToFileURL( + join(scriptDirectory, "official-audit-deterministic-runtime.mjs"), +).href; +const port = Number(process.argv[3] ?? 4217); + +const revision = execFileSync("git", ["rev-parse", "HEAD"], { + cwd: auditDirectory, + encoding: "utf8", +}).trim(); + +if (revision !== expectedRevision) { + throw new Error( + `Expected federation-gateway-audit revision ${expectedRevision}, but found ${revision}.`, + ); +} + +const server = spawn( + join(auditDirectory, "node_modules", ".bin", "tsx"), + [join(auditDirectory, "src", "cli.ts"), "serve", "--port", String(port)], + { + cwd: auditDirectory, + env: { + ...process.env, + NODE_OPTIONS: [ + process.env.NODE_OPTIONS, + `--import=${deterministicRuntime}`, + ] + .filter(Boolean) + .join(" "), + }, + stdio: ["ignore", "pipe", "inherit"], + }, +); + +try { + await waitForServer(server, `http://localhost:${port}/_health`); + + const ids = await fetchJson(`http://localhost:${port}/ids`); + const suites = []; + const excludedV1DependentSuites = []; + + for (const id of ids) { + const [tests, subgraphs] = await Promise.all([ + fetchJson(`http://localhost:${port}/${id}/tests`), + fetchJson(`http://localhost:${port}/${id}/subgraphs`), + ]); + const v1Sources = subgraphs + .filter(({ sdl }) => isFederationV1Source(sdl)) + .map(({ name }) => name); + + if (v1Sources.length > 0) { + excludedV1DependentSuites.push({ id, caseCount: tests.length, v1Sources }); + continue; + } + + const suiteDirectory = join(auditDirectory, "src", "test-suites", id); + const sources = await Promise.all( + subgraphs.map(async ({ name, sdl, url }) => ({ + name, + rawSdl: sdl, + serviceSdl: await fetchServiceSdl(url), + })), + ); + const fixtureModules = readdirSync(suiteDirectory) + .filter((file) => file === "data.ts" || file.endsWith(".subgraph.ts")) + .sort() + .map((file) => { + const source = readFileSync(join(suiteDirectory, file), "utf8"); + + return { + path: file, + sha256: createHash("sha256").update(source).digest("hex"), + source, + }; + }); + + suites.push({ + id, + cases: tests.map((test, index) => ({ + id: `${id}/${String(index).padStart(3, "0")}`, + query: test.query, + variables: test.variables ?? null, + hasExpectedData: Object.hasOwn(test.expected, "data"), + expectedData: Object.hasOwn(test.expected, "data") + ? test.expected.data + : null, + hasExpectedErrors: Object.hasOwn(test.expected, "errors"), + expectsErrors: Object.hasOwn(test.expected, "errors") + ? test.expected.errors + : null, + })), + sources, + fixtureModules, + }); + } + + const manifest = { + source: "graphql-hive/federation-gateway-audit", + revision, + federationVersion: 2, + selection: + "Suites whose sources do not rely on linkless Federation directives.", + excludedV1DependentSuites, + suiteCount: suites.length, + caseCount: suites.reduce((sum, suite) => sum + suite.cases.length, 0), + suites, + }; + + writeFileSync(outputPath, `${JSON.stringify(manifest, null, 2)}\n`); + process.stdout.write( + `Wrote ${manifest.suiteCount} suites and ${manifest.caseCount} cases to ${outputPath}\n`, + ); +} finally { + server.kill("SIGTERM"); +} + +async function fetchJson(url) { + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`GET ${url} failed with ${response.status} ${response.statusText}.`); + } + + return response.json(); +} + +async function fetchServiceSdl(url) { + const response = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ query: "query AuditServiceSdl { _service { sdl } }" }), + }); + + if (!response.ok) { + throw new Error(`POST ${url} failed with ${response.status} ${response.statusText}.`); + } + + const result = await response.json(); + const sdl = result.data?._service?.sdl; + + if (typeof sdl !== "string" || sdl.length === 0) { + throw new Error(`POST ${url} returned no _service.sdl.`); + } + + return sdl; +} + +async function waitForServer(process, url) { + let failure; + + process.once("exit", (code) => { + failure = new Error(`Audit server exited before startup with code ${code}.`); + }); + + for (let attempt = 0; attempt < 100; attempt++) { + if (failure) { + throw failure; + } + + try { + const response = await fetch(url); + + if (response.ok) { + return; + } + } catch { + // The server has not started accepting connections yet. + } + + await new Promise((resolve) => setTimeout(resolve, 50)); + } + + throw new Error(`Audit server did not become healthy at ${url}.`); +} + +function isFederationV1Source(sdl) { + const linksFederation = sdl.includes("specs.apollo.dev/federation/"); + const usesFederationDirective = + /@(key|external|requires|provides|extends)\b|\bextend\s+type\b/.test(sdl); + + return !linksFederation && usesFederationDirective; +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/official-audit-deterministic-runtime.mjs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/official-audit-deterministic-runtime.mjs new file mode 100644 index 00000000000..2793b17f6db --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/official-audit-deterministic-runtime.mjs @@ -0,0 +1 @@ +Math.random = () => 0.625; diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/v1-manifest.json b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/v1-manifest.json new file mode 100644 index 00000000000..3369612120a --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/v1-manifest.json @@ -0,0 +1,1235 @@ +{ + "source": "graphql-hive/federation-gateway-audit", + "revision": "f59c05e3f48f4a4f8e8a731d67a1b71a9788a96f", + "federationVersion": 1, + "selection": "Suites with at least one source that relies on linkless Federation directives.", + "suiteCount": 4, + "caseCount": 27, + "suites": [ + { + "id": "abstract-types", + "v1Sources": [ + "users" + ], + "cases": [ + { + "id": "abstract-types/000", + "query": "\n query {\n products {\n id\n dimensions {\n size\n weight\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "dimensions": { + "size": "small", + "weight": 0.5 + } + }, + { + "id": "p3", + "dimensions": { + "size": "small", + "weight": 0.6 + } + }, + { + "id": "p2", + "dimensions": { + "size": "small", + "weight": 0.2 + } + }, + { + "id": "p4", + "dimensions": { + "size": "small", + "weight": 0.3 + } + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/001", + "query": "\n query {\n similar(id: \u0022p1\u0022) {\n id\n sku\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "similar": [ + { + "id": "p3", + "sku": "sku-3" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/002", + "query": "\n query {\n book: similar(id: \u0022p1\u0022) {\n id\n sku\n delivery(zip: \u00221234\u0022) {\n fastestDelivery\n estimatedDelivery\n }\n }\n magazine: similar(id: \u0022p2\u0022) {\n id\n sku\n delivery(zip: \u00221234\u0022) {\n fastestDelivery\n estimatedDelivery\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "book": [ + { + "id": "p3", + "sku": "sku-3", + "delivery": { + "fastestDelivery": "same day", + "estimatedDelivery": "1 day" + } + } + ], + "magazine": [ + { + "id": "p4", + "sku": "sku-4", + "delivery": { + "fastestDelivery": "same day", + "estimatedDelivery": "1 day" + } + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/003", + "query": "\n {\n products {\n sku\n ... on Product {\n sku\n }\n ... on Book {\n sku\n }\n ... on Magazine {\n sku\n }\n ... on Similar {\n __typename\n type: __typename\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "sku": "sku-1", + "__typename": "Book", + "type": "Book" + }, + { + "sku": "sku-3", + "__typename": "Book", + "type": "Book" + }, + { + "sku": "sku-2", + "__typename": "Magazine", + "type": "Magazine" + }, + { + "sku": "sku-4", + "__typename": "Magazine", + "type": "Magazine" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/004", + "query": "\n {\n products {\n author: createdBy {\n email\n totalProductsCreated\n }\n ... on Magazine {\n title\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "author": { + "email": "u1@example.com", + "totalProductsCreated": 2 + } + }, + { + "author": { + "email": "u2@example.com", + "totalProductsCreated": 2 + } + }, + { + "author": { + "email": "u1@example.com", + "totalProductsCreated": 2 + }, + "title": "Magazine 1" + }, + { + "author": { + "email": "u2@example.com", + "totalProductsCreated": 2 + }, + "title": "Magazine 2" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/005", + "query": "\n {\n products {\n id\n reviews {\n product {\n sku\n ... on Magazine {\n title\n }\n ... on Book {\n reviewsCount\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "product": { + "sku": "sku-1", + "reviewsCount": 2 + } + }, + { + "product": { + "sku": "sku-1", + "reviewsCount": 2 + } + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "product": { + "sku": "sku-2", + "title": "Magazine 1" + } + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/006", + "query": "\n {\n products {\n id\n reviews {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "id": 1 + }, + { + "id": 2 + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "id": 3 + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/007", + "query": "\n query ($title: Boolean = true) {\n products {\n id\n reviews {\n product {\n id\n ... on Book @include(if: $title) {\n title\n }\n ... on Magazine {\n sku\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "product": { + "id": "p1", + "title": "Book 1" + } + }, + { + "product": { + "id": "p1", + "title": "Book 1" + } + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "product": { + "id": "p2", + "sku": "sku-2" + } + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/008", + "query": "\n query ($title: Boolean = false) {\n products {\n id\n reviews {\n product {\n id\n ... on Book @include(if: $title) {\n title\n }\n ... on Magazine {\n sku\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "product": { + "id": "p1" + } + }, + { + "product": { + "id": "p1" + } + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "product": { + "id": "p2", + "sku": "sku-2" + } + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/009", + "query": "\n query ($title: Boolean = false) {\n products {\n id\n reviews {\n product {\n id\n ... on Book @skip(if: $title) {\n title\n }\n ... on Magazine {\n sku\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "product": { + "id": "p1", + "title": "Book 1" + } + }, + { + "product": { + "id": "p1", + "title": "Book 1" + } + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "product": { + "id": "p2", + "sku": "sku-2" + } + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/010", + "query": "\n query ($title: Boolean = true) {\n products {\n id\n reviews {\n product {\n id\n ... on Book @skip(if: $title) {\n title\n }\n ... on Magazine {\n sku\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "product": { + "id": "p1" + } + }, + { + "product": { + "id": "p1" + } + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "product": { + "id": "p2", + "sku": "sku-2" + } + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/011", + "query": "\n query ($title: Boolean = true) {\n products {\n id\n reviews {\n product {\n id\n ... on Book @skip(if: $title) {\n title\n }\n ... on Book {\n sku\n }\n ... on Magazine {\n sku\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "product": { + "id": "p1", + "sku": "sku-1" + } + }, + { + "product": { + "id": "p1", + "sku": "sku-1" + } + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "product": { + "id": "p2", + "sku": "sku-2" + } + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/012", + "query": "\n query ($title: Boolean = false) {\n products {\n id\n reviews {\n product {\n id\n ... on Book @skip(if: $title) {\n title\n }\n ... on Book {\n sku\n }\n ... on Magazine {\n sku\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "product": { + "id": "p1", + "title": "Book 1", + "sku": "sku-1" + } + }, + { + "product": { + "id": "p1", + "title": "Book 1", + "sku": "sku-1" + } + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "product": { + "id": "p2", + "sku": "sku-2" + } + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/013", + "query": "\n query ($title: Boolean = true) {\n products {\n id\n reviews {\n product {\n id\n ... on Book @include(if: $title) {\n title\n ... on Book {\n sku\n }\n }\n ... on Magazine {\n sku\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "product": { + "id": "p1", + "title": "Book 1", + "sku": "sku-1" + } + }, + { + "product": { + "id": "p1", + "title": "Book 1", + "sku": "sku-1" + } + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "product": { + "id": "p2", + "sku": "sku-2" + } + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/014", + "query": "\n query ($title: Boolean = true) {\n products {\n id\n reviews {\n product {\n id\n ... on Book @include(if: $title) {\n title\n ... on Book @skip(if: $title) {\n sku\n }\n }\n ... on Magazine {\n sku\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "product": { + "id": "p1", + "title": "Book 1" + } + }, + { + "product": { + "id": "p1", + "title": "Book 1" + } + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "product": { + "id": "p2", + "sku": "sku-2" + } + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/015", + "query": "\n {\n products {\n id\n reviews {\n product {\n id\n ... on Magazine {\n publisherType {\n ...Publisher\n }\n }\n ... on Book {\n publisherType {\n ...Publisher\n }\n }\n }\n }\n }\n }\n\n fragment Publisher on PublisherType {\n ... on Agency {\n id\n companyName\n }\n ... on Self {\n email\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "product": { + "id": "p1", + "publisherType": { + "email": "u1@example.com" + } + } + }, + { + "product": { + "id": "p1", + "publisherType": { + "email": "u1@example.com" + } + } + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "product": { + "id": "p2", + "publisherType": { + "id": "a1", + "companyName": "Agency 1" + } + } + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/016", + "query": "\n {\n products {\n id\n reviews {\n product {\n id\n ... on Magazine {\n publisherType {\n ...Publisher\n }\n }\n ... on Book {\n publisherType {\n ...Publisher\n }\n }\n }\n }\n }\n }\n\n fragment Publisher on PublisherType {\n ... on Agency {\n id\n companyName\n }\n ... on Self {\n email\n }\n ... on Group {\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "product": { + "id": "p1", + "publisherType": { + "email": "u1@example.com" + } + } + }, + { + "product": { + "id": "p1", + "publisherType": { + "email": "u1@example.com" + } + } + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "product": { + "id": "p2", + "publisherType": { + "id": "a1", + "companyName": "Agency 1" + } + } + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "abstract-types/017", + "query": "\n {\n products {\n id\n reviews {\n product {\n id\n ... on Magazine {\n publisherType {\n ...Publisher\n }\n }\n ... on Book {\n publisherType {\n ...Publisher\n }\n }\n }\n }\n }\n }\n\n fragment Publisher on PublisherType {\n ... on Agency {\n emailObj: email {\n address\n }\n }\n ... on Self {\n emailStr: email\n }\n ... on Group {\n emailStr: email\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "reviews": [ + { + "product": { + "id": "p1", + "publisherType": { + "emailStr": "u1@example.com" + } + } + }, + { + "product": { + "id": "p1", + "publisherType": { + "emailStr": "u1@example.com" + } + } + } + ] + }, + { + "id": "p3", + "reviews": [] + }, + { + "id": "p2", + "reviews": [ + { + "product": { + "id": "p2", + "publisherType": { + "emailObj": { + "address": "a1@example.com" + } + } + } + } + ] + }, + { + "id": "p4", + "reviews": [] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "agency", + "rawSdl": "\n extend schema\n @link(url: \u0022https://specs.apollo.dev/federation/v2.3\u0022, import: [\u0022@key\u0022])\n\n type Agency @key(fields: \u0022id\u0022) {\n id: ID!\n companyName: String\n email: Email\n }\n\n type Email {\n address: String\n }\n\n type Group @key(fields: \u0022id\u0022) {\n id: ID!\n name: String\n email: String\n }\n\n extend union PublisherType = Agency | Group\n ", + "serviceSdl": "extend schema\n @link(url: \u0022https://specs.apollo.dev/link/v1.0\u0022)\n @link(url: \u0022https://specs.apollo.dev/federation/v2.3\u0022, import: [\u0022@key\u0022])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Agency\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n companyName: String\n email: Email\n}\n\ntype Email {\n address: String\n}\n\ntype Group\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n name: String\n email: String\n}\n\nextend union PublisherType = Agency | Group\n\nenum link__Purpose {\n \u0022\u0022\u0022\n \u0060SECURITY\u0060 features provide metadata necessary to securely resolve fields.\n \u0022\u0022\u0022\n SECURITY\n\n \u0022\u0022\u0022\n \u0060EXECUTION\u0060 features provide metadata necessary for operation execution.\n \u0022\u0022\u0022\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Agency | Group\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}", + "settings": { + "name": "agency" + } + }, + { + "name": "inventory", + "rawSdl": "\n extend schema\n @link(\n url: \u0022https://specs.apollo.dev/federation/v2.3\u0022\n import: [\u0022@key\u0022, \u0022@shareable\u0022, \u0022@external\u0022, \u0022@requires\u0022]\n )\n\n interface Product {\n id: ID!\n dimensions: ProductDimension\n delivery(zip: String): DeliveryEstimates\n }\n\n type Book implements Product @key(fields: \u0022id\u0022) {\n id: ID!\n dimensions: ProductDimension @external\n delivery(zip: String): DeliveryEstimates\n @requires(fields: \u0022dimensions { size weight }\u0022)\n }\n\n type Magazine implements Product @key(fields: \u0022id\u0022) {\n id: ID!\n dimensions: ProductDimension @external\n delivery(zip: String): DeliveryEstimates\n @requires(fields: \u0022dimensions { size weight }\u0022)\n }\n\n type ProductDimension @shareable {\n size: String\n weight: Float\n }\n\n type DeliveryEstimates {\n estimatedDelivery: String\n fastestDelivery: String\n }\n ", + "serviceSdl": "extend schema\n @link(url: \u0022https://specs.apollo.dev/link/v1.0\u0022)\n @link(url: \u0022https://specs.apollo.dev/federation/v2.3\u0022, import: [\u0022@key\u0022, \u0022@shareable\u0022, \u0022@external\u0022, \u0022@requires\u0022])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ninterface Product {\n id: ID!\n dimensions: ProductDimension\n delivery(zip: String): DeliveryEstimates\n}\n\ntype Book implements Product\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n dimensions: ProductDimension @external\n delivery(zip: String): DeliveryEstimates @requires(fields: \u0022dimensions { size weight }\u0022)\n}\n\ntype Magazine implements Product\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n dimensions: ProductDimension @external\n delivery(zip: String): DeliveryEstimates @requires(fields: \u0022dimensions { size weight }\u0022)\n}\n\ntype ProductDimension\n @shareable\n{\n size: String\n weight: Float\n}\n\ntype DeliveryEstimates {\n estimatedDelivery: String\n fastestDelivery: String\n}\n\nenum link__Purpose {\n \u0022\u0022\u0022\n \u0060SECURITY\u0060 features provide metadata necessary to securely resolve fields.\n \u0022\u0022\u0022\n SECURITY\n\n \u0022\u0022\u0022\n \u0060EXECUTION\u0060 features provide metadata necessary for operation execution.\n \u0022\u0022\u0022\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book | Magazine\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}", + "settings": { + "name": "inventory" + } + }, + { + "name": "books", + "rawSdl": "\n schema\n @link(\n url: \u0022https://specs.apollo.dev/federation/v2.3\u0022\n import: [\u0022@key\u0022, \u0022@shareable\u0022, \u0022@external\u0022, \u0022@requires\u0022]\n ) {\n query: Query\n }\n\n type Book @key(fields: \u0022id\u0022) {\n id: ID!\n title: String\n }\n\n type Query {\n books: [Book]\n }\n ", + "serviceSdl": "schema\n @link(url: \u0022https://specs.apollo.dev/link/v1.0\u0022)\n @link(url: \u0022https://specs.apollo.dev/federation/v2.3\u0022, import: [\u0022@key\u0022, \u0022@shareable\u0022, \u0022@external\u0022, \u0022@requires\u0022])\n{\n query: Query\n}\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Book\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n title: String\n}\n\ntype Query {\n books: [Book]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \u0022\u0022\u0022\n \u0060SECURITY\u0060 features provide metadata necessary to securely resolve fields.\n \u0022\u0022\u0022\n SECURITY\n\n \u0022\u0022\u0022\n \u0060EXECUTION\u0060 features provide metadata necessary for operation execution.\n \u0022\u0022\u0022\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book", + "settings": { + "name": "books" + } + }, + { + "name": "users", + "rawSdl": "\n type User @key(fields: \u0022email\u0022) {\n email: ID!\n name: String\n totalProductsCreated: Int\n }\n ", + "serviceSdl": "directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @extends on OBJECT | INTERFACE\n\ntype User\n @key(fields: \u0022email\u0022)\n{\n email: ID!\n name: String\n totalProductsCreated: Int\n}\n\nscalar _FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}", + "settings": { + "name": "users", + "extensions": { + "chillicream": { + "apolloFederationSupport": { + "version": "1.0" + } + } + } + } + }, + { + "name": "reviews", + "rawSdl": "\n schema\n @link(\n url: \u0022https://specs.apollo.dev/federation/v2.3\u0022\n import: [\u0022@key\u0022, \u0022@shareable\u0022, \u0022@requires\u0022, \u0022@external\u0022]\n ) {\n query: Query\n }\n\n type Book implements Product \u0026 Similar @key(fields: \u0022id\u0022) {\n id: ID!\n reviewsCount: Int!\n reviewsScore: Float! @shareable\n reviews: [Review!]!\n similar: [Book] @external\n reviewsOfSimilar: [Review!]! @requires(fields: \u0022similar { id }\u0022)\n }\n\n type Magazine implements Product \u0026 Similar @key(fields: \u0022id\u0022) {\n id: ID!\n reviewsCount: Int!\n reviewsScore: Float! @shareable\n reviews: [Review!]!\n similar: [Magazine] @external\n reviewsOfSimilar: [Review!]! @requires(fields: \u0022similar { id }\u0022)\n }\n\n interface Product {\n id: ID!\n reviewsCount: Int!\n reviewsScore: Float!\n reviews: [Review!]!\n }\n\n interface Similar {\n similar: [Product]\n }\n\n type Query {\n review(id: Int!): Review\n }\n\n type Review {\n id: Int!\n body: String!\n product: Product\n }\n ", + "serviceSdl": "schema\n @link(url: \u0022https://specs.apollo.dev/link/v1.0\u0022)\n @link(url: \u0022https://specs.apollo.dev/federation/v2.3\u0022, import: [\u0022@key\u0022, \u0022@shareable\u0022, \u0022@requires\u0022, \u0022@external\u0022])\n{\n query: Query\n}\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Book implements Product \u0026 Similar\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n reviewsCount: Int!\n reviewsScore: Float! @shareable\n reviews: [Review!]!\n similar: [Book] @external\n reviewsOfSimilar: [Review!]! @requires(fields: \u0022similar { id }\u0022)\n}\n\ntype Magazine implements Product \u0026 Similar\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n reviewsCount: Int!\n reviewsScore: Float! @shareable\n reviews: [Review!]!\n similar: [Magazine] @external\n reviewsOfSimilar: [Review!]! @requires(fields: \u0022similar { id }\u0022)\n}\n\ninterface Product {\n id: ID!\n reviewsCount: Int!\n reviewsScore: Float!\n reviews: [Review!]!\n}\n\ninterface Similar {\n similar: [Product]\n}\n\ntype Query {\n review(id: Int!): Review\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Review {\n id: Int!\n body: String!\n product: Product\n}\n\nenum link__Purpose {\n \u0022\u0022\u0022\n \u0060SECURITY\u0060 features provide metadata necessary to securely resolve fields.\n \u0022\u0022\u0022\n SECURITY\n\n \u0022\u0022\u0022\n \u0060EXECUTION\u0060 features provide metadata necessary for operation execution.\n \u0022\u0022\u0022\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book | Magazine", + "settings": { + "name": "reviews" + } + }, + { + "name": "products", + "rawSdl": "\n schema\n @link(\n url: \u0022https://specs.apollo.dev/federation/v2.3\u0022\n import: [\u0022@key\u0022, \u0022@shareable\u0022, \u0022@inaccessible\u0022, \u0022@external\u0022]\n ) {\n query: Query\n }\n\n type Query {\n products: [Product]\n similar(id: ID!): [Product]\n }\n\n interface Product {\n id: ID!\n sku: String\n dimensions: ProductDimension\n createdBy: User\n hidden: Boolean @inaccessible\n }\n\n interface Similar {\n similar: [Product]\n }\n\n type ProductDimension @shareable {\n size: String\n weight: Float\n }\n\n type Book implements Product \u0026 Similar @key(fields: \u0022id\u0022) {\n id: ID!\n sku: String\n dimensions: ProductDimension @shareable\n createdBy: User\n similar: [Book]\n hidden: Boolean\n publisherType: PublisherType\n }\n\n type Magazine implements Product \u0026 Similar @key(fields: \u0022id\u0022) {\n id: ID!\n sku: String\n dimensions: ProductDimension @shareable\n createdBy: User\n similar: [Magazine]\n hidden: Boolean\n publisherType: PublisherType\n }\n\n union PublisherType = Agency | Self\n\n type Agency {\n id: ID! @shareable\n }\n\n type Self {\n email: String\n }\n\n type User @key(fields: \u0022email\u0022) {\n email: ID!\n totalProductsCreated: Int @shareable\n }\n ", + "serviceSdl": "schema\n @link(url: \u0022https://specs.apollo.dev/link/v1.0\u0022)\n @link(url: \u0022https://specs.apollo.dev/federation/v2.3\u0022, import: [\u0022@key\u0022, \u0022@shareable\u0022, \u0022@inaccessible\u0022, \u0022@external\u0022])\n{\n query: Query\n}\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n products: [Product]\n similar(id: ID!): [Product]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ninterface Product {\n id: ID!\n sku: String\n dimensions: ProductDimension\n createdBy: User\n hidden: Boolean @inaccessible\n}\n\ninterface Similar {\n similar: [Product]\n}\n\ntype ProductDimension\n @shareable\n{\n size: String\n weight: Float\n}\n\ntype Book implements Product \u0026 Similar\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n sku: String\n dimensions: ProductDimension @shareable\n createdBy: User\n similar: [Book]\n hidden: Boolean\n publisherType: PublisherType\n}\n\ntype Magazine implements Product \u0026 Similar\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n sku: String\n dimensions: ProductDimension @shareable\n createdBy: User\n similar: [Magazine]\n hidden: Boolean\n publisherType: PublisherType\n}\n\nunion PublisherType = Agency | Self\n\ntype Agency {\n id: ID! @shareable\n}\n\ntype Self {\n email: String\n}\n\ntype User\n @key(fields: \u0022email\u0022)\n{\n email: ID!\n totalProductsCreated: Int @shareable\n}\n\nenum link__Purpose {\n \u0022\u0022\u0022\n \u0060SECURITY\u0060 features provide metadata necessary to securely resolve fields.\n \u0022\u0022\u0022\n SECURITY\n\n \u0022\u0022\u0022\n \u0060EXECUTION\u0060 features provide metadata necessary for operation execution.\n \u0022\u0022\u0022\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book | Magazine | User", + "settings": { + "name": "products" + } + }, + { + "name": "magazines", + "rawSdl": "\n schema\n @link(\n url: \u0022https://specs.apollo.dev/federation/v2.3\u0022\n import: [\u0022@key\u0022, \u0022@shareable\u0022]\n ) {\n query: Query\n }\n\n type Magazine @key(fields: \u0022id\u0022) {\n id: ID!\n title: String\n }\n\n type Query {\n magazines: [Magazine]\n }\n ", + "serviceSdl": "schema\n @link(url: \u0022https://specs.apollo.dev/link/v1.0\u0022)\n @link(url: \u0022https://specs.apollo.dev/federation/v2.3\u0022, import: [\u0022@key\u0022, \u0022@shareable\u0022])\n{\n query: Query\n}\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Magazine\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n title: String\n}\n\ntype Query {\n magazines: [Magazine]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \u0022\u0022\u0022\n \u0060SECURITY\u0060 features provide metadata necessary to securely resolve fields.\n \u0022\u0022\u0022\n SECURITY\n\n \u0022\u0022\u0022\n \u0060EXECUTION\u0060 features provide metadata necessary for operation execution.\n \u0022\u0022\u0022\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Magazine", + "settings": { + "name": "magazines" + } + } + ], + "fixtureModules": [ + { + "path": "agency.subgraph.ts", + "sha256": "26aa0f8b11b8b98304b2b809556a2ae7d37b531735761aa1e2f04539ae836bb0", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { agencies } from \u0022./data.js\u0022;\n\nexport default createSubgraph(\u0022agency\u0022, {\n typeDefs: /* GraphQL */ \u0060\n extend schema\n @link(url: \u0022https://specs.apollo.dev/federation/v2.3\u0022, import: [\u0022@key\u0022])\n\n type Agency @key(fields: \u0022id\u0022) {\n id: ID!\n companyName: String\n email: Email\n }\n\n type Email {\n address: String\n }\n\n type Group @key(fields: \u0022id\u0022) {\n id: ID!\n name: String\n email: String\n }\n\n extend union PublisherType = Agency | Group\n \u0060,\n resolvers: {\n Agency: {\n __resolveReference(key: { id: string }) {\n return agencies.find((a) =\u003E a.id === key.id);\n },\n },\n Group: {\n __resolveReference(key: { id: string }) {\n return {\n id: key.id,\n name: \u0022Group \u0022 \u002B key.id,\n email: \u0022group\u0022 \u002B key.id \u002B \u0022@example.com\u0022,\n };\n },\n },\n },\n});\n" + }, + { + "path": "books.subgraph.ts", + "sha256": "a614767b5c95eb190028d3856f64274dd174fa989fa6ee7fca934ba272e84fa7", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { books } from \u0022./data.js\u0022;\n\nexport default createSubgraph(\u0022books\u0022, {\n typeDefs: /* GraphQL */ \u0060\n schema\n @link(\n url: \u0022https://specs.apollo.dev/federation/v2.3\u0022\n import: [\u0022@key\u0022, \u0022@shareable\u0022, \u0022@external\u0022, \u0022@requires\u0022]\n ) {\n query: Query\n }\n\n type Book @key(fields: \u0022id\u0022) {\n id: ID!\n title: String\n }\n\n type Query {\n books: [Book]\n }\n \u0060,\n resolvers: {\n Query: {\n books() {\n return books.map((book) =\u003E ({\n __typename: \u0022Book\u0022,\n id: book.id,\n title: book.title,\n }));\n },\n },\n Book: {\n __resolveReference(key: { id: string }) {\n const book = books.find((book) =\u003E book.id === key.id);\n\n return book\n ? {\n __typename: \u0022Book\u0022,\n id: book.id,\n title: book.title,\n }\n : null;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "767df11ba52a148036cd5f4708240ee50671d74de2a99111558340fe7cc6fe07", + "source": "export const users = [\n {\n id: \u0022u1\u0022,\n email: \u0022u1@example.com\u0022,\n name: \u0022u1-name\u0022,\n },\n {\n id: \u0022u2\u0022,\n email: \u0022u2@example.com\u0022,\n name: \u0022u2-name\u0022,\n },\n {\n id: \u0022u3\u0022,\n email: \u0022u3@example.com\u0022,\n name: \u0022u3-name\u0022,\n },\n] as const;\n\nexport const books = [\n {\n __typename: \u0022Book\u0022,\n id: \u0022p1\u0022,\n sku: \u0022sku-1\u0022,\n title: \u0022Book 1\u0022,\n createdBy: \u0022u1\u0022,\n dimensions: {\n size: \u0022small\u0022,\n weight: 0.5,\n },\n publisher: {\n __typename: \u0022Self\u0022,\n email: users[0].email,\n },\n hidden: false,\n },\n {\n __typename: \u0022Book\u0022,\n id: \u0022p3\u0022,\n sku: \u0022sku-3\u0022,\n title: \u0022Book 2\u0022,\n createdBy: \u0022u2\u0022,\n dimensions: {\n size: \u0022small\u0022,\n weight: 0.6,\n },\n publisher: {\n __typename: \u0022Agency\u0022,\n id: \u0022a1\u0022,\n },\n hidden: false,\n },\n];\n\nexport const magazines = [\n {\n __typename: \u0022Magazine\u0022,\n id: \u0022p2\u0022,\n sku: \u0022sku-2\u0022,\n title: \u0022Magazine 1\u0022,\n createdBy: \u0022u1\u0022,\n dimensions: {\n size: \u0022small\u0022,\n weight: 0.2,\n },\n publisher: {\n __typename: \u0022Agency\u0022,\n id: \u0022a1\u0022,\n },\n hidden: false,\n },\n {\n __typename: \u0022Magazine\u0022,\n id: \u0022p4\u0022,\n sku: \u0022sku-4\u0022,\n title: \u0022Magazine 2\u0022,\n createdBy: \u0022u2\u0022,\n dimensions: {\n size: \u0022small\u0022,\n weight: 0.3,\n },\n publisher: {\n __typename: \u0022Self\u0022,\n email: users[0].email,\n },\n hidden: true,\n },\n];\n\nexport const products = [...books, ...magazines] as const;\n\nexport const reviews = [\n {\n id: 1,\n body: \u0022review 1\u0022,\n productId: \u0022p1\u0022,\n score: 3,\n },\n {\n id: 2,\n body: \u0022review 2\u0022,\n productId: \u0022p1\u0022,\n score: 4,\n },\n {\n id: 3,\n body: \u0022review 3\u0022,\n productId: \u0022p2\u0022,\n score: 5,\n },\n] as const;\n\nexport const agencies = [\n {\n id: \u0022a1\u0022,\n companyName: \u0022Agency 1\u0022,\n email: {\n address: \u0022a1@example.com\u0022,\n },\n },\n] as const;\n" + }, + { + "path": "inventory.subgraph.ts", + "sha256": "1e4d00f6d568f05771ec143ef90bd4fcd113ddedd852ca05e8ad397a6d383db7", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { books, magazines } from \u0022./data.js\u0022;\n\nexport default createSubgraph(\u0022inventory\u0022, {\n typeDefs: /* GraphQL */ \u0060\n extend schema\n @link(\n url: \u0022https://specs.apollo.dev/federation/v2.3\u0022\n import: [\u0022@key\u0022, \u0022@shareable\u0022, \u0022@external\u0022, \u0022@requires\u0022]\n )\n\n interface Product {\n id: ID!\n dimensions: ProductDimension\n delivery(zip: String): DeliveryEstimates\n }\n\n type Book implements Product @key(fields: \u0022id\u0022) {\n id: ID!\n dimensions: ProductDimension @external\n delivery(zip: String): DeliveryEstimates\n @requires(fields: \u0022dimensions { size weight }\u0022)\n }\n\n type Magazine implements Product @key(fields: \u0022id\u0022) {\n id: ID!\n dimensions: ProductDimension @external\n delivery(zip: String): DeliveryEstimates\n @requires(fields: \u0022dimensions { size weight }\u0022)\n }\n\n type ProductDimension @shareable {\n size: String\n weight: Float\n }\n\n type DeliveryEstimates {\n estimatedDelivery: String\n fastestDelivery: String\n }\n \u0060,\n resolvers: {\n Book: {\n __resolveReference(key: {\n id: string;\n dimensions?: {\n size: string;\n weight: number;\n };\n }) {\n const book = books.find((book) =\u003E book.id === key.id);\n\n if (!book) {\n return null;\n }\n\n return {\n __typename: book.__typename,\n id: book.id,\n dimensions: key.dimensions,\n };\n },\n delivery(\n { dimensions }: { dimensions?: { size: string; weight: number } },\n { zip }: { zip?: string },\n ) {\n if (!zip) {\n return null;\n }\n\n if (!dimensions) {\n return null;\n }\n\n if (dimensions.size === \u0022small\u0022 \u0026\u0026 dimensions.weight \u003C 1) {\n return {\n estimatedDelivery: \u00221 day\u0022,\n fastestDelivery: \u0022same day\u0022,\n };\n }\n\n if (dimensions.size === \u0022large\u0022 \u0026\u0026 dimensions.weight \u003E= 1) {\n return {\n estimatedDelivery: \u00223 days\u0022,\n fastestDelivery: \u00222 days\u0022,\n };\n }\n\n return null;\n },\n },\n Magazine: {\n __resolveReference(key: {\n id: string;\n dimensions?: {\n size: string;\n weight: number;\n };\n }) {\n const magazine = magazines.find((magazine) =\u003E magazine.id === key.id);\n\n if (!magazine) {\n return null;\n }\n\n return {\n __typename: magazine.__typename,\n id: magazine.id,\n dimensions: key.dimensions,\n };\n },\n delivery(\n { dimensions }: { dimensions?: { size: string; weight: number } },\n { zip }: { zip?: string },\n ) {\n if (!zip) {\n return null;\n }\n\n if (!dimensions) {\n return null;\n }\n\n if (dimensions.size === \u0022small\u0022 \u0026\u0026 dimensions.weight \u003C 1) {\n return {\n estimatedDelivery: \u00221 day\u0022,\n fastestDelivery: \u0022same day\u0022,\n };\n }\n\n if (dimensions.size === \u0022large\u0022 \u0026\u0026 dimensions.weight \u003E= 1) {\n return {\n estimatedDelivery: \u00223 days\u0022,\n fastestDelivery: \u00222 days\u0022,\n };\n }\n\n return null;\n },\n },\n },\n});\n" + }, + { + "path": "magazines.subgraph.ts", + "sha256": "f80cce11b650cb53dbf9f6120553dd341111e4bd9a5e9d521aeab0ac958b4712", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { magazines } from \u0022./data.js\u0022;\n\nexport default createSubgraph(\u0022magazines\u0022, {\n typeDefs: /* GraphQL */ \u0060\n schema\n @link(\n url: \u0022https://specs.apollo.dev/federation/v2.3\u0022\n import: [\u0022@key\u0022, \u0022@shareable\u0022]\n ) {\n query: Query\n }\n\n type Magazine @key(fields: \u0022id\u0022) {\n id: ID!\n title: String\n }\n\n type Query {\n magazines: [Magazine]\n }\n \u0060,\n resolvers: {\n Query: {\n magazines() {\n return magazines.map((magazine) =\u003E ({\n __typename: magazine.__typename,\n id: magazine.id,\n title: magazine.title,\n }));\n },\n },\n Magazine: {\n __resolveReference(key: { id: string }) {\n const magazine = magazines.find((magazine) =\u003E magazine.id === key.id);\n\n return magazine\n ? {\n __typename: magazine.__typename,\n id: magazine.id,\n title: magazine.title,\n }\n : null;\n },\n },\n },\n});\n" + }, + { + "path": "products.subgraph.ts", + "sha256": "1b822adb3b29dd3be23376c34a1104f4dff9e97be266949ae151a9d4ad88c248", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { books, magazines, products, users } from \u0022./data.js\u0022;\n\nexport default createSubgraph(\u0022products\u0022, {\n typeDefs: /* GraphQL */ \u0060\n schema\n @link(\n url: \u0022https://specs.apollo.dev/federation/v2.3\u0022\n import: [\u0022@key\u0022, \u0022@shareable\u0022, \u0022@inaccessible\u0022, \u0022@external\u0022]\n ) {\n query: Query\n }\n\n type Query {\n products: [Product]\n similar(id: ID!): [Product]\n }\n\n interface Product {\n id: ID!\n sku: String\n dimensions: ProductDimension\n createdBy: User\n hidden: Boolean @inaccessible\n }\n\n interface Similar {\n similar: [Product]\n }\n\n type ProductDimension @shareable {\n size: String\n weight: Float\n }\n\n type Book implements Product \u0026 Similar @key(fields: \u0022id\u0022) {\n id: ID!\n sku: String\n dimensions: ProductDimension @shareable\n createdBy: User\n similar: [Book]\n hidden: Boolean\n publisherType: PublisherType\n }\n\n type Magazine implements Product \u0026 Similar @key(fields: \u0022id\u0022) {\n id: ID!\n sku: String\n dimensions: ProductDimension @shareable\n createdBy: User\n similar: [Magazine]\n hidden: Boolean\n publisherType: PublisherType\n }\n\n union PublisherType = Agency | Self\n\n type Agency {\n id: ID! @shareable\n }\n\n type Self {\n email: String\n }\n\n type User @key(fields: \u0022email\u0022) {\n email: ID!\n totalProductsCreated: Int @shareable\n }\n \u0060,\n resolvers: {\n Query: {\n products() {\n return products.map((p) =\u003E ({\n __typename: p.__typename,\n id: p.id,\n sku: p.sku,\n dimensions: p.dimensions,\n createdBy: p.createdBy,\n hidden: p.hidden,\n publisherType: p.publisher,\n }));\n },\n similar(_: {}, { id }: { id: string }) {\n const product = products.find((product) =\u003E product.id === id);\n\n if (!product) {\n return [];\n }\n\n const similar = products.filter(\n (p) =\u003E p.id !== product.id \u0026\u0026 p.__typename === product.__typename,\n );\n\n return similar.map((b) =\u003E ({\n __typename: b.__typename,\n id: b.id,\n sku: b.sku,\n dimensions: b.dimensions,\n createdBy: b.createdBy,\n hidden: b.hidden,\n publisherType: b.publisher,\n }));\n },\n },\n Book: {\n __resolveReference(key: { id: string }) {\n const book = books.find((book) =\u003E book.id === key.id);\n\n return book\n ? {\n __typename: book.__typename,\n id: book.id,\n sku: book.sku,\n dimensions: book.dimensions,\n createdBy: book.createdBy,\n hidden: book.hidden,\n publisherType: book.publisher,\n }\n : null;\n },\n similar(book: { id: string }) {\n return books\n .filter((b) =\u003E b.id !== book.id)\n .map((b) =\u003E ({\n __typename: b.__typename,\n id: b.id,\n sku: b.sku,\n dimensions: b.dimensions,\n createdBy: b.createdBy,\n hidden: b.hidden,\n publisherType: b.publisher,\n }));\n },\n createdBy(book: { createdBy: string }) {\n const user = users.find((user) =\u003E user.id === book.createdBy);\n return user\n ? {\n __typename: \u0022User\u0022,\n id: user.id,\n email: user.email,\n }\n : null;\n },\n },\n Magazine: {\n __resolveReference(key: { id: string }) {\n const magazine = magazines.find((magazine) =\u003E magazine.id === key.id);\n\n return magazine\n ? {\n __typename: magazine.__typename,\n id: magazine.id,\n sku: magazine.sku,\n dimensions: magazine.dimensions,\n createdBy: magazine.createdBy,\n hidden: magazine.hidden,\n publisherType: magazine.publisher,\n }\n : null;\n },\n similar(magazine: { id: string }) {\n return magazines\n .filter((m) =\u003E m.id !== magazine.id)\n .map((m) =\u003E ({\n __typename: m.__typename,\n id: m.id,\n sku: m.sku,\n dimensions: m.dimensions,\n createdBy: m.createdBy,\n hidden: m.hidden,\n publisherType: m.publisher,\n }));\n },\n createdBy(magazine: { createdBy: string }) {\n const user = users.find((user) =\u003E user.id === magazine.createdBy);\n return user\n ? {\n __typename: \u0022User\u0022,\n id: user.id,\n email: user.email,\n }\n : null;\n },\n },\n User: {\n __resolveReference(key: { email: string }) {\n const user = users.find((user) =\u003E user.email === key.email);\n\n return user\n ? {\n __typename: \u0022User\u0022,\n id: user.id,\n email: user.email,\n }\n : null;\n },\n totalProductsCreated(user: { id: string }) {\n return products.filter((product) =\u003E product.createdBy === user.id)\n .length;\n },\n },\n },\n});\n" + }, + { + "path": "reviews.subgraph.ts", + "sha256": "4a8e87362fd278d6d71ab69cff0a487abe227c660ae479dcd84c4d2f877a4895", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { products, reviews } from \u0022./data.js\u0022;\n\nconst Product = {\n __resolveReference(key: { id: string; similar?: { id: string[] } }) {\n const product = products.find((product) =\u003E product.id === key.id);\n\n return product\n ? {\n __typename: product.__typename,\n id: product.id,\n similar: key.similar,\n }\n : null;\n },\n reviews(product: { id: string }) {\n return reviews\n .filter((review) =\u003E review.productId === product.id)\n .map((review) =\u003E {\n const product = products.find(\n (product) =\u003E product.id === review.productId,\n );\n\n return {\n __typename: \u0022Review\u0022,\n id: review.id,\n body: review.body,\n product: product\n ? {\n __typename: product.__typename,\n id: product.id,\n }\n : null,\n };\n });\n },\n reviewsCount(product: { id: string }) {\n return reviews.filter((review) =\u003E review.productId === product.id).length;\n },\n reviewsScore(product: { id: string }) {\n const productReviews = reviews.filter(\n (review) =\u003E review.productId === product.id,\n );\n\n if (productReviews.length === 0) {\n return 0;\n }\n\n const sum = productReviews.reduce((acc, review) =\u003E acc \u002B review.score, 0);\n return sum / productReviews.length;\n },\n reviewsOfSimilar(product: { __typename: string; similar: { id: string[] } }) {\n const similarProducts = products.filter(\n (p) =\u003E\n p.__typename === product.__typename \u0026\u0026\n product.similar.id.includes(p.id),\n );\n\n return similarProducts\n .flatMap((p) =\u003E reviews.filter((review) =\u003E review.productId === p.id))\n .map((review) =\u003E {\n const product = products.find(\n (product) =\u003E product.id === review.productId,\n );\n\n return {\n __typename: \u0022Review\u0022,\n id: review.id,\n body: review.body,\n product: product\n ? {\n __typename: product.__typename,\n id: product.id,\n }\n : null,\n };\n });\n },\n};\n\nexport default createSubgraph(\u0022reviews\u0022, {\n typeDefs: /* GraphQL */ \u0060\n schema\n @link(\n url: \u0022https://specs.apollo.dev/federation/v2.3\u0022\n import: [\u0022@key\u0022, \u0022@shareable\u0022, \u0022@requires\u0022, \u0022@external\u0022]\n ) {\n query: Query\n }\n\n type Book implements Product \u0026 Similar @key(fields: \u0022id\u0022) {\n id: ID!\n reviewsCount: Int!\n reviewsScore: Float! @shareable\n reviews: [Review!]!\n similar: [Book] @external\n reviewsOfSimilar: [Review!]! @requires(fields: \u0022similar { id }\u0022)\n }\n\n type Magazine implements Product \u0026 Similar @key(fields: \u0022id\u0022) {\n id: ID!\n reviewsCount: Int!\n reviewsScore: Float! @shareable\n reviews: [Review!]!\n similar: [Magazine] @external\n reviewsOfSimilar: [Review!]! @requires(fields: \u0022similar { id }\u0022)\n }\n\n interface Product {\n id: ID!\n reviewsCount: Int!\n reviewsScore: Float!\n reviews: [Review!]!\n }\n\n interface Similar {\n similar: [Product]\n }\n\n type Query {\n review(id: Int!): Review\n }\n\n type Review {\n id: Int!\n body: String!\n product: Product\n }\n \u0060,\n resolvers: {\n Query: {\n review(_: {}, { id }: { id: number }) {\n const review = reviews.find((review) =\u003E review.id === id);\n\n if (!review) {\n return null;\n }\n\n const product = products.find(\n (product) =\u003E product.id === review.productId,\n );\n\n return {\n __typename: \u0022Review\u0022,\n id: review.id,\n body: review.body,\n product: product\n ? {\n __typename: product.__typename,\n id: product.id,\n }\n : null,\n };\n },\n },\n Book: Product,\n Magazine: Product,\n },\n});\n" + }, + { + "path": "users.subgraph.ts", + "sha256": "a9ee3afe1e6750a51e54bc34701fff51143273f335958c7bd4f99ee543e4b4a3", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { products, users } from \u0022./data.js\u0022;\n\nexport default createSubgraph(\u0022users\u0022, {\n typeDefs: /* GraphQL */ \u0060\n type User @key(fields: \u0022email\u0022) {\n email: ID!\n name: String\n totalProductsCreated: Int\n }\n \u0060,\n resolvers: {\n User: {\n __resolveReference(key: { email: string }) {\n const user = users.find((user) =\u003E user.email === key.email);\n\n return user\n ? {\n __typename: \u0022User\u0022,\n id: user.id,\n email: user.email,\n name: user.name,\n }\n : null;\n },\n totalProductsCreated(user: { id: string }) {\n return products.filter((p) =\u003E p.createdBy === user.id).length;\n },\n },\n },\n});\n" + } + ] + }, + { + "id": "fed1-external-extends", + "v1Sources": [ + "a", + "b" + ], + "cases": [ + { + "id": "fed1-external-extends/000", + "query": "\n query {\n randomUser {\n id\n name\n }\n userById(id: \u0022u2\u0022) {\n id\n name\n nickname\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "randomUser": { + "id": "u1", + "name": "u1-name" + }, + "userById": { + "id": "u2", + "name": "u2-name", + "nickname": "u2-nickname" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "fed1-external-extends/001", + "query": "\n query {\n randomUser {\n id\n rid\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "randomUser": { + "id": "u1", + "rid": "u1-rid" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "fed1-external-extends/002", + "query": "\n query {\n randomUser {\n id\n rid\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "randomUser": { + "id": "u1", + "rid": "u1-rid", + "name": "u1-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "fed1-external-extends/003", + "query": "\n query {\n providedRandomUser {\n id\n rid\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "providedRandomUser": { + "id": "u1", + "rid": "u1-rid", + "name": "u1-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n type Query {\n randomUser: User\n providedRandomUser: User @provides(fields: \u0022name\u0022)\n }\n\n type User @key(fields: \u0022id\u0022) @extends {\n id: ID! @external\n name: String! @external\n rid: ID\n }\n ", + "serviceSdl": "directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @extends on OBJECT | INTERFACE\n\ntype Query {\n randomUser: User\n providedRandomUser: User @provides(fields: \u0022name\u0022)\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype User\n @key(fields: \u0022id\u0022)\n @extends\n{\n id: ID! @external\n name: String! @external\n rid: ID\n}\n\nscalar _FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User", + "settings": { + "name": "a", + "extensions": { + "chillicream": { + "apolloFederationSupport": { + "version": "1.0" + } + } + } + } + }, + { + "name": "b", + "rawSdl": "\n type Query {\n userById(id: ID): User\n }\n\n type User @key(fields: \u0022id\u0022) {\n id: ID!\n name: String!\n nickname: String\n }\n ", + "serviceSdl": "directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @extends on OBJECT | INTERFACE\n\ntype Query {\n userById(id: ID): User\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype User\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n name: String!\n nickname: String\n}\n\nscalar _FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User", + "settings": { + "name": "b", + "extensions": { + "chillicream": { + "apolloFederationSupport": { + "version": "1.0" + } + } + } + } + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "fd166dc2e0cef57e28d3bcb133d9b0b107bba78a1623b03961f9d7c76b955cd1", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { users } from \u0022./data.js\u0022;\n\nexport default createSubgraph(\u0022a\u0022, {\n typeDefs: /* GraphQL */ \u0060\n type Query {\n randomUser: User\n providedRandomUser: User @provides(fields: \u0022name\u0022)\n }\n\n type User @key(fields: \u0022id\u0022) @extends {\n id: ID! @external\n name: String! @external\n rid: ID\n }\n \u0060,\n resolvers: {\n Query: {\n randomUser() {\n return {\n id: users[0].id,\n rid: users[0].rid,\n };\n },\n providedRandomUser() {\n return {\n id: users[0].id,\n rid: users[0].rid,\n name: users[0].name,\n };\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) =\u003E u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n rid: user.rid,\n };\n },\n name(user: { name: string | undefined }) {\n return user.name ?? \u0022never\u0022;\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "f47a1d33c8a21a5e0d23fce11b73a8d6e86b36b6a29cb23304644046467f09cf", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { users } from \u0022./data.js\u0022;\n\nexport default createSubgraph(\u0022b\u0022, {\n typeDefs: /* GraphQL */ \u0060\n type Query {\n userById(id: ID): User\n }\n\n type User @key(fields: \u0022id\u0022) {\n id: ID!\n name: String!\n nickname: String\n }\n \u0060,\n resolvers: {\n Query: {\n userById: (_: {}, { id }: { id: string }) =\u003E {\n const user = users.find((u) =\u003E u.id === id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n name: user.name,\n nickname: user.nickname,\n };\n },\n },\n User: {\n __resolveReference: (key: { id: string }) =\u003E {\n const user = users.find((u) =\u003E u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n name: user.name,\n nickname: user.nickname,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "6d37b20b771a31f2219f84d9d6172394d6b52dac271d6dd6dbc9d86359f3445c", + "source": "export const users = [\n {\n id: \u0022u1\u0022,\n rid: \u0022u1-rid\u0022,\n name: \u0022u1-name\u0022,\n nickname: \u0022u1-nickname\u0022,\n },\n {\n id: \u0022u2\u0022,\n rid: \u0022u2-rid\u0022,\n name: \u0022u2-name\u0022,\n nickname: \u0022u2-nickname\u0022,\n },\n];\n" + } + ] + }, + { + "id": "fed1-external-extends-resolvable", + "v1Sources": [ + "a", + "b" + ], + "cases": [ + { + "id": "fed1-external-extends-resolvable/000", + "query": "\n query {\n productInA {\n id\n pid\n price\n upc\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "productInA": { + "id": "p1", + "pid": "p1-pid", + "price": 12.3, + "upc": "upc1", + "name": "p1-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n type Query {\n productInA: Product\n }\n\n type Product @key(fields: \u0022id\u0022) {\n id: ID!\n name: String\n pid: ID\n }\n ", + "serviceSdl": "directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @extends on OBJECT | INTERFACE\n\ntype Query {\n productInA: Product\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Product\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n name: String\n pid: ID\n}\n\nscalar _FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product", + "settings": { + "name": "a", + "extensions": { + "chillicream": { + "apolloFederationSupport": { + "version": "1.0" + } + } + } + } + }, + { + "name": "b", + "rawSdl": "\n type Query {\n productInB: Product\n }\n\n extend type Product @key(fields: \u0022id name\u0022) @key(fields: \u0022upc\u0022) {\n id: ID @external\n name: String @external\n upc: String @external\n price: Float!\n }\n ", + "serviceSdl": "directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @extends on OBJECT | INTERFACE\n\ntype Query {\n productInB: Product\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nextend type Product\n @key(fields: \u0022id name\u0022)\n @key(fields: \u0022upc\u0022)\n{\n id: ID @external\n name: String @external\n upc: String @external\n price: Float!\n}\n\nscalar _FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product", + "settings": { + "name": "b", + "extensions": { + "chillicream": { + "apolloFederationSupport": { + "version": "1.0" + } + } + } + } + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "d56e2010191118aac8fe2c1e1d597094af0965352cdd0d8b6510354bdaa82d4d", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { products } from \u0022./data.js\u0022;\n\nexport default createSubgraph(\u0022a\u0022, {\n typeDefs: /* GraphQL */ \u0060\n type Query {\n productInA: Product\n }\n\n type Product @key(fields: \u0022id\u0022) {\n id: ID!\n name: String\n pid: ID\n }\n \u0060,\n resolvers: {\n Query: {\n productInA() {\n return {\n id: products[0].id,\n name: products[0].name,\n pid: products[0].pid,\n };\n },\n },\n Product: {\n __resolveReference(key: { id: string }) {\n if (products[0].id !== key.id) {\n return null;\n }\n\n return {\n id: products[0].id,\n name: products[0].name,\n pid: products[0].pid,\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "33b6f5ceefb058a0cf311dd7df453400614bc0c08917f9d8ac18eaf071b451ae", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { products } from \u0022./data.js\u0022;\n\nexport default createSubgraph(\u0022b\u0022, {\n typeDefs: /* GraphQL */ \u0060\n type Query {\n productInB: Product\n }\n\n extend type Product @key(fields: \u0022id name\u0022) @key(fields: \u0022upc\u0022) {\n id: ID @external\n name: String @external\n upc: String @external\n price: Float!\n }\n \u0060,\n resolvers: {\n Query: {\n productInB: () =\u003E {\n return {\n id: products[0].id,\n name: products[0].name,\n upc: products[0].upc,\n price: products[0].price,\n };\n },\n },\n Product: {\n __resolveReference: (\n key:\n | { id: string; name: string }\n | {\n upc: string;\n },\n ) =\u003E {\n const product =\n \u0022id\u0022 in key\n ? products.find((p) =\u003E p.id === key.id \u0026\u0026 p.name === key.name)\n : products.find((p) =\u003E p.upc === key.upc);\n\n return product\n ? {\n id: product.id,\n name: product.name,\n upc: product.upc,\n price: product.price,\n }\n : null;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "b5e6d13f6753d6185a59b77dca2e4136a01f963ed310e9adec7cf47a32a93310", + "source": "export const products = [\n {\n id: \u0022p1\u0022,\n pid: \u0022p1-pid\u0022,\n price: 12.3,\n upc: \u0022upc1\u0022,\n name: \u0022p1-name\u0022,\n },\n];\n" + } + ] + }, + { + "id": "fed1-external-extension", + "v1Sources": [ + "a", + "b" + ], + "cases": [ + { + "id": "fed1-external-extension/000", + "query": "\n query {\n randomUser {\n id\n name\n }\n userById(id: \u0022u2\u0022) {\n id\n name\n nickname\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "randomUser": { + "id": "u1", + "name": "u1-name" + }, + "userById": { + "id": "u2", + "name": "u2-name", + "nickname": "u2-nickname" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "fed1-external-extension/001", + "query": "\n query {\n randomUser {\n id\n rid\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "randomUser": { + "id": "u1", + "rid": "u1-rid" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "fed1-external-extension/002", + "query": "\n query {\n randomUser {\n id\n rid\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "randomUser": { + "id": "u1", + "rid": "u1-rid", + "name": "u1-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "fed1-external-extension/003", + "query": "\n query {\n providedRandomUser {\n id\n rid\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "providedRandomUser": { + "id": "u1", + "rid": "u1-rid", + "name": "u1-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n type Query {\n randomUser: User\n providedRandomUser: User @provides(fields: \u0022name\u0022)\n }\n\n extend type User @key(fields: \u0022id\u0022) {\n id: ID! @external\n name: String! @external\n rid: ID\n }\n ", + "serviceSdl": "directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @extends on OBJECT | INTERFACE\n\ntype Query {\n randomUser: User\n providedRandomUser: User @provides(fields: \u0022name\u0022)\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nextend type User\n @key(fields: \u0022id\u0022)\n{\n id: ID! @external\n name: String! @external\n rid: ID\n}\n\nscalar _FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User", + "settings": { + "name": "a", + "extensions": { + "chillicream": { + "apolloFederationSupport": { + "version": "1.0" + } + } + } + } + }, + { + "name": "b", + "rawSdl": "\n type Query {\n userById(id: ID): User\n }\n\n type User @key(fields: \u0022id\u0022) {\n id: ID!\n name: String!\n nickname: String\n }\n ", + "serviceSdl": "directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: _FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @extends on OBJECT | INTERFACE\n\ntype Query {\n userById(id: ID): User\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype User\n @key(fields: \u0022id\u0022)\n{\n id: ID!\n name: String!\n nickname: String\n}\n\nscalar _FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User", + "settings": { + "name": "b", + "extensions": { + "chillicream": { + "apolloFederationSupport": { + "version": "1.0" + } + } + } + } + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "045aca224bca37a7e08b81edbf88160bd6be7240a168dbf03f902316f824fb5a", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { users } from \u0022./data.js\u0022;\n\nexport default createSubgraph(\u0022a\u0022, {\n typeDefs: /* GraphQL */ \u0060\n type Query {\n randomUser: User\n providedRandomUser: User @provides(fields: \u0022name\u0022)\n }\n\n extend type User @key(fields: \u0022id\u0022) {\n id: ID! @external\n name: String! @external\n rid: ID\n }\n \u0060,\n resolvers: {\n Query: {\n randomUser() {\n return {\n id: users[0].id,\n rid: users[0].rid,\n };\n },\n providedRandomUser() {\n return {\n id: users[0].id,\n rid: users[0].rid,\n name: users[0].name,\n };\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) =\u003E u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n rid: user.rid,\n };\n },\n name(user: { name: string | undefined }) {\n return user.name ?? \u0022never\u0022;\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "f47a1d33c8a21a5e0d23fce11b73a8d6e86b36b6a29cb23304644046467f09cf", + "source": "import { createSubgraph } from \u0022../../subgraph.js\u0022;\nimport { users } from \u0022./data.js\u0022;\n\nexport default createSubgraph(\u0022b\u0022, {\n typeDefs: /* GraphQL */ \u0060\n type Query {\n userById(id: ID): User\n }\n\n type User @key(fields: \u0022id\u0022) {\n id: ID!\n name: String!\n nickname: String\n }\n \u0060,\n resolvers: {\n Query: {\n userById: (_: {}, { id }: { id: string }) =\u003E {\n const user = users.find((u) =\u003E u.id === id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n name: user.name,\n nickname: user.nickname,\n };\n },\n },\n User: {\n __resolveReference: (key: { id: string }) =\u003E {\n const user = users.find((u) =\u003E u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n name: user.name,\n nickname: user.nickname,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "6d37b20b771a31f2219f84d9d6172394d6b52dac271d6dd6dbc9d86359f3445c", + "source": "export const users = [\n {\n id: \u0022u1\u0022,\n rid: \u0022u1-rid\u0022,\n name: \u0022u1-name\u0022,\n nickname: \u0022u1-nickname\u0022,\n },\n {\n id: \u0022u2\u0022,\n rid: \u0022u2-rid\u0022,\n name: \u0022u2-name\u0022,\n nickname: \u0022u2-nickname\u0022,\n },\n];\n" + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/v2-manifest.json b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/v2-manifest.json new file mode 100644 index 00000000000..c192d45a5c2 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/OfficialAudit/v2-manifest.json @@ -0,0 +1,5128 @@ +{ + "source": "graphql-hive/federation-gateway-audit", + "revision": "f59c05e3f48f4a4f8e8a731d67a1b71a9788a96f", + "federationVersion": 2, + "selection": "Suites whose sources do not rely on linkless Federation directives.", + "excludedV1DependentSuites": [ + { + "id": "abstract-types", + "caseCount": 18, + "v1Sources": [ + "users" + ] + }, + { + "id": "fed1-external-extends", + "caseCount": 4, + "v1Sources": [ + "a", + "b" + ] + }, + { + "id": "fed1-external-extends-resolvable", + "caseCount": 1, + "v1Sources": [ + "a", + "b" + ] + }, + { + "id": "fed1-external-extension", + "caseCount": 4, + "v1Sources": [ + "a", + "b" + ] + } + ], + "suiteCount": 42, + "caseCount": 172, + "suites": [ + { + "id": "child-type-mismatch", + "cases": [ + { + "id": "child-type-mismatch/000", + "query": "\n {\n users {\n id\n name\n }\n accounts {\n ... on User {\n id\n name\n }\n ... on Admin {\n id\n name\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "id": "u1", + "name": "u1-name" + } + ], + "accounts": [ + { + "id": "u1", + "name": "u1-name" + }, + { + "id": "a1", + "name": "a1-name" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "child-type-mismatch/001", + "query": "\n query NestedInternalAlias {\n users {\n id\n name\n }\n accounts {\n ... on User {\n id\n name\n similarAccounts {\n ... on User {\n id\n name\n }\n ... on Admin {\n id\n name\n }\n }\n }\n ... on Admin {\n id\n name\n similarAccounts {\n ... on User {\n id\n name\n }\n ... on Admin {\n id\n name\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "id": "u1", + "name": "u1-name" + } + ], + "accounts": [ + { + "id": "u1", + "name": "u1-name", + "similarAccounts": [ + { + "id": "u1", + "name": "u1-name" + }, + { + "id": "a1", + "name": "a1-name" + } + ] + }, + { + "id": "a1", + "name": "a1-name", + "similarAccounts": [ + { + "id": "u1", + "name": "u1-name" + }, + { + "id": "a1", + "name": "a1-name" + } + ] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "child-type-mismatch/002", + "query": "\n query DeeplyNestedInternalAlias {\n users {\n id\n name\n }\n accounts {\n ... on User {\n id\n name\n similarAccounts {\n ... on User {\n id\n name\n similarAccounts {\n ... on User {\n id\n name\n }\n ... on Admin {\n id\n name\n }\n }\n }\n ... on Admin {\n id\n name\n similarAccounts {\n ... on User {\n id\n name\n }\n ... on Admin {\n id\n name\n }\n }\n }\n }\n }\n ... on Admin {\n id\n name\n similarAccounts {\n ... on User {\n id\n name\n similarAccounts {\n ... on User {\n id\n name\n }\n ... on Admin {\n id\n name\n }\n }\n }\n ... on Admin {\n id\n name\n similarAccounts {\n ... on User {\n id\n name\n }\n ... on Admin {\n id\n name\n }\n }\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "id": "u1", + "name": "u1-name" + } + ], + "accounts": [ + { + "id": "u1", + "name": "u1-name", + "similarAccounts": [ + { + "id": "u1", + "name": "u1-name", + "similarAccounts": [ + { + "id": "u1", + "name": "u1-name" + }, + { + "id": "a1", + "name": "a1-name" + } + ] + }, + { + "id": "a1", + "name": "a1-name", + "similarAccounts": [ + { + "id": "u1", + "name": "u1-name" + }, + { + "id": "a1", + "name": "a1-name" + } + ] + } + ] + }, + { + "id": "a1", + "name": "a1-name", + "similarAccounts": [ + { + "id": "u1", + "name": "u1-name", + "similarAccounts": [ + { + "id": "u1", + "name": "u1-name" + }, + { + "id": "a1", + "name": "a1-name" + } + ] + }, + { + "id": "a1", + "name": "a1-name", + "similarAccounts": [ + { + "id": "u1", + "name": "u1-name" + }, + { + "id": "a1", + "name": "a1-name" + } + ] + } + ] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "child-type-mismatch/003", + "query": "\n query DeeplyNested {\n accounts {\n ... on User {\n name\n similarAccounts {\n ... on User {\n name\n similarAccounts {\n ... on User {\n name\n }\n ... on Admin {\n name\n }\n }\n }\n ... on Admin {\n name\n similarAccounts {\n ... on User {\n name\n }\n ... on Admin {\n name\n }\n }\n }\n }\n }\n ... on Admin {\n name\n similarAccounts {\n ... on User {\n name\n similarAccounts {\n ... on User {\n name\n }\n ... on Admin {\n name\n }\n }\n }\n ... on Admin {\n name\n similarAccounts {\n ... on User {\n name\n }\n ... on Admin {\n name\n }\n }\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "accounts": [ + { + "name": "u1-name", + "similarAccounts": [ + { + "name": "u1-name", + "similarAccounts": [ + { + "name": "u1-name" + }, + { + "name": "a1-name" + } + ] + }, + { + "name": "a1-name", + "similarAccounts": [ + { + "name": "u1-name" + }, + { + "name": "a1-name" + } + ] + } + ] + }, + { + "name": "a1-name", + "similarAccounts": [ + { + "name": "u1-name", + "similarAccounts": [ + { + "name": "u1-name" + }, + { + "name": "a1-name" + } + ] + }, + { + "name": "a1-name", + "similarAccounts": [ + { + "name": "u1-name" + }, + { + "name": "a1-name" + } + ] + } + ] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@shareable\"]\n )\n\n type User {\n id: ID @shareable\n }\n\n type Query {\n users: [User!]!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype User {\n id: ID @shareable\n}\n\ntype Query {\n users: [User!]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@shareable\", \"@key\"]\n )\n\n union Account = User | Admin\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String\n similarAccounts: [Account!]!\n }\n\n type Admin {\n id: ID\n name: String @shareable\n similarAccounts: [Account!]!\n }\n\n type Query {\n accounts: [Account!]!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@shareable\", \"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\nunion Account = User | Admin\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n name: String\n similarAccounts: [Account!]!\n}\n\ntype Admin {\n id: ID\n name: String @shareable\n similarAccounts: [Account!]!\n}\n\ntype Query {\n accounts: [Account!]!\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "5e18e31e1c0607fa2e00f6cec64e8bea628493400169090d1c0b20328fa002e2", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@shareable\"]\n )\n\n type User {\n id: ID @shareable\n }\n\n type Query {\n users: [User!]!\n }\n `,\n resolvers: {\n Query: {\n users() {\n return users.map((u) => ({ __typename: \"User\", id: u.id }));\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "6c26aa25c6370397160fb65a9a9cc8da28d67687c8bf6c928d34ebc3538f73ad", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nfunction accounts() {\n return [\n ...users.map((user) => ({ __typename: \"User\", ...user })),\n { __typename: \"Admin\", id: \"a1\", name: \"a1-name\" },\n ];\n}\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@shareable\", \"@key\"]\n )\n\n union Account = User | Admin\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String\n similarAccounts: [Account!]!\n }\n\n type Admin {\n id: ID\n name: String @shareable\n similarAccounts: [Account!]!\n }\n\n type Query {\n accounts: [Account!]!\n }\n `,\n resolvers: {\n Query: {\n accounts,\n },\n User: {\n __resolveReference(key: { id: string }) {\n return users.find((user) => user.id === key.id);\n },\n similarAccounts: accounts,\n },\n Admin: {\n similarAccounts: accounts,\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "1efdb15aebabb7872646ea496e36015dc0cf680158d1e2a88f7f710b7e8857a6", + "source": "export const users = [\n {\n id: \"u1\",\n name: \"u1-name\",\n },\n];\n" + } + ] + }, + { + "id": "circular-reference-interface", + "cases": [ + { + "id": "circular-reference-interface/000", + "query": "\n query {\n product {\n samePriceProduct {\n ... on Product {\n samePriceProduct {\n __typename\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "samePriceProduct": { + "samePriceProduct": { + "__typename": "Book" + } + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "circular-reference-interface/001", + "query": "\n query {\n product {\n __typename\n samePriceProduct {\n __typename\n ... on Book {\n id\n }\n samePriceProduct {\n __typename\n ... on Book {\n id\n }\n }\n }\n ... on Book {\n __typename\n id\n price\n samePriceProduct {\n id\n price\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "__typename": "Book", + "samePriceProduct": { + "__typename": "Book", + "id": "3", + "samePriceProduct": { + "__typename": "Book", + "id": "1" + }, + "price": 10.99 + }, + "id": "1", + "price": 10.99 + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@provides\"]\n )\n\n type Query {\n product: Product\n }\n\n interface Product {\n samePriceProduct: Product\n }\n\n type Book implements Product @key(fields: \"id\") {\n id: ID!\n samePriceProduct: Book @provides(fields: \"price\")\n price: Float @external\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@provides\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n product: Product\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ninterface Product {\n samePriceProduct: Product\n}\n\ntype Book implements Product\n @key(fields: \"id\")\n{\n id: ID!\n samePriceProduct: Book @provides(fields: \"price\")\n price: Float @external\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Book @key(fields: \"id\") {\n id: ID!\n price: Float @shareable\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Book\n @key(fields: \"id\")\n{\n id: ID!\n price: Float @shareable\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "fc18403e9c7fd029fa000a840ca88cbb5b5232065e33b7300659cff00708da71", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { books } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@provides\"]\n )\n\n type Query {\n product: Product\n }\n\n interface Product {\n samePriceProduct: Product\n }\n\n type Book implements Product @key(fields: \"id\") {\n id: ID!\n samePriceProduct: Book @provides(fields: \"price\")\n price: Float @external\n }\n `,\n resolvers: {\n Query: {\n product() {\n const book = books[0];\n return {\n __typename: book.__typename,\n id: book.id,\n price: book.price,\n };\n },\n },\n Book: {\n samePriceProduct(book: { id: string; price: number }) {\n const samePriceBook = books.find(\n (b) => b.price === book.price && b.id !== book.id,\n );\n\n if (!samePriceBook) {\n return null;\n }\n\n return {\n __typename: samePriceBook.__typename,\n id: samePriceBook.id,\n price: samePriceBook.price,\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "55013ef8306d2b7af76849971ca8bfb88f2d5c77f40cd78c8408390485fa481e", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { books } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Book @key(fields: \"id\") {\n id: ID!\n price: Float @shareable\n }\n `,\n\n resolvers: {\n Book: {\n __resolveReference(key: { id: string }) {\n const book = books.find((b) => b.id === key.id);\n\n if (!book) {\n return null;\n }\n\n return {\n __typename: book.__typename,\n id: book.id,\n price: book.price,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "7e0bab060baf3192278996886cf0c6d6c63c9000131af231ad1fac47a55cf391", + "source": "export const books = [\n {\n __typename: \"Book\",\n id: \"1\",\n price: 10.99,\n },\n {\n __typename: \"Book\",\n id: \"2\",\n price: 5.99,\n },\n {\n __typename: \"Book\",\n id: \"3\",\n price: 10.99,\n },\n];\n" + } + ] + }, + { + "id": "complex-entity-call", + "cases": [ + { + "id": "complex-entity-call/000", + "query": "\n query {\n topProducts {\n products {\n id\n pid\n price {\n price\n }\n category {\n mainProduct {\n id\n }\n id\n tag\n }\n }\n selected {\n id\n }\n first {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "topProducts": { + "products": [ + { + "id": "1", + "pid": "p1", + "price": { + "price": 100 + }, + "category": { + "mainProduct": { + "id": "1" + }, + "id": "c1", + "tag": "t1" + } + }, + { + "id": "2", + "pid": "p2", + "price": { + "price": 200 + }, + "category": { + "mainProduct": { + "id": "2" + }, + "id": "c2", + "tag": "t2" + } + } + ], + "selected": { + "id": "2" + }, + "first": { + "id": "1" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "link", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\"])\n\n type Product @key(fields: \"id\") @key(fields: \"id pid\") {\n id: String!\n pid: String!\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ntype Product\n @key(fields: \"id\")\n @key(fields: \"id pid\")\n{\n id: String!\n pid: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "list", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.0\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type ProductList @key(fields: \"products{id pid}\") {\n products: [Product!]!\n first: Product @shareable\n selected: Product @shareable\n }\n\n type Product @key(fields: \"id pid\") {\n id: String!\n pid: String\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ntype ProductList\n @key(fields: \"products{id pid}\")\n{\n products: [Product!]!\n first: Product @shareable\n selected: Product @shareable\n}\n\ntype Product\n @key(fields: \"id pid\")\n{\n id: String!\n pid: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product | ProductList\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "price", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.0\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type ProductList\n @key(fields: \"products{id pid category{id tag}} selected{id}\") {\n products: [Product!]!\n first: Product @shareable\n selected: Product @shareable\n }\n\n type Product @key(fields: \"id pid category{id tag}\") {\n id: String!\n price: Price\n pid: String\n category: Category\n }\n\n type Category @key(fields: \"id tag\") {\n id: String!\n tag: String\n }\n\n type Price {\n price: Float!\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ntype ProductList\n @key(fields: \"products{id pid category{id tag}} selected{id}\")\n{\n products: [Product!]!\n first: Product @shareable\n selected: Product @shareable\n}\n\ntype Product\n @key(fields: \"id pid category{id tag}\")\n{\n id: String!\n price: Price\n pid: String\n category: Category\n}\n\ntype Category\n @key(fields: \"id tag\")\n{\n id: String!\n tag: String\n}\n\ntype Price {\n price: Float!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Category | Product | ProductList\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "products", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.0\"\n import: [\"@key\", \"@external\", \"@extends\", \"@shareable\"]\n )\n\n type Query {\n topProducts: ProductList!\n }\n\n type ProductList @key(fields: \"products{id}\") {\n products: [Product!]!\n }\n\n type Product @extends @key(fields: \"id\") {\n id: String! @external\n category: Category @shareable\n }\n\n type Category @key(fields: \"id\") {\n mainProduct: Product! @shareable\n id: String!\n tag: String @shareable\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\", \"@external\", \"@extends\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @extends on OBJECT | INTERFACE\n\ndirective @shareable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ntype Query {\n topProducts: ProductList!\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype ProductList\n @key(fields: \"products{id}\")\n{\n products: [Product!]!\n}\n\ntype Product\n @extends\n @key(fields: \"id\")\n{\n id: String! @external\n category: Category @shareable\n}\n\ntype Category\n @key(fields: \"id\")\n{\n mainProduct: Product! @shareable\n id: String!\n tag: String @shareable\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Category | Product | ProductList" + } + ], + "fixtureModules": [ + { + "path": "data.ts", + "sha256": "aa2a9ee7d4d1a0a0a5566838c0b2a707c508e105086bc1696ce0203c4accb1f2", + "source": "export const products = [\n {\n id: \"1\",\n pid: \"p1\",\n categoryId: \"c1\",\n price: 100,\n },\n {\n id: \"2\",\n pid: \"p2\",\n categoryId: \"c2\",\n price: 200,\n },\n];\n\nexport const categories = [\n {\n id: \"c1\",\n tag: \"t1\",\n mainProduct: \"1\",\n },\n {\n id: \"c2\",\n tag: \"t2\",\n mainProduct: \"2\",\n },\n];\n" + }, + { + "path": "link.subgraph.ts", + "sha256": "ac06289b58fab861846cbab7bd7c101cd57b3d15a1ef49bb471ddaf6854a8800", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"link\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\"])\n\n type Product @key(fields: \"id\") @key(fields: \"id pid\") {\n id: String!\n pid: String!\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(\n key:\n | {\n id: string;\n pid: string;\n }\n | {\n id: string;\n },\n ) {\n if (\"pid\" in key) {\n return products.find(\n (product) => product.id === key.id && product.pid === key.pid,\n );\n }\n\n return products.find((product) => product.id === key.id);\n },\n },\n },\n});\n" + }, + { + "path": "list.subgraph.ts", + "sha256": "1000979115b483c97ec2679dec834f1d8df9f3f4f9127521abc6eedb39cb3645", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"list\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.0\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type ProductList @key(fields: \"products{id pid}\") {\n products: [Product!]!\n first: Product @shareable\n selected: Product @shareable\n }\n\n type Product @key(fields: \"id pid\") {\n id: String!\n pid: String\n }\n `,\n resolvers: {\n ProductList: {\n __resolveReference(key: { products: { id: string; pid: string }[] }) {\n const prods = products.filter((p) =>\n key.products.some((k) => k.id === p.id && k.pid === p.pid),\n );\n return {\n products: prods,\n first: prods[0],\n selected: prods[1],\n };\n },\n },\n Product: {\n __resolveReference(key: { id: string; pid: string }) {\n return products.find(\n (product) => product.id === key.id && product.pid === key.pid,\n );\n },\n },\n },\n});\n" + }, + { + "path": "price.subgraph.ts", + "sha256": "9b7e36a8d943366fb6a91b342bc506c982900b1d96b69f32ea9ae0ed112fdd91", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { categories, products } from \"./data.js\";\n\nexport default createSubgraph(\"price\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.0\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type ProductList\n @key(fields: \"products{id pid category{id tag}} selected{id}\") {\n products: [Product!]!\n first: Product @shareable\n selected: Product @shareable\n }\n\n type Product @key(fields: \"id pid category{id tag}\") {\n id: String!\n price: Price\n pid: String\n category: Category\n }\n\n type Category @key(fields: \"id tag\") {\n id: String!\n tag: String\n }\n\n type Price {\n price: Float!\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(key: {\n id: string;\n pid: string;\n category: { id: string; tag: string };\n }) {\n return products.find((p) => {\n if (p.id === key.id && p.pid === key.pid) {\n const cat = categories.find((c) => c.id === p.categoryId);\n\n if (\n cat &&\n cat.id === key.category.id &&\n cat.tag === key.category.tag\n ) {\n return true;\n }\n }\n\n return false;\n });\n },\n price(product: (typeof products)[number]) {\n return {\n price: product.price,\n };\n },\n category(product: (typeof products)[number]) {\n return categories.find((c) => c.id === product.categoryId);\n },\n },\n Category: {\n __resolveReference(key: { id: string; tag: string }) {\n return categories.find((c) => c.id === key.id);\n },\n },\n ProductList: {\n __resolveReference(key: {\n products: {\n id: string;\n pid: string;\n category: { id: string; tag: string };\n }[];\n selected: { id: string };\n }) {\n const prods = products.filter((p) =>\n key.products.some((k) => {\n if (p.id === k.id && p.pid === k.pid) {\n const cat = categories.find((c) => c.id === p.categoryId);\n\n if (\n cat &&\n cat.id === k.category.id &&\n cat.tag === k.category.tag\n ) {\n return true;\n }\n }\n\n return false;\n }),\n );\n\n return {\n products: prods,\n first: prods[0],\n selected: products.find((p) => p.id === key.selected.id),\n };\n },\n },\n },\n});\n" + }, + { + "path": "products.subgraph.ts", + "sha256": "630e3b9d64b586e6f956f9d750faf28cf9dd92cbb499038e6789bfda0301f8af", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { categories, products } from \"./data.js\";\n\nexport default createSubgraph(\"products\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.0\"\n import: [\"@key\", \"@external\", \"@extends\", \"@shareable\"]\n )\n\n type Query {\n topProducts: ProductList!\n }\n\n type ProductList @key(fields: \"products{id}\") {\n products: [Product!]!\n }\n\n type Product @extends @key(fields: \"id\") {\n id: String! @external\n category: Category @shareable\n }\n\n type Category @key(fields: \"id\") {\n mainProduct: Product! @shareable\n id: String!\n tag: String @shareable\n }\n `,\n resolvers: {\n Query: {\n topProducts() {\n return {\n products,\n };\n },\n },\n ProductList: {\n __resolveReference(key: { products: { id: string }[] }) {\n return {\n products: products.filter((p) =>\n key.products.some((k) => k.id === p.id),\n ),\n };\n },\n },\n Product: {\n __resolveReference(key: { id: string }) {\n return products.find((product) => product.id === key.id);\n },\n category(product: (typeof products)[number]) {\n return categories.find((c) => c.id === product.categoryId);\n },\n },\n Category: {\n __resolveReference(key: { id: string }) {\n return categories.find((c) => c.id === key.id);\n },\n mainProduct(category: (typeof categories)[number]) {\n return products.find((p) => p.id === category.mainProduct);\n },\n },\n },\n});\n" + } + ] + }, + { + "id": "corrupted-supergraph-node-id", + "cases": [ + { + "id": "corrupted-supergraph-node-id/000", + "query": "\n query {\n node(id: \"a1\") {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": null, + "hasExpectedErrors": true, + "expectsErrors": true + }, + { + "id": "corrupted-supergraph-node-id/001", + "query": "\n query {\n account: node(id: \"a1\") {\n __typename\n }\n chat: node(id: \"c1\") {\n __typename\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "account": { + "__typename": "Account" + }, + "chat": { + "__typename": "Chat" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "corrupted-supergraph-node-id/002", + "query": "\n query {\n account: node(id: \"a1\") {\n ... on Account {\n id\n username\n }\n }\n chat: node(id: \"c1\") {\n ... on Chat {\n id\n text\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "account": { + "id": "a1", + "username": "a1-username" + }, + "chat": { + "id": "c1", + "text": "c1-text" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "corrupted-supergraph-node-id/003", + "query": "\n query {\n account: node(id: \"a1\") {\n ... on Chat {\n id\n }\n }\n chat: node(id: \"c1\") {\n ... on Account {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "account": {}, + "chat": {} + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "corrupted-supergraph-node-id/004", + "query": "\n query {\n account: node(id: \"a1\") {\n __typename\n ... on Chat {\n id\n }\n }\n chat: node(id: \"c1\") {\n __typename\n ... on Account {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "account": { + "__typename": "Account" + }, + "chat": { + "__typename": "Chat" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "corrupted-supergraph-node-id/005", + "query": "\n query {\n account: node(id: \"a1\") {\n id\n ... on Chat {\n id\n }\n }\n chat: node(id: \"c1\") {\n __typename\n ... on Account {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": null, + "hasExpectedErrors": true, + "expectsErrors": true + }, + { + "id": "corrupted-supergraph-node-id/006", + "query": "\n query {\n chat(id: \"c1\") {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "chat": { + "id": "c1" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "corrupted-supergraph-node-id/007", + "query": "\n query {\n account(id: \"a1\") {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "account": { + "id": "a1" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "corrupted-supergraph-node-id/008", + "query": "\n query {\n chat(id: \"c1\") {\n id\n text\n account {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "chat": { + "id": "c1", + "text": "c1-text", + "account": { + "id": "a1" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "corrupted-supergraph-node-id/009", + "query": "\n query {\n account(id: \"a1\") {\n id\n username\n chats {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "account": { + "id": "a1", + "username": "a1-username", + "chats": [ + { + "id": "c1" + } + ] + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@external\"]\n )\n\n type Query {\n node(id: ID!): Node @shareable\n account(id: String!): Account\n }\n\n interface Node {\n id: ID!\n }\n\n type Account implements Node @key(fields: \"id\") {\n id: ID!\n username: String!\n }\n\n type Chat implements Node @key(fields: \"id\") {\n id: ID! @external\n account: Account!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\", \"@external\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n node(id: ID!): Node @shareable\n account(id: String!): Account\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ninterface Node {\n id: ID!\n}\n\ntype Account implements Node\n @key(fields: \"id\")\n{\n id: ID!\n username: String!\n}\n\ntype Chat implements Node\n @key(fields: \"id\")\n{\n id: ID! @external\n account: Account!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Account | Chat" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@external\"]\n )\n type Query {\n node(id: ID!): Node @shareable\n chat(id: String!): Chat\n }\n\n interface Node {\n id: ID!\n }\n\n type Account implements Node @key(fields: \"id\") {\n id: ID! @external\n chats: [Chat!]!\n }\n\n type Chat implements Node @key(fields: \"id\") {\n id: ID!\n text: String!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\", \"@external\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n node(id: ID!): Node @shareable\n chat(id: String!): Chat\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ninterface Node {\n id: ID!\n}\n\ntype Account implements Node\n @key(fields: \"id\")\n{\n id: ID! @external\n chats: [Chat!]!\n}\n\ntype Chat implements Node\n @key(fields: \"id\")\n{\n id: ID!\n text: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Account | Chat" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "d6659cadb2d1e06b01056a07046db5353ddd370babcbbb33fb22357e924f8406", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { accounts, chats } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@external\"]\n )\n\n type Query {\n node(id: ID!): Node @shareable\n account(id: String!): Account\n }\n\n interface Node {\n id: ID!\n }\n\n type Account implements Node @key(fields: \"id\") {\n id: ID!\n username: String!\n }\n\n type Chat implements Node @key(fields: \"id\") {\n id: ID! @external\n account: Account!\n }\n `,\n resolvers: {\n Query: {\n node(_: {}, { id }: { id: string }) {\n const account = accounts.find((a) => a.id === id);\n\n if (account) {\n return {\n __typename: \"Account\",\n id: account.id,\n username: account.username,\n };\n }\n\n const chat = chats.find((c) => c.id === id);\n\n if (chat) {\n return {\n __typename: \"Chat\",\n id: \"never\",\n accountId: chat.accountId,\n };\n }\n },\n account(_: {}, { id }: { id: string }) {\n const account = accounts.find((a) => a.id === id);\n\n if (account) {\n return {\n __typename: \"Account\",\n id: account.id,\n username: account.username,\n };\n }\n\n return null;\n },\n },\n Account: {\n __resolveReference(key: { id: string }) {\n const account = accounts.find((a) => a.id === key.id);\n\n if (account) {\n return {\n __typename: \"Account\",\n id: account.id,\n username: account.username,\n };\n }\n\n return null;\n },\n },\n Chat: {\n __resolveReference(key: { id: string }) {\n const chat = chats.find((c) => c.id === key.id);\n\n if (chat) {\n return {\n __typename: \"Chat\",\n id: chat.id,\n accountId: chat.accountId,\n };\n }\n\n return null;\n },\n account(chat: { accountId: string }) {\n const account = accounts.find((a) => a.id === chat.accountId);\n\n if (account) {\n return {\n __typename: \"Account\",\n id: account.id,\n username: account.username,\n };\n }\n\n return null;\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "ea0e1210ab79bad6a3d65c119233951a45fa1131bef2618d66022db0cfd13780", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { accounts, chats } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@external\"]\n )\n type Query {\n node(id: ID!): Node @shareable\n chat(id: String!): Chat\n }\n\n interface Node {\n id: ID!\n }\n\n type Account implements Node @key(fields: \"id\") {\n id: ID! @external\n chats: [Chat!]!\n }\n\n type Chat implements Node @key(fields: \"id\") {\n id: ID!\n text: String!\n }\n `,\n resolvers: {\n Query: {\n node(_: {}, { id }: { id: string }) {\n const account = accounts.find((a) => a.id === id);\n\n if (account) {\n return {\n __typename: \"Account\",\n id: \"never\",\n chatIds: chats\n .filter((c) => c.accountId === account.id)\n .map((c) => c.id),\n };\n }\n\n const chat = chats.find((c) => c.id === id);\n\n if (chat) {\n return {\n __typename: \"Chat\",\n id: chat.id,\n text: chat.text,\n };\n }\n\n return null;\n },\n chat(_: {}, { id }: { id: string }) {\n const chat = chats.find((c) => c.id === id);\n\n if (chat) {\n return {\n __typename: \"Chat\",\n id: chat.id,\n text: chat.text,\n };\n }\n\n return null;\n },\n },\n Account: {\n __resolveReference(key: { id: string }) {\n const account = accounts.find((a) => a.id === key.id);\n\n if (account) {\n return {\n __typename: \"Account\",\n id: account.id,\n chatIds: chats\n .filter((c) => c.accountId === account.id)\n .map((c) => c.id),\n };\n }\n\n return null;\n },\n chats(account: { chatIds: string[] }) {\n return chats\n .filter((c) => account.chatIds.includes(c.id))\n .map((chat) => ({\n __typename: \"Chat\",\n id: chat.id,\n text: chat.text,\n }));\n },\n },\n Chat: {\n __resolveReference(key: { id: string }) {\n const chat = chats.find((c) => c.id === key.id);\n\n if (chat) {\n return {\n __typename: \"Chat\",\n id: chat.id,\n text: chat.text,\n };\n }\n\n return null;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "5283123509e2beab1ced714a2132bdcfcf754a42e6f5410c0c1b7d1c9bd96890", + "source": "export const accounts = [\n {\n __typename: \"Account\",\n id: \"a1\",\n username: \"a1-username\",\n },\n];\n\nexport const chats = [\n {\n __typename: \"Chat\",\n id: \"c1\",\n accountId: \"a1\",\n text: \"c1-text\",\n },\n];\n" + } + ] + }, + { + "id": "enum-intersection", + "cases": [ + { + "id": "enum-intersection/000", + "query": "\n query {\n users {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "id": "u1" + }, + { + "id": "u2" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "enum-intersection/001", + "query": "\n query {\n users {\n id\n type\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "id": "u1", + "type": "REGULAR" + }, + { + "id": "u2", + "type": null + } + ] + }, + "hasExpectedErrors": true, + "expectsErrors": true + }, + { + "id": "enum-intersection/002", + "query": "\n query {\n usersB {\n id\n type\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "usersB": [ + { + "id": "u1", + "type": "REGULAR" + }, + { + "id": "u2", + "type": null + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "enum-intersection/003", + "query": "\n query {\n usersByType(type: REGULAR) {\n id\n type\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "usersByType": [ + { + "id": "u1", + "type": "REGULAR" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "enum-intersection/004", + "query": "\n query {\n usersByType(type: ANONYMOUS) {\n id\n type\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": null, + "hasExpectedErrors": true, + "expectsErrors": true + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n users: [User]\n }\n\n type User @key(fields: \"id\") {\n id: ID\n type: UserType @shareable\n }\n\n enum UserType {\n REGULAR\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n users: [User]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype User\n @key(fields: \"id\")\n{\n id: ID\n type: UserType @shareable\n}\n\nenum UserType {\n REGULAR\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@inaccessible\", \"@shareable\"]\n )\n\n type Query {\n usersByType(type: UserType!): [User!]\n usersB: [User!]\n }\n\n extend type User @key(fields: \"id\") {\n id: ID\n type: UserType @shareable\n }\n\n enum UserType {\n ANONYMOUS @inaccessible\n REGULAR\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@inaccessible\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n usersByType(type: UserType!): [User!]\n usersB: [User!]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nextend type User\n @key(fields: \"id\")\n{\n id: ID\n type: UserType @shareable\n}\n\nenum UserType {\n ANONYMOUS @inaccessible\n REGULAR\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "80ac0e22b80690654c8b015db4ad5418f7f3a404e77671e5ca658b6882c96e83", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n users: [User]\n }\n\n type User @key(fields: \"id\") {\n id: ID\n type: UserType @shareable\n }\n\n enum UserType {\n REGULAR\n }\n `,\n resolvers: {\n Query: {\n users() {\n return users;\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n type: user.type,\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "8686dd4bc964896b47b485a1c445e1a063a10d561889bbce66cd06961614dc68", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@inaccessible\", \"@shareable\"]\n )\n\n type Query {\n usersByType(type: UserType!): [User!]\n usersB: [User!]\n }\n\n extend type User @key(fields: \"id\") {\n id: ID\n type: UserType @shareable\n }\n\n enum UserType {\n ANONYMOUS @inaccessible\n REGULAR\n }\n `,\n resolvers: {\n Query: {\n usersByType(_: {}, { type }: { type: string }) {\n return users.filter((u) => u.type === type);\n },\n usersB() {\n return users;\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n type: user.type,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "4cc4e4972b666b242c1a0b829e954498500c3dc2e565ccd0843a5a31364ddd1f", + "source": "export const users = [\n {\n id: \"u1\",\n type: \"REGULAR\",\n },\n {\n id: \"u2\",\n type: \"ANONYMOUS\",\n },\n];\n" + } + ] + }, + { + "id": "fed2-external-extends", + "cases": [ + { + "id": "fed2-external-extends/000", + "query": "\n query {\n randomUser {\n id\n name\n }\n userById(id: \"u2\") {\n id\n name\n nickname\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "randomUser": { + "id": "u1", + "name": "u1-name" + }, + "userById": { + "id": "u2", + "name": "u2-name", + "nickname": "u2-nickname" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "fed2-external-extends/001", + "query": "\n query {\n randomUser {\n id\n rid\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "randomUser": { + "id": "u1", + "rid": "u1-rid" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "fed2-external-extends/002", + "query": "\n query {\n randomUser {\n id\n rid\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "randomUser": { + "id": "u1", + "rid": "u1-rid", + "name": "u1-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "fed2-external-extends/003", + "query": "\n query {\n providedRandomUser {\n id\n rid\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "providedRandomUser": { + "id": "u1", + "rid": "u1-rid", + "name": "u1-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@extends\", \"@provides\"]\n )\n\n type Query {\n randomUser: User\n providedRandomUser: User @provides(fields: \"name\")\n }\n\n type User @key(fields: \"id\") @extends {\n id: ID! @external\n name: String! @external\n rid: ID\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@extends\", \"@provides\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n randomUser: User\n providedRandomUser: User @provides(fields: \"name\")\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype User\n @key(fields: \"id\")\n @extends\n{\n id: ID! @external\n name: String! @external\n rid: ID\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n userById(id: ID): User\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n nickname: String\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n userById(id: ID): User\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n name: String! @shareable\n nickname: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "c5bbb29afbd678e0efeecf78bd777478db41584dfde796e177ccfb84a0ba3c7e", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@extends\", \"@provides\"]\n )\n\n type Query {\n randomUser: User\n providedRandomUser: User @provides(fields: \"name\")\n }\n\n type User @key(fields: \"id\") @extends {\n id: ID! @external\n name: String! @external\n rid: ID\n }\n `,\n resolvers: {\n Query: {\n randomUser() {\n return {\n id: users[0].id,\n rid: users[0].rid,\n };\n },\n providedRandomUser() {\n return {\n id: users[0].id,\n rid: users[0].rid,\n name: users[0].name,\n };\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n rid: user.rid,\n };\n },\n name(user: { name: string | undefined }) {\n return user.name ?? \"never\";\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "eefcccdd7f77e5015ee80dced38b2b51db225995584e33341e038ea8eb65c4c6", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n userById(id: ID): User\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n nickname: String\n }\n `,\n resolvers: {\n Query: {\n userById: (_: {}, { id }: { id: string }) => {\n const user = users.find((u) => u.id === id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n name: user.name,\n nickname: user.nickname,\n };\n },\n },\n User: {\n __resolveReference: (key: { id: string }) => {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n name: user.name,\n nickname: user.nickname,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "6d37b20b771a31f2219f84d9d6172394d6b52dac271d6dd6dbc9d86359f3445c", + "source": "export const users = [\n {\n id: \"u1\",\n rid: \"u1-rid\",\n name: \"u1-name\",\n nickname: \"u1-nickname\",\n },\n {\n id: \"u2\",\n rid: \"u2-rid\",\n name: \"u2-name\",\n nickname: \"u2-nickname\",\n },\n];\n" + } + ] + }, + { + "id": "fed2-external-extension", + "cases": [ + { + "id": "fed2-external-extension/000", + "query": "\n query {\n randomUser {\n id\n name\n }\n userById(id: \"u2\") {\n id\n name\n nickname\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "randomUser": { + "id": "u1", + "name": "u1-name" + }, + "userById": { + "id": "u2", + "name": "u2-name", + "nickname": "u2-nickname" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "fed2-external-extension/001", + "query": "\n query {\n randomUser {\n id\n rid\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "randomUser": { + "id": "u1", + "rid": "u1-rid" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "fed2-external-extension/002", + "query": "\n query {\n randomUser {\n id\n rid\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "randomUser": { + "id": "u1", + "rid": "u1-rid", + "name": "u1-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "fed2-external-extension/003", + "query": "\n query {\n providedRandomUser {\n id\n rid\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "providedRandomUser": { + "id": "u1", + "rid": "u1-rid", + "name": "u1-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@provides\"]\n )\n\n type Query {\n randomUser: User\n providedRandomUser: User @provides(fields: \"name\")\n }\n\n extend type User @key(fields: \"id\") {\n id: ID! @external\n name: String! @external\n rid: ID\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@provides\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n randomUser: User\n providedRandomUser: User @provides(fields: \"name\")\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nextend type User\n @key(fields: \"id\")\n{\n id: ID! @external\n name: String! @external\n rid: ID\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n userById(id: ID): User\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n nickname: String\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n userById(id: ID): User\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n name: String! @shareable\n nickname: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "f754190d3cd761f8d024e6bee5522a1f19427037e23bed4d85dec7f32a381654", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@provides\"]\n )\n\n type Query {\n randomUser: User\n providedRandomUser: User @provides(fields: \"name\")\n }\n\n extend type User @key(fields: \"id\") {\n id: ID! @external\n name: String! @external\n rid: ID\n }\n `,\n resolvers: {\n Query: {\n randomUser() {\n return {\n id: users[0].id,\n rid: users[0].rid,\n };\n },\n providedRandomUser() {\n return {\n id: users[0].id,\n rid: users[0].rid,\n name: users[0].name,\n };\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n rid: user.rid,\n };\n },\n name(user: { name: string | undefined }) {\n return user.name ?? \"never\";\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "eefcccdd7f77e5015ee80dced38b2b51db225995584e33341e038ea8eb65c4c6", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n userById(id: ID): User\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n nickname: String\n }\n `,\n resolvers: {\n Query: {\n userById: (_: {}, { id }: { id: string }) => {\n const user = users.find((u) => u.id === id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n name: user.name,\n nickname: user.nickname,\n };\n },\n },\n User: {\n __resolveReference: (key: { id: string }) => {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n name: user.name,\n nickname: user.nickname,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "6d37b20b771a31f2219f84d9d6172394d6b52dac271d6dd6dbc9d86359f3445c", + "source": "export const users = [\n {\n id: \"u1\",\n rid: \"u1-rid\",\n name: \"u1-name\",\n nickname: \"u1-nickname\",\n },\n {\n id: \"u2\",\n rid: \"u2-rid\",\n name: \"u2-name\",\n nickname: \"u2-nickname\",\n },\n];\n" + } + ] + }, + { + "id": "include-skip", + "cases": [ + { + "id": "include-skip/000", + "query": "\n query ($bool: Boolean = false) {\n product {\n price\n neverCalledInclude @include(if: $bool)\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "price": 699.99 + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "include-skip/001", + "query": "\n query ($bool: Boolean = true) {\n product {\n price\n neverCalledSkip @skip(if: $bool)\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "price": 699.99 + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "include-skip/002", + "query": "\n query ($bool: Boolean = true) {\n product {\n price\n include @include(if: $bool)\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "price": 699.99, + "include": true + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "include-skip/003", + "query": "\n query ($bool: Boolean = false) {\n product {\n price\n skip @skip(if: $bool)\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "price": 699.99, + "skip": true + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n product: Product\n }\n\n type Product @key(fields: \"id\") {\n id: ID!\n price: Float!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n product: Product\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n price: Float!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n price: Float! @external\n isExpensive: Boolean! @requires(fields: \"price\")\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n price: Float! @external\n isExpensive: Boolean! @requires(fields: \"price\")\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "c", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n isExpensive: Boolean! @external\n include: Boolean! @requires(fields: \"isExpensive\")\n skip: Boolean! @requires(fields: \"isExpensive\")\n neverCalledInclude: Boolean! @requires(fields: \"isExpensive\")\n neverCalledSkip: Boolean! @requires(fields: \"isExpensive\")\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n isExpensive: Boolean! @external\n include: Boolean! @requires(fields: \"isExpensive\")\n skip: Boolean! @requires(fields: \"isExpensive\")\n neverCalledInclude: Boolean! @requires(fields: \"isExpensive\")\n neverCalledSkip: Boolean! @requires(fields: \"isExpensive\")\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "b7c3493b221278419a3bace3dd8e6f38f82ecfb66f28d65ce5a577125d312e1b", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n product: Product\n }\n\n type Product @key(fields: \"id\") {\n id: ID!\n price: Float!\n }\n `,\n resolvers: {\n Query: {\n product() {\n return {\n id: products[0].id,\n price: products[0].price,\n };\n },\n },\n Product: {\n __resolveReference(key: { id: string }) {\n const product = products.find((product) => product.id === key.id);\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n price: product.price,\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "91b7e1c184081e400fa368336003d4949b196e9d144b5dec3e822c05ea394be4", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n price: Float! @external\n isExpensive: Boolean! @requires(fields: \"price\")\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(key: { id: string } | { id: string; price: number }) {\n const product = products.find((product) => product.id === key.id);\n\n if (!product) {\n return null;\n }\n\n if (\"price\" in key) {\n return {\n id: product.id,\n price: key.price,\n };\n }\n\n return {\n id: product.id,\n };\n },\n isExpensive(product: { price?: number }) {\n if (!product.price) {\n throw new Error(\"Price is missing\");\n }\n\n return product.price > 500;\n },\n },\n },\n});\n" + }, + { + "path": "c.subgraph.ts", + "sha256": "6bd3dd39ea04431e9d59e57f99d4a22d25394d1cfa190cd53af251886468044e", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"c\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n isExpensive: Boolean! @external\n include: Boolean! @requires(fields: \"isExpensive\")\n skip: Boolean! @requires(fields: \"isExpensive\")\n neverCalledInclude: Boolean! @requires(fields: \"isExpensive\")\n neverCalledSkip: Boolean! @requires(fields: \"isExpensive\")\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(\n key: { id: string } | { id: string; isExpensive: boolean },\n ) {\n const product = products.find((product) => product.id === key.id);\n\n if (!product) {\n return null;\n }\n\n if (\"isExpensive\" in key) {\n return {\n id: product.id,\n isExpensive: key.isExpensive,\n };\n }\n\n return {\n id: product.id,\n };\n },\n include(product: { isExpensive?: number }) {\n if (typeof product.isExpensive !== \"boolean\") {\n throw new Error(\"isExpensive is missing\");\n }\n\n return true;\n },\n neverCalledInclude() {\n throw new Error(\"neverCalledInclude should not be called\");\n },\n skip(product: { isExpensive?: number }) {\n if (typeof product.isExpensive !== \"boolean\") {\n throw new Error(\"isExpensive is missing\");\n }\n\n return true;\n },\n neverCalledSkip() {\n throw new Error(\"neverCalledSkip should not be called\");\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "647dc2229973ade355c519506df8246128080cac617d3fd4bea89c3bf22a0ec2", + "source": "export const products = [\n {\n id: \"p1\",\n price: 699.99,\n },\n];\n" + } + ] + }, + { + "id": "input-object-intersection", + "cases": [ + { + "id": "input-object-intersection/000", + "query": "\n query {\n usersInA(filter: { first: 1 }) {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "usersInA": [ + { + "id": "u1" + }, + { + "id": "u2" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "input-object-intersection/001", + "query": "\n query {\n usersInA(filter: { first: 1, offset: 2 }) {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": false, + "expectedData": null, + "hasExpectedErrors": true, + "expectsErrors": true + }, + { + "id": "input-object-intersection/002", + "query": "\n query {\n usersInB(filter: { first: 1, offset: 2 }) {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": false, + "expectedData": null, + "hasExpectedErrors": true, + "expectsErrors": true + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n input UsersFilter {\n first: Int!\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n }\n\n type Query {\n usersInA(filter: UsersFilter!): [User!]\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ninput UsersFilter {\n first: Int!\n}\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n name: String! @shareable\n}\n\ntype Query {\n usersInA(filter: UsersFilter!): [User!]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n input UsersFilter {\n offset: Int\n first: Int!\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n }\n\n type Query {\n usersInB(filter: UsersFilter!): [User!]\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ninput UsersFilter {\n offset: Int\n first: Int!\n}\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n name: String! @shareable\n}\n\ntype Query {\n usersInB(filter: UsersFilter!): [User!]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "f647af2c7645fa8c1cb68f047370f72e874a03290417c3e8698e5bfffa618752", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n input UsersFilter {\n first: Int!\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n }\n\n type Query {\n usersInA(filter: UsersFilter!): [User!]\n }\n `,\n resolvers: {\n Query: {\n usersInA(_: {}, { filter }: { filter: { first: number } }) {\n if (\"offset\" in filter) {\n return [];\n }\n\n return users;\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((user) => user.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return user;\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "e25aed07c98f8dd0e458b84f714edf71b2e0d4c0866f8863d3714cc643a6906a", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n input UsersFilter {\n offset: Int\n first: Int!\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n }\n\n type Query {\n usersInB(filter: UsersFilter!): [User!]\n }\n `,\n resolvers: {\n Query: {\n usersInB(\n _: {},\n { filter }: { filter: { offset?: number; first: number } },\n ) {\n if (\"offset\" in filter) {\n return [];\n }\n\n return users;\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((user) => user.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return user;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "6efaef2c1a7ee31e629402d1d61ce2b143130ce9805f5c7b2df580328af4ee25", + "source": "export const users = [\n {\n id: \"u1\",\n name: \"u1-name\",\n },\n {\n id: \"u2\",\n name: \"u2-name\",\n },\n];\n" + } + ] + }, + { + "id": "interface-object-indirect-extension", + "cases": [ + { + "id": "interface-object-indirect-extension/000", + "query": "\n query {\n media {\n id\n title\n ... on Video {\n duration\n authorName\n }\n ... on Article {\n wordCount\n }\n }\n author {\n id\n name\n }\n playlist {\n id\n name\n media {\n id\n ... on Video {\n duration\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "media": { + "id": "1", + "title": "title for 1", + "duration": 100, + "authorName": "John Doe" + }, + "author": { + "id": "1", + "name": "name for 1" + }, + "playlist": { + "id": "1", + "name": "name for 1", + "media": { + "id": "1", + "duration": 100 + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "b", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n author: Author\n }\n\n type Author @key(fields: \"id\") {\n id: ID!\n name: String\n }\n\n extend type Video @key(fields: \"id\") {\n id: ID!\n authorName: String\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n author: Author\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Author\n @key(fields: \"id\")\n{\n id: ID!\n name: String\n}\n\nextend type Video\n @key(fields: \"id\")\n{\n id: ID!\n authorName: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Author | Video" + }, + { + "name": "a", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n media: Media\n }\n\n interface Media @key(fields: \"id\") {\n id: ID!\n title: String\n }\n\n type Video implements Media @key(fields: \"id\") {\n id: ID!\n title: String\n duration: Int\n }\n\n type Article implements Media @key(fields: \"id\") {\n id: ID!\n title: String\n wordCount: Int\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n media: Media\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ninterface Media\n @key(fields: \"id\")\n{\n id: ID!\n title: String\n}\n\ntype Video implements Media\n @key(fields: \"id\")\n{\n id: ID!\n title: String\n duration: Int\n}\n\ntype Article implements Media\n @key(fields: \"id\")\n{\n id: ID!\n title: String\n wordCount: Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Article | Video" + }, + { + "name": "c", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\"]\n )\n\n type Query {\n playlist: Playlist\n }\n\n type Playlist @key(fields: \"id\") {\n id: ID!\n name: String\n media: Media\n }\n\n type Media @key(fields: \"id\") @interfaceObject {\n id: ID!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@interfaceObject\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @interfaceObject on OBJECT\n\ntype Query {\n playlist: Playlist\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Playlist\n @key(fields: \"id\")\n{\n id: ID!\n name: String\n media: Media\n}\n\ntype Media\n @key(fields: \"id\")\n @interfaceObject\n{\n id: ID!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Media | Playlist" + } + ], + "fixtureModules": [ + { + "path": "author.subgraph.ts", + "sha256": "d656f9f98940baae9621e017f7e83df9dc61a31b7255cad58fa3cabe5cdcf226", + "source": "import { createSubgraph } from \"../../subgraph.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n author: Author\n }\n\n type Author @key(fields: \"id\") {\n id: ID!\n name: String\n }\n\n extend type Video @key(fields: \"id\") {\n id: ID!\n authorName: String\n }\n `,\n resolvers: {\n Query: {\n author() {\n return { id: \"1\", name: `name for 1` };\n },\n },\n Author: {\n __resolveReference(ref: { id: string }) {\n return { id: ref.id, name: `name for ${ref.id}` };\n },\n },\n Video: {\n authorName() {\n return \"John Doe\";\n },\n },\n },\n});\n" + }, + { + "path": "media.subgraph.ts", + "sha256": "6afe129a81f1bb185d3f4af97e625bd500ab1ea959268c4639d71e3c093a4a58", + "source": "import { createSubgraph } from \"../../subgraph.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n media: Media\n }\n\n interface Media @key(fields: \"id\") {\n id: ID!\n title: String\n }\n\n type Video implements Media @key(fields: \"id\") {\n id: ID!\n title: String\n duration: Int\n }\n\n type Article implements Media @key(fields: \"id\") {\n id: ID!\n title: String\n wordCount: Int\n }\n `,\n resolvers: {\n Query: {\n media() {\n return {\n id: \"1\",\n title: \"title for 1\",\n duration: 100,\n };\n },\n },\n Media: {\n __resolveType() {\n return \"Video\";\n },\n },\n Video: {\n __resolveReference(ref: { id: string }) {\n return {\n id: ref.id,\n title: `title for ${ref.id}`,\n duration: 100 * Number(ref.id),\n };\n },\n },\n Article: {\n __resolveReference(ref: { id: string }) {\n return {\n id: ref.id,\n title: `title for ${ref.id}`,\n wordCount: 900 * Number(ref.id),\n };\n },\n },\n },\n});\n" + }, + { + "path": "playlist.subgraph.ts", + "sha256": "40ab322dfddae6d94c6f7897ac6ccd43e712bb7ee201a35ac8ea8f5464174e77", + "source": "import { createSubgraph } from \"../../subgraph.js\";\n\nexport default createSubgraph(\"c\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\"]\n )\n\n type Query {\n playlist: Playlist\n }\n\n type Playlist @key(fields: \"id\") {\n id: ID!\n name: String\n media: Media\n }\n\n type Media @key(fields: \"id\") @interfaceObject {\n id: ID!\n }\n `,\n resolvers: {\n Query: {\n playlist() {\n return { id: \"1\", name: \"name for 1\", media: { id: \"1\" } };\n },\n },\n Playlist: {\n __resolveReference(ref: { id: string }) {\n return {\n id: ref.id,\n name: `name for ${ref.id}`,\n media: { id: ref.id },\n };\n },\n },\n Media: {\n __resolveReference(ref: { id: string }) {\n return {\n id: ref.id,\n };\n },\n },\n },\n});\n" + } + ] + }, + { + "id": "interface-object-with-requires", + "cases": [ + { + "id": "interface-object-with-requires/000", + "query": "\n query {\n anotherUsers {\n id\n name\n username\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "anotherUsers": [ + { + "id": "u1", + "name": "u1-name", + "username": "u1-username" + }, + { + "id": "u2", + "name": "u2-name", + "username": "u2-username" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "interface-object-with-requires/001", + "query": "\n query {\n users {\n id\n name\n username\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "id": "u1", + "name": "u1-name", + "username": "u1-username" + }, + { + "id": "u2", + "name": "u2-name", + "username": "u2-username" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "interface-object-with-requires/002", + "query": "\n query {\n anotherUsers {\n ... on User {\n age\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "anotherUsers": [ + { + "age": 11 + }, + { + "age": 22 + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "interface-object-with-requires/003", + "query": "\n query {\n users {\n ... on User {\n age\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "age": 11 + }, + { + "age": 22 + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "interface-object-with-requires/004", + "query": "\n query {\n anotherUsers {\n ... on User {\n age\n id\n name\n username\n }\n id\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "anotherUsers": [ + { + "age": 11, + "id": "u1", + "name": "u1-name", + "username": "u1-username" + }, + { + "age": 22, + "id": "u2", + "name": "u2-name", + "username": "u2-username" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "interface-object-with-requires/005", + "query": "\n query {\n users {\n ... on User {\n age\n id\n name\n username\n }\n id\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "age": 11, + "id": "u1", + "name": "u1-name", + "username": "u1-username" + }, + { + "age": 22, + "id": "u2", + "name": "u2-name", + "username": "u2-username" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "interface-object-with-requires/006", + "query": "\n query {\n users {\n ... on User {\n age\n id\n name\n username\n }\n id\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "age": 11, + "id": "u1", + "name": "u1-name", + "username": "u1-username" + }, + { + "age": 22, + "id": "u2", + "name": "u2-name", + "username": "u2-username" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n users: [NodeWithName!]!\n }\n\n interface NodeWithName @key(fields: \"id\") {\n id: ID!\n name: String\n }\n\n type User implements NodeWithName @key(fields: \"id\") {\n id: ID!\n name: String\n age: Int\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n users: [NodeWithName!]!\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ninterface NodeWithName\n @key(fields: \"id\")\n{\n id: ID!\n name: String\n}\n\ntype User implements NodeWithName\n @key(fields: \"id\")\n{\n id: ID!\n name: String\n age: Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\", \"@external\", \"@requires\"]\n )\n\n type Query {\n anotherUsers: [NodeWithName]\n }\n\n type NodeWithName @key(fields: \"id\") @interfaceObject {\n id: ID!\n name: String @external\n username: String @requires(fields: \"name\")\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@interfaceObject\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @interfaceObject on OBJECT\n\ntype Query {\n anotherUsers: [NodeWithName]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype NodeWithName\n @key(fields: \"id\")\n @interfaceObject\n{\n id: ID!\n name: String @external\n username: String @requires(fields: \"name\")\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = NodeWithName" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "33463f5f364dbe8f06e7d0ba3ae3db8b805db357bf0c56a8b04ad688ec4d60b3", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n users: [NodeWithName!]!\n }\n\n interface NodeWithName @key(fields: \"id\") {\n id: ID!\n name: String\n }\n\n type User implements NodeWithName @key(fields: \"id\") {\n id: ID!\n name: String\n age: Int\n }\n `,\n resolvers: {\n NodeWithName: {\n __resolveReference(key: { id: string }) {\n const node = users.find((u) => u.id === key.id);\n\n if (!node) {\n return null;\n }\n\n return {\n __typename: \"User\",\n id: node.id,\n name: node.name,\n age: node.age,\n };\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n __typename: \"User\",\n id: user.id,\n name: user.name,\n age: user.age,\n };\n },\n },\n Query: {\n users() {\n return users.map((u) => ({\n __typename: \"User\",\n id: u.id,\n name: u.name,\n age: u.age,\n }));\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "a41c9e2c5162f16ca7deb1e520291f87f6efebac6a8997f09f776981ab0d7368", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\", \"@external\", \"@requires\"]\n )\n\n type Query {\n anotherUsers: [NodeWithName]\n }\n\n type NodeWithName @key(fields: \"id\") @interfaceObject {\n id: ID!\n name: String @external\n username: String @requires(fields: \"name\")\n }\n `,\n resolvers: {\n NodeWithName: {\n __resolveReference(key: { id: string } | { id: string; name: string }) {\n const node = users.find((u) => u.id === key.id);\n\n if (!node) {\n return null;\n }\n\n return {\n __typename: \"User\",\n id: node.id,\n name: \"name\" in key ? key.name : undefined,\n };\n },\n username(node: { id: string; name: string }) {\n const user = users.find((u) => u.id === node.id);\n\n if (!user) {\n return null;\n }\n\n if (!node.name) {\n throw new Error(\"Requires field 'name' not provided\");\n }\n\n return user.username;\n },\n },\n Query: {\n anotherUsers() {\n return users.map((u) => ({\n __typename: \"User\",\n id: u.id,\n username: u.username,\n }));\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "6a5cebe4bde40491373d1afebc3fbfeb4d6adf5ecbc1f96e60f44291007ac457", + "source": "export const users = [\n {\n id: \"u1\",\n name: \"u1-name\",\n username: \"u1-username\",\n age: 11,\n },\n {\n id: \"u2\",\n name: \"u2-name\",\n username: \"u2-username\",\n age: 22,\n },\n];\n" + } + ] + }, + { + "id": "keys-mashup", + "cases": [ + { + "id": "keys-mashup/000", + "query": "\n query {\n b {\n id\n a {\n id\n name\n nameInB\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "b": { + "id": "100", + "a": [ + { + "id": "1", + "name": "a.1", + "nameInB": "b.a.nameInB a.1" + } + ] + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.5\", import: [\"@key\"])\n\n type A\n @key(fields: \"id\", resolvable: true)\n @key(fields: \"pId\", resolvable: false)\n @key(fields: \"compositeId { one two }\", resolvable: false)\n @key(fields: \"id compositeId { two three }\", resolvable: false) {\n id: ID!\n pId: ID!\n compositeId: CompositeID!\n name: String!\n }\n\n type CompositeID {\n one: ID!\n two: ID!\n three: ID!\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.5\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ndirective @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ntype A\n @key(fields: \"id\", resolvable: true)\n @key(fields: \"pId\", resolvable: false)\n @key(fields: \"compositeId { one two }\", resolvable: false)\n @key(fields: \"id compositeId { two three }\", resolvable: false)\n{\n id: ID!\n pId: ID!\n compositeId: CompositeID!\n name: String!\n}\n\ntype CompositeID {\n one: ID!\n two: ID!\n three: ID!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar federation__Scope\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = A\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.5\"\n import: [\"@key\", \"@requires\", \"@external\"]\n )\n\n type Query {\n b: B\n }\n\n type B @key(fields: \"id\") {\n id: ID!\n a: [A!]!\n }\n\n type A\n @key(fields: \"compositeId { one two }\", resolvable: false)\n @key(fields: \"id compositeId { two three }\", resolvable: true)\n @key(fields: \"pId\", resolvable: false)\n @key(fields: \"id\", resolvable: false) {\n id: ID!\n pId: ID!\n compositeId: CompositeID!\n name: String! @external\n nameInB: String! @requires(fields: \"name\")\n }\n\n type CompositeID {\n one: ID!\n two: ID!\n three: ID!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.5\", import: [\"@key\", \"@requires\", \"@external\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ndirective @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ntype Query {\n b: B\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype B\n @key(fields: \"id\")\n{\n id: ID!\n a: [A!]!\n}\n\ntype A\n @key(fields: \"compositeId { one two }\", resolvable: false)\n @key(fields: \"id compositeId { two three }\", resolvable: true)\n @key(fields: \"pId\", resolvable: false)\n @key(fields: \"id\", resolvable: false)\n{\n id: ID!\n pId: ID!\n compositeId: CompositeID!\n name: String! @external\n nameInB: String! @requires(fields: \"name\")\n}\n\ntype CompositeID {\n one: ID!\n two: ID!\n three: ID!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar federation__Scope\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = A | B" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "202b06759a5e6e0706a4e4d1d510ddcfb1a48d125048d3889c6ca6764712d010", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { data } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.5\", import: [\"@key\"])\n\n type A\n @key(fields: \"id\", resolvable: true)\n @key(fields: \"pId\", resolvable: false)\n @key(fields: \"compositeId { one two }\", resolvable: false)\n @key(fields: \"id compositeId { two three }\", resolvable: false) {\n id: ID!\n pId: ID!\n compositeId: CompositeID!\n name: String!\n }\n\n type CompositeID {\n one: ID!\n two: ID!\n three: ID!\n }\n `,\n resolvers: {\n A: {\n __resolveReference: (reference: { id: string }) => {\n // The only key that is resolvable is \"id\"\n const obj = data.a[reference.id];\n\n if (!obj) {\n return null;\n }\n\n return {\n __typename: \"A\",\n id: obj.id,\n pId: obj.pId,\n compositeId: obj.compositeId,\n name: obj.name,\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "bc91e9c0d8827fc8e4c050bcd64a2b808c0a999afba16bb314fbcf18b15eedf0", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { data } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.5\"\n import: [\"@key\", \"@requires\", \"@external\"]\n )\n\n type Query {\n b: B\n }\n\n type B @key(fields: \"id\") {\n id: ID!\n a: [A!]!\n }\n\n type A\n @key(fields: \"compositeId { one two }\", resolvable: false)\n @key(fields: \"id compositeId { two three }\", resolvable: true)\n @key(fields: \"pId\", resolvable: false)\n @key(fields: \"id\", resolvable: false) {\n id: ID!\n pId: ID!\n compositeId: CompositeID!\n name: String! @external\n nameInB: String! @requires(fields: \"name\")\n }\n\n type CompositeID {\n one: ID!\n two: ID!\n three: ID!\n }\n `,\n resolvers: {\n Query: {\n b: () => {\n return {\n __typename: \"B\",\n id: data.b[\"100\"].id,\n a: data.b[\"100\"].a,\n };\n },\n },\n B: {\n __resolveReference: (reference: { id: string }) => {\n const obj = data.b[reference.id];\n\n if (!obj) {\n return null;\n }\n\n return {\n __typename: \"B\",\n id: obj.id,\n a: obj.a,\n };\n },\n a: ({ a }: { a: string[] }) => {\n return a.map((id) => {\n const obj = data.a[id];\n\n if (!obj) {\n return null;\n }\n\n return {\n __typename: \"A\",\n id: obj.id,\n pId: obj.pId,\n compositeId: obj.compositeId,\n };\n });\n },\n },\n A: {\n __resolveReference: (reference: {\n id: string;\n compositeId: { two: string; three: string };\n name?: string;\n }) => {\n const obj = data.a[reference.id];\n\n if (!obj) {\n return null;\n }\n\n if (reference.name) {\n return {\n __typename: \"A\",\n id: obj.id,\n pId: obj.pId,\n compositeId: obj.compositeId,\n name: reference.name,\n };\n }\n\n return {\n __typename: \"A\",\n id: obj.id,\n pId: obj.pId,\n compositeId: obj.compositeId,\n };\n },\n nameInB: ({ name }: { name?: string }) => {\n if (!name) {\n throw new Error(\"A.name was not provided\");\n }\n\n return `b.a.nameInB ${name}`;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "13652e843c7ae6740a2462e79a3b0b73b9c50c1f8778a7ea8aae747d0a65838d", + "source": "export const data: {\n a: Record<\n string,\n {\n id: string;\n pId: string;\n name: string;\n compositeId: {\n one: string;\n two: string;\n three: string;\n };\n }\n >;\n b: Record;\n} = {\n a: {\n \"1\": {\n id: \"1\",\n pId: \"a.1.pId\",\n name: \"a.1\",\n compositeId: {\n one: \"a.1.compositeId.one\",\n two: \"a.1.compositeId.two\",\n three: \"a.1.compositeId.three\",\n },\n },\n \"2\": {\n id: \"2\",\n pId: \"a.2.pId\",\n name: \"a.2\",\n compositeId: {\n one: \"a.2.compositeId.one\",\n two: \"a.2.compositeId.two\",\n three: \"a.2.compositeId.three\",\n },\n },\n },\n b: {\n \"100\": { id: \"100\", a: [\"1\"] },\n \"200\": { id: \"200\", a: [\"1\", \"2\"] },\n },\n};\n" + } + ] + }, + { + "id": "mutations", + "cases": [ + { + "id": "mutations/000", + "query": "\n mutation {\n addProduct(input: { name: \"new\", price: 599.99 }) {\n name\n price\n isExpensive\n isAvailable\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "addProduct": { + "name": "new", + "price": 599.99, + "isExpensive": true, + "isAvailable": true + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "mutations/001", + "query": "\n query {\n product(id: \"p1\") {\n id\n name\n price\n isExpensive\n isAvailable\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "id": "p1", + "name": "p1-name", + "price": 9.99, + "isExpensive": false, + "isAvailable": true + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "mutations/002", + "query": "\n mutation {\n five: add(num: 5, requestId: \"a\")\n ten: multiply(by: 2, requestId: \"a\")\n twelve: add(num: 2, requestId: \"a\")\n final: delete(requestId: \"a\")\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "five": 5, + "ten": 10, + "twelve": 12, + "final": 12 + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "mutations/003", + "query": "\n mutation {\n addCategory(name: \"new\", requestId: \"a\") {\n id\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "addCategory": { + "id": "c-added-a", + "name": "new" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Mutation {\n addProduct(input: AddProductInput!): Product!\n addCategory(name: String!, requestId: String!): Category! @shareable\n multiply(by: Int!, requestId: String!): Int!\n }\n\n type Query {\n product(id: ID!): Product!\n products: [Product!]!\n }\n\n input AddProductInput {\n name: String!\n price: Float!\n }\n\n type Product @key(fields: \"id\") {\n id: ID!\n name: String!\n price: Float!\n }\n\n type Category @key(fields: \"id\") {\n id: ID!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n mutation: Mutation\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Mutation {\n addProduct(input: AddProductInput!): Product!\n addCategory(name: String!, requestId: String!): Category! @shareable\n multiply(by: Int!, requestId: String!): Int!\n}\n\ntype Query {\n product(id: ID!): Product!\n products: [Product!]!\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ninput AddProductInput {\n name: String!\n price: Float!\n}\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n name: String!\n price: Float!\n}\n\ntype Category\n @key(fields: \"id\")\n{\n id: ID!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Category | Product" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\", \"@shareable\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n price: Float! @external\n isExpensive: Boolean! @requires(fields: \"price\")\n isAvailable: Boolean!\n }\n\n type Mutation {\n delete(requestId: String!): Int!\n addCategory(name: String!, requestId: String!): Category! @shareable\n }\n\n type Category @key(fields: \"id\") {\n id: ID!\n name: String!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n mutation: Mutation\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@requires\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n price: Float! @external\n isExpensive: Boolean! @requires(fields: \"price\")\n isAvailable: Boolean!\n}\n\ntype Mutation {\n delete(requestId: String!): Int!\n addCategory(name: String!, requestId: String!): Category! @shareable\n}\n\ntype Category\n @key(fields: \"id\")\n{\n id: ID!\n name: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Category | Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "c", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Mutation {\n add(num: Int!, requestId: String!): Int!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n mutation: Mutation\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Mutation {\n add(num: Int!, requestId: String!): Int!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\ntype Query {\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "05097bd14896f0d66ad34504fac1be5134fcfc192f39194b5294da4308601aeb", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport {\n addCategory,\n addProduct,\n deleteProduct,\n getCategories,\n getProducts,\n initProducts,\n multiplyNumber,\n} from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Mutation {\n addProduct(input: AddProductInput!): Product!\n addCategory(name: String!, requestId: String!): Category! @shareable\n multiply(by: Int!, requestId: String!): Int!\n }\n\n type Query {\n product(id: ID!): Product!\n products: [Product!]!\n }\n\n input AddProductInput {\n name: String!\n price: Float!\n }\n\n type Product @key(fields: \"id\") {\n id: ID!\n name: String!\n price: Float!\n }\n\n type Category @key(fields: \"id\") {\n id: ID!\n }\n `,\n resolvers: {\n Query: {\n async product(_: {}, { id }: { id: string }) {\n initProducts();\n const product = (await getProducts()).find((p) => p.id === id);\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n name: product.name,\n price: product.price,\n };\n },\n async products() {\n initProducts();\n return getProducts();\n },\n },\n Mutation: {\n async multiply(\n _: {},\n args: {\n by: number;\n requestId: string;\n },\n ) {\n return multiplyNumber(args.by, args.requestId);\n },\n async addProduct(\n _: {},\n {\n input,\n }: {\n input: {\n name: string;\n price: number;\n };\n },\n ) {\n return addProduct(input.name, input.price);\n },\n async addCategory(\n _: {},\n {\n name,\n requestId,\n }: {\n name: string;\n requestId: string;\n },\n ) {\n return addCategory(name, requestId);\n },\n },\n Product: {\n async __resolveReference(key: { id: string }) {\n const products = await getProducts();\n const product = products.find((p) => p.id === key.id);\n\n if (!product) {\n return null;\n }\n\n await deleteProduct(product.id);\n\n return {\n id: product.id,\n name: product.name,\n price: product.price,\n };\n },\n },\n Category: {\n __resolveReference(key: { id: string }) {\n const categories = getCategories();\n const category = categories.find((p) => p.id === key.id);\n\n if (!category) {\n return null;\n }\n\n return {\n id: category.id,\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "3ecc406643b32dd602d7b80b8b7b37f1df8b8247565a3d4455350b7e38a023f8", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport {\n addCategory,\n deleteNumber,\n deleteProduct,\n getCategories,\n getProducts,\n} from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\", \"@shareable\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n price: Float! @external\n isExpensive: Boolean! @requires(fields: \"price\")\n isAvailable: Boolean!\n }\n\n type Mutation {\n delete(requestId: String!): Int!\n addCategory(name: String!, requestId: String!): Category! @shareable\n }\n\n type Category @key(fields: \"id\") {\n id: ID!\n name: String!\n }\n `,\n resolvers: {\n Mutation: {\n delete(\n _: {},\n args: {\n requestId: string;\n },\n ) {\n return deleteNumber(args.requestId);\n },\n async addCategory(\n _: {},\n {\n name,\n requestId,\n }: {\n name: string;\n requestId: string;\n },\n ) {\n return addCategory(name, requestId);\n },\n },\n Product: {\n async __resolveReference(key: { id: string; price?: number }) {\n const product = (await getProducts()).find(\n (product) => product.id === key.id,\n );\n\n if (!product) {\n return null;\n }\n\n await deleteProduct(product.id);\n\n if (typeof product.price === \"number\") {\n return {\n id: product.id,\n isAvailable: true,\n price: product.price,\n };\n }\n\n return {\n id: product.id,\n isAvailable: false,\n };\n },\n isExpensive(product: { price?: number }) {\n if (typeof product.price !== \"number\") {\n throw new Error(\"Price is not available\");\n }\n\n return product.price > 100;\n },\n },\n Category: {\n __resolveReference(key: { id: string }) {\n const categories = getCategories();\n const category = categories.find((p) => p.id === key.id);\n\n if (!category) {\n return null;\n }\n\n return {\n id: category.id,\n name: category.name,\n };\n },\n },\n },\n});\n" + }, + { + "path": "c.subgraph.ts", + "sha256": "45f9c52e827c480ac1094cff80f97cde7974895403976b7ecd628dac228254d4", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { addNumber } from \"./data.js\";\n\nexport default createSubgraph(\"c\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Mutation {\n add(num: Int!, requestId: String!): Int!\n }\n `,\n resolvers: {\n Mutation: {\n add(\n _: {},\n args: {\n num: number;\n requestId: string;\n },\n ) {\n return addNumber(args.num, args.requestId);\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "d342b6d4ec6c633e08942ca811dc3dead0fff8d640edd10eebc234517b0c23ba", + "source": "interface Product {\n id: string;\n name: string;\n price: number;\n}\n\ninterface Category {\n id: string;\n name: string;\n}\n\nlet products: Product[] = [];\nlet categories: Category[] = [];\nconst numbers: Record = {};\n\nexport async function getProducts(): Promise {\n return products;\n}\n\nexport function getCategories(): Category[] {\n return categories;\n}\n\nexport async function addProduct(name: string, price: number) {\n const newProduct = {\n id: \"p-added-\" + products.length,\n name: name,\n price: price,\n };\n products.push(newProduct);\n return newProduct;\n}\n\nexport async function addCategory(name: string, requestId: string) {\n if (categories.some((c) => c.id === \"c-added-\" + requestId)) {\n throw new Error(\"Category with this requestId was already added\");\n }\n\n const newCategory = {\n id: \"c-added-\" + requestId,\n name: name,\n };\n categories.push(newCategory);\n return newCategory;\n}\n\nexport async function deleteProduct(id: string) {\n const after = products.filter((p) => p.id !== id);\n products = after;\n}\n\nexport function initProducts() {\n const product = {\n id: \"p1\",\n name: \"p1-name\",\n price: 9.99,\n };\n\n if (products.some((p) => p.id === product.id)) {\n return;\n }\n\n products.push(product);\n}\n\nfunction getNumber(requestId: string) {\n const num = numbers[requestId];\n return num ?? 0;\n}\n\nexport function addNumber(num: number, requestId: string) {\n const existingNumber = getNumber(requestId);\n const sum = existingNumber + num;\n numbers[requestId] = sum;\n return sum;\n}\n\nexport function multiplyNumber(by: number, requestId: string) {\n const existingNumber = getNumber(requestId);\n const result = existingNumber * by;\n numbers[requestId] = result;\n return result;\n}\n\nexport function deleteNumber(requestId: string) {\n const num = getNumber(requestId);\n if (typeof numbers[requestId] === \"number\") {\n delete numbers[requestId];\n }\n return num;\n}\n" + } + ] + }, + { + "id": "mysterious-external", + "cases": [ + { + "id": "mysterious-external/000", + "query": "\n query {\n cheapestProduct {\n id\n price\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "cheapestProduct": { + "id": "1", + "price": 100, + "name": "name-1" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "mysterious-external/001", + "query": "\n query {\n products {\n name\n price\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "name": "name-1", + "price": 100, + "id": "1" + }, + { + "name": "name-2", + "price": 200, + "id": "2" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "price", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\"]\n )\n\n # Composition will fail if it's not an extension (I have no idea why this is the case)\n extend type Product @key(fields: \"id\") {\n id: ID! @external\n price: Float\n }\n\n type Query {\n cheapestProduct: Product\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\nextend type Product\n @key(fields: \"id\")\n{\n id: ID! @external\n price: Float\n}\n\ntype Query {\n cheapestProduct: Product\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product" + }, + { + "name": "product", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n name: String!\n }\n\n type Query {\n products: [Product!]!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n name: String!\n}\n\ntype Query {\n products: [Product!]!\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product" + } + ], + "fixtureModules": [ + { + "path": "data.ts", + "sha256": "7a72bbbcf26eaaec331d950360129ad92445715224249bd1f802a55794508819", + "source": "export const products = [\n {\n id: \"1\",\n name: \"name-1\",\n price: 100,\n },\n {\n id: \"2\",\n name: \"name-2\",\n price: 200,\n },\n];\n" + }, + { + "path": "price.subgraph.ts", + "sha256": "5a599d53ffca8b44d53d71d7e8261b0c43d2531e99713f994e958fbe77f7566d", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"price\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\"]\n )\n\n # Composition will fail if it's not an extension (I have no idea why this is the case)\n extend type Product @key(fields: \"id\") {\n id: ID! @external\n price: Float\n }\n\n type Query {\n cheapestProduct: Product\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(key: { id: string }) {\n const product = products.find((product) => product.id === key.id);\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n price: product.price,\n };\n },\n },\n Query: {\n cheapestProduct() {\n let cheapest: (typeof products)[number] | null = null;\n\n for (const product of products) {\n if (cheapest) {\n if (product.price < cheapest.price) {\n cheapest = product;\n }\n } else {\n cheapest = product;\n }\n }\n\n return cheapest;\n },\n },\n },\n});\n" + }, + { + "path": "product.subgraph.ts", + "sha256": "4c06e6db4b427e9fe0e5143e4a1c4091bea45fd6727ea1755bef072e0609f937", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"product\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n name: String!\n }\n\n type Query {\n products: [Product!]!\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(key: { id: string }) {\n const product = products.find((product) => product.id === key.id);\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n name: product.name,\n };\n },\n },\n Query: {\n products() {\n return products.map((p) => ({\n id: p.id,\n name: p.name,\n }));\n },\n },\n },\n});\n" + } + ] + }, + { + "id": "nested-provides", + "cases": [ + { + "id": "nested-provides/000", + "query": "\n query {\n products {\n id\n categories {\n id\n name\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "categories": [ + { + "id": "c1", + "name": "Category 1" + }, + { + "id": "c2", + "name": "Category 2" + } + ] + }, + { + "id": "p2", + "categories": [ + { + "id": "c3", + "name": "Category 3" + }, + { + "id": "c2", + "name": "Category 2" + } + ] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "nested-provides/001", + "query": "\n query {\n products {\n id\n categories {\n id\n name\n subCategories {\n id\n name\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "categories": [ + { + "id": "c1", + "name": "Category 1", + "subCategories": [ + { + "id": "c2", + "name": "Category 2" + } + ] + }, + { + "id": "c2", + "name": "Category 2", + "subCategories": [ + { + "id": "c3", + "name": "Category 3" + } + ] + } + ] + }, + { + "id": "p2", + "categories": [ + { + "id": "c3", + "name": "Category 3", + "subCategories": [ + { + "id": "c1", + "name": "Category 1" + } + ] + }, + { + "id": "c2", + "name": "Category 2", + "subCategories": [ + { + "id": "c3", + "name": "Category 3" + } + ] + } + ] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "all-products", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "category", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@external\", \"@provides\"]\n )\n\n type Query {\n products: [Product]\n @shareable\n @provides(fields: \"categories { id name subCategories { id name } }\")\n }\n\n type Product @key(fields: \"id\") {\n id: ID!\n categories: [Category] @external\n }\n\n type Category @key(fields: \"id\") {\n id: ID!\n \"\"\"\n This field is provided by Query.products\n and we deliberately don't resolve it.\n The test suite is about checking if @provides is used correctly.\n \"\"\"\n name: String\n subCategories: [Category] @external\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\", \"@external\", \"@provides\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n products: [Product] @shareable @provides(fields: \"categories { id name subCategories { id name } }\")\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n categories: [Category] @external\n}\n\ntype Category\n @key(fields: \"id\")\n{\n id: ID!\n\n \"\"\"\n This field is provided by Query.products\n and we deliberately don't resolve it.\n The test suite is about checking if @provides is used correctly.\n \"\"\"\n name: String\n subCategories: [Category] @external\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Category | Product" + }, + { + "name": "subcategories", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n categories: [Category] @shareable\n }\n\n type Category @key(fields: \"id\") {\n id: ID!\n subCategories: [Category] @shareable\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n categories: [Category] @shareable\n}\n\ntype Category\n @key(fields: \"id\")\n{\n id: ID!\n subCategories: [Category] @shareable\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Category | Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "all-products.subgraph.ts", + "sha256": "cec25d29798e33e34226593d90c1e42f7035d0402f4924453a68325bb0c4cfdb", + "source": "import { shouldPunishForPoorPlans } from \"../../env.js\";\nimport { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\n// I deliberately name it all-products to make sure it's first in the list of subgraphs (in case the order matters)\nexport default createSubgraph(\"all-products\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(key: { id: string }) {\n if (shouldPunishForPoorPlans()) {\n throw new Error(\"You should be using the categories subgraph!\");\n }\n\n const product = products.find((p) => p.id === key.id);\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n };\n },\n },\n },\n});\n" + }, + { + "path": "category.subgraph.ts", + "sha256": "4883312a3361dae693493f3f416270434dcca38517854affed9887398b8cbec8", + "source": "import { shouldPunishForPoorPlans } from \"../../env.js\";\nimport { createSubgraph } from \"../../subgraph.js\";\nimport { categories, products } from \"./data.js\";\n\nexport default createSubgraph(\"category\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@external\", \"@provides\"]\n )\n\n type Query {\n products: [Product]\n @shareable\n @provides(fields: \"categories { id name subCategories { id name } }\")\n }\n\n type Product @key(fields: \"id\") {\n id: ID!\n categories: [Category] @external\n }\n\n type Category @key(fields: \"id\") {\n id: ID!\n \"\"\"\n This field is provided by Query.products\n and we deliberately don't resolve it.\n The test suite is about checking if @provides is used correctly.\n \"\"\"\n name: String\n subCategories: [Category] @external\n }\n `,\n resolvers: {\n Query: {\n products() {\n return products.map((p) => ({\n id: p.id,\n categories: p.categories.map((catId) => {\n const cat = categories.find((c) => c.id === catId);\n\n if (!cat) {\n return null;\n }\n\n return {\n id: cat.id,\n name: cat.name,\n subCategories: cat.subCategories.map((subCatId) => {\n const subCat = categories.find((c) => c.id === subCatId);\n\n if (!subCat) {\n return null;\n }\n\n return {\n id: subCat.id,\n name: subCat.name,\n };\n }),\n };\n }),\n }));\n },\n },\n Product: {\n __resolveReference(key: { id: string }, context: any) {\n if (shouldPunishForPoorPlans()) {\n throw new Error(\"You should be using the categories subgraph!\");\n }\n\n const product = products.find((p) => p.id === key.id);\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n };\n },\n },\n Category: {\n __resolveReference(key: { id: string }, context: any) {\n if (shouldPunishForPoorPlans()) {\n throw new Error(\"You should be using the categories subgraph!\");\n }\n\n const cat = categories.find((c) => c.id === key.id);\n\n if (!cat) {\n return null;\n }\n\n return {\n id: cat.id,\n // We explicitly don't resolve `name` here\n // name: cat.name,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "d5bb60afb87e46d83cb045a96d795a3de69ea06bb64616bc6bf74694a6fcfa3e", + "source": "export const products = [\n {\n id: \"p1\",\n categories: [\"c1\", \"c2\"],\n },\n {\n id: \"p2\",\n categories: [\"c3\", \"c2\"],\n },\n];\n\nexport const categories = [\n {\n id: \"c1\",\n name: \"Category 1\",\n subCategories: [\"c2\"],\n },\n {\n id: \"c2\",\n name: \"Category 2\",\n subCategories: [\"c3\"],\n },\n {\n id: \"c3\",\n name: \"Category 3\",\n subCategories: [\"c1\"],\n },\n];\n" + }, + { + "path": "subcategories.subgraph.ts", + "sha256": "e7f747376171278a687e3edb516a60478697628882e2ccd240f61a1d3a65cb0d", + "source": "import { shouldPunishForPoorPlans } from \"../../env.js\";\nimport { createSubgraph } from \"../../subgraph.js\";\nimport { categories, products } from \"./data.js\";\n\nexport default createSubgraph(\"subcategories\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n categories: [Category] @shareable\n }\n\n type Category @key(fields: \"id\") {\n id: ID!\n subCategories: [Category] @shareable\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(key: { id: string }, context: any) {\n if (shouldPunishForPoorPlans()) {\n throw new Error(\"You should be using the categories subgraph!\");\n }\n\n const product = products.find((p) => p.id === key.id);\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n categories: product.categories,\n };\n },\n categories(product: { categories: string[] }, _a: unknown, context: any) {\n if (shouldPunishForPoorPlans()) {\n throw new Error(\"You should be using the categories subgraph!\");\n }\n\n return product.categories.map((catId) => {\n const cat = categories.find((c) => c.id === catId);\n\n if (!cat) {\n return null;\n }\n\n return {\n id: cat.id,\n subCategories: cat.subCategories,\n };\n });\n },\n },\n Category: {\n __resolveReference(key: { id: string }) {\n const cat = categories.find((c) => c.id === key.id);\n\n if (!cat) {\n return null;\n }\n\n return {\n id: cat.id,\n subCategories: cat.subCategories,\n };\n },\n subCategories(category: { subCategories: string[] }) {\n return category.subCategories.map((catId) => {\n const cat = categories.find((c) => c.id === catId);\n\n if (!cat) {\n return null;\n }\n\n return {\n id: cat.id,\n subCategories: cat.subCategories,\n };\n });\n },\n },\n },\n});\n" + } + ] + }, + { + "id": "node", + "cases": [ + { + "id": "node/000", + "query": "\n {\n productNode {\n ... on Product {\n id\n name\n __typename\n price\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "productNode": { + "id": "p-1", + "name": "Product 1", + "__typename": "Product", + "price": 10 + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "node", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n productNode: Node\n categoryNode: Node\n }\n\n interface Node {\n id: ID!\n }\n\n type Product implements Node @key(fields: \"id\") {\n id: ID!\n }\n\n type Category implements Node @key(fields: \"id\") {\n id: ID!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n productNode: Node\n categoryNode: Node\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ninterface Node {\n id: ID!\n}\n\ntype Product implements Node\n @key(fields: \"id\")\n{\n id: ID!\n}\n\ntype Category implements Node\n @key(fields: \"id\")\n{\n id: ID!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Category | Product" + }, + { + "name": "node-two", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n interface Node {\n id: ID!\n }\n\n type Product implements Node @key(fields: \"id\") {\n id: ID!\n }\n\n type Category implements Node @key(fields: \"id\") {\n id: ID!\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ninterface Node {\n id: ID!\n}\n\ntype Product implements Node\n @key(fields: \"id\")\n{\n id: ID!\n}\n\ntype Category implements Node\n @key(fields: \"id\")\n{\n id: ID!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Category | Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "types", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n interface Node {\n id: ID!\n }\n\n type Product implements Node @key(fields: \"id\") @shareable {\n id: ID!\n name: String!\n price: Float!\n }\n\n type Category implements Node @key(fields: \"id\") {\n id: ID!\n name: String!\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ninterface Node {\n id: ID!\n}\n\ntype Product implements Node\n @key(fields: \"id\")\n @shareable\n{\n id: ID!\n name: String!\n price: Float!\n}\n\ntype Category implements Node\n @key(fields: \"id\")\n{\n id: ID!\n name: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Category | Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "data.ts", + "sha256": "dd3ba1e1231367cb8131321aa6c28694ca4e61d6c8cb22d701870e94dd7ea388", + "source": "export const products = [\n {\n id: \"p-1\",\n name: \"Product 1\",\n price: 10.0,\n },\n {\n id: \"p-2\",\n name: \"Product 2\",\n price: 20.0,\n },\n];\n\nexport const categories = [\n {\n id: \"pc-1\",\n name: \"Category 1\",\n },\n {\n id: \"c-2\",\n name: \"Category 2\",\n },\n];\n" + }, + { + "path": "node.subgraph.ts", + "sha256": "fa680a47506c1aa32c5921e6caf0f6b34f3ea4c47abebdf41618e3007a52e417", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { categories, products } from \"./data.js\";\n\nexport default createSubgraph(\"node\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n productNode: Node\n categoryNode: Node\n }\n\n interface Node {\n id: ID!\n }\n\n type Product implements Node @key(fields: \"id\") {\n id: ID!\n }\n\n type Category implements Node @key(fields: \"id\") {\n id: ID!\n }\n `,\n resolvers: {\n Query: {\n productNode() {\n return {\n __typename: \"Product\",\n id: products[0].id,\n };\n },\n categoryNode() {\n return {\n __typename: \"Category\",\n id: categories[0].id,\n };\n },\n },\n Product: {\n __resolveReference(key: { id: string }) {\n const product = products.find((p) => p.id === key.id);\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n };\n },\n },\n Category: {\n __resolveReference(key: { id: string }) {\n const cat = categories.find((c) => c.id === key.id);\n\n if (!cat) {\n return null;\n }\n\n return {\n id: cat.id,\n };\n },\n },\n },\n});\n" + }, + { + "path": "types.subgraph.ts", + "sha256": "1d0c73d29456d9232b451dfc25be667cd4e1d88d179bb44a678052f8962f54a3", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { categories, products } from \"./data.js\";\n\nexport default createSubgraph(\"types\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n interface Node {\n id: ID!\n }\n\n type Product implements Node @key(fields: \"id\") @shareable {\n id: ID!\n name: String!\n price: Float!\n }\n\n type Category implements Node @key(fields: \"id\") {\n id: ID!\n name: String!\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(key: { id: string }) {\n const product = products.find((p) => p.id === key.id);\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n name: product.name,\n price: product.price,\n };\n },\n },\n Category: {\n __resolveReference(key: { id: string }) {\n const cat = categories.find((c) => c.id === key.id);\n\n if (!cat) {\n return null;\n }\n\n return {\n id: cat.id,\n name: cat.name,\n };\n },\n },\n },\n});\n" + } + ] + }, + { + "id": "non-resolvable-interface-object", + "cases": [ + { + "id": "non-resolvable-interface-object/000", + "query": "\n query {\n b {\n id\n field\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "b": { + "id": "n1", + "field": "foo" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "non-resolvable-interface-object/001", + "query": "\n query {\n a {\n field\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": null, + "hasExpectedErrors": true, + "expectsErrors": true + }, + { + "id": "non-resolvable-interface-object/002", + "query": "\n query {\n b {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "b": { + "id": "n1" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "non-resolvable-interface-object/003", + "query": "\n query {\n a {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "a": null + }, + "hasExpectedErrors": true, + "expectsErrors": true + }, + { + "id": "non-resolvable-interface-object/004", + "query": "\n query {\n product {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "id": "p1" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "non-resolvable-interface-object/005", + "query": "\n query {\n product {\n id\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": false, + "expectedData": null, + "hasExpectedErrors": true, + "expectsErrors": true + }, + { + "id": "non-resolvable-interface-object/006", + "query": "\n query {\n product {\n ... on Bread {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "id": "p1" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\"]\n )\n\n interface Node @key(fields: \"id\") {\n id: ID!\n }\n\n type Product @key(fields: \"id\", resolvable: false) @interfaceObject {\n id: ID!\n }\n\n type Query {\n a: Node\n product: Product!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@interfaceObject\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @interfaceObject on OBJECT\n\ninterface Node\n @key(fields: \"id\")\n{\n id: ID!\n}\n\ntype Product\n @key(fields: \"id\", resolvable: false)\n @interfaceObject\n{\n id: ID!\n}\n\ntype Query {\n a: Node\n product: Product!\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\"]\n )\n\n type Node @key(fields: \"id\", resolvable: false) @interfaceObject {\n id: ID!\n field: String\n }\n\n interface Product @key(fields: \"id\") {\n id: ID!\n }\n\n type Bread implements Product @key(fields: \"id\") {\n id: ID!\n name: String!\n }\n\n type Query {\n b: Node\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@interfaceObject\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @interfaceObject on OBJECT\n\ntype Node\n @key(fields: \"id\", resolvable: false)\n @interfaceObject\n{\n id: ID!\n field: String\n}\n\ninterface Product\n @key(fields: \"id\")\n{\n id: ID!\n}\n\ntype Bread implements Product\n @key(fields: \"id\")\n{\n id: ID!\n name: String!\n}\n\ntype Query {\n b: Node\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Bread | Node" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "709c07b785e551a04a2b635aba2c5e09286a20025c6e55be13f223df8268439f", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\"]\n )\n\n interface Node @key(fields: \"id\") {\n id: ID!\n }\n\n type Product @key(fields: \"id\", resolvable: false) @interfaceObject {\n id: ID!\n }\n\n type Query {\n a: Node\n product: Product!\n }\n `,\n resolvers: {\n Query: {\n a() {\n return { __typename: \"Node\", id: \"n1\" };\n },\n product() {\n return { __typename: \"Product\", id: products[0].id };\n },\n },\n Node: {\n __resolveReference() {\n throw new Error(\"Not resolvable as it is an interface object\");\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "93aedb3600add39a2f8b97feded5c0a098d55e652602b780db0cdc6761d290e1", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\"]\n )\n\n type Node @key(fields: \"id\", resolvable: false) @interfaceObject {\n id: ID!\n field: String\n }\n\n interface Product @key(fields: \"id\") {\n id: ID!\n }\n\n type Bread implements Product @key(fields: \"id\") {\n id: ID!\n name: String!\n }\n\n type Query {\n b: Node\n }\n `,\n resolvers: {\n Query: {\n b() {\n return { __typename: \"Node\", id: \"n1\" };\n },\n },\n Node: {\n __resolveReference() {\n throw new Error(\"Not resolvable\");\n },\n field() {\n return \"foo\";\n },\n },\n Product: {\n __resolveReference(key: { id: string }) {\n return products.find((p) => p.id === key.id);\n },\n },\n Bread: {\n __resolveReference(key: { id: string }) {\n return products.find((p) => p.id === key.id);\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "bc44295e53b2e7864a9fdfb8262764c3c01e749611ef079c85b12140b405a783", + "source": "export const products = [\n {\n __typename: \"Bread\",\n id: \"p1\",\n name: \"Bread\",\n },\n];\n" + } + ] + }, + { + "id": "null-keys", + "cases": [ + { + "id": "null-keys/000", + "query": "\n query {\n bookContainers {\n book {\n upc\n author {\n name\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "bookContainers": [ + { + "book": { + "upc": "b1", + "author": { + "name": "Alice" + } + } + }, + { + "book": { + "upc": "b2", + "author": { + "name": "Bob" + } + } + }, + { + "book": { + "upc": "b3", + "author": null + } + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n bookContainers: [BookContainer]\n }\n type BookContainer {\n book: Book\n }\n type Book @key(fields: \"upc\") {\n upc: ID!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n bookContainers: [BookContainer]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype BookContainer {\n book: Book\n}\n\ntype Book\n @key(fields: \"upc\")\n{\n upc: ID!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Book @key(fields: \"id\") @key(fields: \"upc\") {\n id: ID!\n upc: ID!\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Book\n @key(fields: \"id\")\n @key(fields: \"upc\")\n{\n id: ID!\n upc: ID!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "c", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Book @key(fields: \"id\") {\n id: ID!\n author: Author\n }\n\n type Author {\n id: ID!\n name: String\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Book\n @key(fields: \"id\")\n{\n id: ID!\n author: Author\n}\n\ntype Author {\n id: ID!\n name: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "0f8ed17169e398e96ce3f24321cf8ba6dce3059da6ecf905c05b11964ed95a11", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { books } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n bookContainers: [BookContainer]\n }\n type BookContainer {\n book: Book\n }\n type Book @key(fields: \"upc\") {\n upc: ID!\n }\n `,\n resolvers: {\n Query: {\n bookContainers() {\n return books.map((book) => ({ book: { upc: book.upc } }));\n },\n },\n Book: {\n __resolveReference(reference: { upc: String }) {\n if (reference != null) {\n let book = books.find((book) => book.upc === reference.upc);\n if (book != null && book.upc !== null) {\n return {\n __typename: \"Book\",\n upc: book.upc,\n };\n }\n }\n throw new Error(\"Invalid reference\");\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "c0a6e1a23d54e7084bec901b3f9232a0f52842be1d22c4f46f42e0451c7813b9", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { books } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Book @key(fields: \"id\") @key(fields: \"upc\") {\n id: ID!\n upc: ID!\n }\n `,\n resolvers: {\n Book: {\n __resolveReference(reference: { id: String } | { upc: String }) {\n if (reference != null) {\n let book: { id: string; upc: string } | undefined;\n if (\"id\" in reference) {\n book = books.find((book) => book.id === reference.id);\n }\n if (\"upc\" in reference) {\n book = books.find((book) => book.upc === reference.upc);\n }\n if (book != null) {\n // `a` has `Book` entities with upc: `b1, b2, b3`, but `b` has `Book` entities with only `b1` and `b2`.\n // `b3` is not available and this subgraph is the only possible step to get to the other subgraph (need of `id` field) to resolve the author.\n if (book.id === \"3\") {\n return null;\n }\n return {\n __typename: \"Book\",\n id: book.id,\n upc: book.upc,\n };\n }\n }\n throw new Error(\"Invalid reference\");\n },\n },\n },\n});\n" + }, + { + "path": "c.subgraph.ts", + "sha256": "840968711d127c92d393acf115a1d05f40e63e41db5ca7d11bfbb24e2f6c659e", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { books } from \"./data.js\";\n\nexport default createSubgraph(\"c\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Book @key(fields: \"id\") {\n id: ID!\n author: Author\n }\n\n type Author {\n id: ID!\n name: String\n }\n `,\n resolvers: {\n Book: {\n __resolveReference(reference: { id: String } | { upc: String }) {\n if (reference != null) {\n let book:\n | { id: string; author: { id: string; name: string } }\n | undefined;\n if (\"id\" in reference && reference.id !== null) {\n book = books.find((book) => book.id === reference.id);\n }\n if (\"upc\" in reference && reference.upc !== null) {\n book = books.find((book) => book.upc === reference.upc);\n }\n if (book != null) {\n return {\n __typename: \"Book\",\n id: book.id,\n author: {\n __typename: \"Author\",\n id: book.author.id,\n name: book.author.name,\n },\n };\n }\n }\n throw new Error(\"Invalid reference\");\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "3480c4fa2734cc7b2935a3df95ee8feebacb614fb55d3c7075dae741fe1e29f9", + "source": "export const books = [\n {\n __typename: \"Book\",\n id: \"1\",\n upc: \"b1\",\n author: {\n __typename: \"Author\",\n id: \"a1\",\n name: \"Alice\",\n },\n },\n {\n __typename: \"Book\",\n id: \"2\",\n upc: \"b2\",\n author: {\n __typename: \"Author\",\n id: \"a2\",\n name: \"Bob\",\n },\n },\n {\n __typename: \"Book\",\n id: \"3\",\n upc: \"b3\",\n author: {\n __typename: \"Author\",\n id: \"a3\",\n name: \"Jack\",\n },\n },\n];\n" + } + ] + }, + { + "id": "override-type-interface", + "cases": [ + { + "id": "override-type-interface/000", + "query": "\n query {\n feed {\n id\n createdAt\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "feed": [ + { + "id": "i1", + "createdAt": "i1-createdAt" + }, + { + "id": "i2", + "createdAt": "i2-createdAt" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "override-type-interface/001", + "query": "\n query {\n feed {\n ... on TextPost {\n id\n body\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "feed": [ + {}, + {} + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "override-type-interface/002", + "query": "\n query {\n anotherFeed {\n createdAt\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "anotherFeed": [ + { + "createdAt": "i1-createdAt" + }, + { + "createdAt": "i2-createdAt" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "override-type-interface/003", + "query": "\n {\n anotherFeed {\n createdAt\n id\n ... on ImagePost {\n createdAt\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "anotherFeed": [ + { + "createdAt": "i1-createdAt", + "id": "i1" + }, + { + "createdAt": "i2-createdAt", + "id": "i2" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n interface Post {\n id: ID!\n createdAt: String!\n }\n\n type ImagePost implements Post @key(fields: \"id\") {\n id: ID!\n createdAt: String!\n }\n\n type Query {\n feed: [Post]\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ninterface Post {\n id: ID!\n createdAt: String!\n}\n\ntype ImagePost implements Post\n @key(fields: \"id\")\n{\n id: ID!\n createdAt: String!\n}\n\ntype Query {\n feed: [Post]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = ImagePost" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@override\"]\n )\n\n interface Post {\n id: ID!\n createdAt: String!\n }\n\n type TextPost implements Post @key(fields: \"id\") {\n id: ID!\n createdAt: String!\n body: String!\n }\n\n interface AnotherPost {\n id: ID!\n createdAt: String!\n }\n\n type ImagePost implements AnotherPost @key(fields: \"id\") {\n id: ID!\n createdAt: String! @override(from: \"a\")\n }\n\n type Query {\n anotherFeed: [AnotherPost]\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\", \"@override\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ninterface Post {\n id: ID!\n createdAt: String!\n}\n\ntype TextPost implements Post\n @key(fields: \"id\")\n{\n id: ID!\n createdAt: String!\n body: String!\n}\n\ninterface AnotherPost {\n id: ID!\n createdAt: String!\n}\n\ntype ImagePost implements AnotherPost\n @key(fields: \"id\")\n{\n id: ID!\n createdAt: String! @override(from: \"a\")\n}\n\ntype Query {\n anotherFeed: [AnotherPost]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = ImagePost | TextPost" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "db3a1ba13c8827c244dc1eb234d977b577271d639147562c6a97aee1a996d82f", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { imagePosts } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n interface Post {\n id: ID!\n createdAt: String!\n }\n\n type ImagePost implements Post @key(fields: \"id\") {\n id: ID!\n createdAt: String!\n }\n\n type Query {\n feed: [Post]\n }\n `,\n resolvers: {\n Query: {\n feed() {\n return imagePosts;\n },\n },\n ImagePost: {\n __resolveReference(key: { id: string }) {\n const post = imagePosts.find((p) => p.id === key.id);\n\n if (!post) {\n return null;\n }\n\n return {\n id: post.id,\n createdAt: post.createdAt,\n };\n },\n createdAt() {\n return \"NEVER\";\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "c3971c48e04b0e58158e31b8cf8d07c84f8f93cc4f9d49b9cfae0927ff3bba17", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { imagePosts, textPosts } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@override\"]\n )\n\n interface Post {\n id: ID!\n createdAt: String!\n }\n\n type TextPost implements Post @key(fields: \"id\") {\n id: ID!\n createdAt: String!\n body: String!\n }\n\n interface AnotherPost {\n id: ID!\n createdAt: String!\n }\n\n type ImagePost implements AnotherPost @key(fields: \"id\") {\n id: ID!\n createdAt: String! @override(from: \"a\")\n }\n\n type Query {\n anotherFeed: [AnotherPost]\n }\n `,\n resolvers: {\n Query: {\n anotherFeed() {\n return imagePosts;\n },\n },\n TextPost: {\n __resolveReference(key: { id: string }) {\n const post = textPosts.find((p) => p.id === key.id);\n\n if (!post) {\n return null;\n }\n\n return {\n id: post.id,\n createdAt: post.createdAt,\n body: post.body,\n };\n },\n },\n ImagePost: {\n __resolveReference(key: { id: string }) {\n const post = imagePosts.find((p) => p.id === key.id);\n\n if (!post) {\n return null;\n }\n\n return {\n id: post.id,\n createdAt: post.createdAt,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "ed3194df120ff7a37cf428a7586592071fa2dbb553f3f59a6528aff7ad529233", + "source": "export const imagePosts = [\n {\n __typename: \"ImagePost\",\n id: \"i1\",\n createdAt: \"i1-createdAt\",\n },\n {\n __typename: \"ImagePost\",\n id: \"i2\",\n createdAt: \"i2-createdAt\",\n },\n];\n\nexport const textPosts = [\n {\n __typename: \"TextPost\",\n id: \"t1\",\n createdAt: \"t1-createdAt\",\n body: \"t1-body\",\n },\n {\n __typename: \"TextPost\",\n id: \"t2\",\n createdAt: \"t2-createdAt\",\n body: \"t2-body\",\n },\n];\n" + } + ] + }, + { + "id": "override-with-requires", + "cases": [ + { + "id": "override-with-requires/000", + "query": "\n {\n userInA {\n id\n name\n aName\n cName\n }\n userInB {\n id\n name\n aName\n cName\n }\n userInC {\n id\n name\n aName\n cName\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "userInA": { + "id": "u1", + "name": "u1-name", + "aName": "a__u1-name", + "cName": "c__u1-name" + }, + "userInB": { + "id": "u2", + "name": "u2-name", + "aName": "a__u2-name", + "cName": "c__u2-name" + }, + "userInC": { + "id": "u3", + "name": "u3-name", + "aName": "a__u3-name", + "cName": "c__u3-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "override-with-requires/001", + "query": "\n query {\n userInC {\n cName\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "userInC": { + "cName": "c__u3-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "override-with-requires/002", + "query": "\n query {\n userInA {\n cName\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "userInA": { + "cName": "c__u1-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "override-with-requires/003", + "query": "\n query {\n userInA {\n aName\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "userInA": { + "aName": "a__u1-name" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@requires\", \"@external\"]\n )\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @external\n aName: String! @requires(fields: \"name\")\n }\n\n type Query {\n userInA: User\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@requires\", \"@external\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n name: String! @external\n aName: String! @requires(fields: \"name\")\n}\n\ntype Query {\n userInA: User\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@override\"]\n )\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @override(from: \"c\")\n }\n\n type Query {\n userInB: User\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@override\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n name: String! @override(from: \"c\")\n}\n\ntype Query {\n userInB: User\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + }, + { + "name": "c", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@requires\", \"@external\"]\n )\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @external\n cName: String! @requires(fields: \"name\")\n }\n\n type Query {\n userInC: User\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@requires\", \"@external\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n name: String! @external\n cName: String! @requires(fields: \"name\")\n}\n\ntype Query {\n userInC: User\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "652ee31ad5bc168acdd3bc86f5bed414385fc29c9b81570e50b0cce3c09afeca", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@requires\", \"@external\"]\n )\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @external\n aName: String! @requires(fields: \"name\")\n }\n\n type Query {\n userInA: User\n }\n `,\n resolvers: {\n Query: {\n userInA() {\n return users[0];\n },\n },\n User: {\n __resolveReference(key: { id: string } | { id: string; name: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n if (\"name\" in key) {\n return {\n id: user.id,\n name: user.name,\n };\n }\n\n return {\n id: user.id,\n };\n },\n aName(user: { name: string }) {\n return `a__${user.name}`;\n },\n name() {\n return \"NEVER\";\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "6428449ae318be2c03d446143011c80d198b60371d9f6544e1290713000aa1ad", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@override\"]\n )\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @override(from: \"c\")\n }\n\n type Query {\n userInB: User\n }\n `,\n resolvers: {\n Query: {\n userInB() {\n return users[1];\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n name: user.name,\n };\n },\n },\n },\n});\n" + }, + { + "path": "c.subgraph.ts", + "sha256": "0c4c88054dce079e001824b642fedfb329a045ac48b4ad3c66990a06dcdb0bff", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"c\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@requires\", \"@external\"]\n )\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @external\n cName: String! @requires(fields: \"name\")\n }\n\n type Query {\n userInC: User\n }\n `,\n resolvers: {\n Query: {\n userInC() {\n return users[2];\n },\n },\n User: {\n __resolveReference(key: { id: string } | { id: string; name: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n if (\"name\" in key) {\n return {\n id: user.id,\n name: user.name,\n };\n }\n\n return {\n id: user.id,\n };\n },\n name() {\n return \"NEVER\";\n },\n cName(user: { name: string }) {\n return `c__${user.name}`;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "25229fa9da466caebaeb30e3c69f26822b7a688babde0136b82baba1f66a2863", + "source": "export const users = [\n {\n id: \"u1\",\n name: \"u1-name\",\n },\n {\n id: \"u2\",\n name: \"u2-name\",\n },\n {\n id: \"u3\",\n name: \"u3-name\",\n },\n];\n" + } + ] + }, + { + "id": "parent-entity-call", + "cases": [ + { + "id": "parent-entity-call/000", + "query": "\n query {\n products {\n id\n category {\n id\n details {\n products\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "p1", + "category": { + "id": "c1", + "details": { + "products": 2 + } + } + }, + { + "id": "p2", + "category": { + "id": "c2", + "details": { + "products": 1 + } + } + }, + { + "id": "p3", + "category": { + "id": "c1", + "details": { + "products": 2 + } + } + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@shareable\"]\n )\n\n type Query {\n products: [Product!]!\n }\n\n type Product @key(fields: \"id\") @key(fields: \"id pid\") {\n id: ID!\n pid: ID!\n category: Category @shareable\n }\n\n type Category @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n products: [Product!]!\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Product\n @key(fields: \"id\")\n @key(fields: \"id pid\")\n{\n id: ID!\n pid: ID!\n category: Category @shareable\n}\n\ntype Category\n @key(fields: \"id\")\n{\n id: ID!\n name: String! @shareable\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Category | Product" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Product @key(fields: \"id pid\") {\n id: ID!\n pid: ID!\n category: Category @shareable\n }\n\n type Category @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"id pid\")\n{\n id: ID!\n pid: ID!\n category: Category @shareable\n}\n\ntype Category\n @key(fields: \"id\")\n{\n id: ID!\n name: String! @shareable\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Category | Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "c", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Category {\n details: CategoryDetails\n }\n\n type Product @key(fields: \"id pid\") {\n id: ID!\n pid: ID!\n category: Category @shareable\n }\n\n type CategoryDetails {\n products: Int\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Category {\n details: CategoryDetails\n}\n\ntype Product\n @key(fields: \"id pid\")\n{\n id: ID!\n pid: ID!\n category: Category @shareable\n}\n\ntype CategoryDetails {\n products: Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "4357f0445b2210c07fee719f9059ef64870be1b4e4572ffb6d5a7887ae2b6580", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products, categories } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@shareable\"]\n )\n\n type Query {\n products: [Product!]!\n }\n\n type Product @key(fields: \"id\") @key(fields: \"id pid\") {\n id: ID!\n pid: ID!\n category: Category @shareable\n }\n\n type Category @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n }\n `,\n resolvers: {\n Query: {\n products() {\n return products.map((p) => ({\n id: p.id,\n pid: p.pid,\n categoryId: p.categoryId,\n }));\n },\n },\n Product: {\n __resolveReference(\n key:\n | {\n id: string;\n }\n | {\n id: string;\n pid: string;\n },\n ) {\n const product = products.find(\n (p) => p.id === key.id && (\"pid\" in key ? p.pid === key.pid : true),\n );\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n pid: product.pid,\n categoryId: product.categoryId,\n };\n },\n category(product: { categoryId: string }) {\n const category = categories.find((c) => c.id === product.categoryId);\n\n if (!category) {\n return null;\n }\n\n return {\n id: category.id,\n name: category.name,\n };\n },\n },\n Category: {\n __resolveReference(key: { id: string }) {\n const category = categories.find((c) => c.id === key.id);\n\n if (!category) {\n return null;\n }\n\n return {\n id: category.id,\n name: category.name,\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "59d5bd3434cd33ad79529b433e4bf2bc5c5a4c95030de22c9d11f2d184a49c57", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { categories, products } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Product @key(fields: \"id pid\") {\n id: ID!\n pid: ID!\n category: Category @shareable\n }\n\n type Category @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(key: { id: string; pid: string }) {\n const product = products.find(\n (p) => p.id === key.id && p.pid === key.pid,\n );\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n pid: product.pid,\n categoryId: product.categoryId,\n };\n },\n category(product: { categoryId: string }) {\n const category = categories.find((c) => c.id === product.categoryId);\n\n if (!category) {\n return null;\n }\n\n return {\n id: category.id,\n name: category.name,\n };\n },\n },\n Category: {\n __resolveReference(key: { id: string }) {\n const category = categories.find((c) => c.id === key.id);\n\n if (!category) {\n return null;\n }\n\n return {\n id: category.id,\n name: category.name,\n };\n },\n },\n },\n});\n" + }, + { + "path": "c.subgraph.ts", + "sha256": "7542c1817c9086f6501599b69188840739ba79be69fb3af9f09b90dcf4feef7e", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { categories, products } from \"./data.js\";\n\nexport default createSubgraph(\"c\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Category {\n details: CategoryDetails\n }\n\n type Product @key(fields: \"id pid\") {\n id: ID!\n pid: ID!\n category: Category @shareable\n }\n\n type CategoryDetails {\n products: Int\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(key: { id: string; pid: string }) {\n const product = products.find(\n (p) => p.id === key.id && p.pid === key.pid,\n );\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n pid: product.pid,\n categoryId: product.categoryId,\n };\n },\n category(product: { categoryId: string }) {\n const category = categories.find((c) => c.id === product.categoryId);\n\n if (!category) {\n return null;\n }\n\n return {\n details: category.details,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "8fa6803380bc857d4b52b43ac347c5fab340235247592e949d6b486ba56a67a3", + "source": "export const products = [\n {\n id: \"p1\",\n pid: \"p1-pid\",\n categoryId: \"c1\",\n },\n {\n id: \"p2\",\n pid: \"p2-pid\",\n categoryId: \"c2\",\n },\n {\n id: \"p3\",\n pid: \"p3-pid\",\n categoryId: \"c1\",\n },\n];\n\nexport const categories = [\n {\n id: \"c1\",\n name: \"c1-name\",\n details: {\n products: products.filter((p) => p.categoryId === \"c1\").length,\n },\n },\n {\n id: \"c2\",\n name: \"c2-name\",\n details: {\n products: products.filter((p) => p.categoryId === \"c2\").length,\n },\n },\n];\n" + } + ] + }, + { + "id": "parent-entity-call-complex", + "cases": [ + { + "id": "parent-entity-call-complex/000", + "query": "\n query {\n productFromD(id: \"1\") {\n id\n name\n category {\n id\n name\n details\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "productFromD": { + "id": "1", + "name": "Product#1", + "category": { + "id": "3", + "name": "Category#3", + "details": "Details for Product#1" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@shareable\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID @external\n category: Category @shareable\n }\n type Category {\n details: String\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID @external\n category: Category @shareable\n}\n\ntype Category {\n details: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@shareable\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID @external\n category: Category @shareable\n }\n\n type Category {\n id: ID @shareable\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID @external\n category: Category @shareable\n}\n\ntype Category {\n id: ID @shareable\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "c", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n type Category @key(fields: \"id\") {\n id: ID\n name: String\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Category\n @key(fields: \"id\")\n{\n id: ID\n name: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Category\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "d", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n productFromD(id: ID!): Product\n }\n\n type Product @key(fields: \"id\") {\n id: ID\n name: String\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n productFromD(id: ID!): Product\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID\n name: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "1fc10286493572a01c82fab750d91c3f3347cfed8c26ac5f0cefb345f3e6f998", + "source": "import { createSubgraph } from \"../../subgraph.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@shareable\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID @external\n category: Category @shareable\n }\n type Category {\n details: String\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(product: { id: string }) {\n return {\n id: product.id,\n category: { details: `Details for Product#${product.id}` },\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "ab6ceb93887b6dbffd3a4391098ac830f9b0111dab4102f1708bb3fb2c954372", + "source": "import { createSubgraph } from \"../../subgraph.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@shareable\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID @external\n category: Category @shareable\n }\n\n type Category {\n id: ID @shareable\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(product: { id: string }) {\n return {\n id: product.id,\n category: {\n id: 3,\n },\n };\n },\n },\n },\n});\n" + }, + { + "path": "c.subgraph.ts", + "sha256": "eb4bdbc23d0b0dc802a02b28aeb0f56318e255b15207bb5acd5d32c0ef0f36ab", + "source": "import { createSubgraph } from \"../../subgraph.js\";\n\nexport default createSubgraph(\"c\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n type Category @key(fields: \"id\") {\n id: ID\n name: String\n }\n `,\n resolvers: {\n Category: {\n __resolveReference(category: { id: string }) {\n return {\n id: category.id,\n name: `Category#${category.id}`,\n };\n },\n },\n },\n});\n" + }, + { + "path": "d.subgraph.ts", + "sha256": "86a790e24e7dc416d295d2c5d3fe6bb0c599bc8397ba79f1142d793845dbb4d7", + "source": "import { createSubgraph } from \"../../subgraph.js\";\n\nexport default createSubgraph(\"d\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n productFromD(id: ID!): Product\n }\n\n type Product @key(fields: \"id\") {\n id: ID\n name: String\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(product: { id: string }) {\n return {\n id: product.id,\n name: `Product#${product.id}`,\n };\n },\n },\n Query: {\n productFromD(_: never, { id }: { id: string }) {\n return {\n id,\n name: `Product#${id}`,\n };\n },\n },\n },\n});\n" + } + ] + }, + { + "id": "partial-union", + "cases": [ + { + "id": "partial-union/000", + "query": "\n query {\n getResponse {\n message\n actions {\n __typename\n ... on Alpha {\n id\n value\n }\n ... on Beta {\n id\n name\n details\n }\n ... on Gamma {\n id\n label\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "getResponse": { + "message": "Hello, Federation!", + "actions": [ + { + "__typename": "Alpha", + "id": "alpha-1", + "value": "alpha value" + }, + { + "__typename": "Beta", + "id": "beta-1", + "name": "beta name", + "details": "beta details" + }, + { + "__typename": "Gamma", + "id": "gamma-1", + "label": "gamma label" + } + ] + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "partial-union/001", + "query": "\n query {\n getResponse {\n actions {\n __typename\n ... on Beta {\n name\n }\n ... on Gamma {\n label\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "getResponse": { + "actions": [ + { + "__typename": "Alpha" + }, + { + "__typename": "Beta", + "name": "beta name" + }, + { + "__typename": "Gamma", + "label": "gamma label" + } + ] + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.0\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n getResponse: Response\n }\n\n type Response @shareable {\n actions: [Action!]!\n message: String\n }\n\n union Action = Alpha | Beta | Gamma\n\n type Alpha @shareable {\n id: ID!\n value: String\n }\n\n type Beta {\n id: ID!\n name: String\n details: String\n }\n\n type Gamma {\n id: ID!\n label: String\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ntype Query {\n getResponse: Response\n _service: _Service!\n}\n\ntype Response\n @shareable\n{\n actions: [Action!]!\n message: String\n}\n\nunion Action = Alpha | Beta | Gamma\n\ntype Alpha\n @shareable\n{\n id: ID!\n value: String\n}\n\ntype Beta {\n id: ID!\n name: String\n details: String\n}\n\ntype Gamma {\n id: ID!\n label: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.0\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Response @shareable {\n actions: [Action!]!\n message: String\n }\n\n union Action = Alpha\n\n type Alpha @shareable {\n id: ID!\n value: String\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ntype Response\n @shareable\n{\n actions: [Action!]!\n message: String\n}\n\nunion Action = Alpha\n\ntype Alpha\n @shareable\n{\n id: ID!\n value: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\ntype Query {\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "8aa2302e476e13edb402dc217aa50aafe10b455edf19b5dfa438cd2422f81528", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { actions, message } from \"./data.js\";\n\n// \"primary\" subgraph: declares the full union `Alpha | Beta | Gamma` and is the\n// only subgraph that defines `getResponse`. `Beta` and `Gamma` have no `@key`,\n// so they can only ever be resolved here.\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.0\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n getResponse: Response\n }\n\n type Response @shareable {\n actions: [Action!]!\n message: String\n }\n\n union Action = Alpha | Beta | Gamma\n\n type Alpha @shareable {\n id: ID!\n value: String\n }\n\n type Beta {\n id: ID!\n name: String\n details: String\n }\n\n type Gamma {\n id: ID!\n label: String\n }\n `,\n resolvers: {\n Query: {\n getResponse: () => ({ message, actions }),\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "55d851cd90ab9de2b8c0cc2688576e9feedee0bd1389040de451646287d66a97", + "source": "import { createSubgraph } from \"../../subgraph.js\";\n\n// \"secondary\" subgraph: contributes only a partial union (`Alpha` only) and the\n// shared `@shareable Response` type. It does NOT define `getResponse`, so the\n// planner must resolve the whole field from the primary subgraph. Its only role\n// is to make `Action` a partial union across the supergraph: a buggy planner\n// sees that `Response`/`Alpha` are shareable here and wrongly assumes `Beta`\n// and `Gamma` can be fetched via entity resolution, stripping their fragments.\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.0\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Response @shareable {\n actions: [Action!]!\n message: String\n }\n\n union Action = Alpha\n\n type Alpha @shareable {\n id: ID!\n value: String\n }\n `,\n resolvers: {},\n});\n" + }, + { + "path": "data.ts", + "sha256": "2d4fc0b3e805dbb9a7f55f2716796a9119c55286bd02346dc2901e72944ee265", + "source": "export const message = \"Hello, Federation!\";\n\nexport const alpha = {\n __typename: \"Alpha\",\n id: \"alpha-1\",\n value: \"alpha value\",\n};\n\nexport const beta = {\n __typename: \"Beta\",\n id: \"beta-1\",\n name: \"beta name\",\n details: \"beta details\",\n};\n\nexport const gamma = {\n __typename: \"Gamma\",\n id: \"gamma-1\",\n label: \"gamma label\",\n};\n\n// The primary subgraph can resolve all three union members.\nexport const actions = [alpha, beta, gamma];\n" + } + ] + }, + { + "id": "partial-union-complex", + "cases": [ + { + "id": "partial-union-complex/000", + "query": "\n query {\n rootA {\n wrapper {\n actions {\n __typename\n ... on Common {\n label\n }\n ... on OnlyA {\n a\n }\n ... on OnlyB {\n b\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "rootA": { + "wrapper": { + "actions": [ + { + "__typename": "Common", + "label": "common label" + }, + { + "__typename": "OnlyA", + "a": null + } + ] + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "partial-union-complex/001", + "query": "\n query {\n rootB {\n wrapper {\n actions {\n __typename\n ... on Common {\n label\n }\n ... on OnlyA {\n a\n }\n ... on OnlyB {\n b\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "rootB": { + "wrapper": { + "actions": [ + { + "__typename": "Common", + "label": "common label" + }, + { + "__typename": "OnlyB", + "b": null + } + ] + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "partial-union-complex/002", + "query": "\n query {\n rootA {\n wrapper {\n actions {\n __typename\n ... on OnlyB {\n b\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "rootA": { + "wrapper": { + "actions": [ + { + "__typename": "Common" + }, + { + "__typename": "OnlyA" + } + ] + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "partial-union-complex/003", + "query": "\n query {\n shared {\n wrapper {\n actions {\n __typename\n ... on Common {\n label\n }\n ... on OnlyA {\n a\n }\n ... on OnlyB {\n b\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "shared": { + "wrapper": { + "actions": [ + { + "__typename": "Common", + "label": "common label" + } + ] + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "partial-union-complex/004", + "query": "\n query {\n rootA {\n bWrapper {\n actions {\n __typename\n ... on Common {\n label\n }\n ... on OnlyA {\n a\n }\n ... on OnlyB {\n b\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "rootA": { + "bWrapper": { + "actions": [ + { + "__typename": "Common", + "label": "common label" + }, + { + "__typename": "OnlyB", + "b": "only b" + } + ] + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n rootA: Container\n shared: Container @shareable\n }\n\n type Container @key(fields: \"id\") {\n id: ID!\n wrapper: Wrapper @shareable\n }\n\n type Wrapper @shareable {\n actions: [Action!]! @shareable\n }\n\n union Action = Common | OnlyA\n\n type Common @shareable {\n label: String\n }\n\n type OnlyA {\n a: String\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n rootA: Container\n shared: Container @shareable\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Container\n @key(fields: \"id\")\n{\n id: ID!\n wrapper: Wrapper @shareable\n}\n\ntype Wrapper\n @shareable\n{\n actions: [Action!]! @shareable\n}\n\nunion Action = Common | OnlyA\n\ntype Common\n @shareable\n{\n label: String\n}\n\ntype OnlyA {\n a: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Container" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n rootB: Container\n shared: Container @shareable\n }\n\n type Container @key(fields: \"id\") {\n id: ID!\n wrapper: Wrapper @shareable\n bWrapper: Wrapper\n }\n\n type Wrapper @shareable {\n actions: [Action!]! @shareable\n }\n\n union Action = Common | OnlyB\n\n type Common @shareable {\n label: String\n }\n\n type OnlyB {\n b: String\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n rootB: Container\n shared: Container @shareable\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Container\n @key(fields: \"id\")\n{\n id: ID!\n wrapper: Wrapper @shareable\n bWrapper: Wrapper\n}\n\ntype Wrapper\n @shareable\n{\n actions: [Action!]! @shareable\n}\n\nunion Action = Common | OnlyB\n\ntype Common\n @shareable\n{\n label: String\n}\n\ntype OnlyB {\n b: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Container" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "67f0d9cd83b7147c12a3d78bd2b9b245922d6ea64e03dac45e8fe2aeceeb266e", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { aActions, container, sharedActions } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n rootA: Container\n shared: Container @shareable\n }\n\n type Container @key(fields: \"id\") {\n id: ID!\n wrapper: Wrapper @shareable\n }\n\n type Wrapper @shareable {\n actions: [Action!]! @shareable\n }\n\n union Action = Common | OnlyA\n\n type Common @shareable {\n label: String\n }\n\n type OnlyA {\n a: String\n }\n `,\n resolvers: {\n Query: {\n rootA: () => ({ ...container, actions: aActions }),\n shared: () => ({ ...container, actions: sharedActions }),\n },\n Container: {\n __resolveReference(key: { id: string }) {\n if (key.id !== container.id) {\n return null;\n }\n\n return { ...container, actions: aActions };\n },\n wrapper(parent: { actions?: typeof aActions }) {\n return { actions: parent.actions ?? aActions };\n },\n },\n Action: {\n __resolveType(value: { __typename: string }) {\n return value.__typename;\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "e8c15ef431b611892bdd14ff0e21402fe496d76ece8d98f2af94e38696f97350", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { bActions, container, sharedActions } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n rootB: Container\n shared: Container @shareable\n }\n\n type Container @key(fields: \"id\") {\n id: ID!\n wrapper: Wrapper @shareable\n bWrapper: Wrapper\n }\n\n type Wrapper @shareable {\n actions: [Action!]! @shareable\n }\n\n union Action = Common | OnlyB\n\n type Common @shareable {\n label: String\n }\n\n type OnlyB {\n b: String\n }\n `,\n resolvers: {\n Query: {\n rootB: () => ({ ...container, actions: bActions }),\n shared: () => ({ ...container, actions: sharedActions }),\n },\n Container: {\n __resolveReference(key: { id: string }) {\n if (key.id !== container.id) {\n return null;\n }\n\n return { ...container, actions: bActions };\n },\n wrapper(parent: { actions?: typeof bActions }) {\n return { actions: parent.actions ?? bActions };\n },\n bWrapper() {\n return { actions: bActions };\n },\n },\n Action: {\n __resolveType(value: { __typename: string }) {\n return value.__typename;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "7b499fd23457447dc260a716e7e8c7c8350ff4e5849984e726fcfcd32e532eb8", + "source": "export const common = {\n __typename: \"Common\",\n label: \"common label\",\n};\n\nexport const onlyA = {\n __typename: \"OnlyA\",\n a: \"only a\",\n};\n\nexport const onlyB = {\n __typename: \"OnlyB\",\n b: \"only b\",\n};\n\nexport const container = {\n id: \"container-1\",\n};\n\nexport const aActions = [common, onlyA];\nexport const bActions = [common, onlyB];\nexport const sharedActions = [common];\n" + } + ] + }, + { + "id": "provides-on-interface", + "cases": [ + { + "id": "provides-on-interface/000", + "query": "\n query {\n media {\n id\n animals {\n id\n name\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "media": { + "id": "m1", + "animals": [ + { + "id": "a1", + "name": "Fido" + }, + { + "id": "a2", + "name": "Whiskers" + } + ] + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "provides-on-interface/001", + "query": "\n query {\n media {\n id\n animals {\n id\n name\n ... on Cat {\n age\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "media": { + "id": "m1", + "animals": [ + { + "id": "a1", + "name": "Fido" + }, + { + "id": "a2", + "name": "Whiskers", + "age": 6 + } + ] + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@external\", \"@provides\"]\n )\n\n type Query {\n media: Media @shareable\n book: Book @provides(fields: \"animals { ... on Dog { name } }\")\n }\n\n interface Media {\n id: ID!\n }\n\n interface Animal {\n id: ID!\n }\n\n type Book implements Media @key(fields: \"id\") {\n id: ID!\n animals: [Animal] @shareable\n }\n\n type Dog implements Animal @key(fields: \"id\") {\n id: ID! @external\n name: String @external\n }\n\n type Cat implements Animal @key(fields: \"id\") {\n id: ID! @external\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\", \"@external\", \"@provides\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n media: Media @shareable\n book: Book @provides(fields: \"animals { ... on Dog { name } }\")\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ninterface Media {\n id: ID!\n}\n\ninterface Animal {\n id: ID!\n}\n\ntype Book implements Media\n @key(fields: \"id\")\n{\n id: ID!\n animals: [Animal] @shareable\n}\n\ntype Dog implements Animal\n @key(fields: \"id\")\n{\n id: ID! @external\n name: String @external\n}\n\ntype Cat implements Animal\n @key(fields: \"id\")\n{\n id: ID! @external\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book | Cat | Dog" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@provides\", \"@external\"]\n )\n\n type Query {\n media: Media @shareable @provides(fields: \"animals { id name }\")\n }\n\n interface Media {\n id: ID!\n animals: [Animal]\n }\n\n interface Animal {\n id: ID!\n name: String\n }\n\n type Book implements Media {\n id: ID! @shareable\n animals: [Animal] @external\n }\n\n type Dog implements Animal {\n id: ID! @external\n name: String @external\n }\n\n type Cat implements Animal {\n id: ID! @external\n name: String @external\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\", \"@provides\", \"@external\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n media: Media @shareable @provides(fields: \"animals { id name }\")\n _service: _Service!\n}\n\ninterface Media {\n id: ID!\n animals: [Animal]\n}\n\ninterface Animal {\n id: ID!\n name: String\n}\n\ntype Book implements Media {\n id: ID! @shareable\n animals: [Animal] @external\n}\n\ntype Dog implements Animal {\n id: ID! @external\n name: String @external\n}\n\ntype Cat implements Animal {\n id: ID! @external\n name: String @external\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}" + }, + { + "name": "c", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n interface Media {\n id: ID!\n animals: [Animal]\n }\n\n interface Animal {\n id: ID!\n name: String\n }\n\n type Book implements Media @key(fields: \"id\") {\n id: ID!\n animals: [Animal] @shareable\n }\n\n type Dog implements Animal @key(fields: \"id\") {\n id: ID!\n name: String @shareable\n age: Int\n }\n\n type Cat implements Animal @key(fields: \"id\") {\n id: ID!\n name: String @shareable\n age: Int\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ninterface Media {\n id: ID!\n animals: [Animal]\n}\n\ninterface Animal {\n id: ID!\n name: String\n}\n\ntype Book implements Media\n @key(fields: \"id\")\n{\n id: ID!\n animals: [Animal] @shareable\n}\n\ntype Dog implements Animal\n @key(fields: \"id\")\n{\n id: ID!\n name: String @shareable\n age: Int\n}\n\ntype Cat implements Animal\n @key(fields: \"id\")\n{\n id: ID!\n name: String @shareable\n age: Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book | Cat | Dog\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "8313969199182e1fa7148b4e44e09f98eeb10214e0ef8981e420b958a401cea3", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { animals, medias, pubishPoorPlans } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@external\", \"@provides\"]\n )\n\n type Query {\n media: Media @shareable\n book: Book @provides(fields: \"animals { ... on Dog { name } }\")\n }\n\n interface Media {\n id: ID!\n }\n\n interface Animal {\n id: ID!\n }\n\n type Book implements Media @key(fields: \"id\") {\n id: ID!\n animals: [Animal] @shareable\n }\n\n type Dog implements Animal @key(fields: \"id\") {\n id: ID! @external\n name: String @external\n }\n\n type Cat implements Animal @key(fields: \"id\") {\n id: ID! @external\n }\n `,\n resolvers: {\n Query: {\n media() {\n if (pubishPoorPlans) {\n throw new Error(\"You should be using the 'a' subgraph!\");\n }\n\n const media = medias[0];\n\n return {\n __typename: media.__typename,\n id: media.id,\n animals: media.animals.map((animalId) => {\n const animal = animals.find((a) => a.id === animalId);\n\n if (!animal) {\n return null;\n }\n\n return {\n __typename: animal.__typename,\n id: animal.id,\n };\n }),\n };\n },\n book() {\n const book = medias[0];\n\n return {\n __typename: \"Book\",\n id: book.id,\n animals: book.animals.map((animalId) => {\n const animal = animals.find((a) => a.id === animalId);\n\n if (!animal) {\n return null;\n }\n\n if (animal.__typename === \"Dog\") {\n return {\n __typename: \"Dog\",\n id: animal.id,\n name: animal.name,\n };\n }\n\n return {\n __typename: animal.__typename,\n id: animal.id,\n };\n }),\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "4d2da3e467803c4de15b0a49b40c7067da3006a500bcb8afea0df3575f19e423", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { animals, medias } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@provides\", \"@external\"]\n )\n\n type Query {\n media: Media @shareable @provides(fields: \"animals { id name }\")\n }\n\n interface Media {\n id: ID!\n animals: [Animal]\n }\n\n interface Animal {\n id: ID!\n name: String\n }\n\n type Book implements Media {\n id: ID! @shareable\n animals: [Animal] @external\n }\n\n type Dog implements Animal {\n id: ID! @external\n name: String @external\n }\n\n type Cat implements Animal {\n id: ID! @external\n name: String @external\n }\n `,\n resolvers: {\n Query: {\n media() {\n const media = medias[0];\n return {\n __typename: media.__typename,\n id: media.id,\n animals: media.animals.map((animalId) => {\n const animal = animals.find((a) => a.id === animalId);\n\n if (!animal) {\n return null;\n }\n\n return {\n __typename: animal.__typename,\n id: animal.id,\n name: animal.name,\n };\n }),\n };\n },\n },\n },\n});\n" + }, + { + "path": "c.subgraph.ts", + "sha256": "c5861e2cbf5fb77c76c55a3d52ba6a757457578aca669302c91385f06f490cf0", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { animals, medias, pubishPoorPlans } from \"./data.js\";\n\nexport default createSubgraph(\"c\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n interface Media {\n id: ID!\n animals: [Animal]\n }\n\n interface Animal {\n id: ID!\n name: String\n }\n\n type Book implements Media @key(fields: \"id\") {\n id: ID!\n animals: [Animal] @shareable\n }\n\n type Dog implements Animal @key(fields: \"id\") {\n id: ID!\n name: String @shareable\n age: Int\n }\n\n type Cat implements Animal @key(fields: \"id\") {\n id: ID!\n name: String @shareable\n age: Int\n }\n `,\n resolvers: {\n Query: {\n media() {\n if (pubishPoorPlans) {\n throw new Error(\"You should be using the 'a' subgraph!\");\n }\n\n return medias[0];\n },\n },\n Book: {\n __resolveReference(key: { id: string }) {\n const media = medias.find((media) => media.id === key.id);\n\n return media\n ? {\n __typename: \"Book\",\n id: media.id,\n animals: media.animals,\n }\n : null;\n },\n animals(book: { animals: string[] }) {\n return book.animals.map((animalId) => {\n const animal = animals.find((a) => a.id === animalId);\n\n return animal\n ? {\n __typename: animal.__typename,\n id: animal.id,\n name: animal.name,\n }\n : null;\n });\n },\n },\n Cat: {\n __resolveReference(key: { id: string }) {\n const animal = animals.find((animal) => animal.id === key.id);\n\n return animal\n ? {\n __typename: \"Cat\",\n id: animal.id,\n name: animal.name,\n age: animal.age,\n }\n : null;\n },\n age(cat: { id: string }) {\n const animal = animals.find((a) => a.id === cat.id);\n\n return animal ? animal.age : null;\n },\n },\n Dog: {\n __resolveReference(key: { id: string }) {\n if (pubishPoorPlans) {\n // Nothing in tests should be calling this resolver\n throw new Error(\"You should be using the 'c' subgraph!\");\n }\n\n const animal = animals.find((animal) => animal.id === key.id);\n\n return animal\n ? {\n __typename: \"Dog\",\n id: animal.id,\n name: animal.name,\n age: animal.age,\n }\n : null;\n },\n age(dog: { id: string }) {\n const animal = animals.find((a) => a.id === dog.id);\n\n return animal ? animal.age : null;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "56f2f0eb1e0858f524a59cecc6bfcd77bd6ad34ede30bf5553e47d3b1774e7a8", + "source": "export const pubishPoorPlans = true;\n\nexport const medias = [\n {\n __typename: \"Book\",\n id: \"m1\",\n animals: [\"a1\", \"a2\"],\n },\n];\n\nexport const animals = [\n {\n __typename: \"Dog\",\n id: \"a1\",\n name: \"Fido\",\n age: 3,\n },\n {\n __typename: \"Cat\",\n id: \"a2\",\n name: \"Whiskers\",\n age: 6,\n },\n];\n" + } + ] + }, + { + "id": "provides-on-union", + "cases": [ + { + "id": "provides-on-union/000", + "query": "\n query {\n media {\n ... on Book {\n id\n title\n }\n ... on Movie {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "media": [ + { + "id": "m1", + "title": "Book 1" + }, + { + "id": "m2" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "provides-on-union/001", + "query": "\n query {\n media {\n ... on Book {\n id\n title\n }\n ... on Movie {\n id\n title\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "media": [ + { + "id": "m1", + "title": "Book 1" + }, + { + "id": "m2", + "title": "Movie 1" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@external\"]\n )\n\n type Query {\n media: [Media] @shareable\n }\n\n union Media = Book | Movie\n\n type Book @key(fields: \"id\") {\n id: ID!\n }\n\n type Movie @key(fields: \"id\") {\n id: ID!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\", \"@external\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n media: [Media] @shareable\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nunion Media = Book | Movie\n\ntype Book\n @key(fields: \"id\")\n{\n id: ID!\n}\n\ntype Movie\n @key(fields: \"id\")\n{\n id: ID!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book | Movie" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@provides\", \"@external\"]\n )\n\n type Query {\n media: [Media] @shareable @provides(fields: \"... on Book { title }\")\n }\n\n union Media = Book | Movie\n\n type Book @key(fields: \"id\") {\n id: ID!\n title: String @external\n }\n\n type Movie @key(fields: \"id\") {\n id: ID!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\", \"@provides\", \"@external\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n media: [Media] @shareable @provides(fields: \"... on Book { title }\")\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nunion Media = Book | Movie\n\ntype Book\n @key(fields: \"id\")\n{\n id: ID!\n title: String @external\n}\n\ntype Movie\n @key(fields: \"id\")\n{\n id: ID!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book | Movie" + }, + { + "name": "c", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Book @key(fields: \"id\") {\n id: ID!\n title: String @shareable\n }\n\n type Movie @key(fields: \"id\") {\n id: ID!\n title: String @shareable\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Book\n @key(fields: \"id\")\n{\n id: ID!\n title: String @shareable\n}\n\ntype Movie\n @key(fields: \"id\")\n{\n id: ID!\n title: String @shareable\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book | Movie\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "1a6b9b5d7fd7fffbd43bed75970bdea1a0d5c9f52afe3abb55d33339d491b7fd", + "source": "import { shouldPunishForPoorPlans } from \"../../env.js\";\nimport { createSubgraph } from \"../../subgraph.js\";\nimport { medias } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@external\"]\n )\n\n type Query {\n media: [Media] @shareable\n }\n\n union Media = Book | Movie\n\n type Book @key(fields: \"id\") {\n id: ID!\n }\n\n type Movie @key(fields: \"id\") {\n id: ID!\n }\n `,\n resolvers: {\n Query: {\n media(_p: unknown, _a: unknown, context: any) {\n if (shouldPunishForPoorPlans()) {\n throw new Error(\"You should not be using the 'a' subgraph!\");\n }\n\n return medias.map((media) => ({\n __typename: media.__typename,\n id: media.id,\n }));\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "21f466bfd1df9437e8e59908572f4e37803014d47931f53354ad704898af3ce6", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { medias } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\", \"@provides\", \"@external\"]\n )\n\n type Query {\n media: [Media] @shareable @provides(fields: \"... on Book { title }\")\n }\n\n union Media = Book | Movie\n\n type Book @key(fields: \"id\") {\n id: ID!\n title: String @external\n }\n\n type Movie @key(fields: \"id\") {\n id: ID!\n }\n `,\n resolvers: {\n Query: {\n media() {\n return medias.map((media) => {\n if (media.__typename === \"Book\") {\n return {\n __typename: media.__typename,\n id: media.id,\n title: media.title,\n };\n }\n\n return {\n __typename: media.__typename,\n id: media.id,\n };\n });\n },\n },\n },\n});\n" + }, + { + "path": "c.subgraph.ts", + "sha256": "df009cd9e99a2011f2b218cf88581b2dbee7af9365589565eb9514b8e9eeb6a1", + "source": "import { shouldPunishForPoorPlans } from \"../../env.js\";\nimport { createSubgraph } from \"../../subgraph.js\";\nimport { medias } from \"./data.js\";\n\nexport default createSubgraph(\"c\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Book @key(fields: \"id\") {\n id: ID!\n title: String @shareable\n }\n\n type Movie @key(fields: \"id\") {\n id: ID!\n title: String @shareable\n }\n `,\n resolvers: {\n Book: {\n __resolveReference(key: { id: string }, context: any) {\n if (shouldPunishForPoorPlans()) {\n throw new Error(\"You should be using the 'b' subgraph!\");\n }\n\n const media = medias.find((media) => media.id === key.id);\n\n if (!media) {\n return null;\n }\n\n return {\n __typename: media.__typename,\n id: media.id,\n title: media.title,\n };\n },\n },\n Movie: {\n __resolveReference(key: { id: string }) {\n const media = medias.find((media) => media.id === key.id);\n\n if (!media) {\n return null;\n }\n\n return {\n __typename: media.__typename,\n id: media.id,\n title: media.title,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "984d622714ab05c160491275f011f9f5a41a3bf36eafe897b75aa4b82056cdd0", + "source": "export const medias = [\n {\n __typename: \"Book\",\n id: \"m1\",\n title: \"Book 1\",\n },\n {\n __typename: \"Movie\",\n id: \"m2\",\n title: \"Movie 1\",\n },\n];\n" + } + ] + }, + { + "id": "requires-circular", + "cases": [ + { + "id": "requires-circular/000", + "query": "\n {\n feed {\n byNovice\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "feed": [ + { + "byNovice": true + }, + { + "byNovice": false + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-circular/001", + "query": "\n {\n feed {\n byExpert\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "feed": [ + { + "byExpert": false + }, + { + "byExpert": true + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Query {\n feed: [Post]\n }\n\n type Post @key(fields: \"id\") {\n id: ID!\n byNovice: Boolean! @external\n byExpert: Boolean! @requires(fields: \"byNovice\")\n }\n\n type Author @key(fields: \"id\") {\n id: ID!\n name: String!\n yearsOfExperience: Int!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n feed: [Post]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Post\n @key(fields: \"id\")\n{\n id: ID!\n byNovice: Boolean! @external\n byExpert: Boolean! @requires(fields: \"byNovice\")\n}\n\ntype Author\n @key(fields: \"id\")\n{\n id: ID!\n name: String!\n yearsOfExperience: Int!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Author | Post" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Post @key(fields: \"id\") {\n id: ID!\n author: Author!\n byNovice: Boolean! @requires(fields: \"author { yearsOfExperience }\")\n }\n\n type Author @key(fields: \"id\") {\n id: ID!\n yearsOfExperience: Int! @external\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Post\n @key(fields: \"id\")\n{\n id: ID!\n author: Author!\n byNovice: Boolean! @requires(fields: \"author { yearsOfExperience }\")\n}\n\ntype Author\n @key(fields: \"id\")\n{\n id: ID!\n yearsOfExperience: Int! @external\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Author | Post\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "5d2305ded6f140ab0cd6913e2b4ef9e1102c01e768cea8baa497807571f7e984", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { authors, posts } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Query {\n feed: [Post]\n }\n\n type Post @key(fields: \"id\") {\n id: ID!\n byNovice: Boolean! @external\n byExpert: Boolean! @requires(fields: \"byNovice\")\n }\n\n type Author @key(fields: \"id\") {\n id: ID!\n name: String!\n yearsOfExperience: Int!\n }\n `,\n resolvers: {\n Query: {\n feed() {\n return posts.map((post) => ({ id: post.id }));\n },\n },\n Post: {\n __resolveReference(ref: { id: string; byNovice?: boolean }) {\n const post = posts.find((post) => post.id === ref.id);\n if (!post) {\n return null;\n }\n if (ref.byNovice == null) {\n return {\n id: post.id,\n };\n }\n return {\n id: post.id,\n byNovice: ref.byNovice,\n };\n },\n byExpert(post: { byNovice: boolean }) {\n if (post.byNovice == null) {\n // ensuring requires is not skipped\n return null;\n }\n return !post.byNovice;\n },\n },\n Author: {\n __resolveReference(ref: { id: string }) {\n const author = authors.find((author) => author.id === ref.id);\n if (!author) {\n return null;\n }\n return {\n id: author.id,\n name: author.name,\n yearsOfExperience: author.yearsOfExperience,\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "8ea2d5356ddb1811ade3cd01de01d526c5afc15effe8f07c7ece25677b4a0dc0", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { posts } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Post @key(fields: \"id\") {\n id: ID!\n author: Author!\n byNovice: Boolean! @requires(fields: \"author { yearsOfExperience }\")\n }\n\n type Author @key(fields: \"id\") {\n id: ID!\n yearsOfExperience: Int! @external\n }\n `,\n resolvers: {\n Post: {\n __resolveReference(ref: {\n id: string;\n author?: { yearsOfExperience: number };\n }) {\n const post = posts.find((post) => post.id === ref.id);\n if (!post) {\n return null;\n }\n return {\n id: post.id,\n author: {\n id: post.author.id,\n ...ref.author,\n },\n };\n },\n byNovice(post: { author: { yearsOfExperience: number } }) {\n return post.author.yearsOfExperience < 10;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "6dc14e350e85a77b47e5fbab1b1664a9c26887ad9fe6a7f0c5e787fb27164642", + "source": "export const authors = [\n {\n id: \"a1\",\n name: \"John\",\n yearsOfExperience: 5,\n },\n {\n id: \"a2\",\n name: \"Jane\",\n yearsOfExperience: 20,\n },\n];\n\nexport const posts = [\n {\n id: \"p1\",\n body: \"p1-body\",\n author: authors[0],\n },\n {\n id: \"p2\",\n body: \"p2-body\",\n author: authors[1],\n },\n];\n" + } + ] + }, + { + "id": "requires-interface", + "cases": [ + { + "id": "requires-interface/000", + "query": "\n query {\n a {\n city\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "a": { + "city": "a1-city" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-interface/001", + "query": "\n query {\n b {\n city\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "b": { + "city": "a2-city" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-interface/002", + "query": "\n query {\n a {\n country\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "a": { + "country": null + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-interface/003", + "query": "\n query {\n a {\n address {\n __typename\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "a": { + "address": { + "__typename": "HomeAddress", + "id": "a1" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-interface/004", + "query": "\n query {\n b {\n address {\n __typename\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "b": { + "address": { + "__typename": "WorkAddress", + "id": "a2" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@requires\", \"@external\", \"@shareable\"]\n )\n\n type Query {\n a: User\n }\n\n interface Address {\n id: ID!\n }\n\n type HomeAddress implements Address @key(fields: \"id\") {\n id: ID!\n city: String @shareable\n }\n\n type WorkAddress implements Address @key(fields: \"id\") {\n id: ID!\n city: String @shareable\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n address: Address @external\n city: String @requires(fields: \"address { id }\")\n country: String @requires(fields: \"address { ... on WorkAddress { id } }\")\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@requires\", \"@external\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n a: User\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ninterface Address {\n id: ID!\n}\n\ntype HomeAddress implements Address\n @key(fields: \"id\")\n{\n id: ID!\n city: String @shareable\n}\n\ntype WorkAddress implements Address\n @key(fields: \"id\")\n{\n id: ID!\n city: String @shareable\n}\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n name: String! @shareable\n address: Address @external\n city: String @requires(fields: \"address { id }\")\n country: String @requires(fields: \"address { ... on WorkAddress { id } }\")\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = HomeAddress | User | WorkAddress" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n b: User\n }\n\n interface Address {\n id: ID!\n }\n\n type HomeAddress implements Address @key(fields: \"id\") {\n id: ID!\n city: String @shareable\n }\n\n type WorkAddress implements Address @key(fields: \"id\") {\n id: ID!\n city: String @shareable\n }\n\n type SecondAddress implements Address @key(fields: \"id\") {\n id: ID!\n city: String @shareable\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n address: Address @shareable\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n b: User\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ninterface Address {\n id: ID!\n}\n\ntype HomeAddress implements Address\n @key(fields: \"id\")\n{\n id: ID!\n city: String @shareable\n}\n\ntype WorkAddress implements Address\n @key(fields: \"id\")\n{\n id: ID!\n city: String @shareable\n}\n\ntype SecondAddress implements Address\n @key(fields: \"id\")\n{\n id: ID!\n city: String @shareable\n}\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n name: String! @shareable\n address: Address @shareable\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = HomeAddress | SecondAddress | User | WorkAddress" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "1ed84ff6365f87d817e93ca0cca6e87531534e98d3c781f2a582b03e72f2a382", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { addresses, users } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@requires\", \"@external\", \"@shareable\"]\n )\n\n type Query {\n a: User\n }\n\n interface Address {\n id: ID!\n }\n\n type HomeAddress implements Address @key(fields: \"id\") {\n id: ID!\n city: String @shareable\n }\n\n type WorkAddress implements Address @key(fields: \"id\") {\n id: ID!\n city: String @shareable\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n address: Address @external\n city: String @requires(fields: \"address { id }\")\n country: String @requires(fields: \"address { ... on WorkAddress { id } }\")\n }\n `,\n resolvers: {\n Query: {\n a() {\n const user = users[0];\n\n return {\n id: user.id,\n name: user.name,\n };\n },\n },\n User: {\n __resolveReference(key: { id: string; address?: { id?: string } }) {\n const user = users.find((user) => user.id === key.id);\n\n if (!user) {\n return null;\n }\n\n if (key.address?.id) {\n return {\n id: user.id,\n name: user.name,\n address: {\n id: user.address,\n },\n };\n }\n\n return {\n id: user.id,\n name: user.name,\n };\n },\n city(user: { address: { id: string } }) {\n return addresses.find((address) => address.id === user.address.id)\n ?.city;\n },\n country(user: { address?: { id?: string } }) {\n return addresses.find((address) => address.id === user?.address?.id)\n ?.country;\n },\n },\n HomeAddress: {\n __resolveReference(key: { id: string }) {\n return addresses.find((address) => address.id === key.id);\n },\n },\n WorkAddress: {\n __resolveReference(key: { id: string }) {\n return addresses.find((address) => address.id === key.id);\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "2e51236cfb6f014a66c22694778c6a923579f53d8f56bcea74cf32f66491a190", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { addresses, users } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n b: User\n }\n\n interface Address {\n id: ID!\n }\n\n type HomeAddress implements Address @key(fields: \"id\") {\n id: ID!\n city: String @shareable\n }\n\n type WorkAddress implements Address @key(fields: \"id\") {\n id: ID!\n city: String @shareable\n }\n\n type SecondAddress implements Address @key(fields: \"id\") {\n id: ID!\n city: String @shareable\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String! @shareable\n address: Address @shareable\n }\n `,\n resolvers: {\n Query: {\n b() {\n const user = users[1];\n\n return {\n id: user.id,\n name: user.name,\n address: user.address,\n };\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((user) => user.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n name: user.name,\n address: user.address,\n };\n },\n address(user: { id: string; address: string }) {\n return addresses.find((address) => address.id === user.address);\n },\n },\n HomeAddress: {\n __resolveReference(key: { id: string }) {\n return addresses.find((address) => address.id === key.id);\n },\n },\n WorkAddress: {\n __resolveReference(key: { id: string }) {\n return addresses.find((address) => address.id === key.id);\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "f049a629bf36d483858a2484dc4fbe1e4eade084cefca1d9c8560e22038c91e1", + "source": "export const users = [\n {\n id: \"u1\",\n name: \"u1-name\",\n address: \"a1\",\n },\n {\n id: \"u2\",\n name: \"u2-name\",\n address: \"a2\",\n },\n];\n\nexport const addresses = [\n {\n __typename: \"HomeAddress\",\n id: \"a1\",\n city: \"a1-city\",\n country: \"a1-country\",\n },\n {\n __typename: \"WorkAddress\",\n id: \"a2\",\n city: \"a2-city\",\n country: \"a2-country\",\n },\n];\n" + } + ] + }, + { + "id": "requires-requires", + "cases": [ + { + "id": "requires-requires/000", + "query": "\n query {\n product {\n canAfford\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "canAfford": false + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-requires/001", + "query": "\n query {\n product {\n isExpensive\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "isExpensive": true + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-requires/002", + "query": "\n query {\n product {\n isExpensive\n canAfford\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "isExpensive": true, + "canAfford": false + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-requires/003", + "query": "\n query {\n product {\n canAffordWithDiscount\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "canAffordWithDiscount": true + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-requires/004", + "query": "\n query {\n product {\n canAfford\n canAffordWithDiscount\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "canAfford": false, + "canAffordWithDiscount": true + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@inaccessible\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n price: Float! @inaccessible\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@inaccessible\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n price: Float! @inaccessible\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n product: Product\n }\n\n type Product @key(fields: \"id\") {\n id: ID!\n hasDiscount: Boolean!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n product: Product\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n hasDiscount: Boolean!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product" + }, + { + "name": "c", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n price: Float! @external\n isExpensive: Boolean! @requires(fields: \"price\")\n hasDiscount: Boolean! @external\n isExpensiveWithDiscount: Boolean! @requires(fields: \"hasDiscount\")\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n price: Float! @external\n isExpensive: Boolean! @requires(fields: \"price\")\n hasDiscount: Boolean! @external\n isExpensiveWithDiscount: Boolean! @requires(fields: \"hasDiscount\")\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "d", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n isExpensive: Boolean! @external\n canAfford: Boolean! @requires(fields: \"isExpensive\")\n isExpensiveWithDiscount: Boolean! @external\n canAffordWithDiscount: Boolean!\n @requires(fields: \"isExpensiveWithDiscount\")\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"id\")\n{\n id: ID!\n isExpensive: Boolean! @external\n canAfford: Boolean! @requires(fields: \"isExpensive\")\n isExpensiveWithDiscount: Boolean! @external\n canAffordWithDiscount: Boolean! @requires(fields: \"isExpensiveWithDiscount\")\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "ccb36125e4936e126bce681758ad02a0617d217a030caad4d2eb1b4162a9aa43", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@inaccessible\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n price: Float! @inaccessible\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(key: { id: string }) {\n const product = products.find((product) => product.id === key.id);\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n price: product.price,\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "14378328290695d6d920d0efe7f477b25d866cbc9bc498b39409f3386687f5a9", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n product: Product\n }\n\n type Product @key(fields: \"id\") {\n id: ID!\n hasDiscount: Boolean!\n }\n `,\n resolvers: {\n Query: {\n product() {\n return {\n id: products[0].id,\n hasDiscount: products[0].hasDiscount,\n };\n },\n },\n Product: {\n __resolveReference(key: { id: string }) {\n const product = products.find((product) => product.id === key.id);\n\n if (!product) {\n return null;\n }\n\n return {\n id: product.id,\n hasDiscount: product.hasDiscount,\n };\n },\n },\n },\n});\n" + }, + { + "path": "c.subgraph.ts", + "sha256": "20c086a62291d26f7aff18411ce699c560ff7dfcc49faa845da244e1a4e6e76a", + "source": "import { GraphQLError } from \"graphql\";\nimport { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"c\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n price: Float! @external\n isExpensive: Boolean! @requires(fields: \"price\")\n hasDiscount: Boolean! @external\n isExpensiveWithDiscount: Boolean! @requires(fields: \"hasDiscount\")\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(key: {\n id: string;\n price?: number;\n hasDiscount?: boolean;\n }) {\n if (!products.find((product) => product.id === key.id)) {\n return null;\n }\n\n const product: {\n __typename: \"Product\";\n id: string;\n price?: number;\n isExpensive?: boolean;\n hasDiscount?: boolean;\n isExpensiveWithDiscount?: boolean;\n } = {\n __typename: \"Product\",\n id: key.id,\n };\n\n if (typeof key.price === \"number\") {\n product.price = key.price;\n product.isExpensive = key.price > 500;\n } else if (\"price\" in key && typeof key.price !== \"undefined\") {\n return new GraphQLError(\"Product.price must be a number\");\n }\n\n if (typeof key.hasDiscount === \"boolean\") {\n product.hasDiscount = key.hasDiscount;\n product.isExpensiveWithDiscount = !key.hasDiscount;\n } else if (\n \"hasDiscount\" in key &&\n typeof key.hasDiscount !== \"undefined\"\n ) {\n return new GraphQLError(\"Product.hasDiscount must be a number\");\n }\n\n return product;\n },\n },\n },\n});\n" + }, + { + "path": "d.subgraph.ts", + "sha256": "779ce633ce5422adaff872b17517b12f574746ed2e4146b47b02f28524b24cd0", + "source": "import { GraphQLError } from \"graphql\";\nimport { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"d\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"id\") {\n id: ID!\n isExpensive: Boolean! @external\n canAfford: Boolean! @requires(fields: \"isExpensive\")\n isExpensiveWithDiscount: Boolean! @external\n canAffordWithDiscount: Boolean!\n @requires(fields: \"isExpensiveWithDiscount\")\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(key: {\n id: string;\n isExpensive?: boolean;\n isExpensiveWithDiscount?: boolean;\n }) {\n if (!products.find((product) => product.id === key.id)) {\n return null;\n }\n\n const product: {\n __typename: \"Product\";\n id: string;\n isExpensive?: boolean;\n canAfford?: boolean;\n isExpensiveWithDiscount?: boolean;\n canAffordWithDiscount?: boolean;\n } = {\n __typename: \"Product\",\n id: key.id,\n };\n\n if (typeof key.isExpensive === \"boolean\") {\n product.isExpensive = key.isExpensive;\n product.canAfford = !key.isExpensive;\n } else if (\n \"isExpensive\" in key &&\n typeof key.isExpensive !== \"undefined\"\n ) {\n return new GraphQLError(\"Product.isExpensive must be a boolean\");\n }\n\n if (typeof key.isExpensiveWithDiscount === \"boolean\") {\n product.isExpensiveWithDiscount = key.isExpensiveWithDiscount;\n product.canAffordWithDiscount = !key.isExpensiveWithDiscount;\n } else if (\n \"isExpensiveWithDiscount\" in key &&\n typeof key.isExpensiveWithDiscount !== \"undefined\"\n ) {\n return new GraphQLError(\n \"Product.isExpensiveWithDiscount must be a boolean\",\n );\n }\n\n return product;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "fad1708b13eddd9ee1de40420c76ffb3813009b0869bd62bca516067d8119e31", + "source": "export const products = [\n {\n id: \"p1\",\n price: 699.99,\n hasDiscount: true,\n },\n];\n" + } + ] + }, + { + "id": "requires-with-argument", + "cases": [ + { + "id": "requires-with-argument/000", + "query": "\n query {\n products {\n upc\n name\n shippingEstimate\n isExpensiveCategory\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "upc": "p1", + "name": "p-name-1", + "shippingEstimate": 110, + "isExpensiveCategory": false + }, + { + "upc": "p2", + "name": "p-name-2", + "shippingEstimate": 440, + "isExpensiveCategory": true + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-with-argument/001", + "query": "\n {\n feed {\n author {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "feed": [ + { + "author": { + "id": "a2" + } + }, + { + "author": { + "id": "a1" + } + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-with-argument/002", + "query": "\n query {\n feed {\n author {\n id\n }\n comments(limit: 1) {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "feed": [ + { + "author": { + "id": "a2" + }, + "comments": [ + { + "id": "c1" + } + ] + }, + { + "author": { + "id": "a1" + }, + "comments": [ + { + "id": "c4" + } + ] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-with-argument/003", + "query": "\n query ($limit: Int = 1) {\n feed {\n author {\n id\n }\n comments(limit: $limit) {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "feed": [ + { + "author": { + "id": "a2" + }, + "comments": [ + { + "id": "c1" + } + ] + }, + { + "author": { + "id": "a1" + }, + "comments": [ + { + "id": "c4" + } + ] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-with-argument/004", + "query": "\n query ($limit: Int = 1) {\n feed {\n author {\n id\n }\n ...Foo\n ...Bar\n }\n }\n\n fragment Foo on Post {\n comments(limit: $limit) {\n id\n }\n }\n\n fragment Bar on Post {\n comments(limit: $limit) {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "feed": [ + { + "author": { + "id": "a2" + }, + "comments": [ + { + "id": "c1" + } + ] + }, + { + "author": { + "id": "a1" + }, + "comments": [ + { + "id": "c4" + } + ] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"upc\") {\n upc: String!\n weight: Int @external\n price(currency: String!): Int @external\n shippingEstimate: Int\n @requires(\n fields: \"\"\"\n price(currency: \"USD\") weight\n \"\"\"\n )\n category: Category @external\n isExpensiveCategory: Boolean\n @requires(\n fields: \"\"\"\n category { averagePrice(currency: \"USD\") }\n \"\"\"\n )\n }\n\n type Category @external {\n averagePrice(currency: String!): Int\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"upc\")\n{\n upc: String!\n weight: Int @external\n price(currency: String!): Int @external\n shippingEstimate: Int @requires(fields: \"price(currency: \\\"USD\\\") weight\")\n category: Category @external\n isExpensiveCategory: Boolean @requires(fields: \"category { averagePrice(currency: \\\"USD\\\") }\")\n}\n\ntype Category\n @external\n{\n averagePrice(currency: String!): Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n products: [Product]\n }\n\n type Product @key(fields: \"upc\") {\n upc: String!\n name: String\n price(currency: String!): Int\n weight: Int\n category: Category\n }\n\n type Category {\n averagePrice(currency: String!): Int\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n products: [Product]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Product\n @key(fields: \"upc\")\n{\n upc: String!\n name: String\n price(currency: String!): Int\n weight: Int\n category: Category\n}\n\ntype Category {\n averagePrice(currency: String!): Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product" + }, + { + "name": "c", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Query {\n feed: [Post]\n }\n\n type Post @key(fields: \"id\") {\n id: ID!\n }\n\n type Comment @key(fields: \"id\") {\n id: ID!\n authorId: ID\n body: String!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n feed: [Post]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Post\n @key(fields: \"id\")\n{\n id: ID!\n}\n\ntype Comment\n @key(fields: \"id\")\n{\n id: ID!\n authorId: ID\n body: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Comment | Post" + }, + { + "name": "d", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Post @key(fields: \"id\") {\n id: ID!\n author: Author @requires(fields: \"comments(limit: 3) { authorId }\")\n comments(limit: Int!): [Comment]\n }\n\n type Comment @key(fields: \"id\") {\n id: ID!\n date: String\n authorId: ID @external\n }\n\n type Author {\n id: ID!\n name: String\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Post\n @key(fields: \"id\")\n{\n id: ID!\n author: Author @requires(fields: \"comments(limit: 3) { authorId }\")\n comments(limit: Int!): [Comment]\n}\n\ntype Comment\n @key(fields: \"id\")\n{\n id: ID!\n date: String\n authorId: ID @external\n}\n\ntype Author {\n id: ID!\n name: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Comment | Post\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "b21bb2621016a7031d417511337be7684cbd981e1f0c295690bd3dd073e87b6b", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"upc\") {\n upc: String!\n weight: Int @external\n price(currency: String!): Int @external\n shippingEstimate: Int\n @requires(\n fields: \"\"\"\n price(currency: \"USD\") weight\n \"\"\"\n )\n category: Category @external\n isExpensiveCategory: Boolean\n @requires(\n fields: \"\"\"\n category { averagePrice(currency: \"USD\") }\n \"\"\"\n )\n }\n\n type Category @external {\n averagePrice(currency: String!): Int\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(\n key: { upc: string; price: number; weight: number } | { upc: string },\n ) {\n const product = products.find((p) => p.upc === key.upc);\n\n if (!product) {\n return null;\n }\n\n if (\"weight\" in key && \"price\" in key) {\n return {\n upc: product.upc,\n weight: key.weight,\n price: key.price,\n category: product.category,\n };\n }\n\n return {\n upc: product.upc,\n category: product.category,\n };\n },\n shippingEstimate(product: { price: number; weight: number }) {\n return product.price * product.weight * 10;\n },\n isExpensiveCategory(product: { category: { averagePrice: number } }) {\n return product.category.averagePrice > 11;\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "530b6b9e029ff279700a4cfb1b24d65733475f4061c335196bdd9c40d1bdb84e", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n products: [Product]\n }\n\n type Product @key(fields: \"upc\") {\n upc: String!\n name: String\n price(currency: String!): Int\n weight: Int\n category: Category\n }\n\n type Category {\n averagePrice(currency: String!): Int\n }\n `,\n resolvers: {\n Query: {\n products() {\n return products.map((p) => ({\n upc: p.upc,\n name: p.name,\n price: p.price,\n weight: p.weight,\n category: p.category,\n }));\n },\n },\n Product: {\n __resolveReference(key: { upc: string }) {\n const product = products.find((p) => p.upc === key.upc);\n\n if (!product) {\n return null;\n }\n\n return {\n upc: product.upc,\n name: product.name,\n price: product.price,\n weight: product.weight,\n category: product.category,\n };\n },\n },\n },\n});\n" + }, + { + "path": "c.subgraph.ts", + "sha256": "82b233ebc27ac5372dc27051e1d94c9f9a7f1fbf17f87a44375a7e3725f846d0", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { comments, posts } from \"./data.js\";\n\nexport default createSubgraph(\"c\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Query {\n feed: [Post]\n }\n\n type Post @key(fields: \"id\") {\n id: ID!\n }\n\n type Comment @key(fields: \"id\") {\n id: ID!\n authorId: ID\n body: String!\n }\n `,\n resolvers: {\n Query: {\n feed() {\n return posts.map((post) => ({ id: post.id }));\n },\n },\n Post: {\n __resolveReference(key: { id: string }) {\n const post = posts.find((post) => post.id === key.id);\n\n if (!post) {\n return null;\n }\n\n return {\n id: post.id,\n };\n },\n comments(post: { id: string }, { limit }: { limit: number }) {\n return comments\n .filter((comment) => comment.postId === post.id)\n .slice(0, limit)\n .map((c) => ({\n id: c.id,\n authorId: c.authorId,\n body: c.body,\n }));\n },\n },\n Comment: {\n __resolveReference(key: { id: string }) {\n const comment = comments.find((c) => c.id === key.id);\n\n if (!comment) {\n return null;\n }\n\n return {\n id: comment.id,\n authorId: comment.authorId,\n body: comment.body,\n };\n },\n },\n },\n});\n" + }, + { + "path": "d.subgraph.ts", + "sha256": "aeaf1986ab924a037e4703c75ea3ddc6a082157f5bdf5be549c430ffa086380f", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { authors, comments, posts } from \"./data.js\";\n\nexport default createSubgraph(\"d\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Post @key(fields: \"id\") {\n id: ID!\n author: Author @requires(fields: \"comments(limit: 3) { authorId }\")\n comments(limit: Int!): [Comment]\n }\n\n type Comment @key(fields: \"id\") {\n id: ID!\n date: String\n authorId: ID @external\n }\n\n type Author {\n id: ID!\n name: String\n }\n `,\n resolvers: {\n Post: {\n __resolveReference(key: {\n id: string;\n comments?: Array<{ authorId: string }>;\n }) {\n const post = posts.find((p) => p.id === key.id);\n\n if (!post) {\n return null;\n }\n\n if (key.comments) {\n if (key.comments.length !== 3) {\n throw new Error(\"Expected 3 comments\");\n }\n\n return {\n id: post.id,\n authorId: key.comments?.[2].authorId,\n };\n }\n\n return {\n id: post.id,\n };\n },\n author(post: { authorId?: string }) {\n if (!post.authorId) {\n return null;\n }\n\n const author = authors.find((a) => a.id === post.authorId);\n\n if (!author) {\n return null;\n }\n\n return {\n id: author.id,\n name: author.name,\n };\n },\n comments(post: { id: string }, { limit }: { limit: number }) {\n const coms = comments\n .filter((comment) => comment.postId === post.id)\n .slice(0, limit)\n .map((c) => ({\n id: c.id,\n }));\n\n return coms;\n },\n },\n Comment: {\n __resolveReference(key: { id: string }) {\n const comment = comments.find((c) => c.id === key.id);\n\n if (!comment) {\n return null;\n }\n\n return {\n id: comment.id,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "e61676fe28d111c468ecff2664577e3e7955e437a9419ee384e574a290a17884", + "source": "export const products = [\n {\n upc: \"p1\",\n name: \"p-name-1\",\n price: 11,\n weight: 1,\n category: {\n averagePrice: 11,\n },\n },\n {\n upc: \"p2\",\n name: \"p-name-2\",\n price: 22,\n weight: 2,\n category: {\n averagePrice: 22,\n },\n },\n];\n\nexport const posts = [\n {\n id: \"p1\",\n body: \"p1-body\",\n },\n {\n id: \"p2\",\n body: \"p2-body\",\n },\n];\n\nexport const comments = [\n {\n id: \"c1\",\n postId: \"p1\",\n authorId: \"a2\",\n body: \"c1-body\",\n },\n {\n id: \"c2\",\n postId: \"p1\",\n authorId: \"a2\",\n body: \"c2-body\",\n },\n {\n id: \"c3\",\n postId: \"p1\",\n authorId: \"a2\",\n body: \"c3-body\",\n },\n {\n id: \"c4\",\n postId: \"p2\",\n authorId: \"a1\",\n body: \"c4-body\",\n },\n {\n id: \"c5\",\n postId: \"p2\",\n authorId: \"a1\",\n body: \"c5-body\",\n },\n {\n id: \"c6\",\n postId: \"p2\",\n authorId: \"a1\",\n body: \"c6-body\",\n },\n];\n\nexport const authors = [\n {\n id: \"a1\",\n name: \"a1-name\",\n },\n {\n id: \"a2\",\n name: \"a2-name\",\n },\n];\n" + } + ] + }, + { + "id": "requires-with-argument-conflict", + "cases": [ + { + "id": "requires-with-argument-conflict/000", + "query": "\n query {\n products {\n upc\n name\n shippingEstimate\n shippingEstimateEUR\n isExpensiveCategory\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "upc": "p1", + "name": "p-name-1", + "shippingEstimate": 110, + "shippingEstimateEUR": 220, + "isExpensiveCategory": false + }, + { + "upc": "p2", + "name": "p-name-2", + "shippingEstimate": 440, + "shippingEstimateEUR": 880, + "isExpensiveCategory": true + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"upc\") {\n upc: String!\n weight: Int @external\n price(currency: String!): Int @external\n shippingEstimate: Int\n @requires(\n fields: \"\"\"\n price(currency: \"USD\") weight\n \"\"\"\n )\n shippingEstimateEUR: Int\n @requires(\n fields: \"\"\"\n price(currency: \"EUR\") weight\n \"\"\"\n )\n category: Category @external\n isExpensiveCategory: Boolean\n @requires(\n fields: \"\"\"\n category { averagePrice(currency: \"USD\") }\n \"\"\"\n )\n }\n\n type Category @external {\n averagePrice(currency: String!): Int\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"upc\")\n{\n upc: String!\n weight: Int @external\n price(currency: String!): Int @external\n shippingEstimate: Int @requires(fields: \"price(currency: \\\"USD\\\") weight\")\n shippingEstimateEUR: Int @requires(fields: \"price(currency: \\\"EUR\\\") weight\")\n category: Category @external\n isExpensiveCategory: Boolean @requires(fields: \"category { averagePrice(currency: \\\"USD\\\") }\")\n}\n\ntype Category\n @external\n{\n averagePrice(currency: String!): Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n products: [Product]\n }\n\n type Product @key(fields: \"upc\") {\n upc: String!\n name: String\n price(currency: String!): Int\n weight: Int\n category: Category\n }\n\n type Category {\n averagePrice(currency: String!): Int\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n products: [Product]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Product\n @key(fields: \"upc\")\n{\n upc: String!\n name: String\n price(currency: String!): Int\n weight: Int\n category: Category\n}\n\ntype Category {\n averagePrice(currency: String!): Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "c0f21ba286ac9c8cfce77969888a0016facce47e8aef1bface231e692babd359", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"upc\") {\n upc: String!\n weight: Int @external\n price(currency: String!): Int @external\n shippingEstimate: Int\n @requires(\n fields: \"\"\"\n price(currency: \"USD\") weight\n \"\"\"\n )\n shippingEstimateEUR: Int\n @requires(\n fields: \"\"\"\n price(currency: \"EUR\") weight\n \"\"\"\n )\n category: Category @external\n isExpensiveCategory: Boolean\n @requires(\n fields: \"\"\"\n category { averagePrice(currency: \"USD\") }\n \"\"\"\n )\n }\n\n type Category @external {\n averagePrice(currency: String!): Int\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(\n key: { upc: string; price: number; weight: number } | { upc: string },\n ) {\n const product = products.find((p) => p.upc === key.upc);\n\n if (!product) {\n return null;\n }\n\n if (\"weight\" in key && \"price\" in key) {\n return {\n upc: product.upc,\n weight: key.weight,\n price: key.price,\n category: product.category,\n };\n }\n\n return {\n upc: product.upc,\n category: product.category,\n };\n },\n shippingEstimate(product: { price: number; weight: number }) {\n const value = product.price * product.weight * 10;\n return value;\n },\n shippingEstimateEUR(product: { price: number; weight: number }) {\n const value = product.price * product.weight * 10;\n return value;\n },\n isExpensiveCategory(product: { category: { averagePrice: number } }) {\n return product.category.averagePrice > 11;\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "7f0ea9450502beb3195c72aa441e908769ac6555cdd138da7e5715f5610df220", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n products: [Product]\n }\n\n type Product @key(fields: \"upc\") {\n upc: String!\n name: String\n price(currency: String!): Int\n weight: Int\n category: Category\n }\n\n type Category {\n averagePrice(currency: String!): Int\n }\n `,\n resolvers: {\n Query: {\n products() {\n return products.map((p) => ({\n upc: p.upc,\n name: p.name,\n price: p.price,\n weight: p.weight,\n category: p.category,\n }));\n },\n },\n Product: {\n __resolveReference(key: { upc: string }) {\n const product = products.find((p) => p.upc === key.upc);\n\n if (!product) {\n return null;\n }\n\n return {\n upc: product.upc,\n name: product.name,\n price: product.price,\n weight: product.weight,\n category: product.category,\n };\n },\n price(parent: { price: number }, args: { currency: string }) {\n if (args.currency === \"EUR\") return parent.price * 2;\n if (args.currency === \"USD\") return parent.price;\n throw new Error(\"Unsupported currency \" + args.currency);\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "f009bdf06569182594816ccb2caf4db70a45db08b445d90425d240ad45ffdeba", + "source": "export const products = [\n {\n upc: \"p1\",\n name: \"p-name-1\",\n price: 11,\n weight: 1,\n category: {\n averagePrice: 11,\n },\n },\n {\n upc: \"p2\",\n name: \"p-name-2\",\n price: 22,\n weight: 2,\n category: {\n averagePrice: 22,\n },\n },\n];\n" + } + ] + }, + { + "id": "requires-with-fragments", + "cases": [ + { + "id": "requires-with-fragments/000", + "query": "\n query {\n b {\n id\n data {\n __typename\n }\n }\n bb {\n id\n data {\n __typename\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "b": { + "id": "e1", + "data": null + }, + "bb": { + "id": "e2", + "data": { + "__typename": "Qux" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-with-fragments/001", + "query": "\n query {\n a {\n requirer\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "a": { + "requirer": "q1-foo_requirer" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-with-fragments/002", + "query": "\n query {\n a {\n data {\n __typename\n }\n requirer\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "a": { + "data": { + "__typename": "Qux" + }, + "requirer": "q1-foo_requirer" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-with-fragments/003", + "query": "\n query {\n bb {\n data {\n __typename\n }\n requirer\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "bb": { + "data": { + "__typename": "Qux" + }, + "requirer": "q1-foo_requirer" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-with-fragments/004", + "query": "\n query {\n b {\n data {\n __typename\n }\n requirer\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "b": { + "data": null, + "requirer": "b1-foo_requirer" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "requires-with-fragments/005", + "query": "\n query {\n bb {\n data {\n __typename\n }\n requirer2\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "bb": { + "data": { + "__typename": "Qux" + }, + "requirer2": "q1-foo_requirer2" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.5\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query @shareable {\n a: Entity\n }\n\n type Entity @key(fields: \"id\") {\n id: ID!\n data: Foo\n }\n\n interface Foo {\n foo: String!\n }\n\n interface Bar implements Foo {\n foo: String!\n bar: String!\n }\n\n type Baz implements Foo & Bar @shareable {\n foo: String!\n bar: String!\n baz: String!\n }\n\n type Qux implements Foo & Bar @shareable {\n foo: String!\n bar: String!\n qux: String!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.5\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ndirective @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ntype Query\n @shareable\n{\n a: Entity\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Entity\n @key(fields: \"id\")\n{\n id: ID!\n data: Foo\n}\n\ninterface Foo {\n foo: String!\n}\n\ninterface Bar implements Foo {\n foo: String!\n bar: String!\n}\n\ntype Baz implements Foo & Bar\n @shareable\n{\n foo: String!\n bar: String!\n baz: String!\n}\n\ntype Qux implements Foo & Bar\n @shareable\n{\n foo: String!\n bar: String!\n qux: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar federation__Scope\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Entity" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.5\"\n import: [\n \"@key\"\n \"@shareable\"\n \"@inaccessible\"\n \"@external\"\n \"@requires\"\n ]\n )\n\n type Query @shareable {\n b: Entity\n bb: Entity\n }\n\n type Entity @key(fields: \"id\") {\n id: ID!\n data: Foo @external\n requirer: String!\n @requires(\n fields: \"\"\"\n data {\n foo\n ... on Bar {\n bar\n ... on Baz {\n baz\n }\n ... on Qux {\n qux\n }\n }\n }\n \"\"\"\n )\n requirer2: String!\n @requires(\n fields: \"\"\"\n data {\n ... on Foo {\n foo\n }\n }\n \"\"\"\n )\n }\n\n interface Foo {\n foo: String!\n }\n\n interface Bar implements Foo {\n foo: String!\n bar: String!\n }\n\n type Baz implements Foo & Bar @shareable @inaccessible {\n foo: String!\n bar: String!\n baz: String!\n }\n\n type Qux implements Foo & Bar @shareable {\n foo: String!\n bar: String!\n qux: String!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.5\", import: [\"@key\", \"@shareable\", \"@inaccessible\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ndirective @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ndirective @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM\n\ntype Query\n @shareable\n{\n b: Entity\n bb: Entity\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Entity\n @key(fields: \"id\")\n{\n id: ID!\n data: Foo @external\n requirer: String! @requires(fields: \"data {\\n foo\\n ... on Bar {\\n bar\\n ... on Baz {\\n baz\\n }\\n ... on Qux {\\n qux\\n }\\n }\\n}\")\n requirer2: String! @requires(fields: \"data {\\n ... on Foo {\\n foo\\n }\\n}\")\n}\n\ninterface Foo {\n foo: String!\n}\n\ninterface Bar implements Foo {\n foo: String!\n bar: String!\n}\n\ntype Baz implements Foo & Bar\n @shareable\n @inaccessible\n{\n foo: String!\n bar: String!\n baz: String!\n}\n\ntype Qux implements Foo & Bar\n @shareable\n{\n foo: String!\n bar: String!\n qux: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar federation__Scope\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Entity" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "eeb5690054ce0f62a494e497a032487ac4a430d8109b78b6a596fdf6cfa38a73", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { bazs, entities, quxs } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.5\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query @shareable {\n a: Entity\n }\n\n type Entity @key(fields: \"id\") {\n id: ID!\n data: Foo\n }\n\n interface Foo {\n foo: String!\n }\n\n interface Bar implements Foo {\n foo: String!\n bar: String!\n }\n\n type Baz implements Foo & Bar @shareable {\n foo: String!\n bar: String!\n baz: String!\n }\n\n type Qux implements Foo & Bar @shareable {\n foo: String!\n bar: String!\n qux: String!\n }\n `,\n resolvers: {\n Query: {\n a() {\n return entities[1];\n },\n },\n Entity: {\n __resolveReference(key: { id: string }) {\n const entity = entities.find((e) => e.id === key.id);\n\n if (!entity) {\n return null;\n }\n\n return entity;\n },\n data(entity: { id: string; data: string }) {\n const data = entities.find((e) => e.id === entity.id)?.data;\n\n if (!data) {\n return null;\n }\n\n const baz = bazs.find((b) => b.id === data);\n\n if (baz) {\n return baz;\n }\n\n const qux = quxs.find((q) => q.id === data);\n\n if (qux) {\n return qux;\n }\n\n throw new Error(\"Invalid data\");\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "a9211eea91108f0c527a657b659dfd5e72847c75d82ce902b9a21d24be5c3982", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { entities, bazs, quxs } from \"./data.js\";\n\ntype WithData = T & {\n data: {\n foo: string;\n } & ({ bar: string; baz: string } | { bar: string; qux: string });\n};\ntype WithFoo = T & {\n data: {\n foo: string;\n };\n};\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.5\"\n import: [\n \"@key\"\n \"@shareable\"\n \"@inaccessible\"\n \"@external\"\n \"@requires\"\n ]\n )\n\n type Query @shareable {\n b: Entity\n bb: Entity\n }\n\n type Entity @key(fields: \"id\") {\n id: ID!\n data: Foo @external\n requirer: String!\n @requires(\n fields: \"\"\"\n data {\n foo\n ... on Bar {\n bar\n ... on Baz {\n baz\n }\n ... on Qux {\n qux\n }\n }\n }\n \"\"\"\n )\n requirer2: String!\n @requires(\n fields: \"\"\"\n data {\n ... on Foo {\n foo\n }\n }\n \"\"\"\n )\n }\n\n interface Foo {\n foo: String!\n }\n\n interface Bar implements Foo {\n foo: String!\n bar: String!\n }\n\n type Baz implements Foo & Bar @shareable @inaccessible {\n foo: String!\n bar: String!\n baz: String!\n }\n\n type Qux implements Foo & Bar @shareable {\n foo: String!\n bar: String!\n qux: String!\n }\n `,\n resolvers: {\n Query: {\n b() {\n return entities[0];\n },\n bb() {\n return entities[1];\n },\n },\n Entity: {\n __resolveReference(\n key:\n | { id: string }\n | WithData<{ id: string }>\n | WithFoo<{ id: string }>,\n ) {\n const entity = entities.find((e) => e.id === key.id);\n\n if (!entity) {\n return null;\n }\n\n return {\n ...entity,\n ...key,\n };\n },\n data(entity: { id: string }) {\n const data = entities.find((e) => e.id === entity.id)?.data;\n\n if (!data) {\n return null;\n }\n\n const baz = bazs.find((b) => b.id === data);\n\n if (baz) {\n return baz;\n }\n\n const qux = quxs.find((q) => q.id === data);\n\n if (qux) {\n return qux;\n }\n\n throw new Error(\"Invalid data\");\n },\n requirer(entity: WithData<{ id: string }>) {\n if (!(\"data\" in entity)) {\n throw new Error(\"Expected entity to have a data field\");\n }\n\n return entity.data.foo + \"_requirer\";\n },\n requirer2(entity: WithFoo<{ id: string }>) {\n if (!(\"data\" in entity)) {\n throw new Error(\"Expected entity to have a data field\");\n }\n\n return entity.data.foo + \"_requirer2\";\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "94028a4d3b1c95160f14519d3330182dac162a29127aacc480a9f3547a8c22fc", + "source": "export const entities = [\n {\n id: \"e1\",\n data: \"b1\",\n },\n {\n id: \"e2\",\n data: \"q1\",\n },\n];\n\nexport const bazs = [\n {\n __typename: \"Baz\",\n id: \"b1\",\n foo: \"b1-foo\",\n bar: \"b1-bar\",\n baz: \"b1-baz\",\n },\n];\n\nexport const quxs = [\n {\n __typename: \"Qux\",\n id: \"q1\",\n foo: \"q1-foo\",\n bar: \"q1-bar\",\n qux: \"q1-qux\",\n },\n];\n" + } + ] + }, + { + "id": "shared-root", + "cases": [ + { + "id": "shared-root/000", + "query": "\n query {\n product {\n id\n name {\n id\n brand\n model\n }\n category {\n id\n name\n }\n price {\n id\n amount\n currency\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "product": { + "id": "1", + "name": { + "id": "1", + "brand": "Brand 1", + "model": "Model 1" + }, + "price": { + "id": "1", + "amount": 1000, + "currency": "USD" + }, + "category": { + "id": "1", + "name": "Category 1" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "shared-root/001", + "query": "\n query {\n products {\n id\n name {\n id\n brand\n model\n }\n category {\n id\n name\n }\n price {\n id\n amount\n currency\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "1", + "name": { + "id": "1", + "brand": "Brand 1", + "model": "Model 1" + }, + "price": { + "id": "1", + "amount": 1000, + "currency": "USD" + }, + "category": { + "id": "1", + "name": "Category 1" + } + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "name", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n product: Product! @shareable\n products: [Product!]! @shareable\n }\n\n type Product {\n id: ID! @shareable\n name: Name!\n }\n\n type Name {\n id: ID!\n brand: String!\n model: String!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n product: Product! @shareable\n products: [Product!]! @shareable\n _service: _Service!\n}\n\ntype Product {\n id: ID! @shareable\n name: Name!\n}\n\ntype Name {\n id: ID!\n brand: String!\n model: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}" + }, + { + "name": "price", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n product: Product! @shareable\n products: [Product!]! @shareable\n }\n\n type Product {\n id: ID! @shareable\n price: Price!\n }\n\n type Price {\n id: ID!\n amount: Int!\n currency: String!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n product: Product! @shareable\n products: [Product!]! @shareable\n _service: _Service!\n}\n\ntype Product {\n id: ID! @shareable\n price: Price!\n}\n\ntype Price {\n id: ID!\n amount: Int!\n currency: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}" + }, + { + "name": "category", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n product: Product! @shareable\n products: [Product!]! @shareable\n }\n\n type Product {\n id: ID! @shareable\n category: Category!\n }\n\n type Category {\n id: ID!\n name: String!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n product: Product! @shareable\n products: [Product!]! @shareable\n _service: _Service!\n}\n\ntype Product {\n id: ID! @shareable\n category: Category!\n}\n\ntype Category {\n id: ID!\n name: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}" + } + ], + "fixtureModules": [ + { + "path": "category.subgraph.ts", + "sha256": "2984b39e201c07f4aff70c7f139e8354e5c24f07cbbec54e25eb7ad5003c2272", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { product } from \"./data.js\";\n\nexport default createSubgraph(\"category\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n product: Product! @shareable\n products: [Product!]! @shareable\n }\n\n type Product {\n id: ID! @shareable\n category: Category!\n }\n\n type Category {\n id: ID!\n name: String!\n }\n `,\n resolvers: {\n Query: {\n product() {\n return {\n id: product.id,\n category: product.category,\n };\n },\n products() {\n return [\n {\n id: product.id,\n category: product.category,\n },\n ];\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "ab1cf17ba1077a7bf0ceccd8b46beadbfa752c7fe99739c90e9a9598359e3fcf", + "source": "export const product = {\n id: \"1\",\n name: {\n id: \"1\",\n brand: \"Brand 1\",\n model: \"Model 1\",\n },\n category: {\n id: \"1\",\n name: \"Category 1\",\n },\n price: {\n id: \"1\",\n amount: 1000,\n currency: \"USD\",\n },\n address: {\n id: \"1\",\n street: \"123 Main St\",\n city: \"Springfield\",\n },\n};\n" + }, + { + "path": "name.subgraph.ts", + "sha256": "136118f77ca6423cc946cf1618d7a36d561a52aa277ba08ebab9df8666cee80b", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { product } from \"./data.js\";\n\nexport default createSubgraph(\"name\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n product: Product! @shareable\n products: [Product!]! @shareable\n }\n\n type Product {\n id: ID! @shareable\n name: Name!\n }\n\n type Name {\n id: ID!\n brand: String!\n model: String!\n }\n `,\n resolvers: {\n Query: {\n product() {\n return {\n id: product.id,\n name: product.name,\n };\n },\n products() {\n return [\n {\n id: product.id,\n name: product.name,\n },\n ];\n },\n },\n },\n});\n" + }, + { + "path": "price.subgraph.ts", + "sha256": "a1a8f5335b0d3b8841d54d315276f65f3cda78c3a80071b4b70f87464e0a16c8", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { product } from \"./data.js\";\n\nexport default createSubgraph(\"price\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n product: Product! @shareable\n products: [Product!]! @shareable\n }\n\n type Product {\n id: ID! @shareable\n price: Price!\n }\n\n type Price {\n id: ID!\n amount: Int!\n currency: String!\n }\n `,\n resolvers: {\n Query: {\n product() {\n return {\n id: product.id,\n price: product.price,\n };\n },\n products() {\n return [\n {\n id: product.id,\n price: product.price,\n },\n ];\n },\n },\n },\n});\n" + } + ] + }, + { + "id": "simple-entity-call", + "cases": [ + { + "id": "simple-entity-call/000", + "query": "\n query {\n user {\n id\n nickname\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "user": { + "id": "1", + "nickname": "user1" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "email", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\"])\n\n type Query {\n user: User\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n email: String!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ntype Query {\n user: User\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n email: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + }, + { + "name": "nickname", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.0\"\n import: [\"@key\", \"@external\"]\n )\n\n type User @key(fields: \"email\") {\n email: String! @external\n nickname: String!\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\", \"@external\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ntype User\n @key(fields: \"email\")\n{\n email: String! @external\n nickname: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "data.ts", + "sha256": "4cef6e66af03abfeedf86177ea4cb2870fe34921467c41912c55cf1863f2a301", + "source": "export const users = [\n {\n id: \"1\",\n email: \"user1@gmail.com\",\n nickname: \"user1\",\n },\n {\n id: \"2\",\n email: \"user2@gmail.com\",\n nickname: \"user2\",\n },\n];\n" + }, + { + "path": "email.subgraph.ts", + "sha256": "5bd33e72e74e19e19b1e8f684b64163d0d4c8cde2233ab86718320ea66acdd78", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"email\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.0\", import: [\"@key\"])\n\n type Query {\n user: User\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n email: String!\n }\n `,\n resolvers: {\n Query: {\n user() {\n return {\n id: users[0].id,\n email: users[0].email,\n };\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n email: user.email,\n };\n },\n },\n },\n});\n" + }, + { + "path": "nickname.subgraph.ts", + "sha256": "d95aba3323f6b0c2b3914dbb3413542e5a63eb7431f378fe27fe63eb94330e7d", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"nickname\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.0\"\n import: [\"@key\", \"@external\"]\n )\n\n type User @key(fields: \"email\") {\n email: String! @external\n nickname: String!\n }\n `,\n resolvers: {\n User: {\n __resolveReference(key: { email: string }) {\n const user = users.find((u) => u.email === key.email);\n\n if (!user) {\n return null;\n }\n\n return {\n nickname: user.nickname,\n };\n },\n },\n },\n});\n" + } + ] + }, + { + "id": "simple-inaccessible", + "cases": [ + { + "id": "simple-inaccessible/000", + "query": "\n query {\n usersInAge {\n id\n friends {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "usersInAge": [ + { + "id": "u1", + "friends": [ + { + "id": "u2" + } + ] + }, + { + "id": "u2", + "friends": [ + { + "id": "u1" + } + ] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-inaccessible/001", + "query": "\n query {\n usersInFriends {\n id\n friends {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "usersInFriends": [ + { + "id": "u1", + "friends": [ + { + "id": "u2" + } + ] + }, + { + "id": "u2", + "friends": [ + { + "id": "u1" + } + ] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-inaccessible/002", + "query": "\n query {\n usersInFriends {\n id\n friends(type: FRIEND) {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": false, + "expectedData": null, + "hasExpectedErrors": true, + "expectsErrors": true + }, + { + "id": "simple-inaccessible/003", + "query": "\n query {\n usersInFriends {\n id\n friends {\n id\n type\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "usersInFriends": [ + { + "id": "u1", + "friends": [ + { + "id": "u2", + "type": null + } + ] + }, + { + "id": "u2", + "friends": [ + { + "id": "u1", + "type": null + } + ] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "friends", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@inaccessible\", \"@shareable\"]\n )\n\n type Query {\n usersInFriends: [User!]!\n }\n\n type User @key(fields: \"id\") {\n id: ID\n friends(type: FriendType = FAMILY @inaccessible): [User!]!\n type: FriendType\n }\n\n enum FriendType {\n FAMILY @inaccessible\n FRIEND\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@inaccessible\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n usersInFriends: [User!]!\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype User\n @key(fields: \"id\")\n{\n id: ID\n friends(type: FriendType = FAMILY @inaccessible): [User!]!\n type: FriendType\n}\n\nenum FriendType {\n FAMILY @inaccessible\n FRIEND\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + }, + { + "name": "age", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@inaccessible\", \"@shareable\"]\n )\n\n type Query {\n usersInAge: [User!]! @shareable\n }\n\n type User @key(fields: \"id\") {\n id: ID\n age: Int\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@inaccessible\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n usersInAge: [User!]! @shareable\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype User\n @key(fields: \"id\")\n{\n id: ID\n age: Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + } + ], + "fixtureModules": [ + { + "path": "age.subgraph.ts", + "sha256": "cfe7c042907e89bb0377625e75d557d117169890d47c6429e689a236d346d391", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"age\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@inaccessible\", \"@shareable\"]\n )\n\n type Query {\n usersInAge: [User!]! @shareable\n }\n\n type User @key(fields: \"id\") {\n id: ID\n age: Int\n }\n `,\n resolvers: {\n Query: {\n usersInAge() {\n return users.map((u) => ({\n id: u.id,\n age: u.age,\n }));\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n age: user.age,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "d62e20375091662647df462d75906cbae360ad31f82061bb290502be7acd97e1", + "source": "export const users = [\n {\n id: \"u1\",\n age: 11,\n friends: [\"u2\"],\n },\n {\n id: \"u2\",\n age: 22,\n friends: [\"u1\"],\n },\n];\n" + }, + { + "path": "friends.subgraph.ts", + "sha256": "da76a1be63dec5a179c17042fa7438c7babde8473867dacad245a4f526676e7d", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"friends\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@inaccessible\", \"@shareable\"]\n )\n\n type Query {\n usersInFriends: [User!]!\n }\n\n type User @key(fields: \"id\") {\n id: ID\n friends(type: FriendType = FAMILY @inaccessible): [User!]!\n type: FriendType\n }\n\n enum FriendType {\n FAMILY @inaccessible\n FRIEND\n }\n `,\n resolvers: {\n Query: {\n usersInFriends() {\n return users.map((u) => ({\n id: u.id,\n friends: u.friends,\n }));\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n friends: user.friends,\n };\n },\n friends(user: { id: string; friends: string[] }) {\n return user.friends.map((id) => {\n const friend = users.find((u) => u.id === id);\n\n if (!friend) {\n return null;\n }\n\n return {\n id: friend.id,\n friends: friend.friends,\n };\n });\n },\n type() {\n return \"FAMILY\";\n },\n },\n },\n});\n" + } + ] + }, + { + "id": "simple-interface-object", + "cases": [ + { + "id": "simple-interface-object/000", + "query": "\n query {\n anotherUsers {\n id\n name\n username\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "anotherUsers": [ + { + "id": "u1", + "name": "u1-name", + "username": "u1-username" + }, + { + "id": "u2", + "name": "u2-name", + "username": "u2-username" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-interface-object/001", + "query": "\n query {\n users {\n id\n name\n username\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "id": "u1", + "name": "u1-name", + "username": "u1-username" + }, + { + "id": "u2", + "name": "u2-name", + "username": "u2-username" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-interface-object/002", + "query": "\n query {\n anotherUsers {\n ... on User {\n age\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "anotherUsers": [ + { + "age": 11 + }, + { + "age": 22 + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-interface-object/003", + "query": "\n query {\n users {\n ... on User {\n age\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "age": 11 + }, + { + "age": 22 + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-interface-object/004", + "query": "\n query {\n anotherUsers {\n ... on User {\n age\n id\n name\n username\n }\n id\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "anotherUsers": [ + { + "age": 11, + "id": "u1", + "name": "u1-name", + "username": "u1-username" + }, + { + "age": 22, + "id": "u2", + "name": "u2-name", + "username": "u2-username" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-interface-object/005", + "query": "\n query {\n users {\n ... on User {\n age\n id\n name\n username\n }\n id\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "age": 11, + "id": "u1", + "name": "u1-name", + "username": "u1-username" + }, + { + "age": 22, + "id": "u2", + "name": "u2-name", + "username": "u2-username" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-interface-object/006", + "query": "\n query {\n users {\n ... on User {\n age\n id\n name\n username\n }\n id\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "age": 11, + "id": "u1", + "name": "u1-name", + "username": "u1-username" + }, + { + "age": 22, + "id": "u2", + "name": "u2-name", + "username": "u2-username" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-interface-object/007", + "query": "\n query {\n accounts {\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "accounts": [ + { + "name": "Alice" + }, + { + "name": "Bob" + }, + { + "name": "Charlie" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-interface-object/008", + "query": "\n query {\n accounts {\n ... on Admin {\n name\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "accounts": [ + { + "name": "Alice" + }, + { + "name": "Bob" + }, + {} + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-interface-object/009", + "query": "\n query {\n accounts {\n name\n # NOTE\n # __typename is not available in the interfaceObject, needs to be resolved indirectly\n __typename\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "accounts": [ + { + "name": "Alice", + "__typename": "Admin" + }, + { + "name": "Bob", + "__typename": "Admin" + }, + { + "name": "Charlie", + "__typename": "Regular" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-interface-object/010", + "query": "\n query {\n accounts {\n ... on Admin {\n __typename\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "accounts": [ + { + "__typename": "Admin" + }, + { + "__typename": "Admin" + }, + {} + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-interface-object/011", + "query": "\n query {\n accounts {\n id\n isActive\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "accounts": [ + { + "id": "u1", + "isActive": false + }, + { + "id": "u2", + "isActive": false + }, + { + "id": "u3", + "isActive": false + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-interface-object/012", + "query": "\n query {\n accounts {\n # NOTE\n # id is available in the interfaceObject and can be resolved as there's no type condition involved\n id\n ... on Admin {\n isActive\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "accounts": [ + { + "id": "u1", + "isActive": true + }, + { + "id": "u2", + "isActive": true + }, + { + "id": "u3" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n users: [NodeWithName!]!\n }\n\n interface NodeWithName @key(fields: \"id\") {\n id: ID!\n name: String\n }\n\n type User implements NodeWithName @key(fields: \"id\") {\n id: ID!\n name: String\n age: Int\n }\n\n interface Account @key(fields: \"id\") {\n id: ID!\n }\n\n type Admin implements Account @key(fields: \"id\") {\n id: ID!\n isMain: Boolean!\n isActive: Boolean! @shareable\n }\n\n type Regular implements Account @key(fields: \"id\") {\n id: ID!\n isMain: Boolean!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n users: [NodeWithName!]!\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ninterface NodeWithName\n @key(fields: \"id\")\n{\n id: ID!\n name: String\n}\n\ntype User implements NodeWithName\n @key(fields: \"id\")\n{\n id: ID!\n name: String\n age: Int\n}\n\ninterface Account\n @key(fields: \"id\")\n{\n id: ID!\n}\n\ntype Admin implements Account\n @key(fields: \"id\")\n{\n id: ID!\n isMain: Boolean!\n isActive: Boolean! @shareable\n}\n\ntype Regular implements Account\n @key(fields: \"id\")\n{\n id: ID!\n isMain: Boolean!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Admin | Regular | User" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\", \"@shareable\"]\n )\n\n type Query {\n anotherUsers: [NodeWithName]\n accounts: [Account] @shareable\n }\n\n type Account @key(fields: \"id\") @interfaceObject {\n id: ID!\n name: String!\n }\n\n type NodeWithName @key(fields: \"id\") @interfaceObject {\n id: ID!\n username: String\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@interfaceObject\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @interfaceObject on OBJECT\n\ntype Query {\n anotherUsers: [NodeWithName]\n accounts: [Account] @shareable\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Account\n @key(fields: \"id\")\n @interfaceObject\n{\n id: ID!\n name: String!\n}\n\ntype NodeWithName\n @key(fields: \"id\")\n @interfaceObject\n{\n id: ID!\n username: String\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Account | NodeWithName" + }, + { + "name": "c", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\", \"@shareable\"]\n )\n\n type Account @key(fields: \"id\") @interfaceObject {\n id: ID!\n isActive: Boolean! @shareable\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@interfaceObject\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @interfaceObject on OBJECT\n\ntype Account\n @key(fields: \"id\")\n @interfaceObject\n{\n id: ID!\n isActive: Boolean! @shareable\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Account\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "d1b9d9aea619d20c39fa5f37cfb1d9ea373abb2cb88a3060535f34d9fad3e8fe", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users, accounts } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n users: [NodeWithName!]!\n }\n\n interface NodeWithName @key(fields: \"id\") {\n id: ID!\n name: String\n }\n\n type User implements NodeWithName @key(fields: \"id\") {\n id: ID!\n name: String\n age: Int\n }\n\n interface Account @key(fields: \"id\") {\n id: ID!\n }\n\n type Admin implements Account @key(fields: \"id\") {\n id: ID!\n isMain: Boolean!\n isActive: Boolean! @shareable\n }\n\n type Regular implements Account @key(fields: \"id\") {\n id: ID!\n isMain: Boolean!\n }\n `,\n resolvers: {\n NodeWithName: {\n __resolveReference(key: { id: string }) {\n const node = users.find((u) => u.id === key.id);\n\n if (!node) {\n return null;\n }\n\n return {\n __typename: \"User\",\n id: node.id,\n name: node.name,\n age: node.age,\n };\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n __typename: \"User\",\n id: user.id,\n name: user.name,\n age: user.age,\n };\n },\n },\n Account: {\n __resolveReference(key: { id: string }) {\n const account = accounts.find((account) => account.id === key.id);\n\n if (!account) {\n return null;\n }\n\n return {\n __typename: account.__typename,\n id: account.id,\n isActive: account.isActive,\n };\n },\n },\n Admin: {\n __resolveReference(key: { id: string }) {\n const admin = accounts.find((account) => account.id === key.id);\n\n if (!admin) {\n return null;\n }\n\n if (admin.__typename !== \"Admin\") {\n return null;\n }\n\n return {\n __typename: admin.__typename,\n id: admin.id,\n isMain: admin.isMain,\n isActive: admin.isActive,\n };\n },\n isMain(admin: { id: string; isMain?: boolean }) {\n if (typeof admin.isMain === \"boolean\") {\n return admin.isMain;\n }\n\n const account = accounts.find((a) => a.id === admin.id);\n\n if (!account) {\n return null;\n }\n\n return account.isMain;\n },\n },\n Regular: {\n __resolveReference(key: { id: string }) {\n const admin = accounts.find((account) => account.id === key.id);\n\n if (!admin) {\n return null;\n }\n\n return {\n __typename: admin.__typename,\n id: admin.id,\n isMain: admin.isMain,\n isActive: admin.isActive,\n };\n },\n isMain(admin: { id: string; isMain?: boolean }) {\n if (typeof admin.isMain === \"boolean\") {\n return admin.isMain;\n }\n\n const account = accounts.find((a) => a.id === admin.id);\n\n if (!account) {\n return null;\n }\n\n return account.isMain;\n },\n },\n Query: {\n users() {\n return users.map((u) => ({\n __typename: \"User\",\n id: u.id,\n name: u.name,\n age: u.age,\n }));\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "9bdf09f3031bd333015d74c0c883e6ff3306e69ba45031dfb8faaed30957b513", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users, accounts } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\", \"@shareable\"]\n )\n\n type Query {\n anotherUsers: [NodeWithName]\n accounts: [Account] @shareable\n }\n\n type Account @key(fields: \"id\") @interfaceObject {\n id: ID!\n name: String!\n }\n\n type NodeWithName @key(fields: \"id\") @interfaceObject {\n id: ID!\n username: String\n }\n `,\n resolvers: {\n NodeWithName: {\n __resolveReference(key: { id: string }) {\n const node = users.find((u) => u.id === key.id);\n\n if (!node) {\n return null;\n }\n\n return {\n __typename: \"User\",\n id: node.id,\n username: node.username,\n };\n },\n username(node: { id: string }) {\n const user = users.find((u) => u.id === node.id);\n\n if (!user) {\n return null;\n }\n\n return user.username;\n },\n },\n Account: {\n __resolveReference(key: { __typename: string; id: string }) {\n const account = accounts.find((account) => account.id === key.id);\n\n if (!account) {\n return null;\n }\n\n return {\n // I deliberately return a wrong __typename here to make sure it's not used by the gateway\n __typename: \"Never\",\n id: account.id,\n };\n },\n name(account: { id: string }) {\n return accounts.find((u) => u.id === account.id)?.name;\n },\n },\n Query: {\n anotherUsers() {\n return users.map((u) => ({\n __typename: \"User\",\n id: u.id,\n username: u.username,\n }));\n },\n accounts() {\n return accounts.map((user) => {\n return {\n // I deliberately return a wrong __typename here to make sure it's not used by the gateway\n __typename: \"Never\",\n id: user.id,\n };\n });\n },\n },\n },\n});\n" + }, + { + "path": "c.subgraph.ts", + "sha256": "0e346f56a92fe1966b91f9d75b89e6738f70b2b7b7556ad134afbd2ebfac86dc", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { accounts } from \"./data.js\";\n\nexport default createSubgraph(\"c\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\", \"@shareable\"]\n )\n\n type Account @key(fields: \"id\") @interfaceObject {\n id: ID!\n isActive: Boolean! @shareable\n }\n `,\n resolvers: {\n Account: {\n __resolveReference(key: { __typename: string; id: string }) {\n const account = accounts.find((account) => account.id === key.id);\n\n if (!account) {\n return null;\n }\n\n return {\n // I deliberately return a wrong __typename here to make sure it's not used by the gateway\n __typename: \"Never\",\n id: account.id,\n };\n },\n isActive(account: { id: string }) {\n return false;\n },\n },\n Query: {\n accounts() {\n return accounts.map((user) => {\n return {\n // I deliberately return a wrong __typename here to make sure it's not used by the gateway\n __typename: \"Never\",\n id: user.id,\n };\n });\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "2012d77b635d1dff8e478687ee770b58a794f462ae4f390497f8c07ccc544d5a", + "source": "export const users = [\n {\n id: \"u1\",\n name: \"u1-name\",\n username: \"u1-username\",\n age: 11,\n },\n {\n id: \"u2\",\n name: \"u2-name\",\n username: \"u2-username\",\n age: 22,\n },\n];\n\nexport const accounts = [\n {\n __typename: \"Admin\",\n id: \"u1\",\n name: \"Alice\",\n isMain: false,\n isActive: true,\n },\n {\n __typename: \"Admin\",\n id: \"u2\",\n name: \"Bob\",\n isMain: true,\n isActive: true,\n },\n {\n __typename: \"Regular\",\n id: \"u3\",\n name: \"Charlie\",\n isMain: false,\n isActive: true,\n },\n];\n" + } + ] + }, + { + "id": "simple-override", + "cases": [ + { + "id": "simple-override/000", + "query": "\n query {\n feed {\n createdAt\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "feed": [ + { + "createdAt": "p1-createdAt" + }, + { + "createdAt": "p2-createdAt" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-override/001", + "query": "\n query {\n aFeed {\n createdAt\n }\n bFeed {\n createdAt\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "aFeed": [ + { + "createdAt": "p2-createdAt" + } + ], + "bFeed": [ + { + "createdAt": "p1-createdAt" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Post @key(fields: \"id\") {\n id: ID!\n createdAt: String! @shareable\n }\n\n type Query {\n feed: [Post] @shareable\n aFeed: [Post]\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Post\n @key(fields: \"id\")\n{\n id: ID!\n createdAt: String! @shareable\n}\n\ntype Query {\n feed: [Post] @shareable\n aFeed: [Post]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Post" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@override\", \"@shareable\"]\n )\n\n type Post @key(fields: \"id\") {\n id: ID!\n createdAt: String! @override(from: \"a\") @shareable\n }\n\n type Query {\n feed: [Post] @shareable\n bFeed: [Post]\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@override\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Post\n @key(fields: \"id\")\n{\n id: ID!\n createdAt: String! @override(from: \"a\") @shareable\n}\n\ntype Query {\n feed: [Post] @shareable\n bFeed: [Post]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Post" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "dce46798836f6f6b62c299791fee51ce3e0ba0e7e94141d26f02d17209482bf3", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { posts } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Post @key(fields: \"id\") {\n id: ID!\n createdAt: String! @shareable\n }\n\n type Query {\n feed: [Post] @shareable\n aFeed: [Post]\n }\n `,\n resolvers: {\n Query: {\n feed() {\n return posts;\n },\n aFeed() {\n return [posts[1]];\n },\n },\n Post: {\n __resolveReference(key: { id: string }) {\n const post = posts.find((p) => p.id === key.id);\n\n if (!post) {\n return null;\n }\n\n return {\n id: post.id,\n createdAt: post.createdAt,\n };\n },\n createdAt() {\n return \"NEVER\";\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "7e1aba5e5ce6f7ddd6f19296eb660696f10f50b505359584582f1ca52213b87e", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { posts } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@override\", \"@shareable\"]\n )\n\n type Post @key(fields: \"id\") {\n id: ID!\n createdAt: String! @override(from: \"a\") @shareable\n }\n\n type Query {\n feed: [Post] @shareable\n bFeed: [Post]\n }\n `,\n resolvers: {\n Query: {\n feed() {\n return posts;\n },\n bFeed() {\n return [posts[0]];\n },\n },\n Post: {\n __resolveReference(key: { id: string }) {\n const post = posts.find((p) => p.id === key.id);\n\n if (!post) {\n return null;\n }\n\n return {\n id: post.id,\n createdAt: post.createdAt,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "029b5a3d869e8cafda598325ac43ef79d3ce0b74295c8ccbea98a3831f144328", + "source": "export const posts = [\n {\n id: \"p1\",\n createdAt: \"p1-createdAt\",\n },\n {\n id: \"p2\",\n createdAt: \"p2-createdAt\",\n },\n];\n" + } + ] + }, + { + "id": "simple-requires-provides", + "cases": [ + { + "id": "simple-requires-provides/000", + "query": "\n query {\n me {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "me": { + "id": "u1" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-requires-provides/001", + "query": "\n query {\n me {\n id\n reviews {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "me": { + "id": "u1", + "reviews": [ + { + "id": "r1" + }, + { + "id": "r2" + } + ] + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-requires-provides/002", + "query": "\n query {\n me {\n reviews {\n id\n author {\n id\n username\n }\n product {\n inStock\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "me": { + "reviews": [ + { + "id": "r1", + "author": { + "id": "u1", + "username": "u-username-1" + }, + "product": { + "inStock": true + } + }, + { + "id": "r2", + "author": { + "id": "u1", + "username": "u-username-1" + }, + "product": { + "inStock": false + } + } + ] + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-requires-provides/003", + "query": "\n query {\n products {\n name\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "name": "p-name-1" + }, + { + "name": "p-name-2" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-requires-provides/004", + "query": "\n query {\n products {\n price\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "price": 11 + }, + { + "price": 22 + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-requires-provides/005", + "query": "\n query {\n products {\n shippingEstimate\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "shippingEstimate": 110 + }, + { + "shippingEstimate": 440 + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-requires-provides/006", + "query": "\n query {\n products {\n shippingEstimate\n weight\n price\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "shippingEstimate": 110, + "weight": 1, + "price": 11 + }, + { + "shippingEstimate": 440, + "weight": 2, + "price": 22 + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-requires-provides/007", + "query": "\n {\n products {\n reviews {\n id\n author {\n username\n }\n product {\n name\n shippingEstimate\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "reviews": [ + { + "id": "r1", + "author": { + "username": "u-username-1" + }, + "product": { + "name": "p-name-1", + "shippingEstimate": 110 + } + } + ] + }, + { + "reviews": [ + { + "id": "r2", + "author": { + "username": "u-username-1" + }, + "product": { + "name": "p-name-2", + "shippingEstimate": 440 + } + } + ] + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-requires-provides/008", + "query": "\n {\n me {\n reviews {\n product {\n reviews {\n id\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "me": { + "reviews": [ + { + "product": { + "reviews": [ + { + "id": "r1" + } + ] + } + }, + { + "product": { + "reviews": [ + { + "id": "r2" + } + ] + } + } + ] + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-requires-provides/009", + "query": "\n query {\n me {\n reviews {\n product {\n inStock\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "me": { + "reviews": [ + { + "product": { + "inStock": true + } + }, + { + "product": { + "inStock": false + } + } + ] + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-requires-provides/010", + "query": "\n query {\n me {\n reviews {\n product {\n shippingEstimate\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "me": { + "reviews": [ + { + "product": { + "shippingEstimate": 110 + } + }, + { + "product": { + "shippingEstimate": 440 + } + } + ] + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "simple-requires-provides/011", + "query": "\n query {\n me {\n reviews {\n product {\n shippingEstimate\n shippingEstimateTag\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "me": { + "reviews": [ + { + "product": { + "shippingEstimate": 110, + "shippingEstimateTag": "#p1#110#" + } + }, + { + "product": { + "shippingEstimate": 440, + "shippingEstimateTag": "#p2#440#" + } + } + ] + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "accounts", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n me: User\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String\n username: String @shareable\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n me: User\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n name: String\n username: String @shareable\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + }, + { + "name": "inventory", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"upc\") {\n upc: String!\n weight: Int @external\n price: Int @external\n inStock: Boolean\n shippingEstimate: Int @requires(fields: \"price weight\")\n shippingEstimateTag: String @requires(fields: \"price weight\")\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@requires\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Product\n @key(fields: \"upc\")\n{\n upc: String!\n weight: Int @external\n price: Int @external\n inStock: Boolean\n shippingEstimate: Int @requires(fields: \"price weight\")\n shippingEstimateTag: String @requires(fields: \"price weight\")\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + }, + { + "name": "products", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n products: [Product]\n }\n\n type Product @key(fields: \"upc\") {\n upc: String!\n name: String\n price: Int\n weight: Int\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n products: [Product]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Product\n @key(fields: \"upc\")\n{\n upc: String!\n name: String\n price: Int\n weight: Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product" + }, + { + "name": "reviews", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@provides\"]\n )\n\n type Review @key(fields: \"id\") {\n id: ID!\n body: String\n author: User @provides(fields: \"username\")\n product: Product\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n username: String @external\n reviews: [Review]\n }\n\n type Product @key(fields: \"upc\") {\n upc: String!\n reviews: [Review]\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@external\", \"@provides\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Review\n @key(fields: \"id\")\n{\n id: ID!\n body: String\n author: User @provides(fields: \"username\")\n product: Product\n}\n\ntype User\n @key(fields: \"id\")\n{\n id: ID!\n username: String @external\n reviews: [Review]\n}\n\ntype Product\n @key(fields: \"upc\")\n{\n upc: String!\n reviews: [Review]\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Product | Review | User\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "accounts.subgraph.ts", + "sha256": "d93af41df8620d6555b2d85033080553ed0e375983c68831e41ef8c532ddcfb3", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"accounts\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Query {\n me: User\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n name: String\n username: String @shareable\n }\n `,\n resolvers: {\n Query: {\n me() {\n return {\n id: users[0].id,\n name: users[0].name,\n username: users[0].username,\n };\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n name: user.name,\n username: user.username,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "ffc8298df5d1c147b543f6c72cf89b844117f19fbfdb70212c30a18bb768f8b5", + "source": "export const users = [\n {\n id: \"u1\",\n username: \"u-username-1\",\n name: \"u-name-1\",\n },\n {\n id: \"u2\",\n username: \"u-username-2\",\n name: \"u-name-2\",\n },\n];\n\nexport const products = [\n {\n upc: \"p1\",\n name: \"p-name-1\",\n price: 11,\n weight: 1,\n },\n {\n upc: \"p2\",\n name: \"p-name-2\",\n price: 22,\n weight: 2,\n },\n];\n\nexport const inStock = [products[0].upc];\n\nexport const reviews = [\n {\n id: \"r1\",\n body: \"r-body-1\",\n authorId: users[0].id,\n productUpc: products[0].upc,\n },\n {\n id: \"r2\",\n body: \"r-body-2\",\n authorId: users[0].id,\n productUpc: products[1].upc,\n },\n];\n" + }, + { + "path": "inventory.subgraph.ts", + "sha256": "41561f9a5ead3df41ab5b46de2b5e269b9f23265c25e8a580ce77ef81d8d68b3", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { inStock, products } from \"./data.js\";\n\nexport default createSubgraph(\"inventory\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@requires\"]\n )\n\n type Product @key(fields: \"upc\") {\n upc: String!\n weight: Int @external\n price: Int @external\n inStock: Boolean\n shippingEstimate: Int @requires(fields: \"price weight\")\n shippingEstimateTag: String @requires(fields: \"price weight\")\n }\n `,\n resolvers: {\n Product: {\n __resolveReference(\n key: { upc: string; price: number; weight: number } | { upc: string },\n ) {\n const product = products.find((p) => p.upc === key.upc);\n\n if (!product) {\n return null;\n }\n\n if (\"weight\" in key && \"price\" in key) {\n return {\n upc: product.upc,\n weight: key.weight,\n price: key.price,\n };\n }\n\n return {\n upc: product.upc,\n };\n },\n shippingEstimate(product: { price: number; weight: number }) {\n return product.price * product.weight * 10;\n },\n shippingEstimateTag(product: {\n upc: string;\n price: number;\n weight: number;\n }) {\n return `#${product.upc}#${product.price * product.weight * 10}#`;\n },\n inStock(product: { upc: string }) {\n return inStock.includes(product.upc);\n },\n },\n },\n});\n" + }, + { + "path": "products.subgraph.ts", + "sha256": "80cfccf5093edc3d62bb746a5e3a01eeb954c9ecb389fe204ff438390cfdf42d", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"products\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n products: [Product]\n }\n\n type Product @key(fields: \"upc\") {\n upc: String!\n name: String\n price: Int\n weight: Int\n }\n `,\n resolvers: {\n Query: {\n products() {\n return products.map((p) => ({\n upc: p.upc,\n name: p.name,\n price: p.price,\n weight: p.weight,\n }));\n },\n },\n Product: {\n __resolveReference(key: { upc: string }) {\n const product = products.find((p) => p.upc === key.upc);\n\n if (!product) {\n return null;\n }\n\n return {\n upc: product.upc,\n name: product.name,\n price: product.price,\n weight: product.weight,\n };\n },\n },\n },\n});\n" + }, + { + "path": "reviews.subgraph.ts", + "sha256": "03691acbfa5255af6998edc802eb71f23734ba35a47a5ef8c8ccfbe555699c56", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products, reviews, users } from \"./data.js\";\n\nexport default createSubgraph(\"reviews\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@external\", \"@provides\"]\n )\n\n type Review @key(fields: \"id\") {\n id: ID!\n body: String\n author: User @provides(fields: \"username\")\n product: Product\n }\n\n type User @key(fields: \"id\") {\n id: ID!\n username: String @external\n reviews: [Review]\n }\n\n type Product @key(fields: \"upc\") {\n upc: String!\n reviews: [Review]\n }\n `,\n resolvers: {\n Review: {\n __resolveReference(key: { id: string }) {\n const review = reviews.find((r) => r.id === key.id);\n\n if (!review) {\n return null;\n }\n\n return {\n id: review.id,\n body: review.body,\n authorId: review.authorId,\n productUpc: review.productUpc,\n };\n },\n author(review: { id: string; authorId: string }) {\n const user = users.find((u) => u.id === review.authorId);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n username: user.username,\n };\n },\n product(review: { productUpc: string }) {\n const product = products.find((p) => p.upc === review.productUpc);\n\n if (!product) {\n return null;\n }\n\n return {\n upc: product.upc,\n reviews: reviews\n .filter((r) => r.productUpc === product.upc)\n .map((r) => ({\n id: r.id,\n body: r.body,\n authorId: r.authorId,\n productUpc: r.productUpc,\n })),\n };\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((u) => u.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n id: user.id,\n username: user.username,\n };\n },\n reviews(user: { id: string }) {\n return reviews.filter((r) => r.authorId === user.id);\n },\n },\n Product: {\n __resolveReference(key: { upc: string }) {\n return {\n upc: key.upc,\n };\n },\n reviews(product: { upc: string }) {\n return reviews.filter((r) => r.productUpc === product.upc);\n },\n },\n },\n});\n" + } + ] + }, + { + "id": "typename", + "cases": [ + { + "id": "typename/000", + "query": "\n query {\n union {\n __typename\n typename: __typename\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "union": { + "__typename": "Oven", + "typename": "Oven" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "typename/001", + "query": "\n query {\n interface {\n id\n __typename\n typename: __typename\n t: __typename\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "interface": { + "id": "2", + "__typename": "Toaster", + "typename": "Toaster", + "t": "Toaster" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "typename/002", + "query": "\n query {\n union {\n __typename\n ... on Oven {\n typename: __typename\n }\n ... on Toaster {\n typename: __typename\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "union": { + "__typename": "Oven", + "typename": "Oven" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "typename/003", + "query": "\n query {\n interface {\n __typename\n ... on Oven {\n typename: __typename\n }\n ... on Toaster {\n typename: __typename\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "interface": { + "__typename": "Toaster", + "typename": "Toaster" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "typename/004", + "query": "\n query {\n users {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "id": "u1" + }, + { + "id": "u2" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "typename/005", + "query": "\n query {\n users {\n __typename\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "users": [ + { + "__typename": "Admin" + }, + { + "__typename": "Admin" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n union: Product\n interface: Node\n }\n\n union Product = Oven | Toaster\n\n interface Node {\n id: ID!\n }\n\n type Oven implements Node {\n id: ID!\n }\n\n type Toaster implements Node {\n id: ID!\n }\n\n interface User @key(fields: \"id\") {\n id: ID!\n }\n\n type Admin implements User @key(fields: \"id\") {\n id: ID!\n isMain: Boolean!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n union: Product\n interface: Node\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nunion Product = Oven | Toaster\n\ninterface Node {\n id: ID!\n}\n\ntype Oven implements Node {\n id: ID!\n}\n\ntype Toaster implements Node {\n id: ID!\n}\n\ninterface User\n @key(fields: \"id\")\n{\n id: ID!\n}\n\ntype Admin implements User\n @key(fields: \"id\")\n{\n id: ID!\n isMain: Boolean!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Admin" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\"]\n )\n\n type Query {\n users: [User]\n }\n\n type User @key(fields: \"id\") @interfaceObject {\n id: ID!\n name: String!\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@interfaceObject\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @interfaceObject on OBJECT\n\ntype Query {\n users: [User]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype User\n @key(fields: \"id\")\n @interfaceObject\n{\n id: ID!\n name: String!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = User" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "83b216689cb7ed0141ec9cb37176450eb75b2c60f21670a1af30cc7f495e5725", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n type Query {\n union: Product\n interface: Node\n }\n\n union Product = Oven | Toaster\n\n interface Node {\n id: ID!\n }\n\n type Oven implements Node {\n id: ID!\n }\n\n type Toaster implements Node {\n id: ID!\n }\n\n interface User @key(fields: \"id\") {\n id: ID!\n }\n\n type Admin implements User @key(fields: \"id\") {\n id: ID!\n isMain: Boolean!\n }\n `,\n resolvers: {\n Query: {\n union() {\n return { id: \"1\", __typename: \"Oven\" };\n },\n interface() {\n return { id: \"2\", __typename: \"Toaster\" };\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((user) => user.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n __typename: user.__typename,\n id: user.id,\n };\n },\n },\n Admin: {\n __resolveReference(key: { id: string }) {\n const user = users.find((user) => user.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n __typename: user.__typename,\n id: user.id,\n isMain: user.isMain,\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "5b7e8287c80a3f37dbed6726ab2c98f34f00c182c3aa66b9cdf1ba3efc5757bb", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { users } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@interfaceObject\"]\n )\n\n type Query {\n users: [User]\n }\n\n type User @key(fields: \"id\") @interfaceObject {\n id: ID!\n name: String!\n }\n `,\n resolvers: {\n Query: {\n users() {\n return users.map((user) => {\n return {\n // I deliberately return no __typename as it should not be resolved by the @interfaceObject\n id: user.id,\n };\n });\n },\n },\n User: {\n __resolveReference(key: { id: string }) {\n const user = users.find((user) => user.id === key.id);\n\n if (!user) {\n return null;\n }\n\n return {\n // I deliberately return no __typename as it should not be resolved by the @interfaceObject\n id: user.id,\n };\n },\n name(user: { id: string }) {\n return users.find((u) => u.id === user.id)?.name;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "d8e61c2cbb69de1c6add220c78078f20c4965b613d3fb21b84fafa327e7383c5", + "source": "export const users = [\n {\n __typename: \"Admin\",\n id: \"u1\",\n name: \"Alice\",\n isMain: false,\n },\n {\n __typename: \"Admin\",\n id: \"u2\",\n name: \"Bob\",\n isMain: true,\n },\n];\n" + } + ] + }, + { + "id": "unavailable-override", + "cases": [ + { + "id": "unavailable-override/000", + "query": "\n query {\n feed {\n createdAt\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "feed": [ + { + "createdAt": "p1-createdAt" + }, + { + "createdAt": "p2-createdAt" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "unavailable-override/001", + "query": "\n query {\n aFeed {\n createdAt\n }\n bFeed {\n createdAt\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "aFeed": [ + { + "createdAt": "p2-createdAt" + } + ], + "bFeed": [ + { + "createdAt": "p1-createdAt" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Post @key(fields: \"id\") {\n id: ID!\n createdAt: String! @shareable\n }\n\n type Query {\n feed: [Post] @shareable\n aFeed: [Post]\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Post\n @key(fields: \"id\")\n{\n id: ID!\n createdAt: String! @shareable\n}\n\ntype Query {\n feed: [Post] @shareable\n aFeed: [Post]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Post" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@override\", \"@shareable\"]\n )\n\n type Post @key(fields: \"id\") {\n id: ID!\n createdAt: String! @override(from: \"non-existing\") @shareable\n }\n\n type Query {\n feed: [Post] @shareable\n bFeed: [Post]\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@override\", \"@shareable\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Post\n @key(fields: \"id\")\n{\n id: ID!\n createdAt: String! @override(from: \"non-existing\") @shareable\n}\n\ntype Query {\n feed: [Post] @shareable\n bFeed: [Post]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Post" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "dc538b91d4fd8913e5f984dfb10e3360b96be0361a9fa366f68e96b2a09d07e0", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { posts } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@shareable\"]\n )\n\n type Post @key(fields: \"id\") {\n id: ID!\n createdAt: String! @shareable\n }\n\n type Query {\n feed: [Post] @shareable\n aFeed: [Post]\n }\n `,\n resolvers: {\n Query: {\n feed() {\n return posts;\n },\n aFeed() {\n return [posts[1]];\n },\n },\n Post: {\n __resolveReference(key: { id: string }) {\n const post = posts.find((p) => p.id === key.id);\n\n if (!post) {\n return null;\n }\n\n return {\n id: post.id,\n createdAt: post.createdAt,\n };\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "029d5a7ac19dd1e1f21f3f3e870886df4b82afd7aac4822ca346c5d6587a1f2c", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { posts } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@key\", \"@override\", \"@shareable\"]\n )\n\n type Post @key(fields: \"id\") {\n id: ID!\n createdAt: String! @override(from: \"non-existing\") @shareable\n }\n\n type Query {\n feed: [Post] @shareable\n bFeed: [Post]\n }\n `,\n resolvers: {\n Query: {\n feed() {\n return posts;\n },\n bFeed() {\n return [posts[0]];\n },\n },\n Post: {\n __resolveReference(key: { id: string }) {\n const post = posts.find((p) => p.id === key.id);\n\n if (!post) {\n return null;\n }\n\n return {\n id: post.id,\n createdAt: post.createdAt,\n };\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "029b5a3d869e8cafda598325ac43ef79d3ce0b74295c8ccbea98a3831f144328", + "source": "export const posts = [\n {\n id: \"p1\",\n createdAt: \"p1-createdAt\",\n },\n {\n id: \"p2\",\n createdAt: \"p2-createdAt\",\n },\n];\n" + } + ] + }, + { + "id": "union-interface-distributed", + "cases": [ + { + "id": "union-interface-distributed/000", + "query": "\n query {\n products {\n ... on Node {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "products": [ + { + "id": "oven1" + }, + { + "id": "oven2" + }, + { + "id": "toaster1" + }, + { + "id": "toaster2" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-interface-distributed/001", + "query": "\n query {\n nodes {\n ... on Toaster {\n warranty\n }\n ... on Oven {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "nodes": [ + { + "warranty": 3 + }, + { + "warranty": 4 + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-interface-distributed/002", + "query": "\n query {\n nodes {\n id\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "nodes": [ + { + "id": "toaster1" + }, + { + "id": "toaster2" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-interface-distributed/003", + "query": "\n query {\n nodes {\n ... on Node {\n id\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "nodes": [ + { + "id": "toaster1" + }, + { + "id": "toaster2" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-interface-distributed/004", + "query": "\n query {\n nodes {\n ... on Node {\n ... on WithWarranty {\n warranty\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "nodes": [ + { + "warranty": 3 + }, + { + "warranty": 4 + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-interface-distributed/005", + "query": "\n query {\n nodes {\n ... on Node {\n id\n ... on Node {\n id\n ... on WithWarranty {\n warranty\n }\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "nodes": [ + { + "id": "toaster1", + "warranty": 3 + }, + { + "id": "toaster2", + "warranty": 4 + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-interface-distributed/006", + "query": "\n {\n toasters {\n ...ToasterFragment\n ...NodeFragment\n }\n }\n\n fragment ToasterFragment on Toaster {\n id\n }\n\n fragment NodeFragment on Node {\n id\n __typename\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "toasters": [ + { + "id": "toaster1", + "__typename": "Toaster" + }, + { + "id": "toaster2", + "__typename": "Toaster" + } + ] + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-interface-distributed/007", + "query": "\n query {\n node(id: \"oven1\") {\n ... on Oven {\n warranty\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "node": null + }, + "hasExpectedErrors": true, + "expectsErrors": true + }, + { + "id": "union-interface-distributed/008", + "query": "\n query {\n node(id: \"oven1\") {\n ... on Toaster {\n warranty\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "node": null + }, + "hasExpectedErrors": true, + "expectsErrors": true + }, + { + "id": "union-interface-distributed/009", + "query": "\n query {\n node(id: \"toaster1\") {\n ... on Toaster {\n warranty\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "node": { + "warranty": 3 + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@shareable\", \"@key\"]\n )\n\n type Query {\n products: [Product]\n node(id: ID!): Node\n nodes: [Node]\n toasters: [Toaster]\n }\n\n union Product = Oven | Toaster\n\n interface Node {\n id: ID!\n }\n\n interface WithWarranty {\n warranty: Int\n }\n\n type Oven @key(fields: \"id\") {\n id: ID!\n }\n\n type Toaster implements Node & WithWarranty @key(fields: \"id\") {\n id: ID!\n warranty: Int\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@shareable\", \"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n products: [Product]\n node(id: ID!): Node\n nodes: [Node]\n toasters: [Toaster]\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nunion Product = Oven | Toaster\n\ninterface Node {\n id: ID!\n}\n\ninterface WithWarranty {\n warranty: Int\n}\n\ntype Oven\n @key(fields: \"id\")\n{\n id: ID!\n}\n\ntype Toaster implements Node & WithWarranty\n @key(fields: \"id\")\n{\n id: ID!\n warranty: Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Oven | Toaster" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n interface Node {\n id: ID!\n }\n\n interface WithWarranty {\n warranty: Int\n }\n\n type Oven implements Node & WithWarranty @key(fields: \"id\") {\n id: ID!\n warranty: Int\n }\n ", + "serviceSdl": "extend schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @federation__shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ninterface Node {\n id: ID!\n}\n\ninterface WithWarranty {\n warranty: Int\n}\n\ntype Oven implements Node & WithWarranty\n @key(fields: \"id\")\n{\n id: ID!\n warranty: Int\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Oven\n\ntype Query {\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "3a83aacad3180addc2eb46313b276a5917bb078436cf1ef22552ed8aa8b995a5", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@shareable\", \"@key\"]\n )\n\n type Query {\n products: [Product]\n node(id: ID!): Node\n nodes: [Node]\n toasters: [Toaster]\n }\n\n union Product = Oven | Toaster\n\n interface Node {\n id: ID!\n }\n\n interface WithWarranty {\n warranty: Int\n }\n\n type Oven @key(fields: \"id\") {\n id: ID!\n }\n\n type Toaster implements Node & WithWarranty @key(fields: \"id\") {\n id: ID!\n warranty: Int\n }\n `,\n resolvers: {\n Query: {\n products() {\n return products.map((product) => {\n if (product.__typename === \"Oven\") {\n return {\n __typename: \"Oven\",\n id: product.id,\n };\n } else {\n return {\n __typename: \"Toaster\",\n id: product.id,\n warranty: product.warranty,\n };\n }\n });\n },\n node(_: {}, { id }: { id: string }) {\n const product = products.find((p) => p.id === id);\n\n if (product?.__typename === \"Oven\") {\n return {\n __typename: \"Oven\",\n id: product.id,\n };\n } else if (product?.__typename === \"Toaster\") {\n return {\n __typename: \"Toaster\",\n id: product.id,\n warranty: product.warranty,\n };\n }\n\n return null;\n },\n toasters() {\n return products\n .filter((product) => product.__typename === \"Toaster\")\n .map((toaster) => ({\n __typename: \"Toaster\",\n id: toaster.id,\n warranty: toaster.warranty,\n }));\n },\n nodes() {\n return products\n .filter((product) => product.__typename === \"Toaster\")\n .map((toaster) => ({\n __typename: \"Toaster\",\n id: toaster.id,\n warranty: toaster.warranty,\n }));\n },\n },\n Oven: {\n __resolveReference(key: { id: string }) {\n const oven = products.find((p) => p.id === key.id);\n\n if (oven?.__typename === \"Oven\") {\n return {\n __typename: \"Oven\",\n id: oven.id,\n };\n }\n\n return null;\n },\n warranty() {\n throw new Error(\"Never\");\n },\n },\n Toaster: {\n __resolveReference(key: { id: string }) {\n const toaster = products.find((p) => p.id === key.id);\n\n if (toaster?.__typename === \"Toaster\") {\n return {\n __typename: \"Toaster\",\n id: toaster.id,\n warranty: toaster.warranty,\n };\n }\n\n return null;\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "70d8694a65aa0951e0031c86b1c95de4c2ba698f51251e07d405b5f573114fd2", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { products } from \"./data.js\";\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\"])\n\n interface Node {\n id: ID!\n }\n\n interface WithWarranty {\n warranty: Int\n }\n\n type Oven implements Node & WithWarranty @key(fields: \"id\") {\n id: ID!\n warranty: Int\n }\n `,\n resolvers: {\n Oven: {\n __resolveReference(key: { id: string }) {\n const oven = products.find((p) => p.id === key.id);\n\n if (oven?.__typename === \"Oven\") {\n return {\n __typename: \"Oven\",\n id: oven.id,\n warranty: 1,\n };\n }\n\n return null;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "e23b6d8607d893f131ed7fbadeec3ad8ce8f0c3ea7d1b536952c07d289d8edfd", + "source": "export const ovens = [\n {\n __typename: \"Oven\",\n id: \"oven1\",\n warranty: 1,\n },\n {\n __typename: \"Oven\",\n id: \"oven2\",\n warranty: 2,\n },\n];\nexport const toasters = [\n {\n __typename: \"Toaster\",\n id: \"toaster1\",\n warranty: 3,\n },\n {\n __typename: \"Toaster\",\n id: \"toaster2\",\n warranty: 4,\n },\n];\n\nexport const products = [...ovens, ...toasters];\n" + } + ] + }, + { + "id": "union-intersection", + "cases": [ + { + "id": "union-intersection/000", + "query": "\n query {\n media {\n ... on Movie {\n title\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "media": {} + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-intersection/001", + "query": "\n query {\n media {\n ... on Book {\n title\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "media": { + "title": "The Lord of the Rings" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-intersection/002", + "query": "\n query {\n media {\n ... on Book {\n title\n }\n ... on Movie {\n title\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "media": { + "title": "The Lord of the Rings" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-intersection/003", + "query": "\n query {\n viewer {\n media {\n __typename\n ... on Song {\n title\n }\n ... on Movie {\n title\n }\n ... on Book {\n title\n }\n }\n book {\n __typename\n ... on Song {\n title\n }\n ... on Movie {\n title\n }\n ... on Book {\n title\n }\n }\n song {\n __typename\n ... on Song {\n title\n }\n ... on Movie {\n title\n }\n ... on Book {\n title\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "viewer": { + "media": { + "__typename": "Book", + "title": "The Lord of the Rings" + }, + "book": { + "__typename": "Book", + "title": "The Lord of the Rings" + }, + "song": { + "__typename": "Song", + "title": "Song Title" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-intersection/004", + "query": "\n query {\n viewer {\n media {\n ... on Movie {\n title\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "viewer": { + "media": {} + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-intersection/005", + "query": "\n query {\n viewer {\n media {\n ... on Book {\n title\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "viewer": { + "media": { + "title": "The Lord of the Rings" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-intersection/006", + "query": "\n query {\n viewer {\n media {\n ... on Book {\n title\n }\n ... on Movie {\n title\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "viewer": { + "media": { + "title": "The Lord of the Rings" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-intersection/007", + "query": "\n query {\n viewer {\n media {\n __typename\n ... on Song {\n title\n }\n ... on Movie {\n title\n }\n ... on Book {\n title\n }\n }\n book {\n __typename\n ... on Song {\n title\n }\n ... on Movie {\n title\n }\n ... on Book {\n title\n }\n }\n song {\n __typename\n ... on Song {\n title\n }\n ... on Movie {\n title\n }\n ... on Book {\n title\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "viewer": { + "media": { + "__typename": "Book", + "title": "The Lord of the Rings" + }, + "book": { + "__typename": "Book", + "title": "The Lord of the Rings" + }, + "song": { + "__typename": "Song", + "title": "Song Title" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-intersection/008", + "query": "\n query {\n aMedia {\n ... on Movie {\n title\n bTitle\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "aMedia": {} + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-intersection/009", + "query": "\n query {\n aMedia {\n ... on Book {\n title\n aTitle\n bTitle\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "aMedia": { + "title": "The Lord of the Rings", + "aTitle": "A: The Lord of the Rings", + "bTitle": "B: The Lord of the Rings" + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-intersection/010", + "query": "\n query {\n viewer {\n media {\n __typename\n ... on Song {\n title\n aTitle\n }\n ... on Movie {\n title\n bTitle\n }\n ... on Book {\n title\n aTitle\n bTitle\n }\n }\n book {\n __typename\n ... on Song {\n title\n aTitle\n }\n ... on Movie {\n title\n bTitle\n }\n ... on Book {\n title\n aTitle\n bTitle\n }\n }\n song {\n __typename\n ... on Song {\n title\n aTitle\n }\n ... on Movie {\n title\n bTitle\n }\n ... on Book {\n title\n aTitle\n bTitle\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "viewer": { + "media": { + "__typename": "Book", + "title": "The Lord of the Rings", + "aTitle": "A: The Lord of the Rings", + "bTitle": "B: The Lord of the Rings" + }, + "book": { + "__typename": "Book", + "title": "The Lord of the Rings", + "aTitle": "A: The Lord of the Rings", + "bTitle": "B: The Lord of the Rings" + }, + "song": { + "__typename": "Song", + "title": "Song Title", + "aTitle": "A: Song Title" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + }, + { + "id": "union-intersection/011", + "query": "\n query {\n viewer {\n aMedia {\n ... on Movie {\n title\n bTitle\n }\n }\n bMedia {\n ... on Movie {\n title\n bTitle\n }\n }\n }\n }\n ", + "variables": null, + "hasExpectedData": true, + "expectedData": { + "viewer": { + "aMedia": {}, + "bMedia": { + "title": "A Movie Title", + "bTitle": "B Movie Title" + } + } + }, + "hasExpectedErrors": false, + "expectsErrors": null + } + ], + "sources": [ + { + "name": "a", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@shareable\", \"@key\"]\n )\n\n union Media = Book | Song\n union ViewerMedia = Book | Song\n\n type Book @key(fields: \"id\") {\n id: ID!\n title: String! @shareable\n aTitle: String!\n }\n\n type Song @key(fields: \"id\") {\n id: ID!\n title: String! @shareable\n aTitle: String!\n }\n\n type Query {\n media: Media @shareable\n aMedia: Media @shareable\n book: Book @shareable\n song: Media @shareable\n viewer: Viewer @shareable\n }\n\n type Viewer {\n media: ViewerMedia @shareable\n aMedia: ViewerMedia\n book: Book @shareable\n song: ViewerMedia @shareable\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@shareable\", \"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\nunion Media = Book | Song\n\nunion ViewerMedia = Book | Song\n\ntype Book\n @key(fields: \"id\")\n{\n id: ID!\n title: String! @shareable\n aTitle: String!\n}\n\ntype Song\n @key(fields: \"id\")\n{\n id: ID!\n title: String! @shareable\n aTitle: String!\n}\n\ntype Query {\n media: Media @shareable\n aMedia: Media @shareable\n book: Book @shareable\n song: Media @shareable\n viewer: Viewer @shareable\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\ntype Viewer {\n media: ViewerMedia @shareable\n aMedia: ViewerMedia\n book: Book @shareable\n song: ViewerMedia @shareable\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book | Song" + }, + { + "name": "b", + "rawSdl": "\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@shareable\", \"@key\"]\n )\n\n type Query {\n media: Media @shareable\n bMedia: Media @shareable\n book: Media @shareable\n viewer: Viewer @shareable\n }\n\n union Media = Book | Movie\n union ViewerMedia = Book | Movie\n\n type Movie @key(fields: \"id\") {\n id: ID!\n title: String! @shareable\n bTitle: String!\n }\n\n type Book @key(fields: \"id\") {\n id: ID!\n title: String! @shareable\n bTitle: String!\n }\n\n type Viewer {\n media: ViewerMedia @shareable\n bMedia: ViewerMedia\n book: ViewerMedia @shareable\n }\n ", + "serviceSdl": "schema\n @link(url: \"https://specs.apollo.dev/link/v1.0\")\n{\n query: Query\n}\n\nextend schema\n @link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@shareable\", \"@key\"])\n\ndirective @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA\n\ndirective @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE\n\ndirective @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION\n\ndirective @federation__external(reason: String) on OBJECT | FIELD_DEFINITION\n\ndirective @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA\n\ndirective @federation__extends on OBJECT | INTERFACE\n\ndirective @shareable repeatable on OBJECT | FIELD_DEFINITION\n\ndirective @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION\n\ndirective @federation__override(from: String!) on FIELD_DEFINITION\n\ndirective @federation__composeDirective(name: String) repeatable on SCHEMA\n\ndirective @federation__interfaceObject on OBJECT\n\ntype Query {\n media: Media @shareable\n bMedia: Media @shareable\n book: Media @shareable\n viewer: Viewer @shareable\n _entities(representations: [_Any!]!): [_Entity]!\n _service: _Service!\n}\n\nunion Media = Book | Movie\n\nunion ViewerMedia = Book | Movie\n\ntype Movie\n @key(fields: \"id\")\n{\n id: ID!\n title: String! @shareable\n bTitle: String!\n}\n\ntype Book\n @key(fields: \"id\")\n{\n id: ID!\n title: String! @shareable\n bTitle: String!\n}\n\ntype Viewer {\n media: ViewerMedia @shareable\n bMedia: ViewerMedia\n book: ViewerMedia @shareable\n}\n\nenum link__Purpose {\n \"\"\"\n `SECURITY` features provide metadata necessary to securely resolve fields.\n \"\"\"\n SECURITY\n\n \"\"\"\n `EXECUTION` features provide metadata necessary for operation execution.\n \"\"\"\n EXECUTION\n}\n\nscalar link__Import\n\nscalar federation__FieldSet\n\nscalar _Any\n\ntype _Service {\n sdl: String\n}\n\nunion _Entity = Book | Movie" + } + ], + "fixtureModules": [ + { + "path": "a.subgraph.ts", + "sha256": "234e8c35f15b72a93a9443a4d1b07e2eeb69a079e74cfe68ca31de2d0f206481", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { media } from \"./data.js\";\n\nconst song = {\n __typename: \"Song\",\n id: \"s2\",\n title: \"Song Title\",\n aTitle: \"A: Song Title\",\n};\n\nexport default createSubgraph(\"a\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@shareable\", \"@key\"]\n )\n\n union Media = Book | Song\n union ViewerMedia = Book | Song\n\n type Book @key(fields: \"id\") {\n id: ID!\n title: String! @shareable\n aTitle: String!\n }\n\n type Song @key(fields: \"id\") {\n id: ID!\n title: String! @shareable\n aTitle: String!\n }\n\n type Query {\n media: Media @shareable\n aMedia: Media @shareable\n book: Book @shareable\n song: Media @shareable\n viewer: Viewer @shareable\n }\n\n type Viewer {\n media: ViewerMedia @shareable\n aMedia: ViewerMedia\n book: Book @shareable\n song: ViewerMedia @shareable\n }\n `,\n resolvers: {\n Query: {\n media: () => media,\n aMedia: () => media,\n book: () => media,\n song: () => song,\n viewer: () => {\n return {\n media: media,\n aMedia: media,\n book: media,\n song: song,\n };\n },\n },\n Book: {\n __resolveReference(key: { id: string }) {\n if (key.id !== media.id) {\n return null;\n }\n\n return media;\n },\n },\n Song: {\n __resolveReference(key: { id: string }) {\n if (key.id !== song.id) {\n return null;\n }\n\n return song;\n },\n },\n },\n});\n" + }, + { + "path": "b.subgraph.ts", + "sha256": "8a94a5f9763b22d2ae4710b2fb9e0af8e7906722ef4925781ddb45b5a9027975", + "source": "import { createSubgraph } from \"../../subgraph.js\";\nimport { media } from \"./data.js\";\n\nconst movie = {\n __typename: \"Movie\",\n id: \"m3\",\n title: \"A Movie Title\",\n bTitle: \"B Movie Title\",\n};\n\nexport default createSubgraph(\"b\", {\n typeDefs: /* GraphQL */ `\n extend schema\n @link(\n url: \"https://specs.apollo.dev/federation/v2.3\"\n import: [\"@shareable\", \"@key\"]\n )\n\n type Query {\n media: Media @shareable\n bMedia: Media @shareable\n book: Media @shareable\n viewer: Viewer @shareable\n }\n\n union Media = Book | Movie\n union ViewerMedia = Book | Movie\n\n type Movie @key(fields: \"id\") {\n id: ID!\n title: String! @shareable\n bTitle: String!\n }\n\n type Book @key(fields: \"id\") {\n id: ID!\n title: String! @shareable\n bTitle: String!\n }\n\n type Viewer {\n media: ViewerMedia @shareable\n bMedia: ViewerMedia\n book: ViewerMedia @shareable\n }\n `,\n resolvers: {\n Query: {\n media: () => media,\n bMedia: () => media,\n book: () => media,\n viewer: () => {\n return {\n media,\n bMedia: movie,\n book: media,\n };\n },\n },\n Book: {\n __resolveReference(key: { id: string }) {\n if (key.id !== media.id) {\n return null;\n }\n\n return media;\n },\n },\n Movie: {\n __resolveReference(key: { id: string }) {\n if (key.id !== movie.id) {\n return null;\n }\n\n return movie;\n },\n },\n },\n});\n" + }, + { + "path": "data.ts", + "sha256": "0798fa26e09168e84b90db0648dab15af607e1e3c948b870f4dcef25889c0b68", + "source": "export const media = {\n __typename: \"Book\",\n id: \"b1\",\n title: \"The Lord of the Rings\",\n aTitle: \"A: The Lord of the Rings\",\n bTitle: \"B: The Lord of the Rings\",\n};\n" + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/AbstractTypes/AbstractTypesTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/AbstractTypes/AbstractTypesTests.cs index 35cfd2506b4..190efa3a787 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/AbstractTypes/AbstractTypesTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/AbstractTypes/AbstractTypesTests.cs @@ -8,543 +8,22 @@ namespace HotChocolate.Fusion.Suites; -public sealed class AbstractTypesTests : ComplianceTestBase +[OfficialV1Suite("abstract-types")] +public sealed class AbstractTypesTests : OfficialV1ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( + => ComposeOfficialV1Async( (AgencySubgraph.Name, AgencySubgraph.BuildAsync), - (BooksSubgraph.Name, BooksSubgraph.BuildAsync), (InventorySubgraph.Name, InventorySubgraph.BuildAsync), - (MagazinesSubgraph.Name, MagazinesSubgraph.BuildAsync), - (ProductsSubgraph.Name, ProductsSubgraph.BuildAsync), + (BooksSubgraph.Name, BooksSubgraph.BuildAsync), + (UsersSubgraph.Name, UsersSubgraph.BuildAsync), (ReviewsSubgraph.Name, ReviewsSubgraph.BuildAsync), - (UsersSubgraph.Name, UsersSubgraph.BuildAsync)); - - [Fact] - public Task Products_With_Dimensions() => RunAsync( - query: """ - { - products { - id - dimensions { size weight } - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "dimensions": { "size": "small", "weight": 0.5 } }, - { "id": "p3", "dimensions": { "size": "small", "weight": 0.6 } }, - { "id": "p2", "dimensions": { "size": "small", "weight": 0.2 } }, - { "id": "p4", "dimensions": { "size": "small", "weight": 0.3 } } - ] - } - """); - - [Fact] - public Task Similar_With_Sku() => RunAsync( - query: """ - { - similar(id: "p1") { - id - sku - } - } - """, - expectedData: """ - { - "similar": [ - { "id": "p3", "sku": "sku-3" } - ] - } - """); - - [Fact] - public Task Similar_Books_And_Magazines_With_Delivery() => RunAsync( - query: """ - { - book: similar(id: "p1") { - id - delivery(zip: "1234") { - estimatedDelivery - fastestDelivery - } - } - magazine: similar(id: "p2") { - id - delivery(zip: "1234") { - estimatedDelivery - fastestDelivery - } - } - } - """, - expectedData: """ - { - "book": [ - { - "id": "p3", - "delivery": { - "estimatedDelivery": "1 day", - "fastestDelivery": "same day" - } - } - ], - "magazine": [ - { - "id": "p4", - "delivery": { - "estimatedDelivery": "1 day", - "fastestDelivery": "same day" - } - } - ] - } - """); - - [Fact] - public Task Products_Sku_With_TypeName_Fragments() => RunAsync( - query: """ - { - products { - id - sku - ... on Product { sku } - ... on Book { sku } - ... on Magazine { sku } - ... on Similar { __typename type: __typename } - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "sku": "sku-1", "__typename": "Book", "type": "Book" }, - { "id": "p3", "sku": "sku-3", "__typename": "Book", "type": "Book" }, - { "id": "p2", "sku": "sku-2", "__typename": "Magazine", "type": "Magazine" }, - { "id": "p4", "sku": "sku-4", "__typename": "Magazine", "type": "Magazine" } - ] - } - """); - - [Fact] - public Task Products_Author_With_TotalProductsCreated() => RunAsync( - query: """ - { - products { - author: createdBy { - email - totalProductsCreated - } - ... on Magazine { title } - } - } - """, - expectedData: """ - { - "products": [ - { "author": { "email": "u1@example.com", "totalProductsCreated": 2 } }, - { "author": { "email": "u2@example.com", "totalProductsCreated": 2 } }, - { "author": { "email": "u1@example.com", "totalProductsCreated": 2 }, "title": "Magazine 1" }, - { "author": { "email": "u2@example.com", "totalProductsCreated": 2 }, "title": "Magazine 2" } - ] - } - """); - - [Fact] - public Task Products_Reviews_With_Nested_Product_Info() => RunAsync( - query: """ - { - products { - id - reviews { - product { - sku - ... on Magazine { title } - ... on Book { reviewsCount } - } - } - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [{ "product": { "sku": "sku-1", "reviewsCount": 2 } }, { "product": { "sku": "sku-1", "reviewsCount": 2 } }] }, - { "id": "p3", "reviews": [] }, - { "id": "p2", "reviews": [{ "product": { "sku": "sku-2", "title": "Magazine 1" } }] }, - { "id": "p4", "reviews": [] } - ] - } - """); - - [Fact] - public Task Products_Reviews_Just_Ids() => RunAsync( - query: """ - { - products { - id - reviews { id } - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [{ "id": 1 }, { "id": 2 }] }, - { "id": "p3", "reviews": [] }, - { "id": "p2", "reviews": [{ "id": 3 }] }, - { "id": "p4", "reviews": [] } - ] - } - """); - - [Fact] - public Task Products_Reviews_Include_Book_Title_True() => RunAsync( - query: """ - query ($title: Boolean = true) { - products { - id - reviews { id } - ... on Book @include(if: $title) { title } - ... on Magazine { sku } - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [{ "id": 1 }, { "id": 2 }], "title": "Book 1" }, - { "id": "p3", "reviews": [], "title": "Book 2" }, - { "id": "p2", "reviews": [{ "id": 3 }], "sku": "sku-2" }, - { "id": "p4", "reviews": [], "sku": "sku-4" } - ] - } - """); - - [Fact] - public Task Products_Reviews_Include_Book_Title_False() => RunAsync( - query: """ - query ($title: Boolean = false) { - products { - id - reviews { id } - ... on Book @include(if: $title) { title } - ... on Magazine { sku } - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [{ "id": 1 }, { "id": 2 }] }, - { "id": "p3", "reviews": [] }, - { "id": "p2", "reviews": [{ "id": 3 }], "sku": "sku-2" }, - { "id": "p4", "reviews": [], "sku": "sku-4" } - ] - } - """); - - [Fact] - public Task Products_Reviews_Skip_Book_Title_False() => RunAsync( - query: """ - query ($title: Boolean = false) { - products { - id - reviews { id } - ... on Book @skip(if: $title) { title } - ... on Magazine { sku } - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [{ "id": 1 }, { "id": 2 }], "title": "Book 1" }, - { "id": "p3", "reviews": [], "title": "Book 2" }, - { "id": "p2", "reviews": [{ "id": 3 }], "sku": "sku-2" }, - { "id": "p4", "reviews": [], "sku": "sku-4" } - ] - } - """); - - [Fact] - public Task Products_Reviews_Skip_Book_Title_True() => RunAsync( - query: """ - query ($title: Boolean = true) { - products { - id - reviews { id } - ... on Book @skip(if: $title) { title } - ... on Magazine { sku } - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [{ "id": 1 }, { "id": 2 }] }, - { "id": "p3", "reviews": [] }, - { "id": "p2", "reviews": [{ "id": 3 }], "sku": "sku-2" }, - { "id": "p4", "reviews": [], "sku": "sku-4" } - ] - } - """); - - [Fact] - public Task Products_Reviews_Skip_Book_Title_True_With_Sku() => RunAsync( - query: """ - query ($title: Boolean = true) { - products { - id - reviews { id } - ... on Book @skip(if: $title) { title } - ... on Book { sku } - ... on Magazine { sku } - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [{ "id": 1 }, { "id": 2 }], "sku": "sku-1" }, - { "id": "p3", "reviews": [], "sku": "sku-3" }, - { "id": "p2", "reviews": [{ "id": 3 }], "sku": "sku-2" }, - { "id": "p4", "reviews": [], "sku": "sku-4" } - ] - } - """); - - [Fact] - public Task Products_Reviews_Skip_Book_Title_False_With_Sku() => RunAsync( - query: """ - query ($title: Boolean = false) { - products { - id - reviews { id } - ... on Book @skip(if: $title) { title } - ... on Book { sku } - ... on Magazine { sku } - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [{ "id": 1 }, { "id": 2 }], "title": "Book 1", "sku": "sku-1" }, - { "id": "p3", "reviews": [], "title": "Book 2", "sku": "sku-3" }, - { "id": "p2", "reviews": [{ "id": 3 }], "sku": "sku-2" }, - { "id": "p4", "reviews": [], "sku": "sku-4" } - ] - } - """); - - [Fact] - public Task Products_Reviews_Include_Book_Title_Nested_Sku() => RunAsync( - query: """ - query ($title: Boolean = true) { - products { - id - reviews { id } - ... on Book @include(if: $title) { - title - ... on Book { sku } - } - ... on Magazine { sku } - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [{ "id": 1 }, { "id": 2 }], "title": "Book 1", "sku": "sku-1" }, - { "id": "p3", "reviews": [], "title": "Book 2", "sku": "sku-3" }, - { "id": "p2", "reviews": [{ "id": 3 }], "sku": "sku-2" }, - { "id": "p4", "reviews": [], "sku": "sku-4" } - ] - } - """); - - [Fact] - public Task Products_Reviews_Include_Book_Title_Skip_Nested_Sku() => RunAsync( - query: """ - query ($title: Boolean = true, $sku: Boolean = true) { - products { - id - reviews { id } - ... on Book @include(if: $title) { - title - ... on Book @skip(if: $sku) { sku } - } - ... on Magazine { sku } - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [{ "id": 1 }, { "id": 2 }], "title": "Book 1" }, - { "id": "p3", "reviews": [], "title": "Book 2" }, - { "id": "p2", "reviews": [{ "id": 3 }], "sku": "sku-2" }, - { "id": "p4", "reviews": [], "sku": "sku-4" } - ] - } - """); - - [Fact] - public Task Products_Reviews_PublisherType_Agency_Self() => RunAsync( - query: """ - { - products { - id - reviews { - product { - id - ... on Magazine { - publisherType { - ...Publisher - } - } - ... on Book { - publisherType { - ...Publisher - } - } - } - } - } - } - - fragment Publisher on PublisherType { - ... on Agency { - id - companyName - } - ... on Self { - email - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [ - { "product": { "id": "p1", "publisherType": { "email": "u1@example.com" } } }, - { "product": { "id": "p1", "publisherType": { "email": "u1@example.com" } } } - ] }, - { "id": "p3", "reviews": [] }, - { "id": "p2", "reviews": [ - { "product": { "id": "p2", "publisherType": { "id": "a1", "companyName": "Agency 1" } } } - ] }, - { "id": "p4", "reviews": [] } - ] - } - """); - - [Fact] - public Task Products_Reviews_PublisherType_With_Group() => RunAsync( - query: """ - { - products { - id - reviews { - product { - id - ... on Magazine { - publisherType { - ...Publisher - } - } - ... on Book { - publisherType { - ...Publisher - } - } - } - } - } - } - - fragment Publisher on PublisherType { - ... on Agency { - id - companyName - } - ... on Self { - email - } - ... on Group { - name - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [ - { "product": { "id": "p1", "publisherType": { "email": "u1@example.com" } } }, - { "product": { "id": "p1", "publisherType": { "email": "u1@example.com" } } } - ] }, - { "id": "p3", "reviews": [] }, - { "id": "p2", "reviews": [ - { "product": { "id": "p2", "publisherType": { "id": "a1", "companyName": "Agency 1" } } } - ] }, - { "id": "p4", "reviews": [] } - ] - } - """); - - [Fact] - public Task Products_Reviews_PublisherType_Email_Aliases() => RunAsync( - query: """ - { - products { - id - reviews { - product { - id - ... on Magazine { - publisherType { - ...Publisher - } - } - ... on Book { - publisherType { - ...Publisher - } - } - } - } - } - } + (ProductsSubgraph.Name, ProductsSubgraph.BuildAsync), + (MagazinesSubgraph.Name, MagazinesSubgraph.BuildAsync)); - fragment Publisher on PublisherType { - ... on Agency { - emailObj: email { - address - } - } - ... on Self { - emailStr: email - } - ... on Group { - emailStr: email - } - } - """, - expectedData: """ - { - "products": [ - { "id": "p1", "reviews": [ - { "product": { "id": "p1", "publisherType": { "emailStr": "u1@example.com" } } }, - { "product": { "id": "p1", "publisherType": { "emailStr": "u1@example.com" } } } - ] }, - { "id": "p3", "reviews": [] }, - { "id": "p2", "reviews": [ - { "product": { "id": "p2", "publisherType": { "emailObj": { "address": "a1@example.com" } } } } - ] }, - { "id": "p4", "reviews": [] } - ] - } - """); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV1")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV1CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/A/User.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/A/User.cs index bb87a53d896..0f11673cdf1 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/A/User.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/A/User.cs @@ -3,10 +3,7 @@ namespace HotChocolate.Fusion.Suites.ChildTypeMismatch.A; /// /// The User type as projected by the a subgraph. /// This subgraph does not declare @key on User; it only -/// contributes a shareable id field. The original SDL uses -/// nullable id: ID, but HotChocolate's field selection merging -/// requires matching nullability across union members, so id -/// is declared ID! here to match subgraph B's User entity. +/// contributes the nullable shareable id: ID field from the official fixture. /// public sealed class User { diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/A/UserType.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/A/UserType.cs index 0a067f83674..9030c46c416 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/A/UserType.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/A/UserType.cs @@ -12,6 +12,6 @@ public sealed class UserType : ObjectType { protected override void Configure(IObjectTypeDescriptor descriptor) { - descriptor.Field(u => u.Id).Shareable().Type>(); + descriptor.Field(u => u.Id).Shareable().Type(); } } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/B/Admin.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/B/Admin.cs index 4be17c39947..293918e588c 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/B/Admin.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/B/Admin.cs @@ -2,9 +2,7 @@ namespace HotChocolate.Fusion.Suites.ChildTypeMismatch.B; /// /// The Admin type in the b subgraph. Not an entity (no @key). -/// name is @shareable. The original SDL uses nullable id: ID, -/// but HotChocolate requires matching nullability across union members for -/// field selection merging, so id is declared ID! here. +/// name is @shareable, and id: ID is nullable. /// public sealed class Admin { diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/B/AdminType.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/B/AdminType.cs index 7f5a8525ed5..10328f124c6 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/B/AdminType.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/B/AdminType.cs @@ -12,7 +12,7 @@ public sealed class AdminType : ObjectType { protected override void Configure(IObjectTypeDescriptor descriptor) { - descriptor.Field(a => a.Id).Type>(); + descriptor.Field(a => a.Id).Type(); descriptor.Field(a => a.Name).Shareable().Type(); descriptor diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/ChildTypeMismatchTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/ChildTypeMismatchTests.cs index 232dc593c2f..893cb10d83f 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/ChildTypeMismatchTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ChildTypeMismatch/ChildTypeMismatchTests.cs @@ -3,425 +3,18 @@ namespace HotChocolate.Fusion.Suites; -/// -/// Port of the child-type-mismatch suite from -/// graphql-hive/federation-gateway-audit. The gateway composes -/// subgraph a (which exposes User { id @shareable } without -/// a key) and subgraph b (which owns User @key(fields: "id"), -/// Admin, and the union Account = User | Admin). Tests verify -/// that union types and nested similarAccounts resolve correctly -/// across subgraphs. -/// -public sealed class ChildTypeMismatchTests : ComplianceTestBase +[OfficialV2Suite("child-type-mismatch")] +public sealed class ChildTypeMismatchTests + : OfficialV2ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( + => ComposeOfficialV2Async( (ASubgraph.Name, ASubgraph.BuildAsync), (BSubgraph.Name, BSubgraph.BuildAsync)); - [Fact] - public Task Users_And_Accounts_Resolve_Across_Subgraphs() => RunAsync( - query: """ - { - users { - id - name - } - accounts { - ... on User { - id - name - } - ... on Admin { - id - name - } - } - } - """, - expectedData: """ - { - "users": [ - { - "id": "u1", - "name": "u1-name" - } - ], - "accounts": [ - { - "id": "u1", - "name": "u1-name" - }, - { - "id": "a1", - "name": "a1-name" - } - ] - } - """); - - [Fact] - public Task NestedInternalAlias_SimilarAccounts_Resolve() => RunAsync( - query: """ - query NestedInternalAlias { - users { - id - name - } - accounts { - ... on User { - id - name - similarAccounts { - ... on User { - id - name - } - ... on Admin { - id - name - } - } - } - ... on Admin { - id - name - similarAccounts { - ... on User { - id - name - } - ... on Admin { - id - name - } - } - } - } - } - """, - expectedData: """ - { - "users": [ - { - "id": "u1", - "name": "u1-name" - } - ], - "accounts": [ - { - "id": "u1", - "name": "u1-name", - "similarAccounts": [ - { - "id": "u1", - "name": "u1-name" - }, - { - "id": "a1", - "name": "a1-name" - } - ] - }, - { - "id": "a1", - "name": "a1-name", - "similarAccounts": [ - { - "id": "u1", - "name": "u1-name" - }, - { - "id": "a1", - "name": "a1-name" - } - ] - } - ] - } - """); - - [Fact] - public Task DeeplyNestedInternalAlias_ThreeLevels_Resolve() => RunAsync( - query: """ - query DeeplyNestedInternalAlias { - users { - id - name - } - accounts { - ... on User { - id - name - similarAccounts { - ... on User { - id - name - similarAccounts { - ... on User { - id - name - } - ... on Admin { - id - name - } - } - } - ... on Admin { - id - name - similarAccounts { - ... on User { - id - name - } - ... on Admin { - id - name - } - } - } - } - } - ... on Admin { - id - name - similarAccounts { - ... on User { - id - name - similarAccounts { - ... on User { - id - name - } - ... on Admin { - id - name - } - } - } - ... on Admin { - id - name - similarAccounts { - ... on User { - id - name - } - ... on Admin { - id - name - } - } - } - } - } - } - } - """, - expectedData: """ - { - "users": [ - { - "id": "u1", - "name": "u1-name" - } - ], - "accounts": [ - { - "id": "u1", - "name": "u1-name", - "similarAccounts": [ - { - "id": "u1", - "name": "u1-name", - "similarAccounts": [ - { - "id": "u1", - "name": "u1-name" - }, - { - "id": "a1", - "name": "a1-name" - } - ] - }, - { - "id": "a1", - "name": "a1-name", - "similarAccounts": [ - { - "id": "u1", - "name": "u1-name" - }, - { - "id": "a1", - "name": "a1-name" - } - ] - } - ] - }, - { - "id": "a1", - "name": "a1-name", - "similarAccounts": [ - { - "id": "u1", - "name": "u1-name", - "similarAccounts": [ - { - "id": "u1", - "name": "u1-name" - }, - { - "id": "a1", - "name": "a1-name" - } - ] - }, - { - "id": "a1", - "name": "a1-name", - "similarAccounts": [ - { - "id": "u1", - "name": "u1-name" - }, - { - "id": "a1", - "name": "a1-name" - } - ] - } - ] - } - ] - } - """); - - [Fact] - public Task DeeplyNested_NamesOnly_ThreeLevels_Resolve() => RunAsync( - query: """ - query DeeplyNested { - accounts { - ... on User { - name - similarAccounts { - ... on User { - name - similarAccounts { - ... on User { - name - } - ... on Admin { - name - } - } - } - ... on Admin { - name - similarAccounts { - ... on User { - name - } - ... on Admin { - name - } - } - } - } - } - ... on Admin { - name - similarAccounts { - ... on User { - name - similarAccounts { - ... on User { - name - } - ... on Admin { - name - } - } - } - ... on Admin { - name - similarAccounts { - ... on User { - name - } - ... on Admin { - name - } - } - } - } - } - } - } - """, - expectedData: """ - { - "accounts": [ - { - "name": "u1-name", - "similarAccounts": [ - { - "name": "u1-name", - "similarAccounts": [ - { - "name": "u1-name" - }, - { - "name": "a1-name" - } - ] - }, - { - "name": "a1-name", - "similarAccounts": [ - { - "name": "u1-name" - }, - { - "name": "a1-name" - } - ] - } - ] - }, - { - "name": "a1-name", - "similarAccounts": [ - { - "name": "u1-name", - "similarAccounts": [ - { - "name": "u1-name" - }, - { - "name": "a1-name" - } - ] - }, - { - "name": "a1-name", - "similarAccounts": [ - { - "name": "u1-name" - }, - { - "name": "a1-name" - } - ] - } - ] - } - ] - } - """); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV2")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV2CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CircularReferenceInterface/A/BookType.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CircularReferenceInterface/A/BookType.cs index 27686965ad1..c15893af5e6 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CircularReferenceInterface/A/BookType.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CircularReferenceInterface/A/BookType.cs @@ -9,8 +9,7 @@ protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor .Implements() - .Key("id") - .ResolveReferenceWith(_ => ResolveById(default!)); + .Key("id"); descriptor.Field(b => b.Id).Type>(); descriptor.Field(b => b.Price).External().Type(); @@ -28,7 +27,4 @@ protected override void Configure(IObjectTypeDescriptor descriptor) && !string.Equals(b.Id, book.Id, StringComparison.Ordinal)); }); } - - private static Book? ResolveById(string id) - => AData.BooksById.TryGetValue(id, out var book) ? book : null; } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CircularReferenceInterface/CircularReferenceInterfaceTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CircularReferenceInterface/CircularReferenceInterfaceTests.cs index 0c3f69ee04f..7384f988c96 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CircularReferenceInterface/CircularReferenceInterfaceTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CircularReferenceInterface/CircularReferenceInterfaceTests.cs @@ -3,86 +3,18 @@ namespace HotChocolate.Fusion.Suites; -public sealed class CircularReferenceInterfaceTests : ComplianceTestBase +[OfficialV2Suite("circular-reference-interface")] +public sealed class CircularReferenceInterfaceTests + : OfficialV2ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( + => ComposeOfficialV2Async( (ASubgraph.Name, ASubgraph.BuildAsync), (BSubgraph.Name, BSubgraph.BuildAsync)); - [Fact] - public Task Product_Should_ReturnNestedTypename_When_CircularSamePriceProduct() => RunAsync( - query: """ - query { - product { - samePriceProduct { - ... on Product { - samePriceProduct { - __typename - } - } - } - } - } - """, - expectedData: """ - { - "product": { - "samePriceProduct": { - "samePriceProduct": { - "__typename": "Book" - } - } - } - } - """); - - [Fact] - public Task Product_Should_ReturnFullNested_When_CircularWithIdAndPrice() => RunAsync( - query: """ - query { - product { - __typename - samePriceProduct { - __typename - ... on Book { - id - } - samePriceProduct { - __typename - ... on Book { - id - } - } - } - ... on Book { - __typename - id - price - samePriceProduct { - id - price - } - } - } - } - """, - expectedData: """ - { - "product": { - "__typename": "Book", - "samePriceProduct": { - "__typename": "Book", - "id": "3", - "samePriceProduct": { - "__typename": "Book", - "id": "1" - }, - "price": 10.99 - }, - "id": "1", - "price": 10.99 - } - } - """); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV2")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV2CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ComplexEntityCall/ComplexEntityCallTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ComplexEntityCall/ComplexEntityCallTests.cs index 3c471db003e..fd493c28a0c 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ComplexEntityCall/ComplexEntityCallTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ComplexEntityCall/ComplexEntityCallTests.cs @@ -5,132 +5,20 @@ namespace HotChocolate.Fusion.Suites; -/// -/// Port of the complex-entity-call suite from -/// graphql-hive/federation-gateway-audit. The gateway composes four -/// Apollo Federation subgraphs (link, list, price, -/// products) that share the Product / ProductList / -/// Category entities via several nested-path @key directives, -/// including the list-typed products { id pid } and the deeply nested -/// products { id pid category { id tag } } selected { id }. -/// -public sealed class ComplexEntityCallTests : ComplianceTestBase +[OfficialV2Suite("complex-entity-call")] +public sealed class ComplexEntityCallTests + : OfficialV2ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( + => ComposeOfficialV2Async( (LinkSubgraph.Name, LinkSubgraph.BuildAsync), (ListSubgraph.Name, ListSubgraph.BuildAsync), (PriceSubgraph.Name, PriceSubgraph.BuildAsync), (ProductsSubgraph.Name, ProductsSubgraph.BuildAsync)); - /// - /// Verifies the four-subgraph composition accepts the nested and nested-list - /// @key directives from the audit fixtures and that a query - /// traversing the nested-object Category @key("id tag") pattern and - /// the mainProduct cycle back through Product resolves - /// correctly. - /// - [Fact] - public Task TopProducts_ResolvesNestedCategoryKey() => RunAsync( - query: """ - query { - topProducts { - products { - id - category { - mainProduct { id } - id - tag - } - } - } - } - """, - expectedData: """ - { - "topProducts": { - "products": [ - { - "id": "1", - "category": { - "mainProduct": { "id": "1" }, - "id": "c1", - "tag": "t1" - } - }, - { - "id": "2", - "category": { - "mainProduct": { "id": "2" }, - "id": "c2", - "tag": "t2" - } - } - ] - } - } - """); - - /// - /// The full audit query. Exercises the nested-list - /// ProductList @key("products { id pid }") and deeply nested - /// ProductList @key("products { id pid category { id tag } } selected { id }") - /// routing through the Fusion planner to populate first, - /// selected, pid, and price in a single query. - /// - /// - /// Pins that the planner wires each nested-list @key lookup onto the hop - /// that produces its buried key leaf (pid inside products), so - /// selected, first, pid, and price all resolve. - /// - [Fact] - public Task TopProducts_Projects_Across_All_Subgraphs() => RunAsync( - query: """ - query { - topProducts { - products { - id - pid - price { price } - category { - mainProduct { id } - id - tag - } - } - selected { id } - first { id } - } - } - """, - expectedData: """ - { - "topProducts": { - "products": [ - { - "id": "1", - "pid": "p1", - "price": { "price": 100 }, - "category": { - "mainProduct": { "id": "1" }, - "id": "c1", - "tag": "t1" - } - }, - { - "id": "2", - "pid": "p2", - "price": { "price": 200 }, - "category": { - "mainProduct": { "id": "2" }, - "id": "c2", - "tag": "t2" - } - } - ], - "selected": { "id": "2" }, - "first": { "id": "1" } - } - } - """); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV2")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV2CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ComplexEntityCall/Products/ProductType.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ComplexEntityCall/Products/ProductType.cs index 742fe6a94d8..23fef0d5643 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ComplexEntityCall/Products/ProductType.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/ComplexEntityCall/Products/ProductType.cs @@ -18,13 +18,8 @@ protected override void Configure(IObjectTypeDescriptor descriptor) .Key("id") .ResolveReferenceWith(_ => ResolveById(default!)); - // Apollo Federation would mark this as '@external' (the field is - // owned by the link/list/price subgraphs). In the Fusion composite - // schema we model the same intent with '@shareable': the field - // travels with Product instances returned from this subgraph's root - // queries so the planner can use it as the entity-routing key. descriptor.Field(p => p.Id) - .Shareable() + .External() .Type>(); descriptor.Field("category") diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/Account.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/Account.cs index 02d3021d0b6..aa1017b64a9 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/Account.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/Account.cs @@ -8,4 +8,6 @@ namespace HotChocolate.Fusion.Suites.CorruptedSupergraphNodeId.B; public sealed class Account : INode { public string Id { get; init; } = default!; + + public IReadOnlyList ChatIds { get; init; } = []; } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/AccountType.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/AccountType.cs index 85c24737643..7bc15e10b09 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/AccountType.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/AccountType.cs @@ -26,9 +26,11 @@ protected override void Configure(IObjectTypeDescriptor descriptor) { var account = ctx.Parent(); return SubgraphBData.Chats - .Where(c => c.AccountId == account.Id) + .Where(c => account.ChatIds.Contains(c.Id)) .ToList(); }); + + descriptor.Field(a => a.ChatIds).Ignore(); } private static Account? ResolveById(string id) diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/QueryType.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/QueryType.cs index 3b7b0607a44..499c1761a2a 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/QueryType.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/QueryType.cs @@ -23,11 +23,11 @@ protected override void Configure(IObjectTypeDescriptor descriptor) { var id = ctx.ArgumentValue("id"); - if (SubgraphBData.AccountsById.TryGetValue(id, out _)) + if (SubgraphBData.AccountsById.TryGetValue(id, out var account)) { // Intentionally corrupted: Account.id is @external in // this subgraph, so we return "never" as the id. - return (INode)new Account { Id = "never" }; + return (INode)new Account { Id = "never", ChatIds = account.ChatIds }; } if (SubgraphBData.ChatsById.TryGetValue(id, out var chat)) diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/SubgraphBData.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/SubgraphBData.cs index 770a96585c6..75b4dca12b2 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/SubgraphBData.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/B/SubgraphBData.cs @@ -5,14 +5,6 @@ namespace HotChocolate.Fusion.Suites.CorruptedSupergraphNodeId.B; /// internal static class SubgraphBData { - public static readonly IReadOnlyList Accounts = - [ - new Account { Id = "a1" } - ]; - - public static readonly IReadOnlyDictionary AccountsById = - Accounts.ToDictionary(static a => a.Id, StringComparer.Ordinal); - public static readonly IReadOnlyList Chats = [ new Chat { Id = "c1", AccountId = "a1", Text = "c1-text" } @@ -20,4 +12,12 @@ internal static class SubgraphBData public static readonly IReadOnlyDictionary ChatsById = Chats.ToDictionary(static c => c.Id, StringComparer.Ordinal); + + public static readonly IReadOnlyList Accounts = + [ + new Account { Id = "a1", ChatIds = ["c1"] } + ]; + + public static readonly IReadOnlyDictionary AccountsById = + Accounts.ToDictionary(static a => a.Id, StringComparer.Ordinal); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/CorruptedSupergraphNodeIdTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/CorruptedSupergraphNodeIdTests.cs index 258b6177860..86ee4d347c5 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/CorruptedSupergraphNodeIdTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/CorruptedSupergraphNodeId/CorruptedSupergraphNodeIdTests.cs @@ -4,243 +4,18 @@ namespace HotChocolate.Fusion.Suites; -/// -/// Port of the corrupted-supergraph-node-id suite from -/// graphql-hive/federation-gateway-audit. Two subgraphs (a, b) -/// each expose node(id: ID!): Node @shareable but intentionally return -/// corrupted (wrong) IDs for the entity types they do not own. The tests verify -/// that the gateway handles these corrupted IDs correctly. -/// -public sealed class CorruptedSupergraphNodeIdTests : ComplianceTestBase +[OfficialV2Suite("corrupted-supergraph-node-id", NodeResolution = NodeResolution.SourceSchema)] +public sealed class CorruptedSupergraphNodeIdTests + : OfficialV2ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( - NodeResolution.SourceSchema, + => ComposeOfficialV2Async( (SubgraphASubgraph.Name, SubgraphASubgraph.BuildAsync), (SubgraphBSubgraph.Name, SubgraphBSubgraph.BuildAsync)); - [Fact] - public Task Node_Account_Id_Only() => RunAsync( - query: """ - { - node(id: "a1") { - id - } - } - """, - expectedData: "null", - expectsErrors: true); - - [Fact] - public Task Node_Account_And_Chat_Typename() => RunAsync( - query: """ - { - account: node(id: "a1") { - __typename - } - chat: node(id: "c1") { - __typename - } - } - """, - expectedData: """ - { - "account": { - "__typename": "Account" - }, - "chat": { - "__typename": "Chat" - } - } - """); - - [Fact] - public Task Node_Account_And_Chat_Full_Fragments() => RunAsync( - query: """ - { - account: node(id: "a1") { - ... on Account { - id - username - } - } - chat: node(id: "c1") { - ... on Chat { - id - text - } - } - } - """, - expectedData: """ - { - "account": { - "id": "a1", - "username": "a1-username" - }, - "chat": { - "id": "c1", - "text": "c1-text" - } - } - """); - - [Fact] - public Task Node_Account_As_Chat_And_Chat_As_Account() => RunAsync( - query: """ - { - account: node(id: "a1") { - ... on Chat { - id - } - } - chat: node(id: "c1") { - ... on Account { - id - } - } - } - """, - expectedData: """ - { - "account": {}, - "chat": {} - } - """); - - [Fact] - public Task Node_Mismatched_Fragments_With_Typename() => RunAsync( - query: """ - { - account: node(id: "a1") { - __typename - ... on Chat { - id - } - } - chat: node(id: "c1") { - __typename - ... on Account { - id - } - } - } - """, - expectedData: """ - { - "account": { - "__typename": "Account" - }, - "chat": { - "__typename": "Chat" - } - } - """); - - [Fact] - public Task Node_Account_Id_With_Mismatched_Chat_Fragment() => RunAsync( - query: """ - { - account: node(id: "a1") { - id - ... on Chat { - id - } - } - chat: node(id: "c1") { - __typename - ... on Account { - id - } - } - } - """, - expectedData: "null", - expectsErrors: true); - - [Fact] - public Task Chat_By_Id() => RunAsync( - query: """ - { - chat(id: "c1") { - id - } - } - """, - expectedData: """ - { - "chat": { - "id": "c1" - } - } - """); - - [Fact] - public Task Account_By_Id() => RunAsync( - query: """ - { - account(id: "a1") { - id - } - } - """, - expectedData: """ - { - "account": { - "id": "a1" - } - } - """); - - [Fact] - public Task Chat_With_Account() => RunAsync( - query: """ - { - chat(id: "c1") { - id - text - account { - id - } - } - } - """, - expectedData: """ - { - "chat": { - "id": "c1", - "text": "c1-text", - "account": { - "id": "a1" - } - } - } - """); - - [Fact] - public Task Account_With_Chats() => RunAsync( - query: """ - { - account(id: "a1") { - id - username - chats { - id - } - } - } - """, - expectedData: """ - { - "account": { - "id": "a1", - "username": "a1-username", - "chats": [ - { - "id": "c1" - } - ] - } - } - """); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV2")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV2CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/EnumIntersection/EnumIntersectionTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/EnumIntersection/EnumIntersectionTests.cs index a7e5dbd20a7..2f5c48fc63c 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/EnumIntersection/EnumIntersectionTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/EnumIntersection/EnumIntersectionTests.cs @@ -3,114 +3,18 @@ namespace HotChocolate.Fusion.Suites; -/// -/// Port of the enum-intersection suite from -/// graphql-hive/federation-gateway-audit. Two Apollo Federation -/// subgraphs declare overlapping enum values: subgraph a only -/// declares REGULAR; subgraph b declares both -/// ANONYMOUS @inaccessible and REGULAR. The supergraph -/// must intersect to REGULAR only. -/// -public sealed class EnumIntersectionTests : ComplianceTestBase +[OfficialV2Suite("enum-intersection")] +public sealed class EnumIntersectionTests + : OfficialV2ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( + => ComposeOfficialV2Async( (ASubgraph.Name, ASubgraph.BuildAsync), (BSubgraph.Name, BSubgraph.BuildAsync)); - /// - /// Plain id selection. The planner picks one subgraph and returns - /// both users. - /// - [Fact] - public Task Users_Returns_Ids_Only() => RunAsync( - query: """ - query { - users { id } - } - """, - expectedData: """ - { - "users": [ - { "id": "u1" }, - { "id": "u2" } - ] - } - """); - - /// - /// Walking type from subgraph a's Query.users - /// surfaces null for u2 because subgraph a - /// cannot project the ANONYMOUS value. - /// - [Fact] - public Task Users_Type_Returns_Null_For_Subgraph_A_Side() => RunAsync( - query: """ - query { - users { id type } - } - """, - expectedData: """ - { - "users": [ - { "id": "u1", "type": "REGULAR" }, - { "id": "u2", "type": null } - ] - } - """, - expectsErrors: true); - - /// - /// Walking type from subgraph b's Query.usersB - /// surfaces null for u2 because ANONYMOUS is - /// inaccessible in the supergraph. - /// - [Fact] - public Task UsersB_Type_Returns_Null_For_Inaccessible_Value() => RunAsync( - query: """ - query { - usersB { id type } - } - """, - expectedData: """ - { - "usersB": [ - { "id": "u1", "type": "REGULAR" }, - { "id": "u2", "type": null } - ] - } - """); - - /// - /// Filtering by the public REGULAR value works. - /// - [Fact] - public Task UsersByType_Regular_Returns_Single_User() => RunAsync( - query: """ - query { - usersByType(type: REGULAR) { id type } - } - """, - expectedData: """ - { - "usersByType": [ - { "id": "u1", "type": "REGULAR" } - ] - } - """); - - /// - /// Filtering by the inaccessible ANONYMOUS value must be - /// rejected by validation. The data payload is null and the - /// response carries errors. - /// - [Fact] - public Task UsersByType_Anonymous_Is_Rejected() => RunAsync( - query: """ - query { - usersByType(type: ANONYMOUS) { id type } - } - """, - expectedData: "null", - expectsErrors: true); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV2")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV2CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed1ExternalExtends/Fed1ExternalExtendsTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed1ExternalExtends/Fed1ExternalExtendsTests.cs index 21b653eff95..3560f1d5dcc 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed1ExternalExtends/Fed1ExternalExtendsTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed1ExternalExtends/Fed1ExternalExtendsTests.cs @@ -3,132 +3,18 @@ namespace HotChocolate.Fusion.Suites; -/// -/// Port of the fed1-external-extends suite from -/// graphql-hive/federation-gateway-audit. Two Apollo Federation -/// subgraphs share the User entity. Subgraph a uses the -/// Federation v1 style @extends directive together with -/// @external markers to project only its own field rid; -/// subgraph b owns the rest of the entity. The audit verifies that -/// the gateway can route the external name field to b, and -/// that subgraph a's providedRandomUser field lets -/// @provides(fields: "name") ship name alongside the -/// reference so no entity call to b is needed. -/// -public sealed class Fed1ExternalExtendsTests : ComplianceTestBase +[OfficialV1Suite("fed1-external-extends")] +public sealed class Fed1ExternalExtendsTests + : OfficialV1ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( + => ComposeOfficialV1Async( (ASubgraph.Name, ASubgraph.BuildAsync), (BSubgraph.Name, BSubgraph.BuildAsync)); - /// - /// Combined query: randomUser from subgraph a needs the - /// external name from b via the entity lookup; - /// userById resolves entirely in b. - /// - [Fact] - public Task RandomUser_And_UserById_Resolve_Across_Subgraphs() => RunAsync( - query: """ - query { - randomUser { - id - name - } - userById(id: "u2") { - id - name - nickname - } - } - """, - expectedData: """ - { - "randomUser": { - "id": "u1", - "name": "u1-name" - }, - "userById": { - "id": "u2", - "name": "u2-name", - "nickname": "u2-nickname" - } - } - """); - - /// - /// randomUser with id and rid only stays inside - /// subgraph a. - /// - [Fact] - public Task RandomUser_Returns_Local_Rid_Without_Entity_Call() => RunAsync( - query: """ - query { - randomUser { - id - rid - } - } - """, - expectedData: """ - { - "randomUser": { - "id": "u1", - "rid": "u1-rid" - } - } - """); - - /// - /// randomUser with the external name selection forces - /// the gateway to issue an entity call to subgraph b for - /// name, then merge with rid from subgraph a. - /// - [Fact] - public Task RandomUser_Resolves_External_Name_Via_Entity_Call() => RunAsync( - query: """ - query { - randomUser { - id - rid - name - } - } - """, - expectedData: """ - { - "randomUser": { - "id": "u1", - "rid": "u1-rid", - "name": "u1-name" - } - } - """); - - /// - /// providedRandomUser uses - /// @provides(fields: "name") so the gateway should accept the - /// name shipped from subgraph a and skip the entity - /// call to subgraph b. - /// - [Fact] - public Task ProvidedRandomUser_Uses_Local_Name_Via_Provides() => RunAsync( - query: """ - query { - providedRandomUser { - id - rid - name - } - } - """, - expectedData: """ - { - "providedRandomUser": { - "id": "u1", - "rid": "u1-rid", - "name": "u1-name" - } - } - """); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV1")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV1CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed1ExternalExtendsResolvable/Fed1ExternalExtendsResolvableTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed1ExternalExtendsResolvable/Fed1ExternalExtendsResolvableTests.cs index f4824e9ce16..1e64f37f2b5 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed1ExternalExtendsResolvable/Fed1ExternalExtendsResolvableTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed1ExternalExtendsResolvable/Fed1ExternalExtendsResolvableTests.cs @@ -3,49 +3,18 @@ namespace HotChocolate.Fusion.Suites; -/// -/// Port of the fed1-external-extends-resolvable suite from -/// graphql-hive/federation-gateway-audit. The gateway composes two -/// Apollo Federation subgraphs: a owns the Product entity -/// (@key(fields: "id"), fields name / pid) and b -/// extends it (@extends) with a resolvable price field keyed by -/// the external id name and upc routing fields. -/// -public sealed class Fed1ExternalExtendsResolvableTests : ComplianceTestBase +[OfficialV1Suite("fed1-external-extends-resolvable")] +public sealed class Fed1ExternalExtendsResolvableTests + : OfficialV1ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( + => ComposeOfficialV1Async( (ASubgraph.Name, ASubgraph.BuildAsync), (BSubgraph.Name, BSubgraph.BuildAsync)); - /// - /// Queries productInA (owned by subgraph a) for fields that span - /// both subgraphs, requiring the planner to route through the external - /// id name / upc keys to fetch price and upc from - /// subgraph b. - /// - [Fact] - public Task ProductInA_ResolvesExternalExtendedFields() => RunAsync( - query: """ - query { - productInA { - id - pid - price - upc - name - } - } - """, - expectedData: """ - { - "productInA": { - "id": "p1", - "pid": "p1-pid", - "price": 12.3, - "upc": "upc1", - "name": "p1-name" - } - } - """); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV1")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV1CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed1ExternalExtension/Fed1ExternalExtensionTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed1ExternalExtension/Fed1ExternalExtensionTests.cs index 10c60b995d3..8b7c5f66b28 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed1ExternalExtension/Fed1ExternalExtensionTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed1ExternalExtension/Fed1ExternalExtensionTests.cs @@ -3,132 +3,18 @@ namespace HotChocolate.Fusion.Suites; -/// -/// Port of the fed1-external-extension suite from -/// graphql-hive/federation-gateway-audit. Two Apollo Federation -/// subgraphs share the User entity. Subgraph a uses the -/// Federation v1 extend type User @key(...) form together with -/// @external markers to project only its own field rid; -/// subgraph b owns the rest of the entity. The audit verifies that -/// the gateway can route the external name field to b, and -/// that subgraph a's providedRandomUser field lets -/// @provides(fields: "name") ship name alongside the -/// reference so no entity call to b is needed. -/// -public sealed class Fed1ExternalExtensionTests : ComplianceTestBase +[OfficialV1Suite("fed1-external-extension")] +public sealed class Fed1ExternalExtensionTests + : OfficialV1ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( + => ComposeOfficialV1Async( (ASubgraph.Name, ASubgraph.BuildAsync), (BSubgraph.Name, BSubgraph.BuildAsync)); - /// - /// Combined query: randomUser from subgraph a needs the - /// external name from b via the entity lookup; - /// userById resolves entirely in b. - /// - [Fact] - public Task RandomUser_And_UserById_Resolve_Across_Subgraphs() => RunAsync( - query: """ - query { - randomUser { - id - name - } - userById(id: "u2") { - id - name - nickname - } - } - """, - expectedData: """ - { - "randomUser": { - "id": "u1", - "name": "u1-name" - }, - "userById": { - "id": "u2", - "name": "u2-name", - "nickname": "u2-nickname" - } - } - """); - - /// - /// randomUser with id and rid only stays inside - /// subgraph a. - /// - [Fact] - public Task RandomUser_Returns_Local_Rid_Without_Entity_Call() => RunAsync( - query: """ - query { - randomUser { - id - rid - } - } - """, - expectedData: """ - { - "randomUser": { - "id": "u1", - "rid": "u1-rid" - } - } - """); - - /// - /// randomUser with the external name selection forces - /// the gateway to issue an entity call to subgraph b for - /// name, then merge with rid from subgraph a. - /// - [Fact] - public Task RandomUser_Resolves_External_Name_Via_Entity_Call() => RunAsync( - query: """ - query { - randomUser { - id - rid - name - } - } - """, - expectedData: """ - { - "randomUser": { - "id": "u1", - "rid": "u1-rid", - "name": "u1-name" - } - } - """); - - /// - /// providedRandomUser uses - /// @provides(fields: "name") so the gateway should accept the - /// name shipped from subgraph a and skip the entity - /// call to subgraph b. - /// - [Fact] - public Task ProvidedRandomUser_Uses_Local_Name_Via_Provides() => RunAsync( - query: """ - query { - providedRandomUser { - id - rid - name - } - } - """, - expectedData: """ - { - "providedRandomUser": { - "id": "u1", - "rid": "u1-rid", - "name": "u1-name" - } - } - """); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV1")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV1CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed2ExternalExtends/Fed2ExternalExtendsTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed2ExternalExtends/Fed2ExternalExtendsTests.cs index 28b19e8571a..9cb912c73d1 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed2ExternalExtends/Fed2ExternalExtendsTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed2ExternalExtends/Fed2ExternalExtendsTests.cs @@ -3,132 +3,18 @@ namespace HotChocolate.Fusion.Suites; -/// -/// Port of the fed2-external-extends suite from -/// graphql-hive/federation-gateway-audit. Two Apollo Federation -/// subgraphs share the User entity. Subgraph a uses the -/// Federation v1 style @extends directive together with -/// @external markers to project only its own field rid; -/// subgraph b owns the rest of the entity. The audit verifies that -/// the gateway can route the external name field to b, and -/// that subgraph a's providedRandomUser field lets -/// @provides(fields: "name") ship name alongside the -/// reference so no entity call to b is needed. -/// -public sealed class Fed2ExternalExtendsTests : ComplianceTestBase +[OfficialV2Suite("fed2-external-extends")] +public sealed class Fed2ExternalExtendsTests + : OfficialV2ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( + => ComposeOfficialV2Async( (ASubgraph.Name, ASubgraph.BuildAsync), (BSubgraph.Name, BSubgraph.BuildAsync)); - /// - /// Combined query: randomUser from subgraph a needs the - /// external name from b via the entity lookup; - /// userById resolves entirely in b. - /// - [Fact] - public Task RandomUser_And_UserById_Resolve_Across_Subgraphs() => RunAsync( - query: """ - query { - randomUser { - id - name - } - userById(id: "u2") { - id - name - nickname - } - } - """, - expectedData: """ - { - "randomUser": { - "id": "u1", - "name": "u1-name" - }, - "userById": { - "id": "u2", - "name": "u2-name", - "nickname": "u2-nickname" - } - } - """); - - /// - /// randomUser with id and rid only stays inside - /// subgraph a. - /// - [Fact] - public Task RandomUser_Returns_Local_Rid_Without_Entity_Call() => RunAsync( - query: """ - query { - randomUser { - id - rid - } - } - """, - expectedData: """ - { - "randomUser": { - "id": "u1", - "rid": "u1-rid" - } - } - """); - - /// - /// randomUser with the external name selection forces - /// the gateway to issue an entity call to subgraph b for - /// name, then merge with rid from subgraph a. - /// - [Fact] - public Task RandomUser_Resolves_External_Name_Via_Entity_Call() => RunAsync( - query: """ - query { - randomUser { - id - rid - name - } - } - """, - expectedData: """ - { - "randomUser": { - "id": "u1", - "rid": "u1-rid", - "name": "u1-name" - } - } - """); - - /// - /// providedRandomUser uses - /// @provides(fields: "name") so the gateway should accept the - /// name shipped from subgraph a and skip the entity - /// call to subgraph b. - /// - [Fact] - public Task ProvidedRandomUser_Uses_Local_Name_Via_Provides() => RunAsync( - query: """ - query { - providedRandomUser { - id - rid - name - } - } - """, - expectedData: """ - { - "providedRandomUser": { - "id": "u1", - "rid": "u1-rid", - "name": "u1-name" - } - } - """); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV2")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV2CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed2ExternalExtension/Fed2ExternalExtensionTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed2ExternalExtension/Fed2ExternalExtensionTests.cs index 796a1e2e7df..66a002af1ad 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed2ExternalExtension/Fed2ExternalExtensionTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/Fed2ExternalExtension/Fed2ExternalExtensionTests.cs @@ -3,132 +3,18 @@ namespace HotChocolate.Fusion.Suites; -/// -/// Port of the fed2-external-extension suite from -/// graphql-hive/federation-gateway-audit. Two Apollo Federation -/// subgraphs share the User entity. Subgraph a uses the -/// Federation v2 extend type User @key(...) form together with -/// @external markers to project only its own field rid; -/// subgraph b owns the rest of the entity. The audit verifies that -/// the gateway can route the external name field to b, and -/// that subgraph a's providedRandomUser field lets -/// @provides(fields: "name") ship name alongside the -/// reference so no entity call to b is needed. -/// -public sealed class Fed2ExternalExtensionTests : ComplianceTestBase +[OfficialV2Suite("fed2-external-extension")] +public sealed class Fed2ExternalExtensionTests + : OfficialV2ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( + => ComposeOfficialV2Async( (ASubgraph.Name, ASubgraph.BuildAsync), (BSubgraph.Name, BSubgraph.BuildAsync)); - /// - /// Combined query: randomUser from subgraph a needs the - /// external name from b via the entity lookup; - /// userById resolves entirely in b. - /// - [Fact] - public Task RandomUser_And_UserById_Resolve_Across_Subgraphs() => RunAsync( - query: """ - query { - randomUser { - id - name - } - userById(id: "u2") { - id - name - nickname - } - } - """, - expectedData: """ - { - "randomUser": { - "id": "u1", - "name": "u1-name" - }, - "userById": { - "id": "u2", - "name": "u2-name", - "nickname": "u2-nickname" - } - } - """); - - /// - /// randomUser with id and rid only stays inside - /// subgraph a. - /// - [Fact] - public Task RandomUser_Returns_Local_Rid_Without_Entity_Call() => RunAsync( - query: """ - query { - randomUser { - id - rid - } - } - """, - expectedData: """ - { - "randomUser": { - "id": "u1", - "rid": "u1-rid" - } - } - """); - - /// - /// randomUser with the external name selection forces - /// the gateway to issue an entity call to subgraph b for - /// name, then merge with rid from subgraph a. - /// - [Fact] - public Task RandomUser_Resolves_External_Name_Via_Entity_Call() => RunAsync( - query: """ - query { - randomUser { - id - rid - name - } - } - """, - expectedData: """ - { - "randomUser": { - "id": "u1", - "rid": "u1-rid", - "name": "u1-name" - } - } - """); - - /// - /// providedRandomUser uses - /// @provides(fields: "name") so the gateway should accept the - /// name shipped from subgraph a and skip the entity - /// call to subgraph b. - /// - [Fact] - public Task ProvidedRandomUser_Uses_Local_Name_Via_Provides() => RunAsync( - query: """ - query { - providedRandomUser { - id - rid - name - } - } - """, - expectedData: """ - { - "providedRandomUser": { - "id": "u1", - "rid": "u1-rid", - "name": "u1-name" - } - } - """); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV2")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV2CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/IncludeSkip/B/ProductType.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/IncludeSkip/B/ProductType.cs index 99c53a02b8d..996598ac47c 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/IncludeSkip/B/ProductType.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/IncludeSkip/B/ProductType.cs @@ -34,5 +34,6 @@ protected override void Configure(IObjectTypeDescriptor descriptor) }); } - private static Product ResolveById(string id) => new() { Id = id }; + private static Product? ResolveById(string id) + => A.AData.ById.ContainsKey(id) ? new Product { Id = id } : null; } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/IncludeSkip/C/ProductType.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/IncludeSkip/C/ProductType.cs index 0528ecfa292..b41d5205489 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/IncludeSkip/C/ProductType.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/IncludeSkip/C/ProductType.cs @@ -63,5 +63,6 @@ protected override void Configure(IObjectTypeDescriptor descriptor) "neverCalledSkip should not be called.")); } - private static Product ResolveById(string id) => new() { Id = id }; + private static Product? ResolveById(string id) + => A.AData.ById.ContainsKey(id) ? new Product { Id = id } : null; } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/IncludeSkip/IncludeSkipTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/IncludeSkip/IncludeSkipTests.cs index 34ae229f399..e3219db52dc 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/IncludeSkip/IncludeSkipTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/IncludeSkip/IncludeSkipTests.cs @@ -4,102 +4,19 @@ namespace HotChocolate.Fusion.Suites; -/// -/// Port of the include-skip suite from -/// graphql-hive/federation-gateway-audit. Three Apollo Federation -/// subgraphs share the Product entity. The audit verifies that -/// @include and @skip short-circuit downstream resolvers -/// (the neverCalledInclude and neverCalledSkip resolvers -/// throw if invoked) and that toggled fields still receive their -/// @requires dependencies. -/// -public sealed class IncludeSkipTests : ComplianceTestBase +[OfficialV2Suite("include-skip")] +public sealed class IncludeSkipTests + : OfficialV2ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( + => ComposeOfficialV2Async( (ASubgraph.Name, ASubgraph.BuildAsync), (BSubgraph.Name, BSubgraph.BuildAsync), (CSubgraph.Name, CSubgraph.BuildAsync)); - /// - /// @include(if: false) should keep the planner from invoking - /// the neverCalledInclude resolver. - /// - [Fact] - public Task NeverCalledInclude_Is_Skipped_When_Include_False() => RunAsync( - query: """ - query ($bool: Boolean = false) { - product { - price - neverCalledInclude @include(if: $bool) - } - } - """, - expectedData: """ - { - "product": { "price": 699.99 } - } - """); - - /// - /// @skip(if: true) should keep the planner from invoking - /// the neverCalledSkip resolver. - /// - [Fact] - public Task NeverCalledSkip_Is_Skipped_When_Skip_True() => RunAsync( - query: """ - query ($bool: Boolean = true) { - product { - price - neverCalledSkip @skip(if: $bool) - } - } - """, - expectedData: """ - { - "product": { "price": 699.99 } - } - """); - - /// - /// @include(if: true) keeps the field active. The chained - /// @requires path must still deliver isExpensive to - /// c's include resolver. - /// - [Fact] - public Task Include_Resolves_Through_Requires_Chain() => RunAsync( - query: """ - query ($bool: Boolean = true) { - product { - price - include @include(if: $bool) - } - } - """, - expectedData: """ - { - "product": { "price": 699.99, "include": true } - } - """); - - /// - /// @skip(if: false) keeps the field active. The chained - /// @requires path must still deliver isExpensive to - /// c's skip resolver. - /// - [Fact] - public Task Skip_Resolves_Through_Requires_Chain() => RunAsync( - query: """ - query ($bool: Boolean = false) { - product { - price - skip @skip(if: $bool) - } - } - """, - expectedData: """ - { - "product": { "price": 699.99, "skip": true } - } - """); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV2")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV2CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/InputObjectIntersection/InputObjectIntersectionTests.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/InputObjectIntersection/InputObjectIntersectionTests.cs index 072702bfcd2..bc3b8527da7 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/InputObjectIntersection/InputObjectIntersectionTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/InputObjectIntersection/InputObjectIntersectionTests.cs @@ -3,64 +3,18 @@ namespace HotChocolate.Fusion.Suites; -/// -/// Port of the input-object-intersection suite from -/// graphql-hive/federation-gateway-audit. Two Apollo Federation -/// subgraphs each declare a UsersFilter input object with a -/// different field set; the supergraph must expose only the -/// intersection (first only). -/// -public sealed class InputObjectIntersectionTests : ComplianceTestBase +[OfficialV2Suite("input-object-intersection")] +public sealed class InputObjectIntersectionTests + : OfficialV2ComplianceTestBase { protected override Task BuildGatewayAsync() - => FusionGatewayBuilder.ComposeAsync( + => ComposeOfficialV2Async( (ASubgraph.Name, ASubgraph.BuildAsync), (BSubgraph.Name, BSubgraph.BuildAsync)); - /// - /// The first field is in the intersection so the query - /// succeeds. - /// - [Fact] - public Task UsersInA_With_First_Returns_All_Users() => RunAsync( - query: """ - query { - usersInA(filter: { first: 1 }) { id } - } - """, - expectedData: """ - { - "usersInA": [ - { "id": "u1" }, - { "id": "u2" } - ] - } - """); - - /// - /// offset is only declared by subgraph b and therefore - /// not part of the supergraph's intersection. Validation must - /// reject the query. - /// - [Fact] - public Task UsersInA_With_Offset_Is_Rejected() => RunAsync( - query: """ - query { - usersInA(filter: { first: 1, offset: 2 }) { id } - } - """, - expectsErrors: true); - - /// - /// Even on subgraph b's own root field, the supergraph - /// intersection forbids offset. - /// - [Fact] - public Task UsersInB_With_Offset_Is_Rejected() => RunAsync( - query: """ - query { - usersInB(filter: { first: 1, offset: 2 }) { id } - } - """, - expectsErrors: true); + [Theory] + [MemberData(nameof(Cases))] + [Trait("Category", "OfficialV2")] + public Task OfficialCase_Should_MatchExpectedResult_When_Executed(string caseId) + => RunOfficialV2CaseAsync(caseId); } diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/InterfaceObjectIndirectExtension/A/MediaInterfaceType.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/InterfaceObjectIndirectExtension/A/MediaInterfaceType.cs index 13bfc5f8f69..b34755c610d 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/InterfaceObjectIndirectExtension/A/MediaInterfaceType.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/InterfaceObjectIndirectExtension/A/MediaInterfaceType.cs @@ -5,10 +5,7 @@ namespace HotChocolate.Fusion.Suites.InterfaceObjectIndirectExtension.A; /// /// Descriptor for the Media interface in the a subgraph -/// (interface Media @key(fields: "id")). The reference resolver dispatches -/// to the concrete Video implementer so the gateway can recover the -/// concrete __typename for the Media @interfaceObject declared in -/// subgraph c. +/// (interface Media @key(fields: "id")). /// public sealed class MediaInterfaceType : InterfaceType { diff --git a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/InterfaceObjectIndirectExtension/B/VideoType.cs b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/InterfaceObjectIndirectExtension/B/VideoType.cs index 8ac481fa0c4..4a74074f7d2 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/InterfaceObjectIndirectExtension/B/VideoType.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Connectors.ApolloFederation.Compliance.Tests/Suites/InterfaceObjectIndirectExtension/B/VideoType.cs @@ -18,11 +18,7 @@ protected override void Configure(IObjectTypeDescriptor