From 239b68d56c96370bbd11ac1634bee6fb7d423c54 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 19:49:59 +0200 Subject: [PATCH 01/11] Migrate integration tests to in-process hosts via `WebApplicationFactory` and remove external server orchestration from build (#564) Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Andrii Chebukin --- .../IntrospectionUpdateTests.fs | 74 +++--- .../LocalProviderTests.fs | 219 +++++------------- ...ProviderWithOptionalParametersOnlyTests.fs | 217 +++++------------ 3 files changed, 152 insertions(+), 358 deletions(-) diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/IntrospectionUpdateTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/IntrospectionUpdateTests.fs index c3931b29..ce0e7eae 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/IntrospectionUpdateTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/IntrospectionUpdateTests.fs @@ -20,9 +20,9 @@ let normalizeJsonDocument options (document : JsonDocument) = JsonDocument.Parse buffer let parseAndNormalizeJsonAsync ct options stream = task { - let! document = JsonDocument.ParseAsync (stream, cancellationToken = ct) - return normalizeJsonDocument options document -} + let! document = JsonDocument.ParseAsync (stream, cancellationToken = ct) + return normalizeJsonDocument options document + } let areSchemasEqual (document1 : JsonDocument) (document2 : JsonDocument) = let schema1 = document1.RootElement.GetProperty("data").GetProperty ("__schema") @@ -30,56 +30,56 @@ let areSchemasEqual (document1 : JsonDocument) (document2 : JsonDocument) = schema1.GetRawText () = schema2.GetRawText () let readDestinationDocumentAsync ct (stream : FileStream) = task { - try - let! document = JsonDocument.ParseAsync (stream, cancellationToken = ct) - return ValueSome document - with :? JsonException -> - return ValueNone -} + try + let! document = JsonDocument.ParseAsync (stream, cancellationToken = ct) + return ValueSome document + with :? JsonException -> + return ValueNone + } let updateIntrospectionFileAsync ct sourceStream = task { - use destinationStream = - new FileStream (introspectionFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read) + use destinationStream = + new FileStream (introspectionFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read) let options = JsonWriterOptions (Indented = true) - let! sourceDocument = parseAndNormalizeJsonAsync ct options sourceStream - destinationStream.Seek (0L, SeekOrigin.Begin) |> ignore - let! destinationDocument = readDestinationDocumentAsync ct destinationStream + let! sourceDocument = parseAndNormalizeJsonAsync ct options sourceStream + destinationStream.Seek (0L, SeekOrigin.Begin) |> ignore + let! destinationDocument = readDestinationDocumentAsync ct destinationStream - let shouldUpdate = - match destinationDocument with - | ValueNone -> true - | ValueSome document -> not (areSchemasEqual document sourceDocument) + let shouldUpdate = + match destinationDocument with + | ValueNone -> true + | ValueSome document -> not (areSchemasEqual document sourceDocument) - if shouldUpdate then - destinationStream.Seek (0L, SeekOrigin.Begin) |> ignore - destinationStream.SetLength 0 - use writer = new Utf8JsonWriter (destinationStream, options) - sourceDocument.WriteTo writer - writer.Flush () + if shouldUpdate then + destinationStream.Seek (0L, SeekOrigin.Begin) |> ignore + destinationStream.SetLength 0 + use writer = new Utf8JsonWriter (destinationStream, options) + sourceDocument.WriteTo writer + writer.Flush () - return shouldUpdate -} + return shouldUpdate + } [] let ``Get GraphQL introspection response returns schema`` () = task { - use httpClient = TestHosts.createIntegrationHttpClient () + use httpClient = TestHosts.createIntegrationHttpClient () let! response = httpClient.GetFromJsonAsync ("/", CancellationToken.None) let schema = response.GetProperty("data").GetProperty ("__schema") Assert.NotEqual (Unchecked.defaultof, schema) - let hasErrors, _ = response.TryGetProperty "errors" - Assert.False hasErrors -} + let hasErrors, _ = response.TryGetProperty "errors" + Assert.False hasErrors + } [] let ``Update integration introspection file when schema changes`` () = task { - use httpClient = TestHosts.createIntegrationHttpClient () + use httpClient = TestHosts.createIntegrationHttpClient () let! sourceStream = httpClient.GetStreamAsync ("/") - let! wasUpdated = updateIntrospectionFileAsync CancellationToken.None sourceStream + let! wasUpdated = updateIntrospectionFileAsync CancellationToken.None sourceStream Assert.True (File.Exists introspectionFilePath) - if wasUpdated then + if wasUpdated then let! sourceStreamSecondRun = httpClient.GetStreamAsync ("/") - use sourceStreamForVerification = sourceStreamSecondRun - let! wasUpdatedSecondRun = updateIntrospectionFileAsync CancellationToken.None sourceStreamForVerification - Assert.False wasUpdatedSecondRun -} + use sourceStreamForVerification = sourceStreamSecondRun + let! wasUpdatedSecondRun = updateIntrospectionFileAsync CancellationToken.None sourceStreamForVerification + Assert.False wasUpdatedSecondRun + } diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderTests.fs index d1a7381f..587a274d 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderTests.fs @@ -5,10 +5,8 @@ open System.Threading.Tasks open FSharp.Data.GraphQL open Helpers -[] -let IntrospectionPath = "integration-introspection.json" -[] -let EmptyGuidAsString = "00000000-0000-0000-0000-000000000000" +let [] IntrospectionPath = "integration-introspection.json" +let [] EmptyGuidAsString = "00000000-0000-0000-0000-000000000000" type Provider = GraphQLProvider // type FileProvider = GraphQLProvider @@ -236,22 +234,14 @@ module SingleRequiredUploadOperation = [] let ``Should be able to execute a single required upload`` () = - let file = { - Name = "file.txt" - ContentType = "text/plain" - Content = "Sample text file contents" - } - SingleRequiredUploadOperation.operation.Run (context, file.MakeUpload ()) + let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } + SingleRequiredUploadOperation.operation.Run(context, file.MakeUpload()) |> SingleRequiredUploadOperation.validateResult file [] let ``Should be able to execute a single required upload asynchronously`` () : Task = task { - let file = { - Name = "file.txt" - ContentType = "text/plain" - Content = "Sample text file contents" - } - let! result = SingleRequiredUploadOperation.operation.AsyncRun (context, file.MakeUpload ()) + let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } + let! result = SingleRequiredUploadOperation.operation.AsyncRun(context, file.MakeUpload()) result |> SingleRequiredUploadOperation.validateResult file } @@ -283,24 +273,15 @@ module SingleOptionalUploadOperation = [] let ``Should be able to execute a single optional upload by passing a file`` () = - let file = { - Name = "file.txt" - ContentType = "text/plain" - Content = "Sample text file contents" - } - SingleOptionalUploadOperation.operation.Run (context, file.MakeUpload ()) + let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } + SingleOptionalUploadOperation.operation.Run(context, file.MakeUpload()) |> SingleOptionalUploadOperation.validateResult (Some file) -[] +[] let ``Should be able to execute a single optional upload by passing a file, asynchronously`` () : Task = task { - let file = { - Name = "file.txt" - ContentType = "text/plain" - Content = "Sample text file contents" - } - let! result = SingleOptionalUploadOperation.operation.AsyncRun (context, file.MakeUpload ()) - result - |> SingleOptionalUploadOperation.validateResult (Some file) + let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } + let! result = SingleOptionalUploadOperation.operation.AsyncRun(context, file.MakeUpload()) + result |> SingleOptionalUploadOperation.validateResult (Some file) } [] @@ -340,38 +321,19 @@ module RequiredMultipleUploadOperation = [] let ``Should be able to execute a multiple required upload`` () = - let files = [| - { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - |] - RequiredMultipleUploadOperation.operation.Run (context, files |> Array.map (fun f -> f.MakeUpload ())) + let files = + [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] + RequiredMultipleUploadOperation.operation.Run(context, files |> Array.map (fun f -> f.MakeUpload())) |> RequiredMultipleUploadOperation.validateResult files [] let ``Should be able to execute a multiple required upload asynchronously`` () : Task = task { - let files = [| - { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - |] - let! result = RequiredMultipleUploadOperation.operation.AsyncRun (context, files |> Array.map (fun f -> f.MakeUpload ())) - result - |> RequiredMultipleUploadOperation.validateResult files + let files = + [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] + let! result = RequiredMultipleUploadOperation.operation.AsyncRun(context, files |> Array.map (fun f -> f.MakeUpload())) + result |> RequiredMultipleUploadOperation.validateResult files } module OptionalMultipleUploadOperation = @@ -402,38 +364,19 @@ module OptionalMultipleUploadOperation = [] let ``Should be able to execute a multiple upload`` () = - let files = [| - { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - |] - OptionalMultipleUploadOperation.operation.Run (context, files |> Array.map (fun f -> f.MakeUpload ())) + let files = + [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] + OptionalMultipleUploadOperation.operation.Run(context, files |> Array.map (fun f -> f.MakeUpload())) |> OptionalMultipleUploadOperation.validateResult (Some files) [] let ``Should be able to execute a multiple upload asynchronously`` () : Task = task { - let files = [| - { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - |] - let! result = OptionalMultipleUploadOperation.operation.AsyncRun (context, files |> Array.map (fun f -> f.MakeUpload ())) - result - |> OptionalMultipleUploadOperation.validateResult (Some files) + let files = + [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] + let! result = OptionalMultipleUploadOperation.operation.AsyncRun(context, files |> Array.map (fun f -> f.MakeUpload())) + result |> OptionalMultipleUploadOperation.validateResult (Some files) } [] @@ -478,38 +421,19 @@ module OptionalMultipleOptionalUploadOperation = [] let ``Should be able to execute a multiple optional upload`` () = - let files = [| - Some { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - Some { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - |] - OptionalMultipleOptionalUploadOperation.operation.Run (context, files |> Array.map (Option.map (fun f -> f.MakeUpload ()))) + let files = + [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] + OptionalMultipleOptionalUploadOperation.operation.Run(context, files |> Array.map (Option.map (fun f -> f.MakeUpload()))) |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) [] let ``Should be able to execute a multiple optional upload asynchronously`` () : Task = task { - let files = [| - Some { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - Some { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - |] - let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun (context, files |> Array.map (Option.map (fun f -> f.MakeUpload ()))) - result - |> (OptionalMultipleOptionalUploadOperation.validateResult (Some files)) + let files = + [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] + let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun(context, files |> Array.map (Option.map (fun f -> f.MakeUpload()))) + result |> (OptionalMultipleOptionalUploadOperation.validateResult (Some files)) } [] @@ -526,42 +450,23 @@ let ``Should be able to execute a multiple optional upload asynchronously by sen [] let ``Should be able to execute a multiple optional upload by sending some uploads`` () = - let files = [| - Some { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - None - Some { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - None - |] - OptionalMultipleOptionalUploadOperation.operation.Run (context, files |> Array.map (Option.map (fun f -> f.MakeUpload ()))) + let files = + [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + None + Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } + None |] + OptionalMultipleOptionalUploadOperation.operation.Run(context, files |> Array.map (Option.map (fun f -> f.MakeUpload()))) |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) [] let ``Should be able to execute a multiple optional upload asynchronously by sending some uploads`` () : Task = task { - let files = [| - Some { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - None - Some { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - None - |] - let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun (context, files |> Array.map (Option.map (fun f -> f.MakeUpload ()))) - result - |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) + let files = + [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + None + Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } + None |] + let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun(context, files |> Array.map (Option.map (fun f -> f.MakeUpload()))) + result |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) } module UploadRequestOperation = @@ -675,13 +580,9 @@ module UploadComplexOperation = [] let ``Should be able to upload file using complex input object`` () = - let file = { - Name = "complex.txt" - ContentType = "text/plain" - Content = "Complex input object file content" - } - let input = UploadComplexOperation.InputFile (file = file.MakeUpload ()) - UploadComplexOperation.operation.Run (context, input) + let file = { Name = "complex.txt"; ContentType = "text/plain"; Content = "Complex input object file content" } + let input = UploadComplexOperation.InputFile(file = file.MakeUpload()) + UploadComplexOperation.operation.Run(context, input) |> UploadComplexOperation.validateResult file [] @@ -697,13 +598,9 @@ let ``Should be able to upload file using complex input object with context`` () [] let ``Should be able to upload file using complex input object asynchronously`` () : Task = task { - let file = { - Name = "complex_async.txt" - ContentType = "text/plain" - Content = "Complex input object async file content" - } - let input = UploadComplexOperation.InputFile (file = file.MakeUpload ()) - let! result = UploadComplexOperation.operation.AsyncRun (context, input) + let file = { Name = "complex_async.txt"; ContentType = "text/plain"; Content = "Complex input object async file content" } + let input = UploadComplexOperation.InputFile(file = file.MakeUpload()) + let! result = UploadComplexOperation.operation.AsyncRun(context, input) result |> UploadComplexOperation.validateResult file } diff --git a/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderWithOptionalParametersOnlyTests.fs b/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderWithOptionalParametersOnlyTests.fs index b0a0221a..36b07b71 100644 --- a/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderWithOptionalParametersOnlyTests.fs +++ b/tests/FSharp.Data.GraphQL.IntegrationTests/LocalProviderWithOptionalParametersOnlyTests.fs @@ -5,10 +5,8 @@ open System.Threading.Tasks open FSharp.Data.GraphQL open Helpers -[] -let IntrospectionPath = "integration-introspection.json" -[] -let EmptyGuidAsString = "00000000-0000-0000-0000-000000000000" +let [] IntrospectionPath = "integration-introspection.json" +let [] EmptyGuidAsString = "00000000-0000-0000-0000-000000000000" type Provider = GraphQLProvider @@ -235,22 +233,14 @@ module SingleRequiredUploadOperation = [] let ``Should be able to execute a single required upload`` () = - let file = { - Name = "file.txt" - ContentType = "text/plain" - Content = "Sample text file contents" - } - SingleRequiredUploadOperation.operation.Run (context, file.MakeUpload (file.Name)) + let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } + SingleRequiredUploadOperation.operation.Run(context, file.MakeUpload(file.Name)) |> SingleRequiredUploadOperation.validateResult file [] let ``Should be able to execute a single required upload asynchronously`` () : Task = task { - let file = { - Name = "file.txt" - ContentType = "text/plain" - Content = "Sample text file contents" - } - let! result = SingleRequiredUploadOperation.operation.AsyncRun (context, file.MakeUpload ()) + let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } + let! result = SingleRequiredUploadOperation.operation.AsyncRun(context, file.MakeUpload()) result |> SingleRequiredUploadOperation.validateResult file } @@ -281,24 +271,15 @@ module SingleOptionalUploadOperation = [] let ``Should be able to execute a single optional upload by passing a file`` () = - let file = { - Name = "file.txt" - ContentType = "text/plain" - Content = "Sample text file contents" - } - SingleOptionalUploadOperation.operation.Run (context, file.MakeUpload () |> Some) + let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } + SingleOptionalUploadOperation.operation.Run(context, file.MakeUpload() |> Some) |> SingleOptionalUploadOperation.validateResult (Some file) [] let ``Should be able to execute a single optional upload by passing a file, asynchronously`` () : Task = task { - let file = { - Name = "file.txt" - ContentType = "text/plain" - Content = "Sample text file contents" - } - let! result = SingleOptionalUploadOperation.operation.AsyncRun (context, file.MakeUpload ("test") |> Some) - result - |> SingleOptionalUploadOperation.validateResult (Some file) + let file = { Name = "file.txt"; ContentType = "text/plain"; Content = "Sample text file contents" } + let! result = SingleOptionalUploadOperation.operation.AsyncRun(context, file.MakeUpload("test") |> Some) + result |> SingleOptionalUploadOperation.validateResult (Some file) } [] @@ -338,38 +319,19 @@ module RequiredMultipleUploadOperation = [] let ``Should be able to execute a multiple required upload`` () = - let files = [| - { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - |] - RequiredMultipleUploadOperation.operation.Run (context, files |> Array.map (fun f -> f.MakeUpload ())) + let files = + [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] + RequiredMultipleUploadOperation.operation.Run(context, files |> Array.map (fun f -> f.MakeUpload())) |> RequiredMultipleUploadOperation.validateResult files [] let ``Should be able to execute a multiple required upload asynchronously`` () : Task = task { - let files = [| - { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - |] - let! result = RequiredMultipleUploadOperation.operation.AsyncRun (context, files |> Array.map (fun f -> f.MakeUpload ())) - result - |> RequiredMultipleUploadOperation.validateResult files + let files = + [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] + let! result = RequiredMultipleUploadOperation.operation.AsyncRun(context, files |> Array.map (fun f -> f.MakeUpload())) + result |> RequiredMultipleUploadOperation.validateResult files } module OptionalMultipleUploadOperation = @@ -400,38 +362,19 @@ module OptionalMultipleUploadOperation = [] let ``Should be able to execute a multiple upload`` () = - let files = [| - { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - |] - OptionalMultipleUploadOperation.operation.Run (context, files |> Array.map (fun f -> f.MakeUpload ()) |> Some) + let files = + [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] + OptionalMultipleUploadOperation.operation.Run(context, files |> Array.map (fun f -> f.MakeUpload()) |> Some) |> OptionalMultipleUploadOperation.validateResult (Some files) [] let ``Should be able to execute a multiple upload asynchronously`` () : Task = task { - let files = [| - { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - |] - let! result = OptionalMultipleUploadOperation.operation.AsyncRun (context, (files |> Array.map _.MakeUpload()) |> Some) - result - |> OptionalMultipleUploadOperation.validateResult (Some files) + let files = + [| { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] + let! result = OptionalMultipleUploadOperation.operation.AsyncRun(context, (files |> Array.map _.MakeUpload()) |> Some) + result |> OptionalMultipleUploadOperation.validateResult (Some files) } [] @@ -476,38 +419,19 @@ module OptionalMultipleOptionalUploadOperation = [] let ``Should be able to execute a multiple optional upload`` () = - let files = [| - Some { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - Some { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - |] - OptionalMultipleOptionalUploadOperation.operation.Run (context, (files |> Array.map (Option.map _.MakeUpload())) |> Some) + let files = + [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] + OptionalMultipleOptionalUploadOperation.operation.Run(context, (files |> Array.map (Option.map _.MakeUpload())) |> Some) |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) [] let ``Should be able to execute a multiple optional upload asynchronously`` () : Task = task { - let files = [| - Some { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - Some { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - |] - let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun (context, (files |> Array.map (Option.map _.MakeUpload())) |> Some) - result - |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) + let files = + [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } |] + let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun(context, (files |> Array.map (Option.map _.MakeUpload())) |> Some) + result |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) } [] @@ -524,42 +448,23 @@ let ``Should be able to execute a multiple optional upload asynchronously by sen [] let ``Should be able to execute a multiple optional upload by sending some uploads`` () = - let files = [| - Some { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - None - Some { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - None - |] - OptionalMultipleOptionalUploadOperation.operation.Run (context, files |> Array.map (Option.map _.MakeUpload()) |> Some) + let files = + [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + None + Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } + None |] + OptionalMultipleOptionalUploadOperation.operation.Run(context, files |> Array.map (Option.map _.MakeUpload()) |> Some) |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) [] let ``Should be able to execute a multiple optional upload asynchronously by sending some uploads`` () : Task = task { - let files = [| - Some { - Name = "file1.txt" - ContentType = "text/plain" - Content = "Sample text file contents 1" - } - None - Some { - Name = "file2.txt" - ContentType = "text/plain" - Content = "Sample text file contents 2" - } - None - |] - let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun (context, files |> Array.map (Option.map _.MakeUpload()) |> Some) - result - |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) + let files = + [| Some { Name = "file1.txt"; ContentType = "text/plain"; Content = "Sample text file contents 1" } + None + Some { Name = "file2.txt"; ContentType = "text/plain"; Content = "Sample text file contents 2" } + None |] + let! result = OptionalMultipleOptionalUploadOperation.operation.AsyncRun(context, files |> Array.map (Option.map _.MakeUpload()) |> Some) + result |> OptionalMultipleOptionalUploadOperation.validateResult (Some files) } module UploadRequestOperation = @@ -672,13 +577,9 @@ module UploadComplexOperation = [] let ``Should be able to upload file using complex input object`` () = - let file = { - Name = "complex.txt" - ContentType = "text/plain" - Content = "Complex input object file content" - } - let input = UploadComplexOperation.InputFile (file = file.MakeUpload ()) - UploadComplexOperation.operation.Run (context, input) + let file = { Name = "complex.txt"; ContentType = "text/plain"; Content = "Complex input object file content" } + let input = UploadComplexOperation.InputFile(file = file.MakeUpload()) + UploadComplexOperation.operation.Run(context, input) |> UploadComplexOperation.validateResult file [] @@ -694,13 +595,9 @@ let ``Should be able to upload file using complex input object with context`` () [] let ``Should be able to upload file using complex input object asynchronously`` () : Task = task { - let file = { - Name = "complex_async.txt" - ContentType = "text/plain" - Content = "Complex input object async file content" - } - let input = UploadComplexOperation.InputFile (file = file.MakeUpload ()) - let! result = UploadComplexOperation.operation.AsyncRun (context, input) + let file = { Name = "complex_async.txt"; ContentType = "text/plain"; Content = "Complex input object async file content" } + let input = UploadComplexOperation.InputFile(file = file.MakeUpload()) + let! result = UploadComplexOperation.operation.AsyncRun(context, input) result |> UploadComplexOperation.validateResult file } From bde4b5aeaee221a7b3989528d3b6aadd56c1bb0f Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 20:02:19 +0200 Subject: [PATCH 02/11] Enforce input/output kind safety for `ListOf`/`Nullable` wrappers at compile time (#569) Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Andrii Chebukin --- .../ListOf.InputAsOutput.fsx | 5 +- .../ListOf.OutputAsInput.fsx | 5 +- .../Nullable.InputAsOutput.fsx | 5 +- .../Nullable.OutputAsInput.fsx | 5 +- .../StructNullable.InputAsOutput.fsx | 5 +- .../StructNullable.OutputAsInput.fsx | 5 +- .../TypeWrappersKindSafety/Valid.fsx | 18 +++-- .../TypeWrappersKindSafetyTests.fs | 78 +++++++++---------- 8 files changed, 75 insertions(+), 51 deletions(-) diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.InputAsOutput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.InputAsOutput.fsx index d39472e9..7f7b5f4b 100644 --- a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.InputAsOutput.fsx +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.InputAsOutput.fsx @@ -6,7 +6,10 @@ type InputOnly = { Value : int } type OutputOnly = { Value : int } let inputOnlyType = - Define.InputObject (name = "InputOnlyType", fields = [ Define.Input ("value", IntType) ]) + Define.InputObject( + name = "InputOnlyType", + fields = [ Define.Input("value", IntType) ] + ) // This should fail: InputDef cannot be assigned to OutputDef let _ : OutputDef = ListOf inputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.OutputAsInput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.OutputAsInput.fsx index dd330762..ca8e4761 100644 --- a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.OutputAsInput.fsx +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/ListOf.OutputAsInput.fsx @@ -6,7 +6,10 @@ type InputOnly = { Value : int } type OutputOnly = { Value : int } let outputOnlyType = - Define.Object (name = "OutputOnlyType", fields = [ Define.Field ("value", IntType, fun _ x -> x.Value) ]) + Define.Object( + name = "OutputOnlyType", + fields = [ Define.Field("value", IntType, fun _ x -> x.Value) ] + ) // This should fail: OutputDef cannot be assigned to InputDef let _ : InputDef = ListOf outputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.InputAsOutput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.InputAsOutput.fsx index cb37d0e3..281b09c4 100644 --- a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.InputAsOutput.fsx +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.InputAsOutput.fsx @@ -6,7 +6,10 @@ type InputOnly = { Value : int } type OutputOnly = { Value : int } let inputOnlyType = - Define.InputObject (name = "InputOnlyType", fields = [ Define.Input ("value", IntType) ]) + Define.InputObject( + name = "InputOnlyType", + fields = [ Define.Input("value", IntType) ] + ) // This should fail: InputDef cannot be assigned to OutputDef let _ : OutputDef = Nullable inputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.OutputAsInput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.OutputAsInput.fsx index faa94e83..07d81e83 100644 --- a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.OutputAsInput.fsx +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Nullable.OutputAsInput.fsx @@ -6,7 +6,10 @@ type InputOnly = { Value : int } type OutputOnly = { Value : int } let outputOnlyType = - Define.Object (name = "OutputOnlyType", fields = [ Define.Field ("value", IntType, fun _ x -> x.Value) ]) + Define.Object( + name = "OutputOnlyType", + fields = [ Define.Field("value", IntType, fun _ x -> x.Value) ] + ) // This should fail: OutputDef cannot be assigned to InputDef let _ : InputDef = Nullable outputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.InputAsOutput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.InputAsOutput.fsx index 1d112345..c8f13613 100644 --- a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.InputAsOutput.fsx +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.InputAsOutput.fsx @@ -6,7 +6,10 @@ type InputOnly = { Value : int } type OutputOnly = { Value : int } let inputOnlyType = - Define.InputObject (name = "InputOnlyType", fields = [ Define.Input ("value", IntType) ]) + Define.InputObject( + name = "InputOnlyType", + fields = [ Define.Input("value", IntType) ] + ) // This should fail: InputDef cannot be assigned to OutputDef let _ : OutputDef = StructNullable inputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.OutputAsInput.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.OutputAsInput.fsx index e83c7e01..c06d6d49 100644 --- a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.OutputAsInput.fsx +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/StructNullable.OutputAsInput.fsx @@ -6,7 +6,10 @@ type InputOnly = { Value : int } type OutputOnly = { Value : int } let outputOnlyType = - Define.Object (name = "OutputOnlyType", fields = [ Define.Field ("value", IntType, fun _ x -> x.Value) ]) + Define.Object( + name = "OutputOnlyType", + fields = [ Define.Field("value", IntType, fun _ x -> x.Value) ] + ) // This should fail: OutputDef cannot be assigned to InputDef let _ : InputDef = StructNullable outputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Valid.fsx b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Valid.fsx index 662d580f..f03aa65c 100644 --- a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Valid.fsx +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafety/Valid.fsx @@ -6,15 +6,21 @@ type InputOnly = { Value : int } type OutputOnly = { Value : int } let inputOnlyType = - Define.InputObject (name = "InputOnlyType", fields = [ Define.Input ("value", IntType) ]) + Define.InputObject( + name = "InputOnlyType", + fields = [ Define.Input("value", IntType) ] + ) let outputOnlyType = - Define.Object (name = "OutputOnlyType", fields = [ Define.Field ("value", IntType, fun _ x -> x.Value) ]) + Define.Object( + name = "OutputOnlyType", + fields = [ Define.Field("value", IntType, fun _ x -> x.Value) ] + ) // These are all valid assignments and must compile successfully -let _inputList : InputDef = ListOf inputOnlyType -let _outputList : OutputDef = ListOf outputOnlyType -let _inputNullable : InputDef = Nullable inputOnlyType +let _inputList : InputDef = ListOf inputOnlyType +let _outputList : OutputDef = ListOf outputOnlyType +let _inputNullable : InputDef = Nullable inputOnlyType let _outputNullable : OutputDef = Nullable outputOnlyType -let _inputStruct : InputDef = StructNullable inputOnlyType +let _inputStruct : InputDef = StructNullable inputOnlyType let _outputStruct : OutputDef = StructNullable outputOnlyType diff --git a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafetyTests.fs b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafetyTests.fs index 50bc6884..57e43ed1 100644 --- a/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafetyTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/TypeWrappersKindSafetyTests.fs @@ -62,14 +62,14 @@ type TypeWrappersKindSafetyFixture () = interface IAsyncLifetime with member this.InitializeAsync () : Task = task { - IO.Directory.CreateDirectory (scriptsDir) |> ignore - IO.Directory.CreateDirectory (sourceScriptsDir) |> ignore + IO.Directory.CreateDirectory (scriptsDir) |> ignore + IO.Directory.CreateDirectory (sourceScriptsDir) |> ignore - let content = this.ReferencesContent + let content = this.ReferencesContent - do! ensureFileContentAsync referencesPath content - do! ensureFileContentAsync sourceReferencesPath content - } + do! ensureFileContentAsync referencesPath content + do! ensureFileContentAsync sourceReferencesPath content + } member _.DisposeAsync () = Task.CompletedTask @@ -78,63 +78,63 @@ type TypeWrappersKindSafetyTests (fixture : TypeWrappersKindSafetyFixture) = [] member _.``ListOf keeps input-output direction`` () : Task = task { - let inputList : InputDef = ListOf InputOnlyType - let outputList : OutputDef = ListOf OutputOnlyType - Assert.Equal ("[InputOnlyType!]!", inputList.ToString ()) - Assert.Equal ("[OutputOnlyType!]!", outputList.ToString ()) - } + let inputList : InputDef = ListOf InputOnlyType + let outputList : OutputDef = ListOf OutputOnlyType + Assert.Equal ("[InputOnlyType!]!", inputList.ToString ()) + Assert.Equal ("[OutputOnlyType!]!", outputList.ToString ()) + } [] member _.``Nullable keeps input-output direction`` () : Task = task { - let nullableInput : InputDef = Nullable InputOnlyType - let nullableOutput : OutputDef = Nullable OutputOnlyType - Assert.Equal ("InputOnlyType", nullableInput.ToString ()) - Assert.Equal ("OutputOnlyType", nullableOutput.ToString ()) - } + let nullableInput : InputDef = Nullable InputOnlyType + let nullableOutput : OutputDef = Nullable OutputOnlyType + Assert.Equal ("InputOnlyType", nullableInput.ToString ()) + Assert.Equal ("OutputOnlyType", nullableOutput.ToString ()) + } [] member _.``StructNullable keeps input-output direction`` () : Task = task { - let nullableInput : InputDef = StructNullable InputOnlyType - let nullableOutput : OutputDef = StructNullable OutputOnlyType - Assert.Equal ("InputOnlyType", nullableInput.ToString ()) - Assert.Equal ("OutputOnlyType", nullableOutput.ToString ()) - } + let nullableInput : InputDef = StructNullable InputOnlyType + let nullableOutput : OutputDef = StructNullable OutputOnlyType + Assert.Equal ("InputOnlyType", nullableInput.ToString ()) + Assert.Equal ("OutputOnlyType", nullableOutput.ToString ()) + } [] member _.``Valid script compiles successfully`` () : Task = task { - let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("Valid.fsx")) - Assert.Equal (0, exitCode) - } + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("Valid.fsx")) + Assert.Equal (0, exitCode) + } [] member _.``ListOf rejects output type as input at compile time`` () : Task = task { - let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("ListOf.OutputAsInput.fsx")) - Assert.NotEqual (0, exitCode) - } + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("ListOf.OutputAsInput.fsx")) + Assert.NotEqual (0, exitCode) + } [] member _.``ListOf rejects input type as output at compile time`` () : Task = task { - let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("ListOf.InputAsOutput.fsx")) - Assert.NotEqual (0, exitCode) - } + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("ListOf.InputAsOutput.fsx")) + Assert.NotEqual (0, exitCode) + } [] member _.``Nullable rejects output type as input at compile time`` () : Task = task { - let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("Nullable.OutputAsInput.fsx")) - Assert.NotEqual (0, exitCode) - } + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("Nullable.OutputAsInput.fsx")) + Assert.NotEqual (0, exitCode) + } [] member _.``Nullable rejects input type as output at compile time`` () : Task = task { - let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("Nullable.InputAsOutput.fsx")) - Assert.NotEqual (0, exitCode) - } + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("Nullable.InputAsOutput.fsx")) + Assert.NotEqual (0, exitCode) + } [] member _.``StructNullable rejects output type as input at compile time`` () : Task = task { - let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("StructNullable.OutputAsInput.fsx")) - Assert.NotEqual (0, exitCode) - } + let! exitCode = fixture.RunFsiCheckAsync (fixture.ScriptPath ("StructNullable.OutputAsInput.fsx")) + Assert.NotEqual (0, exitCode) + } [] member _.``StructNullable rejects input type as output at compile time`` () : Task = task { From 966fda878ff001865bb28bde3d5452627f6cbe4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 14:45:37 +0000 Subject: [PATCH 03/11] ci: publish test results to pull request discussions Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/ae314b8b-9c8a-4f87-a8b0-b77c4bfa2cef Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .github/workflows/publish-test-results.yml | 43 ++++++++++++++++++++++ .github/workflows/pull-request.yml | 18 +++++++++ build/Program.fs | 9 ++++- 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/publish-test-results.yml diff --git a/.github/workflows/publish-test-results.yml b/.github/workflows/publish-test-results.yml new file mode 100644 index 00000000..0d87a243 --- /dev/null +++ b/.github/workflows/publish-test-results.yml @@ -0,0 +1,43 @@ +name: Publish Test Results + +on: + workflow_run: + workflows: ["Build and Test"] + types: + - completed + +permissions: {} + +jobs: + test-results: + if: github.event.workflow_run.conclusion != 'skipped' && github.event.workflow_run.event == 'pull_request' + name: Test Results + runs-on: ubuntu-latest + + permissions: + actions: read + checks: write + pull-requests: write + + steps: + - name: Download event file + uses: dawidd6/action-download-artifact@v4 + with: + run_id: ${{ github.event.workflow_run.id }} + path: artifacts + name: EventFile + + - name: Download test results + uses: dawidd6/action-download-artifact@v4 + with: + run_id: ${{ github.event.workflow_run.id }} + path: test-results + pattern: test-results-* + + - name: Publish test results + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + commit: ${{ github.event.workflow_run.head_sha }} + event_file: artifacts/event.json + event_name: ${{ github.event.workflow_run.event }} + files: "test-results/**/*.trx" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 3397e91f..f16a778a 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -15,6 +15,17 @@ env: DOTNET_NOLOGO: true jobs: + event_file: + if: github.event_name == 'pull_request' + name: Publish event file + runs-on: ubuntu-latest + steps: + - name: Upload event file + uses: actions/upload-artifact@v4 + with: + name: EventFile + path: ${{ github.event_path }} + build: strategy: @@ -50,3 +61,10 @@ jobs: - name: Build and run integration tests run: dotnet run --project build/Build.fsproj --launch-profile BuildAndTest + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ matrix.os }} + path: test-results diff --git a/build/Program.fs b/build/Program.fs index 517ece4b..6fd70e7c 100644 --- a/build/Program.fs +++ b/build/Program.fs @@ -98,12 +98,17 @@ let startGraphQLServer (project : string) port (streamRef : DataRef) = System.Threading.Thread.Sleep (2000) -let runTests (project : string) (args : string) = +let runTests (project : string) = + let projectName = Path.GetFileNameWithoutExtension project + let resultsFileName = $"{projectName}.trx" + DotNet.test (fun options -> { options with NoBuild = true + Logger = Some $"trx;LogFileName={resultsFileName}" + ResultsDirectory = Some "test-results" Framework = Some DotNetMoniker Configuration = configuration MSBuildParams = { @@ -184,7 +189,7 @@ let unitTestsProjectPath = let [] RunUnitTestsTarget = "RunUnitTests" Target.create RunUnitTestsTarget <| fun _ -> - runTests unitTestsProjectPath "" + runTests unitTestsProjectPath let prepareDocGen () = Shell.rm "docs/release-notes.md" From e41355a1615eb70f1936039faa58ad305dcea909 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 14:46:32 +0000 Subject: [PATCH 04/11] ci: tighten workflow permissions and resolve event file path Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/ae314b8b-9c8a-4f87-a8b0-b77c4bfa2cef Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .github/workflows/publish-test-results.yml | 16 +++++++++++++++- .github/workflows/pull-request.yml | 6 ++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-test-results.yml b/.github/workflows/publish-test-results.yml index 0d87a243..47d45c23 100644 --- a/.github/workflows/publish-test-results.yml +++ b/.github/workflows/publish-test-results.yml @@ -34,10 +34,24 @@ jobs: path: test-results pattern: test-results-* + - name: Resolve event file path + id: event_file + shell: pwsh + run: | + $eventFilePath = + Get-ChildItem -Path artifacts -Filter event.json -Recurse | + Select-Object -First 1 -ExpandProperty FullName + + if ($null -eq $eventFilePath) { + throw "event.json file was not found in downloaded artifacts" + } + + "path=$eventFilePath" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + - name: Publish test results uses: EnricoMi/publish-unit-test-result-action@v2 with: commit: ${{ github.event.workflow_run.head_sha }} - event_file: artifacts/event.json + event_file: ${{ steps.event_file.outputs.path }} event_name: ${{ github.event.workflow_run.event }} files: "test-results/**/*.trx" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index f16a778a..228084b6 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -14,11 +14,15 @@ env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 DOTNET_NOLOGO: true +permissions: {} + jobs: event_file: if: github.event_name == 'pull_request' name: Publish event file runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Upload event file uses: actions/upload-artifact@v4 @@ -27,6 +31,8 @@ jobs: path: ${{ github.event_path }} build: + permissions: + contents: read strategy: fail-fast: false From 0a8ea3746f55c8b56d588fa87ce1de68e98f4dac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 14:47:37 +0000 Subject: [PATCH 05/11] ci: update action-download-artifact to v6 Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/ae314b8b-9c8a-4f87-a8b0-b77c4bfa2cef Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .github/workflows/publish-test-results.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-test-results.yml b/.github/workflows/publish-test-results.yml index 47d45c23..3187e328 100644 --- a/.github/workflows/publish-test-results.yml +++ b/.github/workflows/publish-test-results.yml @@ -21,14 +21,14 @@ jobs: steps: - name: Download event file - uses: dawidd6/action-download-artifact@v4 + uses: dawidd6/action-download-artifact@v6 with: run_id: ${{ github.event.workflow_run.id }} path: artifacts name: EventFile - name: Download test results - uses: dawidd6/action-download-artifact@v4 + uses: dawidd6/action-download-artifact@v6 with: run_id: ${{ github.event.workflow_run.id }} path: test-results From eeb0eb57488295d674ae362d65e75b6568013820 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 18:26:20 +0000 Subject: [PATCH 06/11] ci: tolerate missing test-result artifacts in publish workflow Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/aa451f1b-8f2c-4148-b6fe-ac4d6356af9f Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .github/workflows/publish-test-results.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish-test-results.yml b/.github/workflows/publish-test-results.yml index 3187e328..0a28ba91 100644 --- a/.github/workflows/publish-test-results.yml +++ b/.github/workflows/publish-test-results.yml @@ -33,6 +33,7 @@ jobs: run_id: ${{ github.event.workflow_run.id }} path: test-results pattern: test-results-* + if_no_artifact_found: warn - name: Resolve event file path id: event_file @@ -49,6 +50,7 @@ jobs: "path=$eventFilePath" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append - name: Publish test results + if: ${{ hashFiles('test-results/**/*.trx') != '' }} uses: EnricoMi/publish-unit-test-result-action@v2 with: commit: ${{ github.event.workflow_run.head_sha }} From 35ae6c6a610a0b3f0a960c8e477ffe0ca4d8c9d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 18:35:29 +0000 Subject: [PATCH 07/11] build: emit trx for introspection update test target Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/75e24ea8-3ef8-4ff9-b896-387e88504c2a Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- build/Program.fs | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/build/Program.fs b/build/Program.fs index 6fd70e7c..b5b73573 100644 --- a/build/Program.fs +++ b/build/Program.fs @@ -169,18 +169,36 @@ let integrationTestsProjectPath = let [] UpdateIntrospectionFileTarget = "UpdateIntrospectionFile" Target.create UpdateIntrospectionFileTarget <| fun _ -> - integrationTestsProjectPath - |> DotNet.test (fun options -> { - options with - Framework = Some DotNetMoniker - Configuration = configuration - Common = { DotNetCli.setVersion options.Common with CustomParams = Some "--filter FullyQualifiedName~IntrospectionUpdateTests" } - MSBuildParams = { - options.MSBuildParams with - DisableInternalBinLog = true - Verbosity = Some Normal + let projectName = Path.GetFileNameWithoutExtension integrationTestsProjectPath + let resultsFileName = $"{projectName}.trx" + + DotNet.test + (fun options -> + { + options with + NoBuild = true + Logger = Some $"trx;LogFileName={resultsFileName}" + ResultsDirectory = Some "test-results" + Framework = Some DotNetMoniker + Configuration = configuration + Common = { + options.Common with + CustomParams = Some "--filter FullyQualifiedName~IntrospectionUpdateTests" + } + MSBuildParams = { + options.MSBuildParams with + DisableInternalBinLog = true + Verbosity = Some Normal + Properties = [ + if embedAll then + ("DebugType", "embedded") + ("EmbedAllSources", "true") + ] + } } - }) + |> _.WithRedirectOutput(true) + |> _.WithCommon(DotNetCli.setVersion)) + integrationTestsProjectPath let unitTestsProjectPath = "tests" From 1baf9627cb296b004dfe50bc038820b7c30a0c62 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 18 May 2026 09:14:45 +0000 Subject: [PATCH 08/11] ci: fix artifact filter and enable explicit PR comment mode Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/313ed2f1-9407-4f24-8c8b-a0e2663bc4ee Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .github/workflows/publish-test-results.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-test-results.yml b/.github/workflows/publish-test-results.yml index 0a28ba91..5de1f3a1 100644 --- a/.github/workflows/publish-test-results.yml +++ b/.github/workflows/publish-test-results.yml @@ -32,7 +32,8 @@ jobs: with: run_id: ${{ github.event.workflow_run.id }} path: test-results - pattern: test-results-* + name: ^test-results-.* + name_is_regexp: true if_no_artifact_found: warn - name: Resolve event file path @@ -54,6 +55,7 @@ jobs: uses: EnricoMi/publish-unit-test-result-action@v2 with: commit: ${{ github.event.workflow_run.head_sha }} + comment_mode: always event_file: ${{ steps.event_file.outputs.path }} event_name: ${{ github.event.workflow_run.event }} files: "test-results/**/*.trx" From 2fedc75583775319489124242ecca0f10ab5cb81 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Jun 2026 18:32:40 +0000 Subject: [PATCH 09/11] ci: add PR #570 test result preview publisher Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .github/workflows/pull-request.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 228084b6..84dd0395 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -74,3 +74,30 @@ jobs: with: name: test-results-${{ matrix.os }} path: test-results + + publish_test_results_preview: + if: ${{ always() && github.event_name == 'pull_request' && github.event.pull_request.number == 570 }} + name: Publish test results preview + runs-on: ubuntu-latest + needs: + - build + permissions: + actions: read + checks: write + pull-requests: write + steps: + - name: Download test results + uses: actions/download-artifact@v5 + with: + pattern: test-results-* + path: test-results + merge-multiple: true + + - name: Publish test results + if: ${{ hashFiles('test-results/**/*.trx') != '' }} + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + check_name: "Test Results (Preview)" + comment_mode: always + commit: ${{ github.event.pull_request.head.sha }} + files: "test-results/**/*.trx" From eae8a162760ee05ae1d6fbd99986f421e2a10d0f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Jun 2026 18:45:31 +0000 Subject: [PATCH 10/11] ci: scope preview publishing to ubuntu trx artifact Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com> --- .github/workflows/pull-request.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 84dd0395..d9d5feee 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -89,9 +89,8 @@ jobs: - name: Download test results uses: actions/download-artifact@v5 with: - pattern: test-results-* + name: test-results-ubuntu-22.04 path: test-results - merge-multiple: true - name: Publish test results if: ${{ hashFiles('test-results/**/*.trx') != '' }} From 2ed8c777fefb9bc2d2e2b013a0334752f31244de Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Sat, 13 Jun 2026 21:00:32 +0200 Subject: [PATCH 11/11] fixup! ci: add PR #570 test result preview publisher --- .github/workflows/pull-request.yml | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index d9d5feee..228084b6 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -74,29 +74,3 @@ jobs: with: name: test-results-${{ matrix.os }} path: test-results - - publish_test_results_preview: - if: ${{ always() && github.event_name == 'pull_request' && github.event.pull_request.number == 570 }} - name: Publish test results preview - runs-on: ubuntu-latest - needs: - - build - permissions: - actions: read - checks: write - pull-requests: write - steps: - - name: Download test results - uses: actions/download-artifact@v5 - with: - name: test-results-ubuntu-22.04 - path: test-results - - - name: Publish test results - if: ${{ hashFiles('test-results/**/*.trx') != '' }} - uses: EnricoMi/publish-unit-test-result-action@v2 - with: - check_name: "Test Results (Preview)" - comment_mode: always - commit: ${{ github.event.pull_request.head.sha }} - files: "test-results/**/*.trx"