Skip to content

Commit 5f5eff4

Browse files
Copilotxperiandri
andauthored
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 <XperiAndri@Outlook.com>
1 parent 8fcdc11 commit 5f5eff4

16 files changed

Lines changed: 2240 additions & 171 deletions

FSharp.Data.GraphQL.Integration.slnx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
<Platform Name="x64" />
55
<Platform Name="x86" />
66
</Configurations>
7+
<Folder Name="/samples/">
8+
<Project Path="samples/star-wars-api/star-wars-api.fsproj" />
9+
</Folder>
710
<Folder Name="/Solution Items/">
811
<File Path="Directory.Build.props" />
912
<File Path="Directory.Build.targets" />
@@ -12,7 +15,9 @@
1215
<Folder Name="/src/">
1316
<Project Path="src/FSharp.Data.GraphQL.Server.AspNetCore/FSharp.Data.GraphQL.Server.AspNetCore.fsproj" />
1417
<Project Path="src/FSharp.Data.GraphQL.Server.Giraffe/FSharp.Data.GraphQL.Server.Giraffe.fsproj" />
18+
<Project Path="src/FSharp.Data.GraphQL.Server.Middleware/FSharp.Data.GraphQL.Server.Middleware.fsproj" />
1519
<Project Path="src/FSharp.Data.GraphQL.Server.Oxpecker/FSharp.Data.GraphQL.Server.Oxpecker.fsproj" />
20+
<Project Path="src/FSharp.Data.GraphQL.Server.Relay/FSharp.Data.GraphQL.Server.Relay.fsproj" />
1621
<Project Path="src/FSharp.Data.GraphQL.Server/FSharp.Data.GraphQL.Server.fsproj" />
1722
<Project Path="src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj" />
1823
</Folder>

Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
<PackageReference Update="GraphQL.Server.Ui.Voyager" Version="8.*" />
7777
<PackageReference Update="HotChocolate.AspNetCore" Version="15.*" />
7878
<PackageReference Update="Iced" Version="1.17.*" />
79+
<PackageReference Update="Microsoft.AspNetCore.Mvc.Testing" Version="$(AspNetCoreVersion)" />
7980
<PackageReference Update="Microsoft.Azure.Cosmos" Version="3.*" />
8081
<PackageReference Update="Microsoft.CodeCoverage" Version="17.3.*" />
8182
<PackageReference Update="Microsoft.Data.Sqlite" Version="$(MicrosoftExtensionsVersion)" />

build/Program.fs

Lines changed: 11 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ module Program
22

33
open System
44
open System.IO
5-
open System.Net.Http
6-
open System.Text.Json
75

86
open Fake.Core
97
open Fake.Core.TargetOperators
@@ -123,26 +121,6 @@ let runTests (project : string) (args : string) =
123121
|> _.WithCommon(DotNetCli.setVersion))
124122
project
125123

126-
let starWarsServerStream = StreamRef.Empty
127-
128-
let [<Literal>] StartStarWarsServerTarget = "StartStarWarsServer"
129-
Target.create StartStarWarsServerTarget <| fun _ ->
130-
Target.activateFinal "StopStarWarsServer"
131-
132-
let project =
133-
"samples"
134-
</> "star-wars-api"
135-
</> "star-wars-api.fsproj"
136-
137-
startGraphQLServer project 8086 starWarsServerStream
138-
139-
let [<Literal>] StopStarWarsServerTarget = "StopStarWarsServer"
140-
Target.createFinal StopStarWarsServerTarget <| fun _ ->
141-
try
142-
starWarsServerStream.Value.Write ([| 0uy |], 0, 1)
143-
with e ->
144-
printfn "%s" e.Message
145-
146124
let integrationTestServerProjectPath =
147125
"tests"
148126
</> "FSharp.Data.GraphQL.IntegrationTests.Server"
@@ -179,58 +157,35 @@ Target.createFinal StopIntegrationServerTarget <| fun _ ->
179157
with e ->
180158
printfn "%s" e.Message
181159

