diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..0197b793 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,40 @@ +name: Build artifacts + +# ==== NOTE: do not rename this yml file or the run_number will be reset ==== + +on: + push: + branches: + - master + - develop + +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Install dependencies + run: dotnet restore + - name: Build solution [Release] + run: dotnet build --no-restore -c Release -p:VersionSuffix=$GITHUB_RUN_NUMBER + - name: Pack solution [Release] + run: dotnet pack --no-restore --no-build -c Release -p:VersionSuffix=$GITHUB_RUN_NUMBER -o out + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: Nuget packages + path: | + out/* + - name: Publish Nuget packages to GitHub registry + run: dotnet nuget push "out/*" -k ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml deleted file mode 100644 index d060ebee..00000000 --- a/.github/workflows/dotnetcore.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: .NET Core - -on: [pull_request] - -jobs: - build: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v1 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 6.0.x - - - name: Build solution - run: | - git submodule update --init --recursive - dotnet restore Conventions.sln - dotnet build -c Release src/GraphQL.Conventions/GraphQL.Conventions.csproj - - - name: Run unit tests - run: | - dotnet test test/GraphQL.Conventions.Tests/GraphQL.Conventions.Tests.csproj - - - name: Generate NuGet package - run: | - dotnet pack -c Release diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml new file mode 100644 index 00000000..6462f8a1 --- /dev/null +++ b/.github/workflows/formatting.yml @@ -0,0 +1,29 @@ +name: Check formatting + +on: + pull_request: + branches: + - master + - develop + +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + +jobs: + format: + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v3 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Install dependencies + run: dotnet restore + - name: Check formatting + run: dotnet format --no-restore --verify-no-changes --severity warn || (echo "Run 'dotnet format' to fix issues" && exit 1) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..fe32ce94 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,68 @@ +name: Publish release + +on: + release: + types: + - published + +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check github.ref starts with 'refs/tags/' + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + run: | + echo Error! github.ref does not start with 'refs/tags' + echo github.ref: ${{ github.ref }} + exit 1 + - name: Set version number environment variable + env: + github_ref: ${{ github.ref }} + run: | + version="${github_ref:10}" + echo version=$version + echo "version=$version" >> $GITHUB_ENV + - name: Setup .NET SDK + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + source-url: https://api.nuget.org/v3/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.CONV_NUGET_AUTH_TOKEN}} + - name: Install dependencies + run: dotnet restore + - name: Build solution [Release] + run: dotnet build --no-restore -c Release -p:Version=$version + - name: Pack solution [Release] + run: dotnet pack --no-restore --no-build -c Release -p:Version=$version -o out + - name: Upload Nuget packages as workflow artifacts + uses: actions/upload-artifact@v3 + with: + name: Nuget packages + path: | + out/* + - name: Publish Nuget packages to Nuget registry + run: dotnet nuget push "out/*" -k ${{secrets.CONV_NUGET_AUTH_TOKEN}} + - name: Upload Nuget packages as release artifacts + uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + console.log('environment', process.versions); + const fs = require('fs').promises; + const { repo: { owner, repo }, sha } = context; + for (let file of await fs.readdir('out')) { + console.log('uploading', file); + await github.rest.repos.uploadReleaseAsset({ + owner, + repo, + release_id: ${{ github.event.release.id }}, + name: file, + data: await fs.readFile(`out/${file}`) + }); + } diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..c1667ad1 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: Run code tests + +on: [pull_request] + +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v3 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Install dependencies + run: dotnet restore + - name: Build solution [Release] + run: dotnet build --no-restore -c Release + - name: Build solution [Debug] + run: dotnet build --no-restore -c Debug + - name: Test solution [Debug] + run: dotnet test --no-restore --no-build diff --git a/.github/workflows/wipcheck.yml b/.github/workflows/wipcheck.yml new file mode 100644 index 00000000..a61e9abe --- /dev/null +++ b/.github/workflows/wipcheck.yml @@ -0,0 +1,22 @@ +name: Check if PR title contains [WIP] + +on: + pull_request: + types: + - opened # when PR is opened + - edited # when PR is edited + - synchronize # when code is added + - reopened # when a closed PR is reopened + +jobs: + check-title: + runs-on: ubuntu-latest + + steps: + - name: Fail build if pull request title contains [WIP] + env: + TITLE: ${{ github.event.pull_request.title }} + if: ${{ contains(github.event.pull_request.title, '[WIP]') }} # This function is case insensitive. + run: | + echo Warning! PR title "$TITLE" contains [WIP]. Remove [WIP] from the title when PR is ready. + exit 1 diff --git a/Conventions.sln b/Conventions.sln index e12801e0..8010d8db 100644 --- a/Conventions.sln +++ b/Conventions.sln @@ -1,10 +1,44 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.0.0 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33103.184 MinimumVisualStudioVersion = 15.0.0.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphQL.Conventions", "src\GraphQL.Conventions\GraphQL.Conventions.csproj", "{AEAC9A4B-7D5B-48C6-86A9-E665F6DE4B6C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Conventions", "src\GraphQL.Conventions\GraphQL.Conventions.csproj", "{AEAC9A4B-7D5B-48C6-86A9-E665F6DE4B6C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphQL.Conventions.Tests", "test\GraphQL.Conventions.Tests\GraphQL.Conventions.Tests.csproj", "{5CF44C73-E5C9-4E53-A69D-4013E8AE85E5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Conventions.Tests", "test\GraphQL.Conventions.Tests\GraphQL.Conventions.Tests.csproj", "{5CF44C73-E5C9-4E53-A69D-4013E8AE85E5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleWebApp", "samples\SimpleWebApp\SimpleWebApp.csproj", "{19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataLoaderWithEFCore", "samples\DataLoaderWithEFCore\DataLoaderWithEFCore\DataLoaderWithEFCore.csproj", "{B4F24672-8293-4016-AE29-304C2758D4D1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{117AB27B-C4F4-4072-A857-4D2B868BEB80}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SubscriptionExample", "samples\SubscriptionsGraphQLServer\SubscriptionExample\SubscriptionExample\SubscriptionExample.csproj", "{E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{60A6D426-6E98-45CA-B5C4-59B27C4EFD81}" + ProjectSection(SolutionItems) = preProject + .github\FUNDING.yml = .github\FUNDING.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{67DFCB1B-6331-41E5-8FD3-03964526EDDD}" + ProjectSection(SolutionItems) = preProject + .github\workflows\build.yml = .github\workflows\build.yml + .github\workflows\formatting.yml = .github\workflows\formatting.yml + .github\workflows\publish.yml = .github\workflows\publish.yml + .github\workflows\test.yml = .github\workflows\test.yml + .github\workflows\wipcheck.yml = .github\workflows\wipcheck.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Solution Items", ".Solution Items", "{5A8618B7-7299-4507-A665-CC9CEE8B4674}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + LICENSE.md = LICENSE.md + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples' Tests", "Samples' Tests", "{F9132124-0747-427F-B607-B7BAD25332F7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionExample.Tests", "test\SubscriptionExample.Tests\SubscriptionExample.Tests.csproj", "{0BDE134F-0332-4D46-B762-DC861B6B126D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,9 +49,6 @@ Global Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {AEAC9A4B-7D5B-48C6-86A9-E665F6DE4B6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AEAC9A4B-7D5B-48C6-86A9-E665F6DE4B6C}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -43,5 +74,66 @@ Global {5CF44C73-E5C9-4E53-A69D-4013E8AE85E5}.Release|x64.Build.0 = Release|Any CPU {5CF44C73-E5C9-4E53-A69D-4013E8AE85E5}.Release|x86.ActiveCfg = Release|Any CPU {5CF44C73-E5C9-4E53-A69D-4013E8AE85E5}.Release|x86.Build.0 = Release|Any CPU + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}.Debug|x64.ActiveCfg = Debug|Any CPU + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}.Debug|x64.Build.0 = Debug|Any CPU + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}.Debug|x86.ActiveCfg = Debug|Any CPU + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}.Debug|x86.Build.0 = Debug|Any CPU + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}.Release|Any CPU.Build.0 = Release|Any CPU + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}.Release|x64.ActiveCfg = Release|Any CPU + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}.Release|x64.Build.0 = Release|Any CPU + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}.Release|x86.ActiveCfg = Release|Any CPU + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9}.Release|x86.Build.0 = Release|Any CPU + {B4F24672-8293-4016-AE29-304C2758D4D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4F24672-8293-4016-AE29-304C2758D4D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4F24672-8293-4016-AE29-304C2758D4D1}.Debug|x64.ActiveCfg = Debug|Any CPU + {B4F24672-8293-4016-AE29-304C2758D4D1}.Debug|x64.Build.0 = Debug|Any CPU + {B4F24672-8293-4016-AE29-304C2758D4D1}.Debug|x86.ActiveCfg = Debug|Any CPU + {B4F24672-8293-4016-AE29-304C2758D4D1}.Debug|x86.Build.0 = Debug|Any CPU + {B4F24672-8293-4016-AE29-304C2758D4D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4F24672-8293-4016-AE29-304C2758D4D1}.Release|Any CPU.Build.0 = Release|Any CPU + {B4F24672-8293-4016-AE29-304C2758D4D1}.Release|x64.ActiveCfg = Release|Any CPU + {B4F24672-8293-4016-AE29-304C2758D4D1}.Release|x64.Build.0 = Release|Any CPU + {B4F24672-8293-4016-AE29-304C2758D4D1}.Release|x86.ActiveCfg = Release|Any CPU + {B4F24672-8293-4016-AE29-304C2758D4D1}.Release|x86.Build.0 = Release|Any CPU + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}.Debug|x64.ActiveCfg = Debug|Any CPU + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}.Debug|x64.Build.0 = Debug|Any CPU + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}.Debug|x86.ActiveCfg = Debug|Any CPU + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}.Debug|x86.Build.0 = Debug|Any CPU + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}.Release|Any CPU.Build.0 = Release|Any CPU + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}.Release|x64.ActiveCfg = Release|Any CPU + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}.Release|x64.Build.0 = Release|Any CPU + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}.Release|x86.ActiveCfg = Release|Any CPU + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3}.Release|x86.Build.0 = Release|Any CPU + {0BDE134F-0332-4D46-B762-DC861B6B126D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BDE134F-0332-4D46-B762-DC861B6B126D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BDE134F-0332-4D46-B762-DC861B6B126D}.Debug|x64.ActiveCfg = Debug|Any CPU + {0BDE134F-0332-4D46-B762-DC861B6B126D}.Debug|x64.Build.0 = Debug|Any CPU + {0BDE134F-0332-4D46-B762-DC861B6B126D}.Debug|x86.ActiveCfg = Debug|Any CPU + {0BDE134F-0332-4D46-B762-DC861B6B126D}.Debug|x86.Build.0 = Debug|Any CPU + {0BDE134F-0332-4D46-B762-DC861B6B126D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BDE134F-0332-4D46-B762-DC861B6B126D}.Release|Any CPU.Build.0 = Release|Any CPU + {0BDE134F-0332-4D46-B762-DC861B6B126D}.Release|x64.ActiveCfg = Release|Any CPU + {0BDE134F-0332-4D46-B762-DC861B6B126D}.Release|x64.Build.0 = Release|Any CPU + {0BDE134F-0332-4D46-B762-DC861B6B126D}.Release|x86.ActiveCfg = Release|Any CPU + {0BDE134F-0332-4D46-B762-DC861B6B126D}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {19B85ED5-1F02-4D9D-AE9E-F265FC64C5D9} = {117AB27B-C4F4-4072-A857-4D2B868BEB80} + {B4F24672-8293-4016-AE29-304C2758D4D1} = {117AB27B-C4F4-4072-A857-4D2B868BEB80} + {E7DA8A82-3914-41E5-B5BE-D0BB3F234CE3} = {117AB27B-C4F4-4072-A857-4D2B868BEB80} + {67DFCB1B-6331-41E5-8FD3-03964526EDDD} = {60A6D426-6E98-45CA-B5C4-59B27C4EFD81} + {0BDE134F-0332-4D46-B762-DC861B6B126D} = {F9132124-0747-427F-B607-B7BAD25332F7} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BBCC6FB7-C0AE-43E0-866B-CA12D6D121DC} EndGlobalSection EndGlobal diff --git a/global.json b/global.json index fbe5d10d..3427cd57 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,6 @@ { "projects": [ "src", - "test", - "deps/graphql-dotnet/src" + "test" ] } diff --git a/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/Data/MovieDbContext.cs b/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/Data/MovieDbContext.cs index b4bd3f2e..55d5efb9 100644 --- a/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/Data/MovieDbContext.cs +++ b/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/Data/MovieDbContext.cs @@ -25,19 +25,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) var movie2 = new Movie { Id = Guid.NewGuid(), Title = "A Star Is Born", Genre = "Drama/Romance", ReleaseDateUtc = DateTime.Parse("10/04/2018 00:00:00Z") }; modelBuilder.Entity().HasData( - new Actor { Id = Guid.NewGuid(), CountryCode = "UK", Name = "Rowan Atkinson", MovieId = movie1.Id }, - new Actor { Id = Guid.NewGuid(), CountryCode = "FR", Name = "Olga Kurylenko", MovieId = movie1.Id }, - new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Jake Lacy", MovieId = movie1.Id }, - new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Bradley Cooper", MovieId = movie2.Id }, - new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Lady Gaga", MovieId = movie2.Id }, - new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Sam Elliott", MovieId = movie2.Id }, + new Actor { Id = Guid.NewGuid(), CountryCode = "UK", Name = "Rowan Atkinson", MovieId = movie1.Id }, + new Actor { Id = Guid.NewGuid(), CountryCode = "FR", Name = "Olga Kurylenko", MovieId = movie1.Id }, + new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Jake Lacy", MovieId = movie1.Id }, + new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Bradley Cooper", MovieId = movie2.Id }, + new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Lady Gaga", MovieId = movie2.Id }, + new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Sam Elliott", MovieId = movie2.Id }, new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Andrew Dice Clay", MovieId = movie2.Id }, - new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Dave Chappelle", MovieId = movie2.Id }, - new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Rebecca Field", MovieId = movie2.Id }, - new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Michael Harney", MovieId = movie2.Id }, - new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Rafi Gavron", MovieId = movie2.Id }, - new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Willam Belli", MovieId = movie2.Id }, - new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Halsey", MovieId = movie2.Id }); + new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Dave Chappelle", MovieId = movie2.Id }, + new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Rebecca Field", MovieId = movie2.Id }, + new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Michael Harney", MovieId = movie2.Id }, + new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Rafi Gavron", MovieId = movie2.Id }, + new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Willam Belli", MovieId = movie2.Id }, + new Actor { Id = Guid.NewGuid(), CountryCode = "US", Name = "Halsey", MovieId = movie2.Id }); modelBuilder.Entity().HasData( new Country { Code = "UK", Name = "United Kingdom" }, diff --git a/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/DataLoaderWithEFCore.csproj b/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/DataLoaderWithEFCore.csproj index 3eb7f2b7..63fde06e 100644 --- a/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/DataLoaderWithEFCore.csproj +++ b/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/DataLoaderWithEFCore.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/Properties/launchSettings.json b/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/Properties/launchSettings.json index 887a88fa..1b672300 100644 --- a/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/Properties/launchSettings.json +++ b/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/Properties/launchSettings.json @@ -12,7 +12,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "api/values", + "launchUrl": "ui/playground", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -20,7 +20,7 @@ "DataLoaderWithEFCore": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "api/values", + "launchUrl": "ui/playground", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/Startup.cs b/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/Startup.cs index a39d0611..1cb89dcd 100644 --- a/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/Startup.cs +++ b/samples/DataLoaderWithEFCore/DataLoaderWithEFCore/Startup.cs @@ -61,6 +61,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseHttpsRedirection(); app.UseRouting(); + app.UseGraphQLPlayground(options: new GraphQL.Server.Ui.Playground.PlaygroundOptions { GraphQLEndPoint = "/api/graph" }); app.UseEndpoints(configure => { configure.MapControllers(); diff --git a/samples/SimpleWebApp/Program.cs b/samples/SimpleWebApp/Program.cs index 8196fc17..63f4fc8b 100644 --- a/samples/SimpleWebApp/Program.cs +++ b/samples/SimpleWebApp/Program.cs @@ -9,7 +9,7 @@ public class Program public static void Main(string[] args) { var host = new WebHostBuilder() - .UseKestrel() + .UseKestrel(options => options.AllowSynchronousIO = true) // for Newtonsoft.Json support .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() diff --git a/samples/SimpleWebApp/SimpleWebApp.csproj b/samples/SimpleWebApp/SimpleWebApp.csproj index f4aedde6..55172bb5 100755 --- a/samples/SimpleWebApp/SimpleWebApp.csproj +++ b/samples/SimpleWebApp/SimpleWebApp.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -10,6 +10,10 @@ + + + + diff --git a/samples/SimpleWebApp/Startup.cs b/samples/SimpleWebApp/Startup.cs index 177b90fe..2b19124f 100644 --- a/samples/SimpleWebApp/Startup.cs +++ b/samples/SimpleWebApp/Startup.cs @@ -31,6 +31,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF .WithQueryAndMutation() .Generate(); + app.UseGraphQLPlayground("/"); app.Run(HandleRequest); } diff --git a/samples/SubscriptionsGraphQLServer/SubscriptionExample/SubscriptionExample/Startup.cs b/samples/SubscriptionsGraphQLServer/SubscriptionExample/SubscriptionExample/Startup.cs index de46d0ec..5d59acf2 100644 --- a/samples/SubscriptionsGraphQLServer/SubscriptionExample/SubscriptionExample/Startup.cs +++ b/samples/SubscriptionsGraphQLServer/SubscriptionExample/SubscriptionExample/Startup.cs @@ -1,24 +1,14 @@ -using System.Threading.Tasks; -using GraphQL; +using GraphQL; using GraphQL.Conventions; -using GraphQL.Conventions.Adapters; -using GraphQL.Conventions.Builders; -using GraphQL.Conventions.Types.Resolution; -using GraphQL.DataLoader; -using GraphQL.MicrosoftDI; -using GraphQL.NewtonsoftJson; -using GraphQL.Server; -using GraphQL.Server.Transports.AspNetCore; -using GraphQL.SystemTextJson; using GraphQL.Types; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using SubscriptionExample.Core; using SubscriptionExample.GraphQl; +using System.Threading.Tasks; using DocumentExecuter = GraphQL.Conventions.DocumentExecuter; namespace SubscriptionExample @@ -35,19 +25,15 @@ public void ConfigureServices(IServiceCollection services) services.AddGraphQL(builder => { builder - .AddHttpMiddleware>() - .AddWebSocketsHttpMiddleware() - .AddDefaultEndpointSelectorPolicy() .AddSystemTextJson() .AddErrorInfoProvider(option => { - option.ExposeExceptionStackTrace = true; + option.ExposeExceptionDetails = true; }) .AddDataLoader() - .AddWebSockets() + .UseApolloTracing() .ConfigureExecutionOptions(options => { - options.EnableMetrics = true; var logger = options.RequestServices.GetRequiredService>(); options.UnhandledExceptionDelegate = ctx => { @@ -66,7 +52,7 @@ public void ConfigureServices(IServiceCollection services) .BuildSchema(); var schema = engine.GetSchema(); - + // Add Graph QL Convention Services services.AddSingleton(engine); services.AddSingleton(schema); @@ -83,7 +69,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseRouting(); app.UseEndpoints(endpoints => { - endpoints.MapGraphQLWebSockets(); endpoints.MapGraphQL(); endpoints.MapGraphQLPlayground(); }); diff --git a/samples/SubscriptionsGraphQLServer/SubscriptionExample/SubscriptionExample/SubscriptionExample.csproj b/samples/SubscriptionsGraphQLServer/SubscriptionExample/SubscriptionExample/SubscriptionExample.csproj index 9cc03182..68778703 100644 --- a/samples/SubscriptionsGraphQLServer/SubscriptionExample/SubscriptionExample/SubscriptionExample.csproj +++ b/samples/SubscriptionsGraphQLServer/SubscriptionExample/SubscriptionExample/SubscriptionExample.csproj @@ -9,13 +9,12 @@ - - - - - - + + + + + diff --git a/src/GraphQL.Conventions/Adapters/Engine/GraphQLEngine.cs b/src/GraphQL.Conventions/Adapters/Engine/GraphQLEngine.cs index 38cd9f58..8b26716b 100644 --- a/src/GraphQL.Conventions/Adapters/Engine/GraphQLEngine.cs +++ b/src/GraphQL.Conventions/Adapters/Engine/GraphQLEngine.cs @@ -53,7 +53,7 @@ public class GraphQLEngine private IErrorTransformation _errorTransformation = new DefaultErrorTransformation(); private bool _includeFieldDescriptions; - private bool _throwUnhandledExceptions; + private bool _throwUnhandledExceptions; private bool _includeFieldDeprecationReasons; @@ -72,8 +72,8 @@ public ValueTask EnterAsync(ASTNode node, ValidationContext context) public ValueTask LeaveAsync(ASTNode node, ValidationContext context) { - /* Noop */ - return default; + /* Noop */ + return default; } } @@ -100,11 +100,11 @@ public GraphQLEngine(Func typeResolutionDelegate = null, ITypeReso { TypeResolutionDelegate = typeResolutionDelegate != null ? type => typeResolutionDelegate(type) ?? CreateInstance(type) - : (Func) CreateInstance + : (Func)CreateInstance }; } - - public static GraphQLEngine New(IDocumentExecuter documentExecutor) + + public static GraphQLEngine New(IDocumentExecuter documentExecutor) => new GraphQLEngine(null, null, documentExecutor); public static GraphQLEngine New(Func typeResolutionDelegate = null) diff --git a/src/GraphQL.Conventions/Adapters/ResolutionContext.cs b/src/GraphQL.Conventions/Adapters/ResolutionContext.cs index b2e962dd..299fc927 100644 --- a/src/GraphQL.Conventions/Adapters/ResolutionContext.cs +++ b/src/GraphQL.Conventions/Adapters/ResolutionContext.cs @@ -48,7 +48,7 @@ public void SetArgument(string name, object value) lock (Lock) { var fieldContext = FieldContext is ResolveFieldContext - ? (ResolveFieldContext) FieldContext + ? (ResolveFieldContext)FieldContext : new ResolveFieldContext(FieldContext); if (fieldContext.Arguments == null) diff --git a/src/GraphQL.Conventions/Attributes/Execution/Wrappers/PrimitiveWrapper.cs b/src/GraphQL.Conventions/Attributes/Execution/Wrappers/PrimitiveWrapper.cs index 6382ceba..1c03619d 100644 --- a/src/GraphQL.Conventions/Attributes/Execution/Wrappers/PrimitiveWrapper.cs +++ b/src/GraphQL.Conventions/Attributes/Execution/Wrappers/PrimitiveWrapper.cs @@ -10,7 +10,7 @@ public class PrimitiveWrapper : WrapperBase public override object WrapValue(GraphEntityInfo entityInfo, GraphTypeInfo typeInfo, object value, bool isSpecified) { if (value == null || !typeInfo.IsPrimitive || typeInfo.IsEnumerationType) return value; - + try { var targetType = typeInfo.GetTypeRepresentation().AsType(); @@ -19,7 +19,7 @@ public override object WrapValue(GraphEntityInfo entityInfo, GraphTypeInfo typeI if (converter.CanConvertFrom(value.GetType())) return converter.ConvertFrom(value); - if(value is IConvertible) + if (value is IConvertible) return Convert.ChangeType(value, targetType); } catch (Exception ex) diff --git a/src/GraphQL.Conventions/Attributes/MetaData/Utilities/INameNormalizer.cs b/src/GraphQL.Conventions/Attributes/MetaData/Utilities/INameNormalizer.cs index 634bbb3b..1d63a368 100644 --- a/src/GraphQL.Conventions/Attributes/MetaData/Utilities/INameNormalizer.cs +++ b/src/GraphQL.Conventions/Attributes/MetaData/Utilities/INameNormalizer.cs @@ -7,7 +7,7 @@ public interface INameNormalizer string AsFieldName(string name); string AsArgumentName(string name); - + string AsEnumMemberName(string name); } } \ No newline at end of file diff --git a/src/GraphQL.Conventions/Extensions/Utilities.cs b/src/GraphQL.Conventions/Extensions/Utilities.cs index c053f316..99b8b5d5 100644 --- a/src/GraphQL.Conventions/Extensions/Utilities.cs +++ b/src/GraphQL.Conventions/Extensions/Utilities.cs @@ -138,7 +138,8 @@ private static Func InvokeEnhancedUncachedConstructor(Construc throw new ArgumentException("Constructor must not be static", nameof(constructorInfo)); var argumentsParameter = Expression.Parameter(typeof(object[])); var constructorParameters = constructorInfo.GetParameters(); - var parameters = constructorParameters.Select((param, index) => { + var parameters = constructorParameters.Select((param, index) => + { return Expression.Convert(Expression.ArrayAccess(argumentsParameter, Expression.Constant(index)), param.ParameterType); }); var call = Expression.New(constructorInfo, parameters); diff --git a/src/GraphQL.Conventions/GraphQL.Conventions.csproj b/src/GraphQL.Conventions/GraphQL.Conventions.csproj index 0ce86551..14c59321 100644 --- a/src/GraphQL.Conventions/GraphQL.Conventions.csproj +++ b/src/GraphQL.Conventions/GraphQL.Conventions.csproj @@ -2,8 +2,8 @@ GraphQL Conventions for .NET - 7.0.0 - 7.0.0 + 7.1.0 + 7.1.0 Tommy Lillehagen net6.0;netstandard2.0 9.0 @@ -16,8 +16,6 @@ MIT git https://github.com/graphql-dotnet/conventions - $(PackageTargetFallback);dnxcore50;portable-net45+win8 - 1.6.1 false false false @@ -29,14 +27,8 @@ - - - - - - - - + + diff --git a/src/GraphQL.Conventions/Types/Relay/Extensions/ConnectionExtensions.cs b/src/GraphQL.Conventions/Types/Relay/Extensions/ConnectionExtensions.cs index eb1798d5..41c9c3d6 100644 --- a/src/GraphQL.Conventions/Types/Relay/Extensions/ConnectionExtensions.cs +++ b/src/GraphQL.Conventions/Types/Relay/Extensions/ConnectionExtensions.cs @@ -200,8 +200,8 @@ public ConnectionImpl(IEnumerable> collection, int? first, { PageInfo = new PageInfo { - StartCursor = after ?? before ?? (Cursor) minCursor, - EndCursor = before ?? after ?? (Cursor) maxCursor, + StartCursor = after ?? before ?? (Cursor)minCursor, + EndCursor = before ?? after ?? (Cursor)maxCursor, }; var startValue = PageInfo.Value.StartCursor.IntegerForCursor() ?? 0; diff --git a/src/GraphQL.Conventions/Types/Resolution/TypeResolver.cs b/src/GraphQL.Conventions/Types/Resolution/TypeResolver.cs index 3ff8e9b8..921f1238 100644 --- a/src/GraphQL.Conventions/Types/Resolution/TypeResolver.cs +++ b/src/GraphQL.Conventions/Types/Resolution/TypeResolver.cs @@ -98,7 +98,8 @@ public virtual void IgnoreTypesFromNamespacesStartingWith(params string[] namesp _reflector.IgnoredNamespaces.Add(@namespace); } - public void IgnoreTypes(Func ignoreTypeCallback) { + public void IgnoreTypes(Func ignoreTypeCallback) + { _reflector.IgnoreTypeCallback = ignoreTypeCallback; } diff --git a/src/GraphQL.Conventions/Web/RequestHandler.cs b/src/GraphQL.Conventions/Web/RequestHandler.cs index 89490eec..de98b40b 100644 --- a/src/GraphQL.Conventions/Web/RequestHandler.cs +++ b/src/GraphQL.Conventions/Web/RequestHandler.cs @@ -249,7 +249,7 @@ public async Task ProcessRequestAsync(Request request, IUserContext us if (_useProfiling) { - result.EnrichWithApolloTracing(start); + result.EnrichWithApolloTracing(start); } var response = new Response(request, result); diff --git a/test/GraphQL.Conventions.Tests/Adapters/ArgumentResolutionTests.cs b/test/GraphQL.Conventions.Tests/Adapters/ArgumentResolutionTests.cs index c143f24d..295fcc4c 100644 --- a/test/GraphQL.Conventions.Tests/Adapters/ArgumentResolutionTests.cs +++ b/test/GraphQL.Conventions.Tests/Adapters/ArgumentResolutionTests.cs @@ -99,7 +99,7 @@ public async Task Can_Resolve_Nullable_Identifier_Argument() { var id = Id.New("12345"); var result = await ExecuteQuery( - @"{ nullableIdField(idArg: """ + id + @""") }"); + @"{ nullableIdField(idArg: """ + id + @""") }"); result.ShouldHaveNoErrors(); result.Data.ShouldHaveFieldWithValue("nullableIdField", id.IdentifierForType()); @@ -115,7 +115,7 @@ public async Task Can_Resolve_NonNullable_Identifier_Argument() { var id = Id.New("12345"); var result = await ExecuteQuery( - @"{ nonNullableIdField(idArg: """ + id + @""") }"); + @"{ nonNullableIdField(idArg: """ + id + @""") }"); result.ShouldHaveNoErrors(); result.Data.ShouldHaveFieldWithValue("nonNullableIdField", id.IdentifierForType()); @@ -493,7 +493,7 @@ public async Task Can_Resolve_List_Of_NonNullable_InputObject_Argument() result.ShouldHaveNoErrors(); result.Data.ShouldHaveFieldWithValue("result", 0, "A-B-1-2"); result.Data.ShouldHaveFieldWithValue("result", 1, "X-Y-9-0"); - } + } [Test] public async Task Can_Resolve_Enumerable_Of_Nullable_InputObject_Argument() diff --git a/test/GraphQL.Conventions.Tests/Adapters/Engine/GraphQLEngineTests.cs b/test/GraphQL.Conventions.Tests/Adapters/Engine/GraphQLEngineTests.cs index 54803a9a..aad8ecb7 100644 --- a/test/GraphQL.Conventions.Tests/Adapters/Engine/GraphQLEngineTests.cs +++ b/test/GraphQL.Conventions.Tests/Adapters/Engine/GraphQLEngineTests.cs @@ -416,15 +416,15 @@ enum Enum2 [InputType] class InputObject { - [DefaultValue(Enum2.SomeValue1)] - public Enum2? SomeField { get; set; } + [DefaultValue(Enum2.SomeValue1)] + public Enum2? SomeField { get; set; } - public Enum2? AnotherField { get; set; } + public Enum2? AnotherField { get; set; } - public Enum2 YetAnotherField { get; set; } + public Enum2 YetAnotherField { get; set; } - [DefaultValue(Enum2.SomeValue2)] - public Enum2? YetAnotherDummyField { get; set; } + [DefaultValue(Enum2.SomeValue2)] + public Enum2? YetAnotherDummyField { get; set; } } class QueryWithInterfaces diff --git a/test/GraphQL.Conventions.Tests/Adapters/TypeConstructionTests.cs b/test/GraphQL.Conventions.Tests/Adapters/TypeConstructionTests.cs index ba1db158..4f525b53 100644 --- a/test/GraphQL.Conventions.Tests/Adapters/TypeConstructionTests.cs +++ b/test/GraphQL.Conventions.Tests/Adapters/TypeConstructionTests.cs @@ -163,7 +163,7 @@ public interface ITest { int Test { get; } } - public class TestImpl: ITest + public class TestImpl : ITest { public int Test { get; } } diff --git a/test/GraphQL.Conventions.Tests/Attributes/MetaData/FieldTypeMetaDataAttributeTests.cs b/test/GraphQL.Conventions.Tests/Attributes/MetaData/FieldTypeMetaDataAttributeTests.cs index 27fd3222..a54bca96 100644 --- a/test/GraphQL.Conventions.Tests/Attributes/MetaData/FieldTypeMetaDataAttributeTests.cs +++ b/test/GraphQL.Conventions.Tests/Attributes/MetaData/FieldTypeMetaDataAttributeTests.cs @@ -163,7 +163,7 @@ public ValueTask EnterAsync(ASTNode node, ValidationContext context) var requiredValidation = permissionMetaData.Value as string; if (_user == null || _user.AccessPermissions.All(p => p != requiredValidation)) - context.ReportError( new ValidationError( /* When reporting such errors no data would be returned use with cautious */ + context.ReportError(new ValidationError( /* When reporting such errors no data would be returned use with cautious */ context.Document.Source, "Authorization", $"Required validation '{requiredValidation}' is not present. Query will not be executed.", diff --git a/test/GraphQL.Conventions.Tests/Attributes/MetaData/Utilities/NameNormalizerTests.cs b/test/GraphQL.Conventions.Tests/Attributes/MetaData/Utilities/NameNormalizerTests.cs index 7c9d0c3d..bcaea539 100644 --- a/test/GraphQL.Conventions.Tests/Attributes/MetaData/Utilities/NameNormalizerTests.cs +++ b/test/GraphQL.Conventions.Tests/Attributes/MetaData/Utilities/NameNormalizerTests.cs @@ -77,10 +77,10 @@ public void Can_Derive_Correct_Enum_Member_Names() private string AsTypeName(string name) => _normalizer.AsTypeName(name); - private string AsFieldName(string name) => _normalizer.AsFieldName(name); + private string AsFieldName(string name) => _normalizer.AsFieldName(name); - private string AsArgumentName(string name) => _normalizer.AsArgumentName(name); + private string AsArgumentName(string name) => _normalizer.AsArgumentName(name); - private string AsEnumMemberName(string name) => _normalizer.AsEnumMemberName(name); + private string AsEnumMemberName(string name) => _normalizer.AsEnumMemberName(name); } } diff --git a/test/GraphQL.Conventions.Tests/Builders/SchemaConstructorTests.cs b/test/GraphQL.Conventions.Tests/Builders/SchemaConstructorTests.cs index 9e418c1f..cd0b0fee 100644 --- a/test/GraphQL.Conventions.Tests/Builders/SchemaConstructorTests.cs +++ b/test/GraphQL.Conventions.Tests/Builders/SchemaConstructorTests.cs @@ -26,6 +26,7 @@ public void Can_Construct_Schema() public void Can_Combine_Schemas() { var schema = Schema(); + schema.Initialize(); schema.ShouldHaveQueries(3); schema.ShouldHaveMutations(1); schema.Query.ShouldHaveFieldWithName("foo"); @@ -43,6 +44,7 @@ public void Can_Ignore_Types_From_Unwanted_Namespaces() typeof(Unwanted.SchemaType3) ); + schema.Initialize(); schema.ShouldHaveQueries(4); schema.ShouldHaveMutations(2); schema.Query.ShouldHaveFieldWithName("foo"); @@ -76,6 +78,7 @@ public void Can_Ignore_Types_From_Unwanted_Namespaces() typeof(Unwanted.SchemaType3) ); + schema.Initialize(); schema.ShouldHaveQueries(3); schema.ShouldHaveMutations(1); schema.Query.ShouldHaveFieldWithName("foo"); @@ -93,7 +96,8 @@ public void Can_Ignore_Unwanted_Types() // Ignore specific types from the 'Unwanted' namespace. var schema = new SchemaConstructor(new GraphTypeAdapter()) - .IgnoreTypes((t, m) => { + .IgnoreTypes((t, m) => + { // Ignore based on the type: if (t == typeof(Unwanted.QueryType3)) { return true; } // Ignore based on name of the method: diff --git a/test/GraphQL.Conventions.Tests/GraphQL.Conventions.Tests.csproj b/test/GraphQL.Conventions.Tests/GraphQL.Conventions.Tests.csproj index bcd44341..66b7de3b 100755 --- a/test/GraphQL.Conventions.Tests/GraphQL.Conventions.Tests.csproj +++ b/test/GraphQL.Conventions.Tests/GraphQL.Conventions.Tests.csproj @@ -12,11 +12,10 @@ - - - + + all diff --git a/test/GraphQL.Conventions.Tests/Types/OptionalTests.cs b/test/GraphQL.Conventions.Tests/Types/OptionalTests.cs index 1da00339..0aef8027 100644 --- a/test/GraphQL.Conventions.Tests/Types/OptionalTests.cs +++ b/test/GraphQL.Conventions.Tests/Types/OptionalTests.cs @@ -21,13 +21,13 @@ public void Cannot_Define_NonNullable_Int() Assert.ThrowsException(() => Optional.ValidateType()); } - [Test] + [Test] public void Can_Define_Nullable_Int() { Optional.ValidateType(); } - [Test] + [Test] public void Cannot_Define_NonNullable_DateTime() { Assert.ThrowsException(() => Optional.ValidateType()); @@ -117,7 +117,7 @@ public void Can_Construct_WithNull() var typeResolver = new TypeResolver(); var typeInfo = typeof(Optional).GetTypeInfo(); var graphTypeInfo = new GraphTypeInfo(typeResolver, typeInfo); - var optional = (Optional) Optional.Construct(graphTypeInfo, null, true); + var optional = (Optional)Optional.Construct(graphTypeInfo, null, true); Assert.IsNotNull(optional); Assert.IsNull(optional.Value); } diff --git a/test/GraphQL.Conventions.Tests/Web/RequestHandlerTests.cs b/test/GraphQL.Conventions.Tests/Web/RequestHandlerTests.cs index 3ebb5b2e..23224f97 100644 --- a/test/GraphQL.Conventions.Tests/Web/RequestHandlerTests.cs +++ b/test/GraphQL.Conventions.Tests/Web/RequestHandlerTests.cs @@ -214,7 +214,7 @@ class CompositeQuery public Unwanted.TestQuery2 Mars => new Unwanted.TestQuery2(); } } - + class SimpleQuery { public string Hello => "World"; diff --git a/test/SubscriptionExample.Tests/EndToEndTests.cs b/test/SubscriptionExample.Tests/EndToEndTests.cs new file mode 100644 index 00000000..db49d410 --- /dev/null +++ b/test/SubscriptionExample.Tests/EndToEndTests.cs @@ -0,0 +1,203 @@ +using GraphQL.Transport; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using System.Net; +using System.Text.Json; +using Xunit; + +namespace SubscriptionExample.Tests; + +public class EndToEndTests +{ + [Fact] + public async Task CanPostMessages() + { + var query1 = @" + { + messages { + author + text + } + }"; + + var expected1 = @" + { + ""data"": { + ""messages"": [] + } + }"; + + var query2 = @" + mutation { + addMessage(message: { author: ""John"", text: ""Hello World"" }) { + author + text + } + }"; + + var expected2 = @" + { + ""data"": { + ""addMessage"": { + ""author"": ""John"", + ""text"": ""Hello World"" + } + } + }"; + + var query3 = query1; + + var expected3 = @" + { + ""data"": { + ""messages"": [ + { + ""author"": ""John"", + ""text"": ""Hello World"" + } + ] + } + }"; + + var query4 = @" + mutation { + addMessage(message: { author: ""Jane"", text: ""I like turtles"" }) { + author + text + } + }"; + + var expected4 = @" + { + ""data"": { + ""addMessage"": { + ""author"": ""Jane"", + ""text"": ""I like turtles"" + } + } + }"; + + var query5 = query1; + + var expected5 = @" + { + ""data"": { + ""messages"": [ + { + ""author"": ""John"", + ""text"": ""Hello World"" + }, + { + ""author"": ""Jane"", + ""text"": ""I like turtles"" + } + ] + } + }"; + + var querySubscription = @" + subscription { + messageUpdate { + author + text + } + }"; + + var expectedSubscription = new string[] + { + @"{ + ""data"": { + ""messageUpdate"": { + ""author"": ""John"", + ""text"": ""Hello World"" + } + } + }", + @"{ + ""data"": { + ""messageUpdate"": { + ""author"": ""Jane"", + ""text"": ""I like turtles"" + } + } + }" + }; + + using var webApp = new WebApplicationFactory(); + var server = webApp.Server; + var websocketTask = VerifyGraphQLWebSocketsAsync(server, querySubscription, expectedSubscription); + await VerifyGraphQLPostAsync(server, query: query1, expected: expected1).ConfigureAwait(false); + await VerifyGraphQLPostAsync(server, query: query2, expected: expected2).ConfigureAwait(false); + await VerifyGraphQLPostAsync(server, query: query3, expected: expected3).ConfigureAwait(false); + await VerifyGraphQLPostAsync(server, query: query4, expected: expected4).ConfigureAwait(false); + await VerifyGraphQLPostAsync(server, query: query5, expected: expected5).ConfigureAwait(false); + await websocketTask.ConfigureAwait(false); + } + + private static async Task VerifyGraphQLPostAsync( + TestServer server, + string url = "/graphql", + string query = "{count}", + string expected = @"{""data"":{""count"":0}}", + HttpStatusCode statusCode = HttpStatusCode.OK) + { + using var client = server.CreateClient(); + var body = JsonSerializer.Serialize(new { query }); + var content = new StringContent(body, System.Text.Encoding.UTF8, "application/json"); + using var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Content = content; + using var response = await client.SendAsync(request).ConfigureAwait(false); + Assert.Equal(statusCode, response.StatusCode); + var ret = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var jsonExpected = JsonSerializer.Serialize(JsonSerializer.Deserialize(expected)); + var jsonActual = JsonSerializer.Serialize(JsonSerializer.Deserialize(ret)); + Assert.Equal(jsonExpected, jsonActual); + } + + private static async Task VerifyGraphQLWebSocketsAsync( + TestServer server, + string query, + IEnumerable expectedMessages, + string url = "/graphql") + { + var webSocketClient = server.CreateWebSocketClient(); + webSocketClient.ConfigureRequest = request => + { + request.Headers["Sec-WebSocket-Protocol"] = "graphql-transport-ws"; + }; + webSocketClient.SubProtocols.Add("graphql-transport-ws"); + using var webSocket = await webSocketClient.ConnectAsync(new Uri(server.BaseAddress, url), default).ConfigureAwait(false); + + // send CONNECTION_INIT + await webSocket.SendMessageAsync(new OperationMessage + { + Type = "connection_init", + }); + + // wait for CONNECTION_ACK + var message = await webSocket.ReceiveMessageAsync().ConfigureAwait(false); + Assert.Equal("connection_ack", message.Type); + + // send query + await webSocket.SendMessageAsync(new OperationMessage + { + Id = "1", + Type = "subscribe", + Payload = new GraphQLRequest + { + Query = query + } + }); + + // wait for response + foreach (var expected in expectedMessages) + { + message = await webSocket.ReceiveMessageAsync().WaitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false); + Assert.Equal("next", message.Type); + Assert.Equal("1", message.Id); + var payloadJson = JsonSerializer.Serialize(message.Payload); + var expectedJson = JsonSerializer.Serialize(JsonSerializer.Deserialize(expected)); + Assert.Equal(expectedJson, payloadJson); + } + } +} \ No newline at end of file diff --git a/test/SubscriptionExample.Tests/SubscriptionExample.Tests.csproj b/test/SubscriptionExample.Tests/SubscriptionExample.Tests.csproj new file mode 100644 index 00000000..73df6b1f --- /dev/null +++ b/test/SubscriptionExample.Tests/SubscriptionExample.Tests.csproj @@ -0,0 +1,24 @@ + + + + net6.0 + enable + enable + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/test/SubscriptionExample.Tests/WebSocketExtensions.cs b/test/SubscriptionExample.Tests/WebSocketExtensions.cs new file mode 100644 index 00000000..20ffc625 --- /dev/null +++ b/test/SubscriptionExample.Tests/WebSocketExtensions.cs @@ -0,0 +1,53 @@ +using GraphQL; +using GraphQL.SystemTextJson; +using GraphQL.Transport; +using System.Net.WebSockets; +using System.Text; +using System.Text.Json; +using Xunit; + +namespace SubscriptionExample.Tests; + +public static class WebSocketExtensions +{ + private static readonly IGraphQLTextSerializer _serializer = new GraphQLSerializer(); + + public static Task SendMessageAsync(this WebSocket socket, OperationMessage message) + => SendStringAsync(socket, _serializer.Serialize(message)); + + public static async Task SendStringAsync(this WebSocket socket, string str) + { + var bytes = Encoding.UTF8.GetBytes(str); + await socket.SendAsync(new Memory(bytes), WebSocketMessageType.Text, true, default); + } + + public static async Task ReceiveMessageAsync(this WebSocket socket) + { + using var cts = new CancellationTokenSource(); + cts.CancelAfter(5000); + var mem = new MemoryStream(); + ValueWebSocketReceiveResult response; + do + { + var buffer = new byte[1024]; + response = await socket.ReceiveAsync(new Memory(buffer), cts.Token); + mem.Write(buffer, 0, response.Count); + } while (!response.EndOfMessage); + Assert.Equal(WebSocketMessageType.Text, response.MessageType); + mem.Position = 0; + var message = await _serializer.ReadAsync(mem); + return message; + } + + public static async Task ReceiveCloseMessageAsync(this WebSocket socket) + { + using var cts = new CancellationTokenSource(5000); + var drainBuffer = new byte[1024]; + ValueWebSocketReceiveResult response; + do + { + response = await socket.ReceiveAsync(new Memory(drainBuffer), cts.Token); + } while (!response.EndOfMessage); + Assert.Equal(WebSocketMessageType.Close, response.MessageType); + } +}