@@ -292,6 +292,118 @@ public void TestAsyncServiceFieldReturningListOnSingleObjectFromQueryArg()
292292 Assert . Single ( catalog ) ;
293293 }
294294
295+ /// <summary>
296+ /// Same as TestAsyncServiceFieldReturningListOnSingleObjectFromQueryArg but toolCatalog
297+ /// is on Step (a list element), so the path is workflow -> steps (list) -> toolCatalog (service).
298+ /// </summary>
299+ [ Fact ]
300+ public void TestAsyncServiceFieldReturningListNestedInsideList ( )
301+ {
302+ var schema = SchemaBuilder . FromObject < WorkflowContext > ( ) ;
303+ schema . AddType < WorkflowTool > ( "WorkflowTool" ) . AddAllFields ( ) ;
304+
305+ schema . UpdateType < Step > ( type =>
306+ {
307+ type . AddField ( "toolCatalog" , "Resolved tool catalog for this step." ) . ResolveAsync < IWorkflowToolCatalogService > ( ( step , tools ) => tools . GetWorkflowToolCatalogAsync ( step . WorkflowId ) ) ;
308+ } ) ;
309+
310+ var gql = new QueryRequest
311+ {
312+ Query =
313+ @"query BuilderGetWorkflow($id: String) {
314+ workflow(id: $id) {
315+ id
316+ steps {
317+ id
318+ toolCatalog { name }
319+ }
320+ }
321+ }" ,
322+ Variables = new QueryVariables { { "id" , "wf-1" } } ,
323+ } ;
324+
325+ var context = new WorkflowContext ( ) ;
326+ context . Workflows . Add (
327+ new Workflow
328+ {
329+ Id = "wf-1" ,
330+ Name = "My Workflow" ,
331+ Steps = [ new Step { Id = "s-1" , WorkflowId = "wf-1" } ] ,
332+ }
333+ ) ;
334+
335+ var serviceCollection = new ServiceCollection ( ) ;
336+ serviceCollection . AddSingleton < IWorkflowToolCatalogService > ( new WorkflowToolCatalogService ( ) ) ;
337+
338+ var res = schema . ExecuteRequestWithContext ( gql , context , serviceCollection . BuildServiceProvider ( ) , null ) ;
339+ Assert . Null ( res . Errors ) ;
340+ Assert . NotNull ( res . Data ) ;
341+ dynamic workflow = res . Data ! [ "workflow" ] ! ;
342+ Assert . Equal ( "wf-1" , workflow . id ) ;
343+ var steps = ( IEnumerable < dynamic > ) workflow . steps ;
344+ Assert . Single ( steps ) ;
345+ var catalog = ( IEnumerable < dynamic > ) ( ( dynamic ) steps . First ( ) ) . toolCatalog ;
346+ Assert . Single ( catalog ) ;
347+ Assert . Equal ( "tool-a" , ( ( dynamic ) catalog . First ( ) ) . name ) ;
348+ }
349+
350+ /// <summary>
351+ /// Same as TestAsyncServiceFieldReturningListNestedInsideList but the service returns
352+ /// Task{List{T}} (a concrete collection type) rather than Task{IEnumerable{T}}.
353+ /// Verifies that SelectWithNullCheck can be called regardless of the concrete async return type.
354+ /// </summary>
355+ [ Fact ]
356+ public void TestAsyncServiceFieldReturningConcreteListNestedInsideList ( )
357+ {
358+ var schema = SchemaBuilder . FromObject < WorkflowContext > ( ) ;
359+ schema . AddType < WorkflowTool > ( "WorkflowTool" ) . AddAllFields ( ) ;
360+
361+ schema . UpdateType < Step > ( type =>
362+ {
363+ type . AddField ( "toolCatalog" , "Resolved tool catalog for this step." )
364+ . ResolveAsync < IWorkflowToolCatalogServiceConcreteReturn > ( ( step , tools ) => tools . GetWorkflowToolCatalogAsync ( step . WorkflowId ) ) ;
365+ } ) ;
366+
367+ var gql = new QueryRequest
368+ {
369+ Query =
370+ @"query BuilderGetWorkflow($id: String) {
371+ workflow(id: $id) {
372+ id
373+ steps {
374+ id
375+ toolCatalog { name }
376+ }
377+ }
378+ }" ,
379+ Variables = new QueryVariables { { "id" , "wf-1" } } ,
380+ } ;
381+
382+ var context = new WorkflowContext ( ) ;
383+ context . Workflows . Add (
384+ new Workflow
385+ {
386+ Id = "wf-1" ,
387+ Name = "My Workflow" ,
388+ Steps = [ new Step { Id = "s-1" , WorkflowId = "wf-1" } ] ,
389+ }
390+ ) ;
391+
392+ var serviceCollection = new ServiceCollection ( ) ;
393+ serviceCollection . AddSingleton < IWorkflowToolCatalogServiceConcreteReturn > ( new WorkflowToolCatalogServiceConcreteReturn ( ) ) ;
394+
395+ var res = schema . ExecuteRequestWithContext ( gql , context , serviceCollection . BuildServiceProvider ( ) , null ) ;
396+ Assert . Null ( res . Errors ) ;
397+ Assert . NotNull ( res . Data ) ;
398+ dynamic workflow = res . Data ! [ "workflow" ] ! ;
399+ Assert . Equal ( "wf-1" , workflow . id ) ;
400+ var steps = ( IEnumerable < dynamic > ) workflow . steps ;
401+ Assert . Single ( steps ) ;
402+ var catalog = ( IEnumerable < dynamic > ) ( ( dynamic ) steps . First ( ) ) . toolCatalog ;
403+ Assert . Single ( catalog ) ;
404+ Assert . Equal ( "tool-a" , ( ( dynamic ) catalog . First ( ) ) . name ) ;
405+ }
406+
295407 private class WorkflowContext
296408 {
297409 public List < Workflow > Workflows { get ; set ; } = [ ] ;
@@ -301,6 +413,13 @@ private class Workflow
301413 {
302414 public string Id { get ; set ; } = string . Empty ;
303415 public string Name { get ; set ; } = string . Empty ;
416+ public List < Step > Steps { get ; set ; } = [ ] ;
417+ }
418+
419+ private class Step
420+ {
421+ public string Id { get ; set ; } = string . Empty ;
422+ public string WorkflowId { get ; set ; } = string . Empty ;
304423 }
305424
306425 private class WorkflowTool
@@ -322,6 +441,19 @@ public System.Threading.Tasks.Task<IEnumerable<WorkflowTool>> GetWorkflowToolCat
322441 }
323442 }
324443
444+ private interface IWorkflowToolCatalogServiceConcreteReturn
445+ {
446+ System . Threading . Tasks . Task < List < WorkflowTool > > GetWorkflowToolCatalogAsync ( string workflowId ) ;
447+ }
448+
449+ private class WorkflowToolCatalogServiceConcreteReturn : IWorkflowToolCatalogServiceConcreteReturn
450+ {
451+ public System . Threading . Tasks . Task < List < WorkflowTool > > GetWorkflowToolCatalogAsync ( string workflowId )
452+ {
453+ return System . Threading . Tasks . Task . FromResult ( new List < WorkflowTool > { new WorkflowTool { Name = "tool-a" } } ) ;
454+ }
455+ }
456+
325457 private class JobContext
326458 {
327459 [ GraphQLIgnore ]
0 commit comments