Skip to content

Commit 8c5a23c

Browse files
committed
Added tests to cover all cases of additional errors in executeResolvers/resolveWith
1 parent f0feede commit 8c5a23c

File tree

2 files changed

+131
-12
lines changed

2 files changed

+131
-12
lines changed

src/FSharp.Data.GraphQL.Server/Execution.fs

+3-4
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ and private executeResolvers (ctx : ResolveFieldContext) (path : FieldPath) (par
364364
/// This handles all null resolver errors/error propagation.
365365
let resolveWith (ctx : ResolveFieldContext) (onSuccess : ResolveFieldContext -> FieldPath -> obj -> obj -> AsyncVal<ResolverResult<KeyValuePair<string, obj>>>) : AsyncVal<ResolverResult<KeyValuePair<string, obj>>> = asyncVal {
366366
let! resolved = value |> AsyncVal.rescue path ctx.Schema.ParseError
367-
let additionalErrs =
367+
let additionalErrs =
368368
match ctx.Context.Errors.TryGetValue ctx with
369369
| true, errors ->
370370
errors
@@ -373,10 +373,9 @@ and private executeResolvers (ctx : ResolveFieldContext) (path : FieldPath) (par
373373
| false, _ -> []
374374
match resolved with
375375
| Error errs when ctx.ExecutionInfo.IsNullable -> return Ok (KeyValuePair(name, null), None, errs @ additionalErrs)
376-
| Ok None when ctx.ExecutionInfo.IsNullable ->
377-
return Ok (KeyValuePair(name, null), None, additionalErrs)
376+
| Ok None when ctx.ExecutionInfo.IsNullable -> return Ok (KeyValuePair(name, null), None, additionalErrs)
378377
| Error errs -> return Error (errs @ additionalErrs)
379-
| Ok None -> return Error (nullResolverError name path ctx)
378+
| Ok None -> return Error ((nullResolverError name path ctx) @ additionalErrs)
380379
| Ok (Some v) ->
381380
match! onSuccess ctx path parent v with
382381
| Ok (res, deferred, errs) -> return Ok (res, deferred, errs @ additionalErrs)

tests/FSharp.Data.GraphQL.Tests/ExecutionTests.fs

+128-8
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ let ``Execution handles errors: properly propagates errors`` () =
407407
Define.Field("kaboom", StringType, fun _ x -> x.Kaboom)
408408
])
409409
let InnerPartialSuccessObjType =
410+
// executeResolvers/resolveWith, case 5
410411
let resolvePartialSuccess (ctx : ResolveFieldContext) (_ : InnerNullableTest) =
411412
ctx.AddError { new IGQLError with member _.Message = "Some non-critical error" }
412413
"Success"
@@ -439,14 +440,6 @@ let ``Execution handles errors: properly propagates errors`` () =
439440
data |> equals (upcast expectedData)
440441
errors |> equals expectedErrors
441442

442-
// TODO: Add other tests with possible error cases
443-
// 1. Add additional error and raise an exception in a nullable GraphQL field resolver
444-
// 2. Add additional error and return None from a nullable GraphQL field resolver
445-
// 3. Add additional error and raise an exception in a non-nullable GraphQL field resolver
446-
// 4. Add additional error and return null from a non-nullable GraphQL field resolver
447-
// Covered // 5.1. Add additional error and return value from a non-nullable GraphQL field resolver
448-
// 5.2. Add additional error and raise an exception in a non-nullable GraphQL field resolver
449-
450443
[<Fact>]
451444
let ``Execution handles errors: exceptions`` () =
452445
let schema =
@@ -484,3 +477,130 @@ let ``Execution handles errors: nullable list fields`` () =
484477
result.DocumentId |> notEquals Unchecked.defaultof<int>
485478
data |> equals (upcast expectedData)
486479
errors |> equals expectedErrors
480+
481+
482+
[<Fact>]
483+
let ``Execution handles errors: additional error added when exception is rised in a nullable field resolver`` () =
484+
let InnerNullableExceptionObjType =
485+
// executeResolvers/resolveWith, case 1
486+
let resolveWithException (ctx : ResolveFieldContext) (_ : InnerNullableTest) : string option =
487+
ctx.AddError { new IGQLError with member _.Message = "Non-critical error" }
488+
raise (System.Exception "Unexpected error")
489+
Define.Object<InnerNullableTest>(
490+
"InnerNullableException", [
491+
Define.Field("kaboom", Nullable StringType, resolve = resolveWithException)
492+
])
493+
let schema =
494+
Schema(Define.Object<NullableTest>(
495+
"Type", [
496+
Define.Field("inner", Nullable InnerNullableExceptionObjType, fun _ x -> Some x.Inner)
497+
]))
498+
let expectedData =
499+
NameValueLookup.ofList [
500+
"inner", NameValueLookup.ofList [
501+
"kaboom", null
502+
]
503+
]
504+
let expectedErrors =
505+
[
506+
GQLProblemDetails.CreateWithKind ("Unexpected error", Execution, [ box "inner"; "kaboom" ])
507+
GQLProblemDetails.CreateWithKind ("Non-critical error", Execution, [ box "inner"; "kaboom" ])
508+
]
509+
let result =
510+
let variables = { Inner = { Kaboom = null }; InnerPartialSuccess = { Kaboom = "Success" } }
511+
sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } }", variables)
512+
ensureDirect result <| fun data errors ->
513+
result.DocumentId |> notEquals Unchecked.defaultof<int>
514+
data |> equals (upcast expectedData)
515+
errors |> equals expectedErrors
516+
517+
[<Fact>]
518+
let ``Execution handles errors: additional error added when None returned from a nullable field resolver`` () =
519+
let InnerNullableNoneObjType =
520+
// executeResolvers/resolveWith, case 2
521+
let resolveWithNone (ctx : ResolveFieldContext) (_ : InnerNullableTest) : string option =
522+
ctx.AddError { new IGQLError with member _.Message = "Non-critical error" }
523+
None
524+
Define.Object<InnerNullableTest>(
525+
"InnerNullableException", [
526+
Define.Field("kaboom", Nullable StringType, resolve = resolveWithNone)
527+
])
528+
let schema =
529+
Schema(Define.Object<NullableTest>(
530+
"Type", [
531+
Define.Field("inner", Nullable InnerNullableNoneObjType, fun _ x -> Some x.Inner)
532+
]))
533+
let expectedData =
534+
NameValueLookup.ofList [
535+
"inner", NameValueLookup.ofList [
536+
"kaboom", null
537+
]
538+
]
539+
let expectedErrors =
540+
[
541+
GQLProblemDetails.CreateWithKind ("Non-critical error", Execution, [ box "inner"; "kaboom" ])
542+
]
543+
let result =
544+
let variables = { Inner = { Kaboom = null }; InnerPartialSuccess = { Kaboom = "Success" } }
545+
sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } }", variables)
546+
ensureDirect result <| fun data errors ->
547+
result.DocumentId |> notEquals Unchecked.defaultof<int>
548+
data |> equals (upcast expectedData)
549+
errors |> equals expectedErrors
550+
551+
[<Fact>]
552+
let ``Execution handles errors: additional error added when exception is rised in a non-nullable field resolver`` () =
553+
let InnerNonNullableExceptionObjType =
554+
// executeResolvers/resolveWith, case 3
555+
let resolveWithException (ctx : ResolveFieldContext) (_ : InnerNullableTest) : string =
556+
ctx.AddError { new IGQLError with member _.Message = "Non-critical error" }
557+
raise (System.Exception "Fatal error")
558+
Define.Object<InnerNullableTest>(
559+
"InnerNonNullableException", [
560+
Define.Field("kaboom", StringType, resolve = resolveWithException)
561+
])
562+
let schema =
563+
Schema(Define.Object<NullableTest>(
564+
"Type", [
565+
Define.Field("inner", InnerNonNullableExceptionObjType, fun _ x -> x.Inner)
566+
]))
567+
568+
let expectedErrors =
569+
[
570+
GQLProblemDetails.CreateWithKind ("Fatal error", Execution, [ box "inner"; "kaboom" ])
571+
GQLProblemDetails.CreateWithKind ("Non-critical error", Execution, [ box "inner"; "kaboom" ])
572+
]
573+
let result =
574+
let variables = { Inner = { Kaboom = "Explosion" }; InnerPartialSuccess = { Kaboom = "Success" } }
575+
sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } }", variables)
576+
ensureRequestError result <| fun errors ->
577+
result.DocumentId |> notEquals Unchecked.defaultof<int>
578+
errors |> equals expectedErrors
579+
580+
[<Fact>]
581+
let ``Execution handles errors: additional error added and when null returned from a non-nullable field resolver`` () =
582+
let InnerNonNullableNullObjType =
583+
// executeResolvers/resolveWith, case 4
584+
let resolveWithNull (ctx : ResolveFieldContext) (_ : InnerNullableTest) : string =
585+
ctx.AddError { new IGQLError with member _.Message = "Non-critical error" }
586+
null
587+
Define.Object<InnerNullableTest>(
588+
"InnerNonNullableNull", [
589+
Define.Field("kaboom", StringType, resolveWithNull)
590+
])
591+
let schema =
592+
Schema(Define.Object<NullableTest>(
593+
"Type", [
594+
Define.Field("inner", InnerNonNullableNullObjType, fun _ x -> x.Inner)
595+
]))
596+
let expectedErrors =
597+
[
598+
GQLProblemDetails.CreateWithKind ("Non-Null field kaboom resolved as a null!", Execution, [ box "inner"; "kaboom" ])
599+
GQLProblemDetails.CreateWithKind ("Non-critical error", Execution, [ box "inner"; "kaboom" ])
600+
]
601+
let result =
602+
let variables = { Inner = { Kaboom = "Explosion" }; InnerPartialSuccess = { Kaboom = "Success" } }
603+
sync <| Executor(schema).AsyncExecute("query Example { inner { kaboom } }", variables)
604+
ensureRequestError result <| fun errors ->
605+
result.DocumentId |> notEquals Unchecked.defaultof<int>
606+
errors |> equals expectedErrors

0 commit comments

Comments
 (0)