182-
let [<Literal>] UpdateIntrospectionFileTarget = "UpdateIntrospectionFile"
183-
Target.create UpdateIntrospectionFileTarget <| fun _ ->
184-
let client = new HttpClient ()
185-
(task {
186-
let! result = client.GetAsync ("http://localhost:8086")
187-
let! contentStream = result.Content.ReadAsStreamAsync ()
188-
let! jsonDocument = JsonDocument.ParseAsync contentStream
189-
let file =
190-
new FileStream ("tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json", FileMode.Create, FileAccess.Write, FileShare.None)
191-
let encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
192-
let jsonWriterOptions = JsonWriterOptions (Indented = true, Encoder = encoder)
193-
let writer = new Utf8JsonWriter (file, jsonWriterOptions)
194-
jsonDocument.WriteTo writer
195-
do! writer.FlushAsync ()
196-
do! writer.DisposeAsync ()
197-
do! file.DisposeAsync ()
198-
result.Dispose ()
199-
})
200-
.Wait ()
201-
client.Dispose ()
202-
203-
let unitTestsProjectPath =
204-
"tests"
205-
</> "FSharp.Data.GraphQL.Tests"
206-
</> "FSharp.Data.GraphQL.Tests.fsproj"
207-
208160
let integrationTestsProjectPath =
209161
"tests"
210162
</> "FSharp.Data.GraphQL.IntegrationTests"
211163
</> "FSharp.Data.GraphQL.IntegrationTests.fsproj"
212164

