Skip to content

Commit 56833bd

Browse files
[Fusion] Port v15 tests & Re-add automatic mocking (#8709)
1 parent 8f9a799 commit 56833bd

File tree

168 files changed

+21399
-4794
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

168 files changed

+21399
-4794
lines changed

src/HotChocolate/Fusion-vnext/HotChocolate.Fusion-vnext.slnx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010
<Project Path="src/Fusion.Utilities/HotChocolate.Fusion.Utilities.csproj" />
1111
</Folder>
1212
<Folder Name="/test/">
13+
<Project Path="test/Fusion.AspNetCore.Tests/HotChocolate.Fusion.AspNetCore.Tests.csproj" />
1314
<Project Path="test/Fusion.Composition.Tests/HotChocolate.Fusion.Composition.Tests.csproj" />
14-
<Project Path="test/Fusion.Execution.Tests/HotChocolate.Fusion.Execution.Tests.csproj" />
1515
<Project Path="test/Fusion.EventSources.Tests/HotChocolate.Fusion.EventSources.Tests.csproj" />
16+
<Project Path="test/Fusion.Execution.Tests/HotChocolate.Fusion.Execution.Tests.csproj" />
1617
<Project Path="test/Fusion.Language.Tests/HotChocolate.Fusion.Language.Tests.csproj" />
17-
<Project Path="test/Fusion.Utilities.Tests/HotChocolate.Fusion.Utilities.Tests.csproj" />
1818
<Project Path="test/Fusion.Packaging.Tests/HotChocolate.Fusion.Packaging.Tests.csproj" />
19-
<Project Path="test/Fusion.AspNetCore.Tests/HotChocolate.Fusion.AspNetCore.Tests.csproj" />
19+
<Project Path="test/Fusion.Utilities.Tests/HotChocolate.Fusion.Utilities.Tests.csproj" />
20+
<Project Path="test/Fusion.Tests.Shared/HotChocolate.Fusion.Tests.Shared.csproj" />
2021
</Folder>
2122
</Solution>

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/HotChocolate.Fusion.Composition.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<InternalsVisibleTo Include="HotChocolate.Fusion.Composition.Tests" />
1010
<InternalsVisibleTo Include="HotChocolate.Fusion.Execution.Tests" />
1111
<InternalsVisibleTo Include="HotChocolate.Fusion.Utilities.Tests" />
12+
<InternalsVisibleTo Include="HotChocolate.Fusion.AspNetCore.Tests" />
1213
</ItemGroup>
1314

1415
<ItemGroup>

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/FusionRequestOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ public PersistedOperationOptions PersistedOperations
173173
/// errors in the GraphQL response.
174174
/// This should only be enabled for development purposes
175175
/// and not in production environments.
176+
/// <c>false</c> by default.
176177
/// </summary>
177178
public bool IncludeExceptionDetails
178179
{

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Results/ValueCompletion.cs

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ private static bool PropagateNullValues(ResultData result)
180180
return true;
181181
}
182182

183+
// TODO: When extracting an error from a path below the current field,
184+
// we should try to use the path of the original error if it's
185+
// part of what was selected.
183186
private bool TryCompleteValue(
184187
Selection selection,
185188
IType type,
@@ -188,46 +191,63 @@ private bool TryCompleteValue(
188191
int depth,
189192
ResultData parent)
190193
{
191-
if (errorTrie?.Error is { } error)
192-
{
193-
var errorWithPath = ErrorBuilder.FromError(error)
194-
.SetPath(parent.Path)
195-
.AddLocation(selection.SyntaxNodes[0].Node)
196-
.Build();
197-
errorWithPath = _errorHandler.Handle(errorWithPath);
198-
_errors.Add(errorWithPath);
199-
200-
if (_errorHandlingMode is ErrorHandlingMode.Halt)
201-
{
202-
return false;
203-
}
204-
}
205-
206194
if (type.Kind is TypeKind.NonNull)
207195
{
208196
if (data.IsNullOrUndefined())
209197
{
210-
var nonNullViolationError = ErrorBuilder.New()
211-
.SetMessage("Cannot return null for non-nullable field.")
212-
.SetCode(ErrorCodes.Execution.NonNullViolation)
213-
.SetPath(parent.Path)
214-
.AddLocation(selection.SyntaxNodes[0].Node)
215-
.Build();
216-
nonNullViolationError = _errorHandler.Handle(nonNullViolationError);
198+
IError error;
199+
if (errorTrie?.FindFirstError() is { } errorFromPath)
200+
{
201+
error = ErrorBuilder.FromError(errorFromPath)
202+
.SetPath(parent.Path)
203+
.AddLocation(selection.SyntaxNodes[0].Node)
204+
.Build();
205+
}
206+
else
207+
{
208+
error = ErrorBuilder.New()
209+
.SetMessage("Cannot return null for non-nullable field.")
210+
.SetCode(ErrorCodes.Execution.NonNullViolation)
211+
.SetPath(parent.Path)
212+
.AddLocation(selection.SyntaxNodes[0].Node)
213+
.Build();
214+
}
217215

218-
_errors.Add(nonNullViolationError);
216+
error = _errorHandler.Handle(error);
217+
_errors.Add(error);
219218

220219
if (_errorHandlingMode is ErrorHandlingMode.Propagate or ErrorHandlingMode.Halt)
221220
{
222221
return false;
223222
}
223+
224+
return true;
224225
}
225226

226227
type = type.InnerType();
227228
}
228229

229230
if (data.IsNullOrUndefined())
230231
{
232+
// If the value is null, it might've been nulled due to a
233+
// down-stream null propagation.
234+
// So we try to get an error that is associated with this field
235+
// or with a path below it.
236+
if (errorTrie?.FindFirstError() is { } error)
237+
{
238+
var errorWithPath = ErrorBuilder.FromError(error)
239+
.SetPath(parent.Path)
240+
.AddLocation(selection.SyntaxNodes[0].Node)
241+
.Build();
242+
errorWithPath = _errorHandler.Handle(errorWithPath);
243+
_errors.Add(errorWithPath);
244+
245+
if (_errorHandlingMode is ErrorHandlingMode.Halt)
246+
{
247+
return false;
248+
}
249+
}
250+
231251
return true;
232252
}
233253

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/HotChocolate.Fusion.Execution.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
<ItemGroup>
1010
<InternalsVisibleTo Include="HotChocolate.Fusion.Execution.Tests" />
11+
<InternalsVisibleTo Include="HotChocolate.Fusion.Tests.Shared" />
1112
<InternalsVisibleTo Include="ChilliCream.Nitro.Fusion" />
1213
</ItemGroup>
1314

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/ISelectionSetIndex.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public interface ISelectionSetIndex
1717

1818
bool TryGetId(SelectionSetNode selectionSet, out uint id);
1919

20+
bool TryGetOriginalIdFromCloned(uint clonedId, out uint originalId);
21+
2022
bool IsRegistered(SelectionSetNode selectionSet);
2123

2224
SelectionSetIndexBuilder ToBuilder();

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/OperationPlanner.cs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -459,13 +459,12 @@ private PlanNode InlineLookupRequirements(
459459

460460
index.Register(workItemSelectionSet.Id, selectionSet);
461461

462-
var internalOperation = InlineSelections(
462+
var internalOperation = InlineSelectionsIntoOverallOperation(
463463
current.InternalOperationDefinition,
464464
index,
465465
workItemSelectionSet.Type,
466466
workItemSelectionSet.Id,
467-
selectionSet,
468-
inlineInternal: true);
467+
selectionSet);
469468

470469
foreach (var (step, stepIndex, schemaName) in current.GetCandidateSteps(workItemSelectionSet.Id))
471470
{
@@ -1018,7 +1017,7 @@ private void PlanNode(
10181017
index.GetId(selectionSetNode),
10191018
selectionSetNode,
10201019
type,
1021-
selectionPath);
1020+
selectionPath.AppendFragment(type.Name));
10221021

10231022
var newWorkItem = new NodeLookupWorkItem(
10241023
Lookup: null,
@@ -1134,13 +1133,12 @@ private static List<ArgumentNode> GetLookupArguments(Lookup lookup, string requi
11341133
requirements);
11351134

11361135
var internalOperation =
1137-
InlineSelections(
1136+
InlineSelectionsIntoOverallOperation(
11381137
current.InternalOperationDefinition,
11391138
index,
11401139
workItem.Selection.Field.DeclaringType,
11411140
workItem.Selection.SelectionSetId,
1142-
requirements,
1143-
inlineInternal: true);
1141+
requirements);
11441142
current = current with { InternalOperationDefinition = internalOperation };
11451143

11461144
foreach (var (step, stepIndex, schemaName) in current.GetCandidateSteps(workItem.Selection.SelectionSetId))
@@ -1243,6 +1241,32 @@ private static List<ArgumentNode> GetLookupArguments(Lookup lookup, string requi
12431241
return requirements;
12441242
}
12451243

1244+
private OperationDefinitionNode InlineSelectionsIntoOverallOperation(
1245+
OperationDefinitionNode operation,
1246+
SelectionSetIndexBuilder index,
1247+
ITypeDefinition selectionSetType,
1248+
uint targetSelectionSetId,
1249+
SelectionSetNode selectionsToInline)
1250+
{
1251+
// If we're looking for a selection set in the overall operation,
1252+
// we need to ensure that we're not looking for a cloned selection set
1253+
// and instead are looking for the original selection set.
1254+
// Cloned selections can happen for instance if we're expanding an interface
1255+
// selection set to multiple selection sets for each concrete type.
1256+
if (index.TryGetOriginalIdFromCloned(targetSelectionSetId, out var originalId))
1257+
{
1258+
targetSelectionSetId = originalId;
1259+
}
1260+
1261+
return InlineSelections(
1262+
operation,
1263+
index,
1264+
selectionSetType,
1265+
targetSelectionSetId,
1266+
selectionsToInline,
1267+
inlineInternal: true);
1268+
}
1269+
12461270
private OperationDefinitionNode InlineSelections(
12471271
OperationDefinitionNode operation,
12481272
SelectionSetIndexBuilder index,

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/Partitioners/SelectionSetByTypePartitioner.cs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Diagnostics.CodeAnalysis;
33
using HotChocolate.Fusion.Types;
44
using HotChocolate.Language;
5+
using HotChocolate.Language.Visitors;
56
using HotChocolate.Types;
67

78
namespace HotChocolate.Fusion.Planning.Partitioners;
@@ -17,8 +18,8 @@ internal sealed class SelectionSetByTypePartitioner(FusionSchemaDefinition schem
1718
{
1819
public SelectionSetByTypePartitionerResult Partition(SelectionSetByTypePartitionerInput input)
1920
{
20-
var context = new Context { SharedType = input.SelectionSet.Type };
2121
var indexBuilder = input.SelectionSetIndex.ToBuilder();
22+
var context = new Context { SharedType = input.SelectionSet.Type, SelectionSetIndexBuilder = indexBuilder };
2223

2324
CollectSelections(input.SelectionSet.Node, input.SelectionSet.Type, context);
2425

@@ -117,7 +118,7 @@ private void CollectSelections(
117118
{
118119
foreach (var possibleType in schema.GetPossibleTypes(type))
119120
{
120-
AddSelectionsForConcreteType(context, possibleType, selectionsWithPath);
121+
AddSelectionsForConcreteType(context, possibleType, selectionsWithPath, cloneSelectionSets: true);
121122
}
122123
}
123124
}
@@ -130,15 +131,51 @@ private void CollectSelections(
130131
private void AddSelectionsForConcreteType(
131132
Context context,
132133
FusionObjectTypeDefinition type,
133-
List<ISelectionNode> selections)
134+
List<ISelectionNode> selections,
135+
bool cloneSelectionSets = false)
134136
{
135137
if (!context.SelectionsByType.TryGetValue(type.Name, out var typeSelections))
136138
{
137139
typeSelections = [];
138140
context.SelectionsByType.Add(type.Name, typeSelections);
139141
}
140142

141-
typeSelections.AddRange(selections);
143+
if (cloneSelectionSets)
144+
{
145+
var rewrittenSelections = new List<ISelectionNode>(selections.Count);
146+
147+
foreach (var selection in selections)
148+
{
149+
var rewrittenSelection = SyntaxRewriter.Create(
150+
node =>
151+
{
152+
if (node is SelectionSetNode selectionSetNode)
153+
{
154+
var newSelectionSet = new SelectionSetNode(selectionSetNode.Selections);
155+
156+
// Since we're cloning the selection set,
157+
// we also need to keep track of the original
158+
// selection set the cloned one belongs to,
159+
// so we can later insert requirements in the original one.
160+
context.SelectionSetIndexBuilder.RegisterCloned(
161+
selectionSetNode,
162+
newSelectionSet);
163+
164+
return newSelectionSet;
165+
}
166+
167+
return node;
168+
}).Rewrite(selection)!;
169+
170+
rewrittenSelections.Add(rewrittenSelection);
171+
}
172+
173+
typeSelections.AddRange(rewrittenSelections);
174+
}
175+
else
176+
{
177+
typeSelections.AddRange(selections);
178+
}
142179
}
143180

144181
private static List<ISelectionNode> GetSelectionsWithPath(
@@ -186,6 +223,8 @@ private class Context
186223
/// </summary>
187224
public List<ISelectionNode>? SharedSelections { get; set; }
188225

226+
public required SelectionSetIndexBuilder SelectionSetIndexBuilder { get; init; }
227+
189228
public bool TryGetParentConcreteType([NotNullWhen(true)] out FusionObjectTypeDefinition? objectType)
190229
{
191230
foreach (var type in TypePath)

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/SelectionSetIndex.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@ namespace HotChocolate.Fusion.Planning;
66
public sealed class SelectionSetIndex : ISelectionSetIndex
77
{
88
private readonly ImmutableDictionary<SelectionSetNode, uint> _selectionSets;
9+
private readonly ImmutableDictionary<uint, uint> _clonedToOriginalMap;
910
private readonly uint _nextId;
1011

11-
internal SelectionSetIndex(ImmutableDictionary<SelectionSetNode, uint> selectionSets, uint nextId)
12+
internal SelectionSetIndex(
13+
ImmutableDictionary<SelectionSetNode, uint> selectionSets,
14+
ImmutableDictionary<uint, uint> clonedToOriginalMap,
15+
uint nextId)
1216
{
1317
_selectionSets = selectionSets;
18+
_clonedToOriginalMap = clonedToOriginalMap;
1419
_nextId = nextId;
1520
}
1621

@@ -20,9 +25,12 @@ public uint GetId(SelectionSetNode selectionSet)
2025
public bool TryGetId(SelectionSetNode selectionSet, out uint id)
2126
=> _selectionSets.TryGetValue(selectionSet, out id);
2227

28+
public bool TryGetOriginalIdFromCloned(uint clonedId, out uint originalId)
29+
=> _clonedToOriginalMap.TryGetValue(clonedId, out originalId);
30+
2331
public bool IsRegistered(SelectionSetNode selectionSet)
2432
=> _selectionSets.ContainsKey(selectionSet);
2533

2634
public SelectionSetIndexBuilder ToBuilder()
27-
=> new(_selectionSets, _nextId);
35+
=> new(_selectionSets, _clonedToOriginalMap, _nextId);
2836
}

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Planning/SelectionSetIndexBuilder.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,16 @@ public sealed class SelectionSetIndexBuilder : ISelectionSetIndex, ISelectionSet
88
{
99
private List<SelectionSetNode>? _temp;
1010
private ImmutableDictionary<SelectionSetNode, uint> _selectionSets;
11+
private ImmutableDictionary<uint, uint> _clonedToOriginalMap;
1112
private uint _nextId;
1213

13-
internal SelectionSetIndexBuilder(ImmutableDictionary<SelectionSetNode, uint> selectionSets, uint nextId)
14+
internal SelectionSetIndexBuilder(
15+
ImmutableDictionary<SelectionSetNode, uint> selectionSets,
16+
ImmutableDictionary<uint, uint> clonedToOriginalMap,
17+
uint nextId)
1418
{
1519
_selectionSets = selectionSets;
20+
_clonedToOriginalMap = clonedToOriginalMap;
1621
_nextId = nextId;
1722
}
1823

@@ -22,6 +27,9 @@ public uint GetId(SelectionSetNode selectionSet)
2227
public bool TryGetId(SelectionSetNode selectionSet, out uint id)
2328
=> _selectionSets.TryGetValue(selectionSet, out id);
2429

30+
public bool TryGetOriginalIdFromCloned(uint clonedId, out uint originalId)
31+
=> _clonedToOriginalMap.TryGetValue(clonedId, out originalId);
32+
2533
public bool IsRegistered(SelectionSetNode selectionSet)
2634
=> _selectionSets.ContainsKey(selectionSet);
2735

@@ -40,6 +48,16 @@ public void Register(SelectionSetNode original, SelectionSetNode branch)
4048
public void Register(SelectionSetNode original)
4149
=> _selectionSets = _selectionSets.SetItem(original, _nextId++);
4250

51+
public void RegisterCloned(SelectionSetNode original, SelectionSetNode cloned)
52+
{
53+
Register(cloned);
54+
55+
var clonedId = _selectionSets[cloned];
56+
var originalId = _selectionSets[original];
57+
58+
_clonedToOriginalMap = _clonedToOriginalMap.SetItem(clonedId, originalId);
59+
}
60+
4361
public void OnMerge(FieldNode field1, FieldNode field2)
4462
{
4563
if (field1.SelectionSet is null)
@@ -197,7 +215,7 @@ private void ReturnSelectionSetList(List<SelectionSetNode> selectionSetList)
197215
}
198216

199217
public ISelectionSetIndex Build()
200-
=> new SelectionSetIndex(_selectionSets, _nextId);
218+
=> new SelectionSetIndex(_selectionSets, _clonedToOriginalMap, _nextId);
201219

202220
public SelectionSetIndexBuilder ToBuilder()
203221
=> this;

0 commit comments

Comments
 (0)