diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e524f79f..a8c57d1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,3 +39,32 @@ jobs: - name: Test run: dotnet test Linq2GraphQL.CI.slnf --no-build --verbosity normal + + generated-clients: + + name: Test clients up to date + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + cache: true + cache-dependency-path: '**/packages.lock.json' + + - name: Restore tools + run: dotnet tool restore + + - name: Restore + run: dotnet restore Linq2GraphQL.CI.slnf --locked-mode + + # Regenerates test/Linq2GraphQL.TestClient* from the test servers' live + # schemas and fails if the result differs from what is committed. + - name: Check checked-in test clients + shell: pwsh + run: ./scripts/regenerate-test-clients.ps1 -Check diff --git a/CLAUDE.md b/CLAUDE.md index 9eb69778..44033b2e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,6 +21,12 @@ dotnet restore Linq2GraphQL.CI.slnf --locked-mode dotnet build Linq2GraphQL.CI.slnf --no-restore dotnet test Linq2GraphQL.CI.slnf --no-build +# Also CI: verify the checked-in test clients still match the generator +./scripts/regenerate-test-clients.ps1 -Check + +# Refresh them after a template or test-schema change +./scripts/regenerate-test-clients.ps1 + # Single test / class dotnet test test/Linq2GraphQL.Tests --filter "FullyQualifiedName~QueryTests.Hello_WithNoName_HelloWorld" dotnet test test/Linq2GraphQL.Tests --filter "FullyQualifiedName~ExpressionParserTests" @@ -63,6 +69,6 @@ Key details that bite: `Linq2GraphQL.Tests` is xUnit + Shouldly + Moq. It spins up the real GraphQL server in-process with `WebApplicationFactory` — `Linq2GraphQL.TestServer` is HotChocolate over the POCOs in `TestServer.Shared`, and `TestServerNullable` is the same schema for the nullable client. `SampleClientFixture` / `SampleClientNullableFixture` wire the generated client to that in-memory host (safe mode on, SSE subscriptions), and test classes take them via `IClassFixture<>`. So most tests are end-to-end: an assertion failure can come from the expression parser, the query text, or the server's own resolvers. -`Linq2GraphQL.TestClient` / `TestClientNullable` hold **checked-in generated output**. They are not regenerated by the build — when a template or schema changes, run the generator against a locally running TestServer and commit the result, then verify the round trip still passes. +`Linq2GraphQL.TestClient` / `TestClientNullable` hold **checked-in generated output**. They are not regenerated by the build — run `./scripts/regenerate-test-clients.ps1` (it boots both test servers over plain HTTP, regenerates both clients with the flags that produced the committed output, and writes them back) and commit the diff. The `generated-clients` CI job runs the same script with `-Check` and fails if the committed output no longer matches the generator, so a template or schema change that you forget to regenerate is caught in CI rather than passing against stale output. `docs/`, `docs/StarWars.Client` and `StartGG/` are the Blazor documentation site and sample clients; they are outside the CI solution filter and can drift. diff --git a/REPO_REPORT.md b/REPO_REPORT.md index b37f9625..fbe33764 100644 --- a/REPO_REPORT.md +++ b/REPO_REPORT.md @@ -53,7 +53,7 @@ Three stages, and understanding them is usually the whole job: ## 5. Highlights — the risks - ~~**T4 is the sharpest edge in the repo.**~~ **Fixed.** Templates are now preprocessed at build time by the pinned `dotnet-t4` local tool into gitignored `X.g.cs` files, so a `.tt` edit takes effect on the next `dotnet build` on any platform and stale template logic cannot be built. (Switching it on revealed one already-dormant edit: `ScalarTemplate.tt` referenced a non-existent `GraphqlType.ScalarTypeName` and had never been regenerated.) -- **Generated test clients are checked in but never regenerated by the build.** `TestClient`/`TestClientNullable` must be re-generated by hand against a locally running TestServer after any template or schema change. Nothing in CI detects the drift — the tests keep passing against stale output. +- ~~**Generated test clients are checked in but never regenerated by the build.**~~ **Fixed.** `./scripts/regenerate-test-clients.ps1` boots both test servers over plain HTTP and regenerates `TestClient`/`TestClientNullable`, and the `generated-clients` CI job runs it with `-Check` so committed output that no longer matches the generator fails the build. Refreshing the output picked up three pieces of accumulated drift: the newly active template edits, the signed `Byte` scalar mapping (`byte` → `sbyte`), and the `raiseError`/`raiseAuthError` query methods. - **Coupling between alias hashing and deserialization.** `Utilities.GetArgumentsId` must produce the *same* hash at write time and read time. Any change to argument hashing breaks reads as well as writes, and the failure mode is a silently missing field rather than an exception. - **Ambient static generator state.** `GeneratorSettings.Current` is read from inside templates, so nullable/non-nullable output depends on global mutable state rather than a passed parameter. Fine today; awkward if generation ever needs to run concurrently. - **`docs/` and `StartGG/` are outside the CI filter and can drift.** They are in `Linq2GraphQL.sln` but not `Linq2GraphQL.CI.slnf`, so nothing verifies they still compile — roughly 15k lines CI never touches. @@ -70,7 +70,7 @@ Three stages, and understanding them is usually the whole job: ## 7. If I were picking up work here 1. ~~Wire up **CLI T4 regeneration**~~ — done: `dotnet-t4` runs from the generator's csproj on every build, and the preprocessed output is no longer checked in. -2. Add a **CI job that regenerates the test clients and diffs them** — it closes the stale-generated-output gap for free. +2. ~~Add a **CI job that regenerates the test clients and diffs them**~~ — done: `scripts/regenerate-test-clients.ps1 -Check`, run by the `generated-clients` job. 3. Pull `docs/` and `StartGG/` into a **build-only CI job** so they cannot rot unnoticed. 4. Get the **WebSocket subscription transport under test**, even against a standalone host outside `WebApplicationFactory`. 5. Delete `nuget.config.backup` and the stray root `StarWars.Client/`. diff --git a/scripts/regenerate-test-clients.ps1 b/scripts/regenerate-test-clients.ps1 new file mode 100644 index 00000000..439a38d0 --- /dev/null +++ b/scripts/regenerate-test-clients.ps1 @@ -0,0 +1,148 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Regenerates the checked-in test clients from the test servers' live schemas. + +.DESCRIPTION + test/Linq2GraphQL.TestClient and test/Linq2GraphQL.TestClientNullable hold + generated output that the test suite compiles against. Nothing in the normal + build refreshes it, so it silently drifts whenever a template or the test + schema changes. + + This script boots each test server over plain HTTP, runs the generator + against it with the flags that produced the checked-in output, and writes + the result back over the Generated folders. CI runs the same script and + fails if the result differs from what is committed. + +.PARAMETER Check + Do not keep the regenerated output: restore the working tree afterwards and + exit non-zero if anything differed. Used by CI. + +.EXAMPLE + ./scripts/regenerate-test-clients.ps1 + ./scripts/regenerate-test-clients.ps1 -Check +#> +[CmdletBinding()] +param( + [switch]$Check +) + +$ErrorActionPreference = 'Stop' +$repoRoot = Split-Path -Parent $PSScriptRoot + +$clients = @( + @{ + Name = 'SampleClient' + Server = 'test/Linq2GraphQL.TestServer' + Output = 'test/Linq2GraphQL.TestClient/Generated' + Namespace = 'Linq2GraphQL.TestClient' + Port = 5180 + Nullable = $false + } + @{ + Name = 'SampleNullableClient' + Server = 'test/Linq2GraphQL.TestServerNullable' + Output = 'test/Linq2GraphQL.TestClientNullable/Generated' + Namespace = 'Linq2GraphQL.TestClientNullable' + Port = 5181 + Nullable = $true + } +) + +function Wait-ForServer([string]$url, [System.Diagnostics.Process]$process) { + foreach ($attempt in 1..60) { + if ($process.HasExited) { + throw "Server exited with code $($process.ExitCode) before becoming ready." + } + + try { + Invoke-WebRequest -Uri $url -Method Get -TimeoutSec 2 -SkipHttpErrorCheck | Out-Null + return + } + catch { + Start-Sleep -Milliseconds 500 + } + } + + throw "Server at $url did not become ready in 30 seconds." +} + +Push-Location $repoRoot +try { + Write-Host '==> Building generator and test servers' -ForegroundColor Cyan + dotnet build Linq2GraphQL.CI.slnf --nologo + if ($LASTEXITCODE -ne 0) { throw 'Build failed.' } + + foreach ($client in $clients) { + $endpoint = "http://127.0.0.1:$($client.Port)/graphql/" + Write-Host "==> Starting $($client.Server) on $endpoint" -ForegroundColor Cyan + + $server = Start-Process -PassThru -NoNewWindow -FilePath 'dotnet' -ArgumentList @( + 'run', '--project', $client.Server, '--no-build', '--no-launch-profile' + ) -Environment @{ + ASPNETCORE_URLS = "http://127.0.0.1:$($client.Port)" + ASPNETCORE_ENVIRONMENT = "Development" + } + + try { + Wait-ForServer -url $endpoint -process $server + + Write-Host "==> Generating $($client.Name) into $($client.Output)" -ForegroundColor Cyan + $arguments = @( + 'run', '--project', 'src/Linq2GraphQL.Generator', '--no-build', '--no-launch-profile', '--' + $endpoint + "-c=$($client.Name)" + "-n=$($client.Namespace)" + "-o=$(Join-Path $repoRoot $client.Output)" + '-s=true' + '-d=true' + ) + if ($client.Nullable) { $arguments += '-nu=true' } + + $sentinel = Join-Path $repoRoot "$($client.Output)/Client/$($client.Name).cs" + $generatedAt = Get-Date + + dotnet @arguments + + # The generator reports schema failures on stdout without a non-zero + # exit code, so verify it actually wrote fresh output. + if (-not (Test-Path $sentinel) -or (Get-Item $sentinel).LastWriteTime -lt $generatedAt) { + throw "Generating $($client.Name) failed - $sentinel was not written." + } + } + finally { + if (-not $server.HasExited) { + $server.Kill($true) + } + $server.WaitForExit() + } + } + + if (-not $Check) { + Write-Host '==> Done. Review and commit the regenerated output.' -ForegroundColor Green + exit 0 + } + + $paths = $clients | ForEach-Object { $_.Output } + + # git status refreshes the index first, so files rewritten with identical + # content are not reported as changed just because their mtime moved. + $drifted = @(git status --porcelain -- @paths) + $diff = git diff --stat -- @paths + + git checkout -- @paths + + if ($drifted.Count -gt 0) { + Write-Host '' + Write-Host 'The checked-in test clients do not match the generator output:' -ForegroundColor Red + Write-Host ($diff -join [Environment]::NewLine) + Write-Host '' + Write-Host 'Run ./scripts/regenerate-test-clients.ps1 and commit the result.' -ForegroundColor Red + exit 1 + } + + Write-Host '==> Checked-in test clients match the generator output.' -ForegroundColor Green +} +finally { + Pop-Location +} diff --git a/test/Linq2GraphQL.TestClient/Generated/Client/MutationMethods.cs b/test/Linq2GraphQL.TestClient/Generated/Client/MutationMethods.cs index 3fb3aceb..97af698e 100644 --- a/test/Linq2GraphQL.TestClient/Generated/Client/MutationMethods.cs +++ b/test/Linq2GraphQL.TestClient/Generated/Client/MutationMethods.cs @@ -5,39 +5,56 @@ // Url: https://linq2graphql.com //--------------------------------------------------------------------- -using System.Collections.Generic; using System; +using System.Collections.Generic; using Linq2GraphQL.Client; namespace Linq2GraphQL.TestClient; +/// +/// Implementation of MutationMethods GraphQL operations +/// public class MutationMethods : IMutationMethods { private readonly GraphClient client; + /// + /// Initializes a new instance of the MutationMethods class + /// + /// The GraphQL client instance public MutationMethods(GraphClient client) { - this.client = client; + this.client = client ?? throw new ArgumentNullException(nameof(client)); } + /// + /// Executes setName GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery public GraphQuery SetName(string name = null) { - var arguments = new List + var arguments = new List { - new("name","String", name), + new("name", "String", name), }; - return new GraphQuery(client, "setName", OperationType.Mutation, arguments); + return new GraphQuery(client, "setName", OperationType.Mutation, arguments); } + /// + /// Executes addCustomer GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery public GraphQuery AddCustomer(CustomerInput customer = null) { - var arguments = new List + var arguments = new List { - new("customer","CustomerInput", customer), + new("customer", "CustomerInput", customer), }; - return new GraphQuery(client, "addCustomer", OperationType.Mutation, arguments); + return new GraphQuery(client, "addCustomer", OperationType.Mutation, arguments); } - } +} diff --git a/test/Linq2GraphQL.TestClient/Generated/Client/QueryMethods.cs b/test/Linq2GraphQL.TestClient/Generated/Client/QueryMethods.cs index aef2cb5a..72456615 100644 --- a/test/Linq2GraphQL.TestClient/Generated/Client/QueryMethods.cs +++ b/test/Linq2GraphQL.TestClient/Generated/Client/QueryMethods.cs @@ -5,120 +5,196 @@ // Url: https://linq2graphql.com //--------------------------------------------------------------------- -using System.Collections.Generic; using System; +using System.Collections.Generic; using Linq2GraphQL.Client; namespace Linq2GraphQL.TestClient; +/// +/// Implementation of QueryMethods GraphQL operations +/// public class QueryMethods : IQueryMethods { private readonly GraphClient client; + /// + /// Initializes a new instance of the QueryMethods class + /// + /// The GraphQL client instance public QueryMethods(GraphClient client) { - this.client = client; + this.client = client ?? throw new ArgumentNullException(nameof(client)); } + /// + /// Executes hello GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery public GraphQuery Hello(string name = null) { - var arguments = new List + var arguments = new List + { + new("name", "String", name), + }; + + return new GraphQuery(client, "hello", OperationType.Query, arguments); + } + + /// + /// Executes raiseError GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + public GraphQuery RaiseError(string message = null) + { + var arguments = new List { - new("name","String", name), + new("message", "String", message), }; - return new GraphQuery(client, "hello", OperationType.Query, arguments); + return new GraphQuery(client, "raiseError", OperationType.Query, arguments); } + /// + /// Executes raiseAuthError GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + public GraphQuery RaiseAuthError() + { + var arguments = new List + { + }; + + return new GraphQuery(client, "raiseAuthError", OperationType.Query, arguments); + } + + /// + /// Executes customerReturnNull GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery public GraphQuery CustomerReturnNull() { - var arguments = new List + var arguments = new List { }; - return new GraphQuery(client, "customerReturnNull", OperationType.Query, arguments); + return new GraphQuery(client, "customerReturnNull", OperationType.Query, arguments); } + /// + /// Executes customers GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery> public GraphQuery> Customers() { - var arguments = new List + var arguments = new List { }; - return new GraphQuery>(client, "customers", OperationType.Query, arguments); + return new GraphQuery>(client, "customers", OperationType.Query, arguments); } + /// + /// Executes ordersNoBackwardPagination GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery public GraphQuery OrdersNoBackwardPagination(int? first = null, string after = null, OrderFilterInput where = null, List order = null) { - var arguments = new List + var arguments = new List { - new("first","Int", first), - new("after","String", after), - new("where","OrderFilterInput", where), - new("order","[OrderSortInput!]", order), + new("first", "Int", first), + new("after", "String", after), + new("where", "OrderFilterInput", where), + new("order", "[OrderSortInput!]", order), }; - return new GraphQuery(client, "ordersNoBackwardPagination", OperationType.Query, arguments); + return new GraphQuery(client, "ordersNoBackwardPagination", OperationType.Query, arguments); } + /// + /// Executes ordersNoTotalCount GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphCursorQuery public GraphCursorQuery OrdersNoTotalCount(int? first = null, string after = null, int? last = null, string before = null, OrderFilterInput where = null, List order = null) { - var arguments = new List + var arguments = new List { - new("first","Int", first), - new("after","String", after), - new("last","Int", last), - new("before","String", before), - new("where","OrderFilterInput", where), - new("order","[OrderSortInput!]", order), + new("first", "Int", first), + new("after", "String", after), + new("last", "Int", last), + new("before", "String", before), + new("where", "OrderFilterInput", where), + new("order", "[OrderSortInput!]", order), }; - return new GraphCursorQuery(client, "ordersNoTotalCount", OperationType.Query, arguments); + return new GraphCursorQuery(client, "ordersNoTotalCount", OperationType.Query, arguments); } + /// + /// Executes orders GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphCursorQuery public GraphCursorQuery Orders(int? first = null, string after = null, int? last = null, string before = null, OrderFilterInput where = null, List order = null) { - var arguments = new List + var arguments = new List { - new("first","Int", first), - new("after","String", after), - new("last","Int", last), - new("before","String", before), - new("where","OrderFilterInput", where), - new("order","[OrderSortInput!]", order), + new("first", "Int", first), + new("after", "String", after), + new("last", "Int", last), + new("before", "String", before), + new("where", "OrderFilterInput", where), + new("order", "[OrderSortInput!]", order), }; - return new GraphCursorQuery(client, "orders", OperationType.Query, arguments); + return new GraphCursorQuery(client, "orders", OperationType.Query, arguments); } + /// + /// Executes animals GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphCursorQuery public GraphCursorQuery Animals(int? first = null, string after = null, int? last = null, string before = null, IAnimalFilterInput where = null, List order = null) { - var arguments = new List + var arguments = new List { - new("first","Int", first), - new("after","String", after), - new("last","Int", last), - new("before","String", before), - new("where","IAnimalFilterInput", where), - new("order","[IAnimalSortInput!]", order), + new("first", "Int", first), + new("after", "String", after), + new("last", "Int", last), + new("before", "String", before), + new("where", "IAnimalFilterInput", where), + new("order", "[IAnimalSortInput!]", order), }; - return new GraphCursorQuery(client, "animals", OperationType.Query, arguments); + return new GraphCursorQuery(client, "animals", OperationType.Query, arguments); } + /// + /// Executes ordersOffsetPaging GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphCursorQuery public GraphCursorQuery OrdersOffsetPaging(int? first = null, string after = null, int? last = null, string before = null, OrderFilterInput where = null, List order = null) { - var arguments = new List + var arguments = new List { - new("first","Int", first), - new("after","String", after), - new("last","Int", last), - new("before","String", before), - new("where","OrderFilterInput", where), - new("order","[OrderSortInput!]", order), + new("first", "Int", first), + new("after", "String", after), + new("last", "Int", last), + new("before", "String", before), + new("where", "OrderFilterInput", where), + new("order", "[OrderSortInput!]", order), }; - return new GraphCursorQuery(client, "ordersOffsetPaging", OperationType.Query, arguments); + return new GraphCursorQuery(client, "ordersOffsetPaging", OperationType.Query, arguments); } - } +} diff --git a/test/Linq2GraphQL.TestClient/Generated/Client/SubscriptionMethods.cs b/test/Linq2GraphQL.TestClient/Generated/Client/SubscriptionMethods.cs index 6d3f0010..d4d9bb59 100644 --- a/test/Linq2GraphQL.TestClient/Generated/Client/SubscriptionMethods.cs +++ b/test/Linq2GraphQL.TestClient/Generated/Client/SubscriptionMethods.cs @@ -5,39 +5,56 @@ // Url: https://linq2graphql.com //--------------------------------------------------------------------- -using System.Collections.Generic; using System; +using System.Collections.Generic; using Linq2GraphQL.Client; using Linq2GraphQL.Client.Subscriptions; namespace Linq2GraphQL.TestClient; +/// +/// Implementation of SubscriptionMethods GraphQL operations +/// public class SubscriptionMethods : ISubscriptionMethods { private readonly GraphClient client; + /// + /// Initializes a new instance of the SubscriptionMethods class + /// + /// The GraphQL client instance public SubscriptionMethods(GraphClient client) { - this.client = client; + this.client = client ?? throw new ArgumentNullException(nameof(client)); } + /// + /// Executes customerAdded GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphSubscription public GraphSubscription CustomerAdded() { - var arguments = new List + var arguments = new List { }; - return new GraphSubscription(client, "customerAdded", OperationType.Subscription, arguments); + return new GraphSubscription(client, "customerAdded", OperationType.Subscription, arguments); } + /// + /// Executes customerNameAdded GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphSubscription public GraphSubscription CustomerNameAdded(string name = null) { - var arguments = new List + var arguments = new List { - new("name","String", name), + new("name", "String", name), }; - return new GraphSubscription(client, "customerNameAdded", OperationType.Subscription, arguments); + return new GraphSubscription(client, "customerNameAdded", OperationType.Subscription, arguments); } - } +} diff --git a/test/Linq2GraphQL.TestClient/Generated/Inputs/ByteOperationFilterInput.cs b/test/Linq2GraphQL.TestClient/Generated/Inputs/ByteOperationFilterInput.cs index 4f1ef9e9..93c7d9dc 100644 --- a/test/Linq2GraphQL.TestClient/Generated/Inputs/ByteOperationFilterInput.cs +++ b/test/Linq2GraphQL.TestClient/Generated/Inputs/ByteOperationFilterInput.cs @@ -17,97 +17,97 @@ public partial class ByteOperationFilterInput : GraphInputBase { [GraphQLMember("eq")] [JsonPropertyName("eq")] - public byte? Eq + public sbyte? Eq { - get => GetValue("eq"); + get => GetValue("eq"); set => SetValue("eq", value); } [GraphQLMember("neq")] [JsonPropertyName("neq")] - public byte? Neq + public sbyte? Neq { - get => GetValue("neq"); + get => GetValue("neq"); set => SetValue("neq", value); } [GraphQLMember("in")] [JsonPropertyName("in")] - public List In + public List In { - get => GetValue>("in"); + get => GetValue>("in"); set => SetValue("in", value); } [GraphQLMember("nin")] [JsonPropertyName("nin")] - public List Nin + public List Nin { - get => GetValue>("nin"); + get => GetValue>("nin"); set => SetValue("nin", value); } [GraphQLMember("gt")] [JsonPropertyName("gt")] - public byte? Gt + public sbyte? Gt { - get => GetValue("gt"); + get => GetValue("gt"); set => SetValue("gt", value); } [GraphQLMember("ngt")] [JsonPropertyName("ngt")] - public byte? Ngt + public sbyte? Ngt { - get => GetValue("ngt"); + get => GetValue("ngt"); set => SetValue("ngt", value); } [GraphQLMember("gte")] [JsonPropertyName("gte")] - public byte? Gte + public sbyte? Gte { - get => GetValue("gte"); + get => GetValue("gte"); set => SetValue("gte", value); } [GraphQLMember("ngte")] [JsonPropertyName("ngte")] - public byte? Ngte + public sbyte? Ngte { - get => GetValue("ngte"); + get => GetValue("ngte"); set => SetValue("ngte", value); } [GraphQLMember("lt")] [JsonPropertyName("lt")] - public byte? Lt + public sbyte? Lt { - get => GetValue("lt"); + get => GetValue("lt"); set => SetValue("lt", value); } [GraphQLMember("nlt")] [JsonPropertyName("nlt")] - public byte? Nlt + public sbyte? Nlt { - get => GetValue("nlt"); + get => GetValue("nlt"); set => SetValue("nlt", value); } [GraphQLMember("lte")] [JsonPropertyName("lte")] - public byte? Lte + public sbyte? Lte { - get => GetValue("lte"); + get => GetValue("lte"); set => SetValue("lte", value); } [GraphQLMember("nlte")] [JsonPropertyName("nlte")] - public byte? Nlte + public sbyte? Nlte { - get => GetValue("nlte"); + get => GetValue("nlte"); set => SetValue("nlte", value); } diff --git a/test/Linq2GraphQL.TestClient/Generated/Inputs/InputFactory.cs b/test/Linq2GraphQL.TestClient/Generated/Inputs/InputFactory.cs index f09339b5..17906a10 100644 --- a/test/Linq2GraphQL.TestClient/Generated/Inputs/InputFactory.cs +++ b/test/Linq2GraphQL.TestClient/Generated/Inputs/InputFactory.cs @@ -255,84 +255,84 @@ public static AddressSortInput CreatedDate(this AddressSortInput input, SortEnum public static class ByteOperationFilterInputExtensions { - public static ByteOperationFilterInput Eq(this ByteOperationFilterInput input, byte? val) + public static ByteOperationFilterInput Eq(this ByteOperationFilterInput input, sbyte? val) { input.Eq = val; return input; } - public static ByteOperationFilterInput Neq(this ByteOperationFilterInput input, byte? val) + public static ByteOperationFilterInput Neq(this ByteOperationFilterInput input, sbyte? val) { input.Neq = val; return input; } - public static ByteOperationFilterInput In(this ByteOperationFilterInput input, List val) + public static ByteOperationFilterInput In(this ByteOperationFilterInput input, List val) { input.In = val; return input; } - public static ByteOperationFilterInput Nin(this ByteOperationFilterInput input, List val) + public static ByteOperationFilterInput Nin(this ByteOperationFilterInput input, List val) { input.Nin = val; return input; } - public static ByteOperationFilterInput Gt(this ByteOperationFilterInput input, byte? val) + public static ByteOperationFilterInput Gt(this ByteOperationFilterInput input, sbyte? val) { input.Gt = val; return input; } - public static ByteOperationFilterInput Ngt(this ByteOperationFilterInput input, byte? val) + public static ByteOperationFilterInput Ngt(this ByteOperationFilterInput input, sbyte? val) { input.Ngt = val; return input; } - public static ByteOperationFilterInput Gte(this ByteOperationFilterInput input, byte? val) + public static ByteOperationFilterInput Gte(this ByteOperationFilterInput input, sbyte? val) { input.Gte = val; return input; } - public static ByteOperationFilterInput Ngte(this ByteOperationFilterInput input, byte? val) + public static ByteOperationFilterInput Ngte(this ByteOperationFilterInput input, sbyte? val) { input.Ngte = val; return input; } - public static ByteOperationFilterInput Lt(this ByteOperationFilterInput input, byte? val) + public static ByteOperationFilterInput Lt(this ByteOperationFilterInput input, sbyte? val) { input.Lt = val; return input; } - public static ByteOperationFilterInput Nlt(this ByteOperationFilterInput input, byte? val) + public static ByteOperationFilterInput Nlt(this ByteOperationFilterInput input, sbyte? val) { input.Nlt = val; return input; } - public static ByteOperationFilterInput Lte(this ByteOperationFilterInput input, byte? val) + public static ByteOperationFilterInput Lte(this ByteOperationFilterInput input, sbyte? val) { input.Lte = val; return input; } - public static ByteOperationFilterInput Nlte(this ByteOperationFilterInput input, byte? val) + public static ByteOperationFilterInput Nlte(this ByteOperationFilterInput input, sbyte? val) { input.Nlte = val; return input; @@ -1015,7 +1015,7 @@ public static ItemInput ItemName(this ItemInput input, string val) } - public static ItemInput Data(this ItemInput input, List val) + public static ItemInput Data(this ItemInput input, List val) { input.Data = val; return input; diff --git a/test/Linq2GraphQL.TestClient/Generated/Inputs/ItemInput.cs b/test/Linq2GraphQL.TestClient/Generated/Inputs/ItemInput.cs index aa030c07..ab73488f 100644 --- a/test/Linq2GraphQL.TestClient/Generated/Inputs/ItemInput.cs +++ b/test/Linq2GraphQL.TestClient/Generated/Inputs/ItemInput.cs @@ -33,9 +33,9 @@ public string ItemName [GraphQLMember("data")] [JsonPropertyName("data")] - public List Data + public List Data { - get => GetValue>("data"); + get => GetValue>("data"); set => SetValue("data", value); } diff --git a/test/Linq2GraphQL.TestClient/Generated/Interfaces/IQueryMethods.cs b/test/Linq2GraphQL.TestClient/Generated/Interfaces/IQueryMethods.cs index 49d46dee..cd109fd3 100644 --- a/test/Linq2GraphQL.TestClient/Generated/Interfaces/IQueryMethods.cs +++ b/test/Linq2GraphQL.TestClient/Generated/Interfaces/IQueryMethods.cs @@ -23,6 +23,20 @@ public interface IQueryMethods /// GraphQL query result of type GraphQuery GraphQuery Hello(string name = null); + /// + /// Executes raiseError GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + GraphQuery RaiseError(string message = null); + + /// + /// Executes raiseAuthError GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery + GraphQuery RaiseAuthError(); + /// /// Executes customerReturnNull GraphQL operation /// diff --git a/test/Linq2GraphQL.TestClient/Generated/Types/Item.cs b/test/Linq2GraphQL.TestClient/Generated/Types/Item.cs index 5cc05426..474fe27a 100644 --- a/test/Linq2GraphQL.TestClient/Generated/Types/Item.cs +++ b/test/Linq2GraphQL.TestClient/Generated/Types/Item.cs @@ -26,6 +26,6 @@ public partial class Item : GraphQLTypeBase [GraphQLMember("data")] [JsonPropertyName("data")] - public List Data { get; set; } + public List Data { get; set; } } diff --git a/test/Linq2GraphQL.TestClient/ReadMe.txt b/test/Linq2GraphQL.TestClient/ReadMe.txt index 332fddad..82dd4504 100644 --- a/test/Linq2GraphQL.TestClient/ReadMe.txt +++ b/test/Linq2GraphQL.TestClient/ReadMe.txt @@ -1,10 +1,12 @@ - -*Upgrade Tool -dotnet tool update Linq2GraphQL.Generator -g --prerelease +This folder holds CHECKED-IN GENERATED OUTPUT. Do not edit Generated/ by hand. +To refresh it after a template or test-schema change, run from the repo root: -*Update Schema -Linq2GraphQL https://localhost:7184/graphql/ -c="SampleClient" -n="Linq2GraphQL.TestClient" -o="Generated" -s=true + ./scripts/regenerate-test-clients.ps1 -*Generate local -https://localhost:7184/graphql/ -c="SampleClient" -n="Linq2GraphQL.TestClient" -o="C:\Code\Linq2GraphQL.Client\test\Linq2GraphQL.TestClient\Generated" -s=true -d=true +The script boots both test servers over plain HTTP, runs the generator against +them with the flags that produced this output, and writes the result back here. +Review the diff and commit it. + +The "Test clients up to date" CI job runs the same script with -Check and fails +if the committed output no longer matches what the generator produces. diff --git a/test/Linq2GraphQL.TestClientNullable/Generated/Client/MutationMethods.cs b/test/Linq2GraphQL.TestClientNullable/Generated/Client/MutationMethods.cs index 0942a94a..89ac2339 100644 --- a/test/Linq2GraphQL.TestClientNullable/Generated/Client/MutationMethods.cs +++ b/test/Linq2GraphQL.TestClientNullable/Generated/Client/MutationMethods.cs @@ -5,39 +5,56 @@ // Url: https://linq2graphql.com //--------------------------------------------------------------------- -using System.Collections.Generic; using System; +using System.Collections.Generic; using Linq2GraphQL.Client; namespace Linq2GraphQL.TestClientNullable; +/// +/// Implementation of MutationMethods GraphQL operations +/// public class MutationMethods : IMutationMethods { private readonly GraphClient client; + /// + /// Initializes a new instance of the MutationMethods class + /// + /// The GraphQL client instance public MutationMethods(GraphClient client) { - this.client = client; + this.client = client ?? throw new ArgumentNullException(nameof(client)); } + /// + /// Executes updateCustomer GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery public GraphQuery UpdateCustomer(CustomerInput customer) { - var arguments = new List + var arguments = new List { - new("customer","CustomerInput!", customer), + new("customer", "CustomerInput!", customer), }; - return new GraphQuery(client, "updateCustomer", OperationType.Mutation, arguments); + return new GraphQuery(client, "updateCustomer", OperationType.Mutation, arguments); } + /// + /// Executes updatePerson GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery public GraphQuery UpdatePerson(PersonInput person) { - var arguments = new List + var arguments = new List { - new("person","PersonInput!", person), + new("person", "PersonInput!", person), }; - return new GraphQuery(client, "updatePerson", OperationType.Mutation, arguments); + return new GraphQuery(client, "updatePerson", OperationType.Mutation, arguments); } - } +} diff --git a/test/Linq2GraphQL.TestClientNullable/Generated/Client/QueryMethods.cs b/test/Linq2GraphQL.TestClientNullable/Generated/Client/QueryMethods.cs index 3e264a0b..bbec1f75 100644 --- a/test/Linq2GraphQL.TestClientNullable/Generated/Client/QueryMethods.cs +++ b/test/Linq2GraphQL.TestClientNullable/Generated/Client/QueryMethods.cs @@ -5,92 +5,142 @@ // Url: https://linq2graphql.com //--------------------------------------------------------------------- -using System.Collections.Generic; using System; +using System.Collections.Generic; using Linq2GraphQL.Client; namespace Linq2GraphQL.TestClientNullable; +/// +/// Implementation of QueryMethods GraphQL operations +/// public class QueryMethods : IQueryMethods { private readonly GraphClient client; + /// + /// Initializes a new instance of the QueryMethods class + /// + /// The GraphQL client instance public QueryMethods(GraphClient client) { - this.client = client; + this.client = client ?? throw new ArgumentNullException(nameof(client)); } + /// + /// Executes item GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery public GraphQuery Item() { - var arguments = new List + var arguments = new List { }; - return new GraphQuery(client, "item", OperationType.Query, arguments); + return new GraphQuery(client, "item", OperationType.Query, arguments); } + /// + /// Executes itemDraft GraphQL operation + /// + /// + /// This operation is deprecated: This is an really old method! please d not use it!! + /// [Obsolete("This is an really old method! please d not use it!!")] + /// The operation parameters + /// GraphQL query result of type GraphQuery public GraphQuery ItemDraft() { - var arguments = new List + var arguments = new List { }; - return new GraphQuery(client, "itemDraft", OperationType.Query, arguments); + return new GraphQuery(client, "itemDraft", OperationType.Query, arguments); } + /// + /// Executes customerList GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery> public GraphQuery> CustomerList() { - var arguments = new List + var arguments = new List { }; - return new GraphQuery>(client, "customerList", OperationType.Query, arguments); + return new GraphQuery>(client, "customerList", OperationType.Query, arguments); } + /// + /// Executes customerNullable GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery public GraphQuery CustomerNullable() { - var arguments = new List + var arguments = new List { }; - return new GraphQuery(client, "customerNullable", OperationType.Query, arguments); + return new GraphQuery(client, "customerNullable", OperationType.Query, arguments); } + /// + /// Executes customerListAllNullable GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery?> public GraphQuery?> CustomerListAllNullable() { - var arguments = new List + var arguments = new List { }; - return new GraphQuery?>(client, "customerListAllNullable", OperationType.Query, arguments); + return new GraphQuery?>(client, "customerListAllNullable", OperationType.Query, arguments); } + /// + /// Executes customerListNullable GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery?> public GraphQuery?> CustomerListNullable() { - var arguments = new List + var arguments = new List { }; - return new GraphQuery?>(client, "customerListNullable", OperationType.Query, arguments); + return new GraphQuery?>(client, "customerListNullable", OperationType.Query, arguments); } + /// + /// Executes customerListInList GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery>> public GraphQuery>> CustomerListInList() { - var arguments = new List + var arguments = new List { }; - return new GraphQuery>>(client, "customerListInList", OperationType.Query, arguments); + return new GraphQuery>>(client, "customerListInList", OperationType.Query, arguments); } + /// + /// Executes person GraphQL operation + /// + /// The operation parameters + /// GraphQL query result of type GraphQuery public GraphQuery Person() { - var arguments = new List + var arguments = new List { }; - return new GraphQuery(client, "person", OperationType.Query, arguments); + return new GraphQuery(client, "person", OperationType.Query, arguments); } - } +} diff --git a/test/Linq2GraphQL.TestClientNullable/Generated/Inputs/ItemInput.cs b/test/Linq2GraphQL.TestClientNullable/Generated/Inputs/ItemInput.cs index 51d98fcf..cf19b908 100644 --- a/test/Linq2GraphQL.TestClientNullable/Generated/Inputs/ItemInput.cs +++ b/test/Linq2GraphQL.TestClientNullable/Generated/Inputs/ItemInput.cs @@ -33,9 +33,9 @@ public required string ItemName [GraphQLMember("data")] [JsonPropertyName("data")] - public List? Data + public List? Data { - get => GetValue?>("data"); + get => GetValue?>("data"); set => SetValue("data", value); } diff --git a/test/Linq2GraphQL.TestClientNullable/Generated/Types/Item.cs b/test/Linq2GraphQL.TestClientNullable/Generated/Types/Item.cs index f1b1dfcc..7e6a55cd 100644 --- a/test/Linq2GraphQL.TestClientNullable/Generated/Types/Item.cs +++ b/test/Linq2GraphQL.TestClientNullable/Generated/Types/Item.cs @@ -27,6 +27,6 @@ public partial class Item : GraphQLTypeBase [GraphQLMember("data")] [JsonPropertyName("data")] - public List? Data { get; set; } + public List? Data { get; set; } } diff --git a/test/Linq2GraphQL.TestClientNullable/ReadMe.txt b/test/Linq2GraphQL.TestClientNullable/ReadMe.txt index 90e7d896..82dd4504 100644 --- a/test/Linq2GraphQL.TestClientNullable/ReadMe.txt +++ b/test/Linq2GraphQL.TestClientNullable/ReadMe.txt @@ -1,11 +1,12 @@ - -*Upgrade Tool -dotnet tool update Linq2GraphQL.Generator -g --prerelease +This folder holds CHECKED-IN GENERATED OUTPUT. Do not edit Generated/ by hand. +To refresh it after a template or test-schema change, run from the repo root: -*Update Schema -Linq2GraphQL https://localhost:50741/graphql/ -c="SampleNullableClient" -n="Linq2GraphQL.TestClientNullable" -o="Generated" + ./scripts/regenerate-test-clients.ps1 -*Generate local -https://localhost:50741/graphql/ -c="SampleNullableClient" -n="Linq2GraphQL.TestClientNullable" -o="C:\Code\Github\Linq2GraphQL.Client\test\Linq2GraphQL.TestClientNullable\Generated" -s=true -nu=true +The script boots both test servers over plain HTTP, runs the generator against +them with the flags that produced this output, and writes the result back here. +Review the diff and commit it. +The "Test clients up to date" CI job runs the same script with -Check and fails +if the committed output no longer matches what the generator produces. diff --git a/test/Linq2GraphQL.Tests/QueryNullableTests.cs b/test/Linq2GraphQL.Tests/QueryNullableTests.cs index d023d028..c0366a7a 100644 --- a/test/Linq2GraphQL.Tests/QueryNullableTests.cs +++ b/test/Linq2GraphQL.Tests/QueryNullableTests.cs @@ -34,7 +34,8 @@ public async Task GetItemData() .Select() .ExecuteAsync(); - var data = Encoding.UTF8.GetString(result.Data!.ToArray()); + // The GraphQL Byte scalar is signed (HotChocolate), so Data is List. + var data = Encoding.UTF8.GetString(result.Data!.Select(e => (byte)e).ToArray()); Assert.Equal(result.ItemName, data); }