213-
let [<Literal>] BuildIntegrationTestsTarget = "BuildIntegrationTests"
214-
Target.create BuildIntegrationTestsTarget <| fun _ ->
165+
let [<Literal>] UpdateIntrospectionFileTarget = "UpdateIntrospectionFile"
166+
Target.create UpdateIntrospectionFileTarget <| fun _ ->
215167
integrationTestsProjectPath
216-
|> DotNet.build (fun options -> {
168+
|> DotNet.test (fun options -> {
217169
options with
170+
Framework = Some DotNetMoniker
218171
Configuration = configuration
172+
Common = { DotNetCli.setVersion options.Common with CustomParams = Some "--filter FullyQualifiedName~IntrospectionUpdateTests" }
219173
MSBuildParams = {
220174
options.MSBuildParams with
221175
DisableInternalBinLog = true
176+
Verbosity = Some Normal
222177
}
223-
Common = DotNetCli.setVersion options.Common
224178
})
225179

180+
let unitTestsProjectPath =
181+
"tests"
182+
</> "FSharp.Data.GraphQL.Tests"
183+
</> "FSharp.Data.GraphQL.Tests.fsproj"
184+
226185
let [<Literal>] RunUnitTestsTarget = "RunUnitTests"
227186
Target.create RunUnitTestsTarget <| fun _ ->
228187
runTests unitTestsProjectPath ""
229188

230-
let [<Literal>] RunIntegrationTestsTarget = "RunIntegrationTests"
231-
Target.create RunIntegrationTestsTarget <| fun _ ->
232-
runTests integrationTestsProjectPath "" //"--filter Execution=Sync"
233-
234189
let prepareDocGen () =
235190
Shell.rm "docs/release-notes.md"
236191
Shell.cp "RELEASE_NOTES.md" "docs/RELEASE_NOTES.md"
@@ -406,12 +361,7 @@ Target.create "PackAndPush" ignore
406361
==> RestoreTarget
407362
==> BuildTarget
408363
==> RunUnitTestsTarget
409-
==> StartStarWarsServerTarget
410-
==> BuildIntegrationTestServerTarget
411-
==> StartIntegrationServerTarget
412364
==> UpdateIntrospectionFileTarget
413-
==> BuildIntegrationTestsTarget
414-
==> RunIntegrationTestsTarget
415365
==> "All"
416366
=?> (GenerateDocsTarget, Environment.environVar "GITHUB_ACTIONS" = "True")
417367
|> ignore

samples/star-wars-fabulous-client/StarWars.slnx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
<Project Path="StarWars.Android/StarWars.Android.fsproj">
1515
<BuildType Solution="Ad-Hoc|*" Project="Release" />
1616
<BuildType Solution="AppStore|*" Project="Release" />
17+
<Build Solution="*|Any CPU" Project="false" />
18+
<Build Solution="*|ARM" Project="false" />
19+
<Build Solution="*|iPhone" Project="false" />
20+
<Build Solution="*|iPhoneSimulator" Project="false" />
21+
<Build Solution="*|x64" Project="false" />
22+
<Build Solution="*|x86" Project="false" />
1723
<Deploy Solution="Debug|Any CPU" />
1824
<Deploy Solution="Release|Any CPU" />
1925
</Project>

samples/star-wars-fabulous-client/StarWars/Common.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ open FSharp.Data.GraphQL
55

66
module Commands =
77

8-
type GraphQLApi = GraphQLProvider<"http://localhost:8086">
8+
[<Literal>]
9+
let IntrospectionPath = "../../../tests/FSharp.Data.GraphQL.IntegrationTests/introspection.json"
10+
11+
type GraphQLApi = GraphQLProvider<IntrospectionPath>
912
let GetCharactersData = GraphQLApi.Operation<"queries/FetchCharacters.graphql">()
1013

1114
type Character = GraphQLApi.Operations.FetchCharacters.Types.CharactersFields.Character

samples/star-wars-fabulous-client/StarWars/StarWars.fsproj

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
55
</PropertyGroup>
66
<ItemGroup>
7-
<PackageReference Update="FSharp.Core" Version="4.5.2" />
7+
<PackageReference Update="FSharp.Core" VersionOverride="4.5.2" />
88
<!-- workaround for VSMac bug https://github.com/mono/monodevelop/pull/5137 -->
99
</ItemGroup>
1010
<ItemGroup>
@@ -18,16 +18,16 @@
1818
<Compile Include="Style.fs" />
1919
<Compile Include="Common.fs" />
2020
<None Include="queries\FetchCharacters.graphql" />
21-
<PackageReference Include="Xamarin.Forms" Version="4.0.0.425677" />
22-
<PackageReference Include="Xamarin.Essentials" Version="1.0.0" />
23-
<PackageReference Include="Fabulous.Core" Version="0.35.0" />
24-
<PackageReference Include="Fabulous.CustomControls" Version="0.35.0" />
25-
<PackageReference Include="Fabulous.LiveUpdate" Version="0.35.0" />
26-
<PackageReference Include="FSharp.Core" Version="4.6.2" />
27-
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
28-
<PackageReference Include="FSharp.Data.GraphQL.Client" Version="1.0.1" />
21+
<PackageReference Include="Xamarin.Forms" VersionOverride="4.0.0.425677" />
22+
<PackageReference Include="Xamarin.Essentials" VersionOverride="1.0.0" />
23+
<PackageReference Include="Fabulous.Core" VersionOverride="0.35.0" />
24+
<PackageReference Include="Fabulous.CustomControls" VersionOverride="0.35.0" />
25+
<PackageReference Include="Fabulous.LiveUpdate" VersionOverride="0.35.0" />
26+
<PackageReference Include="FSharp.Core" VersionOverride="4.6.2" />
27+
<PackageReference Include="Newtonsoft.Json" VersionOverride="13.0.1" />
28+
<PackageReference Include="FSharp.Data.GraphQL.Client" VersionOverride="1.0.1" />
2929
<Compile Include="CharacterDetailPage.fs" />
3030
<Compile Include="MainPage.fs" />
3131
<Compile Include="StarWars.fs" />
3232
</ItemGroup>
33-
</Project>
33+
</Project>

tests/FSharp.Data.GraphQL.IntegrationTests/FSharp.Data.GraphQL.IntegrationTests.fsproj

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
1011
<PackageReference Include="Microsoft.Extensions.Http" />
1112
<PackageReference Include="Microsoft.NET.Test.Sdk" />
1213
<PackageReference Include="xunit" />
@@ -15,31 +16,37 @@
1516

1617
<ItemGroup>
1718
<Content Include="introspection.json" />
19+
<Content Include="integration-introspection.json" />
1820
<Content Include="reserved_scalar_input_date_introspection.json" />
1921
<Content Include="reserved_scalar_object_date_introspection.json" />
2022
<None Include="operation.graphql">
2123
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2224
</None>
2325
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
2426
<Compile Include="Helpers.fs" />
27+
<Compile Include="TestHosts.fs" />
2528
<Compile Include="OperationErrorTests.fs" />
29+
<Compile Include="IntrospectionUpdateTests.fs" />
2630
<Compile Include="ReservedScalarNameProviderTests.fs" />
2731
<Compile Include="LocalProviderTests.fs" />
2832
<Compile Include="LocalProviderWithOptionalParametersOnlyTests.fs" />
2933
<Compile Include="SwapiLocalProviderTests.fs" />
3034
<Compile Include="SwapiRemoteProviderTests.fs" />
3135
</ItemGroup>
3236

37+
<ItemGroup>
38+
<ProjectReference Include="..\FSharp.Data.GraphQL.IntegrationTests.Server\FSharp.Data.GraphQL.IntegrationTests.Server.fsproj" />
39+
<ProjectReference Include="..\..\samples\star-wars-api\star-wars-api.fsproj" />
40+
</ItemGroup>
41+
3342
<ItemGroup>
3443
<Reference Include="FSharp.Data.GraphQL.Client">
35-
<HintPath Condition="Exists('..\..\src\FSharp.Data.GraphQL.Client\bin\Debug\netstandard2.0\FSharp.Data.GraphQL.Client.dll')">..\..\src\FSharp.Data.GraphQL.Client\bin\Debug\netstandard2.0\FSharp.Data.GraphQL.Client.dll</HintPath>
36-
<HintPath Condition="Exists('..\..\src\FSharp.Data.GraphQL.Client\bin\Release\netstandard2.0\FSharp.Data.GraphQL.Client.dll')">..\..\src\FSharp.Data.GraphQL.Client\bin\Release\netstandard2.0\FSharp.Data.GraphQL.Client.dll</HintPath>
44+
<HintPath Condition="Exists('..\..\src\FSharp.Data.GraphQL.Client\bin\$(Configuration)\netstandard2.0\FSharp.Data.GraphQL.Client.dll')">..\..\src\FSharp.Data.GraphQL.Client\bin\$(Configuration)\netstandard2.0\FSharp.Data.GraphQL.Client.dll</HintPath>
3745
<HintPath Condition="Exists('..\..\bin\FSharp.Data.GraphQL.Client\netstandard2.0\FSharp.Data.GraphQL.Client.dll')">..\..\bin\FSharp.Data.GraphQL.Client\netstandard2.0\FSharp.Data.GraphQL.Client.dll</HintPath>
3846
</Reference>
3947
<Reference Include="FSharp.Data.GraphQL.Shared">
40-
<HintPath Condition="Exists('..\..\src\FSharp.Data.GraphQL.Client\bin\Debug\netstandard2.0\FSharp.Data.GraphQL.Shared.dll')">..\..\src\FSharp.Data.GraphQL.Client\bin\Debug\netstandard2.0\FSharp.Data.GraphQL.Shared.dll</HintPath>
41-
<HintPath Condition="Exists('..\..\src\FSharp.Data.GraphQL.Client\bin\Release\netstandard2.0\FSharp.Data.GraphQL.Shared.dll')">..\..\src\FSharp.Data.GraphQL.Client\bin\Release\netstandard2.0\FSharp.Data.GraphQL.Shared.dll</HintPath>
42-
<HintPath Condition="Exists('..\..\bin\FSharp.Data.GraphQL.Client\netstandard2.0\FSharp.Data.GraphQL.Shared.dll')">..\..\bin\FSharp.Data.GraphQL.Client\netstandard2.0\FSharp.Data.GraphQL.Shared.dll</HintPath>
48+
<HintPath Condition="Exists('..\..\src\FSharp.Data.GraphQL.Shared\bin\$(Configuration)\net10.0\FSharp.Data.GraphQL.Shared.dll')">..\..\src\FSharp.Data.GraphQL.Shared\bin\$(Configuration)\net10.0\FSharp.Data.GraphQL.Shared.dll</HintPath>
49+
<HintPath Condition="Exists('..\..\bin\FSharp.Data.GraphQL.Shared\net10.0\FSharp.Data.GraphQL.Shared.dll')">..\..\bin\FSharp.Data.GraphQL.Shared\net10.0\FSharp.Data.GraphQL.Shared.dll</HintPath>
4350
</Reference>
4451
</ItemGroup>
4552
</Project>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
module FSharp.Data.GraphQL.IntegrationTests.IntrospectionUpdateTests
2+
3+
open System
4+
open System.IO
5+
open System.Net.Http.Json
6+
open System.Text.Json
7+
open System.Threading
8+
open Xunit
9+
10+
let introspectionFilePath =
11+
Path.Combine (__SOURCE_DIRECTORY__, "integration-introspection.json")
12+
|> Path.GetFullPath
13+
14+
let normalizeJsonDocument options (document : JsonDocument) =
15+
use buffer = new MemoryStream ()
16+
use writer = new Utf8JsonWriter (buffer, options)
17+
document.WriteTo writer
18+
writer.Flush ()
19+
buffer.Seek (0L, SeekOrigin.Begin) |> ignore
20+
JsonDocument.Parse buffer
21+
22+
let parseAndNormalizeJsonAsync ct options stream =
23+
task {
24+
let! document = JsonDocument.ParseAsync (stream, cancellationToken = ct)
25+
return normalizeJsonDocument options document
26+
}
27+
28+
let areSchemasEqual (document1 : JsonDocument) (document2 : JsonDocument) =
29+
let schema1 = document1.RootElement.GetProperty("data").GetProperty("__schema")
30+
let schema2 = document2.RootElement.GetProperty("data").GetProperty("__schema")
31+
schema1.GetRawText() = schema2.GetRawText()
32+
33+
let readDestinationDocumentAsync ct (stream : FileStream) =
34+
task {
35+
try
36+
let! document = JsonDocument.ParseAsync (stream, cancellationToken = ct)
37+
return ValueSome document
38+
with :? JsonException ->
39+
return ValueNone
40+
}
41+
42+
let updateIntrospectionFileAsync ct sourceStream =
43+
task {
44+
use destinationStream =
45+
new FileStream (introspectionFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read)
46+
47+
let options = JsonWriterOptions(Indented = true)
48+
let! sourceDocument = parseAndNormalizeJsonAsync ct options sourceStream
49+
destinationStream.Seek (0L, SeekOrigin.Begin) |> ignore
50+
let! destinationDocument = readDestinationDocumentAsync ct destinationStream
51+
52+
let shouldUpdate =
53+
match destinationDocument with
54+
| ValueNone -> true
55+
| ValueSome document -> not (areSchemasEqual document sourceDocument)
56+
57+
if shouldUpdate then
58+
destinationStream.Seek (0L, SeekOrigin.Begin) |> ignore
59+
destinationStream.SetLength 0
60+
use writer = new Utf8JsonWriter (destinationStream, options)
61+
sourceDocument.WriteTo writer
62+
writer.Flush ()
63+
64+
return shouldUpdate
65+
}
66+
67+
[<Fact>]
68+
let ``Get GraphQL introspection response returns schema`` () =
69+
task {
70+
use httpClient = TestHosts.createIntegrationHttpClient ()
71+
let! response = httpClient.GetFromJsonAsync<JsonElement>("/", CancellationToken.None)
72+
let schema = response.GetProperty("data").GetProperty("__schema")
73+
Assert.NotEqual(Unchecked.defaultof<JsonElement>, schema)
74+
let hasErrors, _ = response.TryGetProperty "errors"
75+
Assert.False hasErrors
76+
}
77+
78+
[<Fact>]
79+
let ``Update integration introspection file when schema changes`` () =
80+
task {
81+
use httpClient = TestHosts.createIntegrationHttpClient ()
82+
let! sourceStream = httpClient.GetStreamAsync("/")
83+
let! wasUpdated = updateIntrospectionFileAsync CancellationToken.None sourceStream
84+
Assert.True(File.Exists introspectionFilePath)
85+
if wasUpdated then
86+
let! sourceStreamSecondRun = httpClient.GetStreamAsync("/")
87+
use sourceStreamForVerification = sourceStreamSecondRun
88+
let! wasUpdatedSecondRun = updateIntrospectionFileAsync CancellationToken.None sourceStreamForVerification
89+
Assert.False wasUpdatedSecondRun
90+
}

0 commit comments

Comments
 (0)