Skip to content

Commit 4cc8cd0

Browse files
authored
Fixed fusion file uploads where IFile is in a list (#7094)
1 parent 9515881 commit 4cc8cd0

File tree

36 files changed

+1120
-5
lines changed

36 files changed

+1120
-5
lines changed

src/HotChocolate/AspNetCore/src/Transport.Abstractions/Serialization/Utf8JsonWriterHelper.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public static void WriteOperationRequest(Utf8JsonWriter writer, OperationRequest
2121
{
2222
writer.WriteString(Utf8GraphQLRequestProperties.IdProp, request.Id);
2323
}
24-
24+
2525
if (request.Query is not null)
2626
{
2727
writer.WriteString(Utf8GraphQLRequestProperties.QueryProp, request.Query);
@@ -115,7 +115,7 @@ internal static void WriteFieldValue(
115115
case Dictionary<string, object?> dict:
116116
WriteDictionary(writer, dict);
117117
break;
118-
118+
119119
case byte[] bytes:
120120
writer.WriteBase64StringValue(bytes);
121121
break;
@@ -405,7 +405,7 @@ private static void CollectFiles(
405405
}
406406

407407
var current = path.Append(i);
408-
CollectFiles(item.Value, current, ref files);
408+
CollectFiles(item, current, ref files);
409409
}
410410
}
411411

src/HotChocolate/Fusion/test/Composition.Tests/__snapshots__/DemoIntegrationTests.Accounts_And_Reviews2_Products_With_Nodes.graphql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ type Mutation {
7070
addUser(input: AddUserInput!): AddUserPayload!
7171
@variable(subgraph: "Accounts", name: "input", argument: "input")
7272
@resolver(subgraph: "Accounts", select: "{ addUser(input: $input) }", arguments: [ { name: "input", type: "AddUserInput!" } ])
73+
uploadMultipleProductPictures(input: UploadMultipleProductPicturesInput!): UploadMultipleProductPicturesPayload!
74+
@variable(subgraph: "Products", name: "input", argument: "input")
75+
@resolver(subgraph: "Products", select: "{ uploadMultipleProductPictures(input: $input) }", arguments: [ { name: "input", type: "UploadMultipleProductPicturesInput!" } ])
7376
uploadProductPicture(input: UploadProductPictureInput!): UploadProductPicturePayload!
7477
@variable(subgraph: "Products", name: "input", argument: "input")
7578
@resolver(subgraph: "Products", select: "{ uploadProductPicture(input: $input) }", arguments: [ { name: "input", type: "UploadProductPictureInput!" } ])
@@ -158,6 +161,13 @@ type SomeData {
158161
@source(subgraph: "Reviews2")
159162
}
160163

164+
type UploadMultipleProductPicturesPayload {
165+
boolean: Boolean
166+
@source(subgraph: "Products")
167+
errors: [UploadMultipleProductPicturesError!]
168+
@source(subgraph: "Products")
169+
}
170+
161171
type UploadProductPicturePayload {
162172
boolean: Boolean
163173
@source(subgraph: "Products")
@@ -210,6 +220,8 @@ interface Node {
210220

211221
union ReviewOrAuthor = User | Review
212222

223+
union UploadMultipleProductPicturesError = ProductNotFoundError
224+
213225
union UploadProductPictureError = ProductNotFoundError
214226

215227
input AddReviewInput {
@@ -224,11 +236,20 @@ input AddUserInput {
224236
username: String!
225237
}
226238

239+
input ProductIdWithUploadInput {
240+
file: Upload!
241+
productId: Int!
242+
}
243+
227244
input SomeDataInput {
228245
data: SomeDataInput
229246
num: Int
230247
}
231248

249+
input UploadMultipleProductPicturesInput {
250+
products: [ProductIdWithUploadInput!]!
251+
}
252+
232253
input UploadProductPictureInput {
233254
file: Upload!
234255
productId: Int!

src/HotChocolate/Fusion/test/Composition.Tests/__snapshots__/DemoIntegrationTests.Accounts_And_Reviews_Products.graphql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ type Mutation {
5050
addUser(input: AddUserInput!): AddUserPayload!
5151
@variable(subgraph: "Accounts", name: "input", argument: "input")
5252
@resolver(subgraph: "Accounts", select: "{ addUser(input: $input) }", arguments: [ { name: "input", type: "AddUserInput!" } ])
53+
uploadMultipleProductPictures(input: UploadMultipleProductPicturesInput!): UploadMultipleProductPicturesPayload!
54+
@variable(subgraph: "Products", name: "input", argument: "input")
55+
@resolver(subgraph: "Products", select: "{ uploadMultipleProductPictures(input: $input) }", arguments: [ { name: "input", type: "UploadMultipleProductPicturesInput!" } ])
5356
uploadProductPicture(input: UploadProductPictureInput!): UploadProductPicturePayload!
5457
@variable(subgraph: "Products", name: "input", argument: "input")
5558
@resolver(subgraph: "Products", select: "{ uploadProductPicture(input: $input) }", arguments: [ { name: "input", type: "UploadProductPictureInput!" } ])
@@ -134,6 +137,13 @@ type SomeData {
134137
@source(subgraph: "Products")
135138
}
136139

140+
type UploadMultipleProductPicturesPayload {
141+
boolean: Boolean
142+
@source(subgraph: "Products")
143+
errors: [UploadMultipleProductPicturesError!]
144+
@source(subgraph: "Products")
145+
}
146+
137147
type UploadProductPicturePayload {
138148
boolean: Boolean
139149
@source(subgraph: "Products")
@@ -183,6 +193,8 @@ interface Node {
183193

184194
union ReviewOrAuthor = User | Review
185195

196+
union UploadMultipleProductPicturesError = ProductNotFoundError
197+
186198
union UploadProductPictureError = ProductNotFoundError
187199

188200
input AddReviewInput {
@@ -197,11 +209,20 @@ input AddUserInput {
197209
username: String!
198210
}
199211

212+
input ProductIdWithUploadInput {
213+
file: Upload!
214+
productId: Int!
215+
}
216+
200217
input SomeDataInput {
201218
data: SomeDataInput
202219
num: Int
203220
}
204221

222+
input UploadMultipleProductPicturesInput {
223+
products: [ProductIdWithUploadInput!]!
224+
}
225+
205226
input UploadProductPictureInput {
206227
file: Upload!
207228
productId: Int!

src/HotChocolate/Fusion/test/Composition.Tests/__snapshots__/DemoIntegrationTests.Accounts_And_Reviews_Products_AutoCompose_With_Node.graphql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ type Mutation {
5151
addUser(input: AddUserInput!): AddUserPayload!
5252
@variable(subgraph: "Accounts", name: "input", argument: "input")
5353
@resolver(subgraph: "Accounts", select: "{ addUser(input: $input) }", arguments: [ { name: "input", type: "AddUserInput!" } ])
54+
uploadMultipleProductPictures(input: UploadMultipleProductPicturesInput!): UploadMultipleProductPicturesPayload!
55+
@variable(subgraph: "Products", name: "input", argument: "input")
56+
@resolver(subgraph: "Products", select: "{ uploadMultipleProductPictures(input: $input) }", arguments: [ { name: "input", type: "UploadMultipleProductPicturesInput!" } ])
5457
uploadProductPicture(input: UploadProductPictureInput!): UploadProductPicturePayload!
5558
@variable(subgraph: "Products", name: "input", argument: "input")
5659
@resolver(subgraph: "Products", select: "{ uploadProductPicture(input: $input) }", arguments: [ { name: "input", type: "UploadProductPictureInput!" } ])
@@ -147,6 +150,13 @@ type SomeData {
147150
@source(subgraph: "Products")
148151
}
149152

153+
type UploadMultipleProductPicturesPayload {
154+
boolean: Boolean
155+
@source(subgraph: "Products")
156+
errors: [UploadMultipleProductPicturesError!]
157+
@source(subgraph: "Products")
158+
}
159+
150160
type UploadProductPicturePayload {
151161
boolean: Boolean
152162
@source(subgraph: "Products")
@@ -188,6 +198,8 @@ interface Node {
188198

189199
union ReviewOrAuthor = Author | Review
190200

201+
union UploadMultipleProductPicturesError = ProductNotFoundError
202+
191203
union UploadProductPictureError = ProductNotFoundError
192204

193205
input AddReviewInput {
@@ -202,11 +214,20 @@ input AddUserInput {
202214
username: String!
203215
}
204216

217+
input ProductIdWithUploadInput {
218+
file: Upload!
219+
productId: Int!
220+
}
221+
205222
input SomeDataInput {
206223
data: SomeDataInput
207224
num: Int
208225
}
209226

227+
input UploadMultipleProductPicturesInput {
228+
products: [ProductIdWithUploadInput!]!
229+
}
230+
210231
input UploadProductPictureInput {
211232
file: Upload!
212233
productId: Int!

src/HotChocolate/Fusion/test/Composition.Tests/__snapshots__/DemoIntegrationTests.Accounts_And_Reviews_Products_With_Nodes.graphql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ type Mutation {
6969
addUser(input: AddUserInput!): AddUserPayload!
7070
@variable(subgraph: "Accounts", name: "input", argument: "input")
7171
@resolver(subgraph: "Accounts", select: "{ addUser(input: $input) }", arguments: [ { name: "input", type: "AddUserInput!" } ])
72+
uploadMultipleProductPictures(input: UploadMultipleProductPicturesInput!): UploadMultipleProductPicturesPayload!
73+
@variable(subgraph: "Products", name: "input", argument: "input")
74+
@resolver(subgraph: "Products", select: "{ uploadMultipleProductPictures(input: $input) }", arguments: [ { name: "input", type: "UploadMultipleProductPicturesInput!" } ])
7275
uploadProductPicture(input: UploadProductPictureInput!): UploadProductPicturePayload!
7376
@variable(subgraph: "Products", name: "input", argument: "input")
7477
@resolver(subgraph: "Products", select: "{ uploadProductPicture(input: $input) }", arguments: [ { name: "input", type: "UploadProductPictureInput!" } ])
@@ -153,6 +156,13 @@ type SomeData {
153156
@source(subgraph: "Products")
154157
}
155158

159+
type UploadMultipleProductPicturesPayload {
160+
boolean: Boolean
161+
@source(subgraph: "Products")
162+
errors: [UploadMultipleProductPicturesError!]
163+
@source(subgraph: "Products")
164+
}
165+
156166
type UploadProductPicturePayload {
157167
boolean: Boolean
158168
@source(subgraph: "Products")
@@ -203,6 +213,8 @@ interface Node {
203213

204214
union ReviewOrAuthor = User | Review
205215

216+
union UploadMultipleProductPicturesError = ProductNotFoundError
217+
206218
union UploadProductPictureError = ProductNotFoundError
207219

208220
input AddReviewInput {
@@ -217,11 +229,20 @@ input AddUserInput {
217229
username: String!
218230
}
219231

232+
input ProductIdWithUploadInput {
233+
file: Upload!
234+
productId: Int!
235+
}
236+
220237
input SomeDataInput {
221238
data: SomeDataInput
222239
num: Int
223240
}
224241

242+
input UploadMultipleProductPicturesInput {
243+
products: [ProductIdWithUploadInput!]!
244+
}
245+
225246
input UploadProductPictureInput {
226247
file: Upload!
227248
productId: Int!

src/HotChocolate/Fusion/test/Composition.Tests/__snapshots__/RequireTests.Require_Scalar_Arguments_No_Overloads.graphql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ type Mutation {
5555
addUser(input: AddUserInput!): AddUserPayload!
5656
@variable(subgraph: "Accounts", name: "input", argument: "input")
5757
@resolver(subgraph: "Accounts", select: "{ addUser(input: $input) }", arguments: [ { name: "input", type: "AddUserInput!" } ])
58+
uploadMultipleProductPictures(input: UploadMultipleProductPicturesInput!): UploadMultipleProductPicturesPayload!
59+
@variable(subgraph: "Products", name: "input", argument: "input")
60+
@resolver(subgraph: "Products", select: "{ uploadMultipleProductPictures(input: $input) }", arguments: [ { name: "input", type: "UploadMultipleProductPicturesInput!" } ])
5861
uploadProductPicture(input: UploadProductPictureInput!): UploadProductPicturePayload!
5962
@variable(subgraph: "Products", name: "input", argument: "input")
6063
@resolver(subgraph: "Products", select: "{ uploadProductPicture(input: $input) }", arguments: [ { name: "input", type: "UploadProductPictureInput!" } ])
@@ -167,6 +170,13 @@ type SomeData {
167170
@source(subgraph: "Products")
168171
}
169172

173+
type UploadMultipleProductPicturesPayload {
174+
boolean: Boolean
175+
@source(subgraph: "Products")
176+
errors: [UploadMultipleProductPicturesError!]
177+
@source(subgraph: "Products")
178+
}
179+
170180
type UploadProductPicturePayload {
171181
boolean: Boolean
172182
@source(subgraph: "Products")
@@ -208,6 +218,8 @@ interface Node {
208218

209219
union ReviewOrAuthor = Author | Review
210220

221+
union UploadMultipleProductPicturesError = ProductNotFoundError
222+
211223
union UploadProductPictureError = ProductNotFoundError
212224

213225
input AddReviewInput {
@@ -222,11 +234,20 @@ input AddUserInput {
222234
username: String!
223235
}
224236

237+
input ProductIdWithUploadInput {
238+
file: Upload!
239+
productId: Int!
240+
}
241+
225242
input SomeDataInput {
226243
data: SomeDataInput
227244
num: Int
228245
}
229246

247+
input UploadMultipleProductPicturesInput {
248+
products: [ProductIdWithUploadInput!]!
249+
}
250+
230251
input UploadProductPictureInput {
231252
file: Upload!
232253
productId: Int!

src/HotChocolate/Fusion/test/Core.Tests/FileUploadTests.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,70 @@ mutation Upload($input: UploadProductPictureInput!) {
150150
await snapshot.MatchMarkdownAsync(cts.Token);
151151
}
152152

153+
[Fact]
154+
public async Task UploadFile_Multiple_In_List()
155+
{
156+
// arrange
157+
using var cts = new CancellationTokenSource(100_000);
158+
using var demoProject = await DemoProject.CreateAsync(cts.Token);
159+
160+
// act
161+
var fusionGraph = await new FusionGraphComposer(logFactory: _logFactory).ComposeAsync(
162+
new[]
163+
{
164+
demoProject.Reviews2.ToConfiguration(Reviews2ExtensionSdl, onlyHttp: true),
165+
demoProject.Accounts.ToConfiguration(AccountsExtensionSdl, onlyHttp: true),
166+
demoProject.Products.ToConfiguration(ProductsExtensionSdl, onlyHttp: true),
167+
demoProject.Shipping.ToConfiguration(ShippingExtensionSdl, onlyHttp: true),
168+
},
169+
default,
170+
cts.Token);
171+
172+
var executor = await new ServiceCollection()
173+
.AddSingleton(demoProject.HttpClientFactory)
174+
.AddSingleton<IWebSocketConnectionFactory>(new NoWebSockets())
175+
.AddFusionGatewayServer()
176+
.ConfigureFromDocument(SchemaFormatter.FormatAsDocument(fusionGraph))
177+
.BuildRequestExecutorAsync(cancellationToken: cts.Token);
178+
179+
var request = Parse(
180+
"""
181+
mutation UploadMultiple($input: UploadMultipleProductPicturesInput!) {
182+
uploadMultipleProductPictures(input: $input) {
183+
boolean
184+
}
185+
}
186+
""");
187+
188+
var input = new Dictionary<string, object?>
189+
{
190+
["products"] = new List<Dictionary<string, object?>> {
191+
new () {
192+
["productId"] = 1,
193+
["file"] = new StreamFile("abc", () => new MemoryStream("abc"u8.ToArray())),
194+
},
195+
new () {
196+
["productId"] = 2,
197+
["file"] = new StreamFile("abc", () => new MemoryStream("abc"u8.ToArray())),
198+
},
199+
},
200+
};
201+
202+
// act
203+
var result = await executor.ExecuteAsync(
204+
QueryRequestBuilder
205+
.New()
206+
.SetQuery(request)
207+
.SetVariableValue("input", input)
208+
.Create(),
209+
cts.Token);
210+
211+
// assert
212+
var snapshot = new Snapshot();
213+
CollectSnapshotData(snapshot, request, result, fusionGraph);
214+
await snapshot.MatchMarkdownAsync(cts.Token);
215+
}
216+
153217
private sealed class NoWebSockets : IWebSocketConnectionFactory
154218
{
155219
public IWebSocketConnection CreateConnection(string name)

0 commit comments

Comments
 (0)