From 0835f108a96d07fca12115dc39dac6b000a3c3d6 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Thu, 16 Apr 2026 15:04:24 +0800 Subject: [PATCH 1/6] Add default security policy to gateway --- ...erverAspNetCoreHostingBuilderExtensions.cs | 8 +- ...FusionServerServiceCollectionExtensions.cs | 24 +- .../DefaultSecurityTests.cs | 293 +++++++++++++ ...Production_FieldCycleDepthIsNotEnforced.md | 411 ++++++++++++++++++ ...evelopment_FieldCycleDepthIsNotEnforced.md | 411 ++++++++++++++++++ 5 files changed, 1143 insertions(+), 4 deletions(-) create mode 100644 src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/DefaultSecurityTests.cs create mode 100644 src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_Disabled_InProduction_FieldCycleDepthIsNotEnforced.md create mode 100644 src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_InDevelopment_FieldCycleDepthIsNotEnforced.md diff --git a/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerAspNetCoreHostingBuilderExtensions.cs b/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerAspNetCoreHostingBuilderExtensions.cs index 1f0270e0611..20d6b3446cf 100644 --- a/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerAspNetCoreHostingBuilderExtensions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerAspNetCoreHostingBuilderExtensions.cs @@ -21,10 +21,14 @@ public static class FusionServerAspNetCoreHostingBuilderExtensions /// /// The max allowed GraphQL request size. /// + /// + /// Defines if the default security policy should be disabled. + /// /// public static IFusionGatewayBuilder AddGraphQLGateway( this IHostApplicationBuilder builder, string? name = null, - int maxAllowedRequestSize = ServerDefaults.MaxAllowedRequestSize) - => builder.Services.AddGraphQLGatewayServer(name, maxAllowedRequestSize); + int maxAllowedRequestSize = ServerDefaults.MaxAllowedRequestSize, + bool disableDefaultSecurity = false) + => builder.Services.AddGraphQLGatewayServer(name, maxAllowedRequestSize, disableDefaultSecurity); } diff --git a/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerServiceCollectionExtensions.cs b/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerServiceCollectionExtensions.cs index 0533795b30f..e7e15b2d122 100644 --- a/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerServiceCollectionExtensions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerServiceCollectionExtensions.cs @@ -10,6 +10,7 @@ using HotChocolate.Fusion.Configuration; using HotChocolate.Language; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; namespace Microsoft.Extensions.DependencyInjection; @@ -18,17 +19,36 @@ public static class FusionServerServiceCollectionExtensions public static IFusionGatewayBuilder AddGraphQLGatewayServer( this IServiceCollection services, string? name = null, - int maxAllowedRequestSize = ServerDefaults.MaxAllowedRequestSize) + int maxAllowedRequestSize = ServerDefaults.MaxAllowedRequestSize, + bool disableDefaultSecurity = false) { ArgumentNullException.ThrowIfNull(services); ArgumentOutOfRangeException.ThrowIfNegative(maxAllowedRequestSize); - return services + var builder = services .AddGraphQLGateway(name) .AddGraphQLGatewayServerCore(maxAllowedRequestSize) .AddStartupInitialization() .AddDefaultHttpRequestInterceptor() .AddSubscriptionServices(); + + if (!disableDefaultSecurity) + { + builder.DisableIntrospection( + (sp, _) => + { + var environment = sp.GetService(); + return environment?.IsDevelopment() == false; + }); + builder.AddMaxAllowedFieldCycleDepthRule( + isEnabled: (sp, _) => + { + var environment = sp.GetService(); + return environment?.IsDevelopment() == false; + }); + } + + return builder; } private static IFusionGatewayBuilder AddGraphQLGatewayServerCore( diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/DefaultSecurityTests.cs b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/DefaultSecurityTests.cs new file mode 100644 index 00000000000..692dc1dc11c --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/DefaultSecurityTests.cs @@ -0,0 +1,293 @@ +using HotChocolate.AspNetCore; +using HotChocolate.Transport.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; + +namespace HotChocolate.Fusion; + +public class DefaultSecurityTests : FusionTestBase +{ + private const string SimpleSchema = + """ + type Query { + field: String + } + """; + + private const string CyclicSchema = + """ + type Query { + human: Human + } + + type Human { + name: String + relatives: [Human] + } + """; + + [Fact] + public async Task DefaultSecurity_InProduction_IntrospectionIsDisabled() + { + // arrange + using var server1 = CreateSourceSchema("A", SimpleSchema); + + // Override the test base's default Development environment with Production. + using var gateway = await CreateCompositeSchemaAsync( + [("A", server1)], + environmentName: Environments.Production); + + // act + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + + using var result = await client.PostAsync( + "{ __schema { description } }", + new Uri("http://localhost:5000/graphql")); + + // assert + using var response = await result.ReadAsResultAsync(); + response.MatchInlineSnapshot( + """ + { + "errors": [ + { + "message": "Introspection is not allowed for the current request.", + "locations": [ + { + "line": 1, + "column": 3 + } + ], + "extensions": { + "code": "HC0046", + "field": "__schema" + } + } + ] + } + """); + } + + [Fact] + public async Task DefaultSecurity_InDevelopment_IntrospectionIsAllowed() + { + // arrange + using var server1 = CreateSourceSchema("A", SimpleSchema); + + // FusionTestBase already defaults to Development, so no configureServices needed. + using var gateway = await CreateCompositeSchemaAsync( + [("A", server1)], + configureGatewayBuilder: b => b.ConfigureSchemaServices((_, s) => + { + s.RemoveAll(); + s.AddSingleton(); + })); + + // act + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + + using var result = await client.PostAsync( + "{ __schema { description } }", + new Uri("http://localhost:5000/graphql")); + + // assert + using var response = await result.ReadAsResultAsync(); + response.MatchInlineSnapshot( + """ + { + "data": { + "__schema": { + "description": null + } + } + } + """); + } + + [Fact] + public async Task DefaultSecurity_Disabled_InProduction_IntrospectionIsAllowed() + { + // arrange + using var server1 = CreateSourceSchema("A", SimpleSchema); + + using var gateway = await CreateCompositeSchemaAsync( + [("A", server1)], + configureGatewayBuilder: b => + { + b.DisableIntrospection(disable: false); + b.ConfigureSchemaServices((_, s) => + { + s.RemoveAll(); + s.AddSingleton(); + }); + }, + environmentName: Environments.Production); + + // act + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + + using var result = await client.PostAsync( + "{ __schema { description } }", + new Uri("http://localhost:5000/graphql")); + + // assert + using var response = await result.ReadAsResultAsync(); + response.MatchInlineSnapshot( + """ + { + "data": { + "__schema": { + "description": null + } + } + } + """); + } + + [Fact] + public async Task DefaultSecurity_InProduction_FieldCycleDepthIsEnforced() + { + // arrange - 4 levels of `relatives` exceeds the default limit of 3 + using var server1 = CreateSourceSchema("A", CyclicSchema); + + using var gateway = await CreateCompositeSchemaAsync( + [("A", server1)], + environmentName: Environments.Production); + + // act + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + + using var result = await client.PostAsync( + """ + { + human { + relatives { + relatives { + relatives { + relatives { + name + } + } + } + } + } + } + """, + new Uri("http://localhost:5000/graphql")); + + // assert + using var response = await result.ReadAsResultAsync(); + response.MatchInlineSnapshot( + """ + { + "errors": [ + { + "message": "Maximum allowed coordinate cycle depth was exceeded.", + "locations": [ + { + "line": 6, + "column": 11 + } + ], + "path": [ + "human", + "relatives", + "relatives", + "relatives" + ], + "extensions": { + "code": "HC0087" + } + } + ] + } + """); + } + + [Fact] + public async Task DefaultSecurity_InDevelopment_FieldCycleDepthIsNotEnforced() + { + // arrange - 4 levels of `relatives` exceeds the limit but the rule is inactive in Development + // FusionTestBase already defaults to Development, so no configureServices needed. + using var server1 = CreateSourceSchema("A", CyclicSchema); + + using var gateway = await CreateCompositeSchemaAsync( + [("A", server1)], + configureGatewayBuilder: b => b.ConfigureSchemaServices((_, s) => + { + s.RemoveAll(); + s.AddSingleton(); + })); + + // act + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + + using var result = await client.PostAsync( + """ + { + human { + relatives { + relatives { + relatives { + relatives { + name + } + } + } + } + } + } + """, + new Uri("http://localhost:5000/graphql")); + + // assert - query passes validation and executes (no HC0087 error) + using var response = await result.ReadAsResultAsync(); + response.MatchMarkdownSnapshot(); + } + + [Fact] + public async Task DefaultSecurity_Disabled_InProduction_FieldCycleDepthIsNotEnforced() + { + // arrange + using var server1 = CreateSourceSchema("A", CyclicSchema); + + using var gateway = await CreateCompositeSchemaAsync( + [("A", server1)], + configureGatewayBuilder: b => + { + b.RemoveMaxAllowedFieldCycleDepthRule(); + b.ConfigureSchemaServices((_, s) => + { + s.RemoveAll(); + s.AddSingleton(); + }); + }, + environmentName: Environments.Production); + + // act + using var client = GraphQLHttpClient.Create(gateway.CreateClient()); + + using var result = await client.PostAsync( + """ + { + human { + relatives { + relatives { + relatives { + relatives { + name + } + } + } + } + } + } + """, + new Uri("http://localhost:5000/graphql")); + + // assert - query passes validation and executes (no HC0087 error) + using var response = await result.ReadAsResultAsync(); + response.MatchMarkdownSnapshot(); + } +} diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_Disabled_InProduction_FieldCycleDepthIsNotEnforced.md b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_Disabled_InProduction_FieldCycleDepthIsNotEnforced.md new file mode 100644 index 00000000000..1cc6c4fece0 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_Disabled_InProduction_FieldCycleDepthIsNotEnforced.md @@ -0,0 +1,411 @@ +# DefaultSecurity_Disabled_InProduction_FieldCycleDepthIsNotEnforced + +```text +{ + "data": { + "human": { + "relatives": [ + { + "relatives": [ + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46NTk=" + }, + { + "name": "Human: SHVtYW46NjA=" + }, + { + "name": "Human: SHVtYW46NjE=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NjI=" + }, + { + "name": "Human: SHVtYW46NjM=" + }, + { + "name": "Human: SHVtYW46NjQ=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NjU=" + }, + { + "name": "Human: SHVtYW46NjY=" + }, + { + "name": "Human: SHVtYW46Njc=" + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46NTA=" + }, + { + "name": "Human: SHVtYW46NTE=" + }, + { + "name": "Human: SHVtYW46NTI=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NTM=" + }, + { + "name": "Human: SHVtYW46NTQ=" + }, + { + "name": "Human: SHVtYW46NTU=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NTY=" + }, + { + "name": "Human: SHVtYW46NTc=" + }, + { + "name": "Human: SHVtYW46NTg=" + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46NDE=" + }, + { + "name": "Human: SHVtYW46NDI=" + }, + { + "name": "Human: SHVtYW46NDM=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NDQ=" + }, + { + "name": "Human: SHVtYW46NDU=" + }, + { + "name": "Human: SHVtYW46NDY=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NDc=" + }, + { + "name": "Human: SHVtYW46NDg=" + }, + { + "name": "Human: SHVtYW46NDk=" + } + ] + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46ODY=" + }, + { + "name": "Human: SHVtYW46ODc=" + }, + { + "name": "Human: SHVtYW46ODg=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46ODk=" + }, + { + "name": "Human: SHVtYW46OTA=" + }, + { + "name": "Human: SHVtYW46OTE=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46OTI=" + }, + { + "name": "Human: SHVtYW46OTM=" + }, + { + "name": "Human: SHVtYW46OTQ=" + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46Nzc=" + }, + { + "name": "Human: SHVtYW46Nzg=" + }, + { + "name": "Human: SHVtYW46Nzk=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46ODA=" + }, + { + "name": "Human: SHVtYW46ODE=" + }, + { + "name": "Human: SHVtYW46ODI=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46ODM=" + }, + { + "name": "Human: SHVtYW46ODQ=" + }, + { + "name": "Human: SHVtYW46ODU=" + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46Njg=" + }, + { + "name": "Human: SHVtYW46Njk=" + }, + { + "name": "Human: SHVtYW46NzA=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NzE=" + }, + { + "name": "Human: SHVtYW46NzI=" + }, + { + "name": "Human: SHVtYW46NzM=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NzQ=" + }, + { + "name": "Human: SHVtYW46NzU=" + }, + { + "name": "Human: SHVtYW46NzY=" + } + ] + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46MTEz" + }, + { + "name": "Human: SHVtYW46MTE0" + }, + { + "name": "Human: SHVtYW46MTE1" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46MTE2" + }, + { + "name": "Human: SHVtYW46MTE3" + }, + { + "name": "Human: SHVtYW46MTE4" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46MTE5" + }, + { + "name": "Human: SHVtYW46MTIw" + }, + { + "name": "Human: SHVtYW46MTIx" + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46MTA0" + }, + { + "name": "Human: SHVtYW46MTA1" + }, + { + "name": "Human: SHVtYW46MTA2" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46MTA3" + }, + { + "name": "Human: SHVtYW46MTA4" + }, + { + "name": "Human: SHVtYW46MTA5" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46MTEw" + }, + { + "name": "Human: SHVtYW46MTEx" + }, + { + "name": "Human: SHVtYW46MTEy" + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46OTU=" + }, + { + "name": "Human: SHVtYW46OTY=" + }, + { + "name": "Human: SHVtYW46OTc=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46OTg=" + }, + { + "name": "Human: SHVtYW46OTk=" + }, + { + "name": "Human: SHVtYW46MTAw" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46MTAx" + }, + { + "name": "Human: SHVtYW46MTAy" + }, + { + "name": "Human: SHVtYW46MTAz" + } + ] + } + ] + } + ] + } + ] + } + } +} +``` diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_InDevelopment_FieldCycleDepthIsNotEnforced.md b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_InDevelopment_FieldCycleDepthIsNotEnforced.md new file mode 100644 index 00000000000..716146a1d51 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_InDevelopment_FieldCycleDepthIsNotEnforced.md @@ -0,0 +1,411 @@ +# DefaultSecurity_InDevelopment_FieldCycleDepthIsNotEnforced + +```text +{ + "data": { + "human": { + "relatives": [ + { + "relatives": [ + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46NTk=" + }, + { + "name": "Human: SHVtYW46NjA=" + }, + { + "name": "Human: SHVtYW46NjE=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NjI=" + }, + { + "name": "Human: SHVtYW46NjM=" + }, + { + "name": "Human: SHVtYW46NjQ=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NjU=" + }, + { + "name": "Human: SHVtYW46NjY=" + }, + { + "name": "Human: SHVtYW46Njc=" + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46NTA=" + }, + { + "name": "Human: SHVtYW46NTE=" + }, + { + "name": "Human: SHVtYW46NTI=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NTM=" + }, + { + "name": "Human: SHVtYW46NTQ=" + }, + { + "name": "Human: SHVtYW46NTU=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NTY=" + }, + { + "name": "Human: SHVtYW46NTc=" + }, + { + "name": "Human: SHVtYW46NTg=" + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46NDE=" + }, + { + "name": "Human: SHVtYW46NDI=" + }, + { + "name": "Human: SHVtYW46NDM=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NDQ=" + }, + { + "name": "Human: SHVtYW46NDU=" + }, + { + "name": "Human: SHVtYW46NDY=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NDc=" + }, + { + "name": "Human: SHVtYW46NDg=" + }, + { + "name": "Human: SHVtYW46NDk=" + } + ] + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46ODY=" + }, + { + "name": "Human: SHVtYW46ODc=" + }, + { + "name": "Human: SHVtYW46ODg=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46ODk=" + }, + { + "name": "Human: SHVtYW46OTA=" + }, + { + "name": "Human: SHVtYW46OTE=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46OTI=" + }, + { + "name": "Human: SHVtYW46OTM=" + }, + { + "name": "Human: SHVtYW46OTQ=" + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46Nzc=" + }, + { + "name": "Human: SHVtYW46Nzg=" + }, + { + "name": "Human: SHVtYW46Nzk=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46ODA=" + }, + { + "name": "Human: SHVtYW46ODE=" + }, + { + "name": "Human: SHVtYW46ODI=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46ODM=" + }, + { + "name": "Human: SHVtYW46ODQ=" + }, + { + "name": "Human: SHVtYW46ODU=" + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46Njg=" + }, + { + "name": "Human: SHVtYW46Njk=" + }, + { + "name": "Human: SHVtYW46NzA=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NzE=" + }, + { + "name": "Human: SHVtYW46NzI=" + }, + { + "name": "Human: SHVtYW46NzM=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46NzQ=" + }, + { + "name": "Human: SHVtYW46NzU=" + }, + { + "name": "Human: SHVtYW46NzY=" + } + ] + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46MTEz" + }, + { + "name": "Human: SHVtYW46MTE0" + }, + { + "name": "Human: SHVtYW46MTE1" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46MTE2" + }, + { + "name": "Human: SHVtYW46MTE3" + }, + { + "name": "Human: SHVtYW46MTE4" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46MTE5" + }, + { + "name": "Human: SHVtYW46MTIw" + }, + { + "name": "Human: SHVtYW46MTIx" + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46MTA0" + }, + { + "name": "Human: SHVtYW46MTA1" + }, + { + "name": "Human: SHVtYW46MTA2" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46MTA3" + }, + { + "name": "Human: SHVtYW46MTA4" + }, + { + "name": "Human: SHVtYW46MTA5" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46MTEw" + }, + { + "name": "Human: SHVtYW46MTEx" + }, + { + "name": "Human: SHVtYW46MTEy" + } + ] + } + ] + }, + { + "relatives": [ + { + "relatives": [ + { + "name": "Human: SHVtYW46OTU=" + }, + { + "name": "Human: SHVtYW46OTY=" + }, + { + "name": "Human: SHVtYW46OTc=" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46OTg=" + }, + { + "name": "Human: SHVtYW46OTk=" + }, + { + "name": "Human: SHVtYW46MTAw" + } + ] + }, + { + "relatives": [ + { + "name": "Human: SHVtYW46MTAx" + }, + { + "name": "Human: SHVtYW46MTAy" + }, + { + "name": "Human: SHVtYW46MTAz" + } + ] + } + ] + } + ] + } + ] + } + } +} +``` From fe6cb02b46567d60a503e8c236883db114ca8850 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Thu, 16 Apr 2026 22:47:10 +0800 Subject: [PATCH 2/6] edits --- .../FusionServerServiceCollectionExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerServiceCollectionExtensions.cs b/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerServiceCollectionExtensions.cs index e7e15b2d122..f342c184ffc 100644 --- a/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerServiceCollectionExtensions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerServiceCollectionExtensions.cs @@ -38,13 +38,13 @@ public static IFusionGatewayBuilder AddGraphQLGatewayServer( (sp, _) => { var environment = sp.GetService(); - return environment?.IsDevelopment() == false; + return environment?.IsDevelopment() != true; }); builder.AddMaxAllowedFieldCycleDepthRule( isEnabled: (sp, _) => { var environment = sp.GetService(); - return environment?.IsDevelopment() == false; + return environment?.IsDevelopment() != true; }); } From c1ab7b77fe080998eafdac61896d4c651b00ca79 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 17 Apr 2026 16:10:44 +0800 Subject: [PATCH 3/6] fix --- .../HotChocolateAspNetCoreServiceCollectionExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs index a2603de0425..769a359be79 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs @@ -61,13 +61,13 @@ public static IRequestExecutorBuilder AddGraphQLServer( (sp, _) => { var environment = sp.GetService(); - return environment?.IsDevelopment() == false; + return environment?.IsDevelopment() != true; }); builder.AddMaxAllowedFieldCycleDepthRule( isEnabled: (sp, _) => { var environment = sp.GetService(); - return environment?.IsDevelopment() == false; + return environment?.IsDevelopment() != true; }); } From 0e3cc3efac69944bec39100a0a5ea831f1f47b61 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 17 Apr 2026 19:46:52 +0800 Subject: [PATCH 4/6] Merge remote-tracking branch 'origin/main' into mst/gateway-default-security # Conflicts: # src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerAspNetCoreHostingBuilderExtensions.cs --- .editorconfig | 11 + .../Attributes/DataLoaderModuleAttribute.cs | 1 - .../src/GreenDonut.Abstractions/IPromise.cs | 4 +- .../src/GreenDonut.Abstractions/Promise.cs | 4 +- .../Extensions/PagingQueryableExtensions.cs | 23 +- .../PagingQueryInterceptor.cs | 2 +- .../src/GreenDonut.Data/Cursors/CursorKey.cs | 2 +- .../src/GreenDonut/PromiseCacheObserver.cs | 4 +- .../TestContext/Product.cs | 8 +- .../src/ApolloFederation/Representation.cs | 1 - .../Directives/KeyDescriptorExtensions.cs | 8 +- .../ApolloFederation/Types/ServerFields.cs | 2 +- .../test/ApolloFederation.Tests/TestHelper.cs | 5 +- .../IOpaService.cs | 2 +- ...olateAspNetCoreHostingBuilderExtensions.cs | 4 +- ...NetCoreServiceCollectionExtensions.Http.cs | 4 +- .../OperationBatchRequest.cs | 10 +- .../DefaultGraphQLHttpClient.cs | 2 +- .../src/Transport.Sockets/MessagePipeline.cs | 4 +- .../Caching/src/Caching.Memory/Cache.cs | 2 +- .../Execution/Tasks/ExecutionTask.cs | 2 +- .../Core/src/Abstractions/ExtensionData.cs | 5 +- .../Core/src/Abstractions/IExecutable.cs | 4 +- .../src/Abstractions/IQueryableExecutable.cs | 2 +- .../Core/src/Abstractions/ModuleAttribute.cs | 1 - .../Execution/IRequestExecutorManager.cs | 2 +- .../Execution/OperationDocumentSourceText.cs | 2 +- .../Execution/OperationRequestBuilder.cs | 9 +- .../IVariableValueCollection.cs | 2 +- .../Core/src/Features/IFeatureCollection.cs | 2 +- .../Types/ArgumentAssignment.cs | 4 +- .../FileBuilders/ObjectTypeFileBuilder.cs | 2 +- .../FileBuilders/TypeFileBuilderBase.cs | 4 +- .../Types.Analyzers/Helpers/GeneratorUtils.cs | 8 +- .../Helpers/SymbolExtensions.cs | 6 +- ...tributeOnRecordParameterCodeFixProvider.cs | 6 +- .../NodeResolverIdParameterCodeFixProvider.cs | 6 +- ...ngAuthorizationAttributeCodeFixProvider.cs | 1 + .../ConnectionPageInfo.cs | 8 +- .../CursorPaginationAlgorithm.cs | 2 +- .../Core/src/Types.CursorPagination/Edge.cs | 2 +- ...fsetPaginationResolverContextExtensions.cs | 6 +- .../OffsetPaginationAlgorithm.cs | 4 +- .../OffsetPagingArguments.cs | 2 - .../OffsetPagingHandler.cs | 1 - .../src/Types.Validation/SchemaValidator.cs | 4 +- .../Configuration/AggregateTypeInterceptor.cs | 5 +- .../Contracts/ITypeCompletionContext.cs | 5 +- .../Contracts/ITypeSystemObjectContext.cs | 11 +- .../Types/Configuration/OnCompleteType~1.cs | 4 +- .../Types/Configuration/OnInitializeType~1.cs | 4 +- .../Types/Configuration/TypeInterceptor.cs | 2 +- ...questExecutorBuilderExtensions.Services.cs | 4 +- ...xecutionObjectFieldDescriptorExtensions.cs | 4 +- .../Extensions/ExecutionResultExtensions.cs | 4 +- .../Execution/Processing/OperationCompiler.cs | 2 + .../Processing/OperationContext.Operation.cs | 4 +- .../Processing/OperationContext.Utilities.cs | 1 - .../Processing/Tasks/ResolverTask.Pooling.cs | 2 +- .../Execution/Processing/VariableRewriter.cs | 8 +- .../Core/src/Types/Execution/ThrowHelper.cs | 4 +- .../SchemaBuilderExtensions.Middleware.cs | 6 +- .../Types/InvalidSchemaCoordinateException.cs | 1 - .../src/Types/Resolvers/IMiddlewareContext.cs | 1 - .../src/Types/Resolvers/IResolverContext.cs | 1 - src/HotChocolate/Core/src/Types/Schema.cs | 1 - .../Core/src/Types/Text/Json/ResultElement.cs | 2 +- .../Configurations/IConfigurationFactory~1.cs | 2 +- .../ITypeSystemConfigurationTask.cs | 1 - .../Configurations/ObjectFieldBinding.cs | 1 - .../Contracts/IArgumentDescriptor.cs | 10 +- .../Contracts/IInputFieldDescriptor.cs | 12 +- .../Contracts/IObjectFieldDescriptor.cs | 20 +- .../Conventions/ConventionContext.cs | 2 +- .../Conventions/DescriptorContext.Services.cs | 12 +- .../Descriptors/Conventions/ITypeInspector.cs | 2 +- .../Descriptors/ObjectFieldDescriptor.cs | 6 +- .../TypeReferences/SyntaxTypeReference.cs | 2 +- .../Core/src/Types/Types/Directive.cs | 4 +- .../Types/Directives/DeprecatedDirective.cs | 2 +- ...olveWithObjectFieldDescriptorExtensions.cs | 8 +- .../Core/src/Types/Types/NamedTypeBase.cs | 2 +- .../Types/Types/Relay/NodeFieldResolvers.cs | 20 +- .../src/Types/Types/Relay/NodeResolverInfo.cs | 1 - .../Core/src/Types/Utilities/ErrorHelper.cs | 3 +- .../StarWarsCodeFirstTests.cs | 1 - .../QueryWithDocumentation.cs | 2 + .../WithDictionaryArgs.cs | 2 + .../test/Types.Tests/SchemaBuilderTests.cs | 5 +- .../List/QueryableListAllOperationHandler.cs | 2 +- .../List/QueryableListNoneOperationHandler.cs | 2 +- .../List/QueryableListOperationHandlerBase.cs | 2 +- .../List/QueryableListSomeOperationHandler.cs | 2 +- .../Handlers/QueryableDataOperationHandler.cs | 2 +- .../Handlers/QueryableDefaultFieldHandler.cs | 2 +- .../FilterSchemaBuilderExtensions.cs | 2 +- .../Visitor/IFilterProviderDescriptor.cs | 4 +- .../IProjectionProviderDescriptor.cs | 10 +- .../QueryableProjectionFieldHandler.cs | 2 +- .../QueryableProjectionListHandler.cs | 2 +- .../QueryableProjectionScalarHandler.cs | 2 +- .../Interceptor/QueryableFilterInterceptor.cs | 2 +- .../QueryableFirstOrDefaultInterceptor.cs | 2 +- .../QueryableSingleOrDefaultInterceptor.cs | 2 +- .../Interceptor/QueryableSortInterceptor.cs | 2 +- .../QueryableFilterProjectionOptimizer.cs | 2 +- .../QueryablePagingProjectionOptimizer.cs | 2 +- .../QueryableSortProjectionOptimizer.cs | 2 +- .../QueryableProjectionScopeExtensions.cs | 2 +- .../ProjectionsSchemaBuilderExtensions.cs | 2 +- .../IsProjectedProjectionOptimizer.cs | 2 +- ...ueryableRequirementsProjectionOptimizer.cs | 2 +- .../src/Data/Projections/SelectionVisitor.cs | 1 - .../QueryableAscendingSortOperationHandler.cs | 2 +- .../QueryableDefaultSortFieldHandler.cs | 2 +- ...QueryableDescendingSortOperationHandler.cs | 2 +- .../TestContext/Product.cs | 8 +- .../TestContext/Product.cs | 8 +- .../TestContext/Product.cs | 8 +- .../Mock/MatchAnyFieldHandler.cs | 2 +- .../Data.PostgreSQL.Tests/Models/Product.cs | 8 +- .../test/Data.Tests/TestContext1/Product.cs | 8 +- .../test/Data.Tests/TestContext2/Product.cs | 8 +- .../k6/eShop.Inventory/ProductNode.cs | 1 - .../k6/eShop.Reviews/ReviewRepository.cs | 2 + ...erverAspNetCoreHostingBuilderExtensions.cs | 4 +- .../Execution/Nodes/OperationExecutionNode.cs | 4 +- .../Serialization/JsonOperationPlanParser.cs | 2 +- .../Planning/ISelectionSetIndex.cs | 4 +- .../Partitioners/SelectionSetPartitioner.cs | 15 +- .../Text/Json/CompositeResultElement.cs | 2 +- .../Rewriters/DocumentRewriter.cs | 7 +- .../src/Json/JsonWriter.WriteValues.String.cs | 2 +- .../ScalarTypeDefinitionNode.cs | 1 - .../Utilities/SyntaxPrinter.cs | 2 + .../Contracts/ISyntaxNavigator.cs | 2 +- .../src/Language.Visitors/SyntaxVisitor~1.cs | 1 - .../MongoDbDataRequestBuilderExtensions.cs | 12 +- .../Extensions/SchemaBuilderExtensions.cs | 12 +- .../List/MongoDbListAllOperationHandler.cs | 2 +- .../List/MongoDbListNoneOperationHandler.cs | 2 +- .../List/MongoDbListOperationHandlerBase.cs | 4 +- .../List/MongoDbListSomeOperationHandler.cs | 2 +- .../Handlers/MongoDbDefaultFieldHandler.cs | 2 +- .../Handlers/MongoDbProjectionFieldHandler.cs | 2 +- .../MongoDbProjectionScalarHandler.cs | 2 +- .../MongoDbAscendingSortOperationHandler.cs | 2 +- .../MongoDbDefaultSortFieldHandler.cs | 2 +- .../MongoDbDescendingSortOperationHandler.cs | 2 +- .../Contracts/IMutableFieldDefinition.cs | 1 - .../MutableInputFieldDefinition.cs | 1 - .../Types.Mutable/MutableSchemaDefinition.cs | 4 +- .../QueryableSpatialBufferDataHandler.cs | 2 +- .../QueryableSpatialGeometryDataHandler.cs | 2 +- ...QueryableSpatialProjectionScalarHandler.cs | 2 +- .../SpatialConventionDescriptor.cs | 2 +- .../GeoJsonGeometrySerializer.cs | 5 +- .../Spatial/src/Types/ThrowHelper.cs | 2 +- .../Mocha.Abstractions/IFeatureCollection.cs | 2 +- .../src/Mocha.Mediator.Abstractions/Unit.cs | 4 + .../RabbitMQAcknowledgementMiddleware.cs | 2 +- .../src/Mocha/Endpoints/EndpointRouter.cs | 2 + .../ActivityMessagingDiagnosticListener.cs | 26 +- .../src/Mocha/MessageTypes/InboundRoute.cs | 2 + .../src/Mocha/MessageTypes/OutboundRoute.cs | 1 + .../src/Mocha/Middlewares/ReceiveContext.cs | 2 + .../Transport/MessagingTransport.Lifecyle.cs | 5 +- .../src/Mocha/Transport/MessagingTransport.cs | 2 + .../NitroClientServiceCollectionExtensions.cs | 4 - .../FusionGraphPackage.cs | 6 +- .../Commands/ApiKeys/CreateApiKeyCommand.cs | 2 +- .../Commands/ApiKeys/DeleteApiKeyCommand.cs | 2 +- .../Commands/Apis/CreateApiCommand.cs | 6 +- .../Commands/Apis/DeleteApiCommand.cs | 2 +- .../Commands/Apis/SetApiSettingsCommand.cs | 2 +- .../Clients/Components/SelectClientPrompt.cs | 2 +- .../Commands/Clients/CreateClientCommand.cs | 2 +- .../Commands/Clients/DeleteClientCommand.cs | 20 +- .../Commands/Clients/ListClientCommand.cs | 31 +- .../ListClientPublishedVersionsCommand.cs | 10 +- .../Clients/ListClientVersionsCommand.cs | 10 +- .../Commands/Clients/PublishClientCommand.cs | 210 ++-- .../Commands/Clients/UploadClientCommand.cs | 73 +- .../Commands/Clients/ValidateClientCommand.cs | 138 ++- .../Environments/CreateEnvironmentCommand.cs | 4 +- .../Commands/Fusion/FusionComposeCommand.cs | 2 +- .../Commands/Fusion/FusionPublishCommand.cs | 19 +- .../Commands/Fusion/FusionPublishHelpers.cs | 23 +- .../Commands/Fusion/FusionUploadCommand.cs | 83 +- .../Commands/Fusion/FusionValidateCommand.cs | 9 +- .../FusionConfigurationPublishBeginCommand.cs | 8 +- ...FusionConfigurationPublishCommitCommand.cs | 2 +- ...sionConfigurationPublishValidateCommand.cs | 9 +- .../Commands/Login/LoginCommand.cs | 60 +- .../SelectMcpFeatureCollectionPrompt.cs | 2 +- .../Mcp/CreateMcpFeatureCollectionCommand.cs | 5 +- .../Mcp/DeleteMcpFeatureCollectionCommand.cs | 28 +- .../Mcp/ListMcpFeatureCollectionCommand.cs | 15 +- .../Mcp/PublishMcpFeatureCollectionCommand.cs | 194 ++-- .../Mcp/UploadMcpFeatureCollectionCommand.cs | 79 +- .../ValidateMcpFeatureCollectionCommand.cs | 144 ++- .../Commands/Mocks/CreateMockCommand.cs | 2 +- .../Commands/Mocks/ListMockCommand.cs | 11 +- .../Commands/Mocks/UpdateMockCommand.cs | 13 +- .../SelectOpenApiCollectionPrompt.cs | 2 +- .../OpenApi/CreateOpenApiCollectionCommand.cs | 5 +- .../OpenApi/DeleteOpenApiCollectionCommand.cs | 28 +- .../OpenApi/ListOpenApiCollectionCommand.cs | 27 +- .../PublishOpenApiCollectionCommand.cs | 194 ++-- .../OpenApi/UploadOpenApiCollectionCommand.cs | 75 +- .../ValidateOpenApiCollectionCommand.cs | 144 ++- .../CreatePersonalAccessTokenCommand.cs | 2 +- .../RevokePersonalAccessTokenCommand.cs | 4 +- .../Commands/Schemas/PublishSchemaCommand.cs | 288 +++--- .../Commands/Schemas/SchemaHelpers.cs | 11 +- .../Commands/Schemas/UploadSchemaCommand.cs | 4 +- .../Commands/Schemas/ValidateSchemaCommand.cs | 9 +- .../Commands/Stages/DeleteStageCommand.cs | 9 +- .../Commands/Stages/EditStagesCommand.cs | 10 +- .../Commands/Stages/ListStagesCommand.cs | 9 +- .../Commands/Status/StatusCommand.cs | 4 +- .../Workspaces/CreateWorkspaceCommand.cs | 2 +- .../Workspaces/SetDefaultWorkspaceCommand.cs | 59 +- .../Extensions/AnsiConsoleExtensions.cs | 2 +- .../Extensions/ConsoleRenderingExtensions.cs | 5 + .../Extensions/NitroConsoleExtensions.cs | 145 +-- .../Extensions/OptionExtensions.cs | 2 +- .../Extensions/ParseResultExtensions.cs | 58 +- .../Extensions/TreeNodeExtensions.cs | 23 +- .../Helpers/PaginationContainer.cs | 10 +- .../Helpers/StrawberryShakeExtensions.cs | 22 - .../CommandLine/src/CommandLine/Messages.cs | 20 +- .../CommandLine/src/CommandLine/Program.cs | 3 +- .../Services/Console/ActivityEntry.cs | 6 +- .../Services/Console/ActivitySinkFactory.cs | 14 + .../Services/Console/ActivityTree.cs | 250 +++-- .../Services/Console/IActivitySink.cs | 24 + .../Services/Console/IActivitySinkFactory.cs | 6 + .../Services/Console/INitroConsole.cs | 2 + .../Services/Console/INitroConsoleActivity.cs | 2 +- .../InteractiveNitroConsoleActivity.cs | 169 ---- .../InteractiveNitroConsoleChildActivity.cs | 141 --- .../Services/Console/LiveActivitySink.cs | 82 ++ .../Services/Console/NitroConsole.cs | 9 +- .../Services/Console/NitroConsoleActivity.cs | 170 ++-- .../Services/Console/StreamingActivitySink.cs | 119 +++ .../Services/Console/TreeLineWriter.cs | 127 +++ .../Services/NitroClientContext.cs | 31 +- .../Services/Sessions/SessionService.cs | 12 +- .../src/CommandLine/ThrowHelper.cs | 23 +- .../ApiKeys/CreateApiKeyCommandTests.cs | 6 +- .../ApiKeys/DeleteApiKeyCommandTests.cs | 2 +- .../ApiKeys/ListApiKeyCommandTests.cs | 2 +- .../Commands/Apis/CreateApiCommandTests.cs | 4 +- .../Commands/Apis/DeleteApiCommandTests.cs | 2 +- .../Commands/Apis/ListApiCommandTests.cs | 4 +- .../Apis/SetApiSettingsCommandTests.cs | 2 +- .../Commands/Apis/ShowApiCommandTests.cs | 2 +- .../Clients/CreateClientCommandTests.cs | 25 +- .../Clients/DeleteClientCommandTests.cs | 4 +- .../Clients/DownloadClientCommandTests.cs | 2 +- .../Clients/ListClientCommandTests.cs | 7 +- ...ListClientPublishedVersionsCommandTests.cs | 5 +- .../Clients/ListClientVersionsCommandTests.cs | 5 +- .../Clients/PublishClientCommandTests.cs | 85 +- .../Clients/ShowClientCommandTests.cs | 2 +- .../Clients/UnpublishClientCommandTests.cs | 2 +- .../Clients/UploadClientCommandTests.cs | 10 +- .../Clients/ValidateClientCommandTests.cs | 39 +- .../Commands/CommandTestBase.cs | 31 +- .../CreateEnvironmentCommandTests.cs | 4 +- .../ListEnvironmentCommandTests.cs | 4 +- .../ShowEnvironmentCommandTests.cs | 2 +- ...onConfigurationPublishBeginCommandTests.cs | 20 +- ...nConfigurationPublishCancelCommandTests.cs | 6 +- ...nConfigurationPublishCommitCommandTests.cs | 40 +- ...onConfigurationPublishStartCommandTests.cs | 6 +- ...onfigurationPublishValidateCommandTests.cs | 91 +- .../Fusion/FusionDownloadCommandTests.cs | 2 +- .../Fusion/FusionPublishCommandTests.cs | 325 ++++--- .../Fusion/FusionUploadCommandTests.cs | 8 +- .../Fusion/FusionValidateCommandTests.cs | 54 +- .../CreateMcpFeatureCollectionCommandTests.cs | 4 +- .../DeleteMcpFeatureCollectionCommandTests.cs | 4 +- .../ListMcpFeatureCollectionCommandTests.cs | 6 +- ...PublishMcpFeatureCollectionCommandTests.cs | 97 +- .../UploadMcpFeatureCollectionCommandTests.cs | 10 +- ...alidateMcpFeatureCollectionCommandTests.cs | 41 +- .../Commands/Mocks/CreateMockCommandTests.cs | 2 +- .../Commands/Mocks/ListMockCommandTests.cs | 6 +- .../Commands/Mocks/UpdateMockCommandTests.cs | 4 +- .../CreateOpenApiCollectionCommandTests.cs | 6 +- .../DeleteOpenApiCollectionCommandTests.cs | 4 +- .../ListOpenApiCollectionCommandTests.cs | 6 +- .../PublishOpenApiCollectionCommandTests.cs | 97 +- .../UploadOpenApiCollectionCommandTests.cs | 10 +- .../ValidateOpenApiCollectionCommandTests.cs | 41 +- .../CreatePersonalAccessTokenCommandTests.cs | 2 +- .../RevokePersonalAccessTokenCommandTests.cs | 2 +- .../Schemas/DownloadSchemaCommandTests.cs | 2 +- .../Schemas/PublishSchemaCommandTests.cs | 175 ++-- .../Schemas/UploadSchemaCommandTests.cs | 10 +- .../Schemas/ValidateSchemaCommandTests.cs | 28 +- .../Commands/Session/LoginCommandTests.cs | 29 +- .../Commands/Session/LogoutCommandTests.cs | 21 +- .../Session/SessionCommandTestBase.cs | 7 - .../Commands/Session/StatusCommandTests.cs | 6 +- .../Stages/DeleteStageCommandTests.cs | 2 +- .../Commands/Stages/EditStagesCommandTests.cs | 17 +- .../Commands/Stages/ListStagesCommandTests.cs | 4 +- .../Workspaces/CreateWorkspaceCommandTests.cs | 2 +- .../SetDefaultWorkspaceCommandTests.cs | 55 +- .../Workspaces/ShowWorkspaceCommandTests.cs | 2 +- .../InteractiveNitroConsoleActivityTests.cs | 908 ++++++++++++++++++ .../Console/NitroConsoleActivityTests.cs | 746 ++++++++++++++ .../Console/SnapshotActivitySink.cs | 56 ++ .../Console/SnapshotActivitySinkFactory.cs | 14 + .../NitroClientRegistrationTests.cs | 35 +- .../EnvironmentVariableDefaultTests.cs | 7 +- .../Client/src/Core/CachePolicy.Defaults.cs | 2 +- .../Client/src/Core/IConnection.cs | 4 +- .../Client/src/Core/IEntityStoreSnapshot.cs | 4 +- .../Client/src/Core/Internal/ArrayWriter.cs | 4 +- .../Client/src/Core/OperationResult.cs | 2 +- .../Core/Serialization/UploadSerializer.cs | 2 +- .../src/Transport.Http/HttpConnection.cs | 5 +- .../src/Transport.WebSockets/ISocketClient.cs | 2 +- .../Transport.WebSockets/ISocketProtocol.cs | 2 +- .../GraphQLWebSocketMessageParser.cs | 2 +- .../WebSocketConnection.cs | 4 +- .../Builders/TypeReferenceBuilder.cs | 5 - .../Analyzers/FieldCollector.cs | 2 +- .../Analyzers/Models/InputFieldModel.cs | 4 +- .../Descriptors/ClientDescriptor.cs | 1 - .../Descriptors/DataTypeDescriptor.cs | 16 +- .../Descriptors/EntityIdDescriptor.cs | 1 - .../Descriptors/EntityTypeDescriptor.cs | 2 +- .../Descriptors/ScalarEntityIdDescriptor.cs | 1 - .../src/CodeGeneration/ErrorHelper.cs | 2 + .../CodeGeneration/ICSharpSyntaxGenerator.cs | 5 +- .../Mappers/StoreAccessorMapper.cs | 2 +- .../Utilities/OperationDocumentHelper.cs | 2 +- 342 files changed, 4658 insertions(+), 2957 deletions(-) delete mode 100644 src/Nitro/CommandLine/src/CommandLine/Helpers/StrawberryShakeExtensions.cs create mode 100644 src/Nitro/CommandLine/src/CommandLine/Services/Console/ActivitySinkFactory.cs create mode 100644 src/Nitro/CommandLine/src/CommandLine/Services/Console/IActivitySink.cs create mode 100644 src/Nitro/CommandLine/src/CommandLine/Services/Console/IActivitySinkFactory.cs delete mode 100644 src/Nitro/CommandLine/src/CommandLine/Services/Console/InteractiveNitroConsoleActivity.cs delete mode 100644 src/Nitro/CommandLine/src/CommandLine/Services/Console/InteractiveNitroConsoleChildActivity.cs create mode 100644 src/Nitro/CommandLine/src/CommandLine/Services/Console/LiveActivitySink.cs create mode 100644 src/Nitro/CommandLine/src/CommandLine/Services/Console/StreamingActivitySink.cs create mode 100644 src/Nitro/CommandLine/src/CommandLine/Services/Console/TreeLineWriter.cs create mode 100644 src/Nitro/CommandLine/test/CommandLine.Tests/Console/InteractiveNitroConsoleActivityTests.cs create mode 100644 src/Nitro/CommandLine/test/CommandLine.Tests/Console/NitroConsoleActivityTests.cs create mode 100644 src/Nitro/CommandLine/test/CommandLine.Tests/Console/SnapshotActivitySink.cs create mode 100644 src/Nitro/CommandLine/test/CommandLine.Tests/Console/SnapshotActivitySinkFactory.cs diff --git a/.editorconfig b/.editorconfig index e6a99ea7ae0..fe571bfed3d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -124,6 +124,7 @@ resharper_enforce_using_statement_braces_highlighting = error resharper_enforce_while_statement_braces_highlighting = error resharper_redundant_string_format_call_highlighting = error resharper_unused_variable_highlighting = warning +resharper_use_null_propagation_highlighting = warning resharper_use_utf8_string_literal_highlighting = error resharper_use_verbatim_string_highlighting = error @@ -299,6 +300,9 @@ roslynator_null_conditional_operator.avoid_negative_boolean_comparison = true dotnet_diagnostic.IDE0031.severity = none # Abstract type should not have public constructors. dotnet_diagnostic.RCS1160.severity = warning +# Unused parameter. +dotnet_diagnostic.RCS1163.severity = warning +dotnet_diagnostic.IDE0060.severity = none # Use constant instead of field. dotnet_diagnostic.RCS1187.severity = warning # Unnecessary usage of verbatim string literal. @@ -309,6 +313,8 @@ dotnet_diagnostic.RCS1205.severity = warning dotnet_diagnostic.RCS1214.severity = warning # Convert interpolated string to concatenation. dotnet_diagnostic.RCS1217.severity = warning +# Unused element in a documentation comment. +dotnet_diagnostic.RCS1228.severity = warning # Unnecessary null-forgiving operator. dotnet_diagnostic.RCS1249.severity = warning # Remove unnecessary braces. @@ -332,3 +338,8 @@ dotnet_diagnostic.CA1511.severity = none dotnet_diagnostic.CA1512.severity = none dotnet_diagnostic.CA1513.severity = none dotnet_diagnostic.IDE0057.severity = none + +# Tests +[src/**/test/**/*.cs] +# Unused parameter. +dotnet_diagnostic.RCS1163.severity = none diff --git a/src/GreenDonut/src/GreenDonut.Abstractions/Attributes/DataLoaderModuleAttribute.cs b/src/GreenDonut/src/GreenDonut.Abstractions/Attributes/DataLoaderModuleAttribute.cs index b64386da01e..43d35dbb553 100644 --- a/src/GreenDonut/src/GreenDonut.Abstractions/Attributes/DataLoaderModuleAttribute.cs +++ b/src/GreenDonut/src/GreenDonut.Abstractions/Attributes/DataLoaderModuleAttribute.cs @@ -21,7 +21,6 @@ public DataLoaderModuleAttribute(string name) /// /// Gets the module name. /// - /// public string Name { get; } /// diff --git a/src/GreenDonut/src/GreenDonut.Abstractions/IPromise.cs b/src/GreenDonut/src/GreenDonut.Abstractions/IPromise.cs index d08a7c79685..b5eb1463e43 100644 --- a/src/GreenDonut/src/GreenDonut.Abstractions/IPromise.cs +++ b/src/GreenDonut/src/GreenDonut.Abstractions/IPromise.cs @@ -23,13 +23,13 @@ public interface IPromise /// /// Tries to set the result of the async work for this promise. /// - /// + /// The result of the async work. void TrySetResult(object? result); /// /// Tries to set an exception for the async work for this promise. /// - /// + /// The exception to set. void TrySetError(Exception exception); /// diff --git a/src/GreenDonut/src/GreenDonut.Abstractions/Promise.cs b/src/GreenDonut/src/GreenDonut.Abstractions/Promise.cs index c93e8a2464e..7759087cc4d 100644 --- a/src/GreenDonut/src/GreenDonut.Abstractions/Promise.cs +++ b/src/GreenDonut/src/GreenDonut.Abstractions/Promise.cs @@ -6,7 +6,7 @@ namespace GreenDonut; /// a , /// or a . /// -/// +/// The type of the value that the promise will produce. public readonly struct Promise : IPromise { private readonly TaskCompletionSource? _completionSource; @@ -172,7 +172,7 @@ public static Promise Create(bool cloned = false) /// Returns a new instance of . /// public static Promise Create(TValue value, bool cloned = true) - => new(System.Threading.Tasks.Task.FromResult(value), null, isClone: true); + => new(System.Threading.Tasks.Task.FromResult(value), null, isClone: cloned); /// /// Implicitly converts a to a promise. diff --git a/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs b/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs index b3e4416c99d..679731798f2 100644 --- a/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs +++ b/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs @@ -295,7 +295,9 @@ public static async ValueTask> ToPageAsync( /// /// The type of the items in the queryable. /// - /// + /// + /// A dictionary mapping each parent key to its page of results. + /// /// /// If the queryable does not have any keys specified. /// @@ -337,7 +339,9 @@ public static ValueTask>> ToBatchPageAsync /// The type of the items in the queryable. /// - /// + /// + /// A dictionary mapping each parent key to its page of results. + /// /// /// If the queryable does not have any keys specified. /// @@ -383,7 +387,9 @@ public static ValueTask>> ToBatchPageAsync /// The type of the source elements from which keys and values are projected. /// - /// + /// + /// A dictionary mapping each parent key to its page of results. + /// /// /// If the queryable does not have any keys specified. /// @@ -432,7 +438,9 @@ public static ValueTask>> ToBatchPageAsync /// The type of the source elements from which keys and values are projected. /// - /// + /// + /// A dictionary mapping each parent key to its page of results. + /// /// /// If the queryable does not have any keys specified. /// @@ -831,11 +839,8 @@ internal static void SetQueryInterceptor(PagingQueryInterceptor pagingQueryInter s_interceptor.Value.Interceptor = pagingQueryInterceptor; } - internal static void ClearQueryInterceptor(PagingQueryInterceptor pagingQueryInterceptor) + internal static void ClearQueryInterceptor() { - if (s_interceptor.Value is not null) - { - s_interceptor.Value.Interceptor = null; - } + s_interceptor.Value?.Interceptor = null; } } diff --git a/src/GreenDonut/src/GreenDonut.Data.EntityFramework/PagingQueryInterceptor.cs b/src/GreenDonut/src/GreenDonut.Data.EntityFramework/PagingQueryInterceptor.cs index a7729d658cb..6de7e660adc 100644 --- a/src/GreenDonut/src/GreenDonut.Data.EntityFramework/PagingQueryInterceptor.cs +++ b/src/GreenDonut/src/GreenDonut.Data.EntityFramework/PagingQueryInterceptor.cs @@ -33,7 +33,7 @@ public void Dispose() { if (!_disposed) { - PagingQueryableExtensions.ClearQueryInterceptor(this); + PagingQueryableExtensions.ClearQueryInterceptor(); _disposed = true; } } diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/CursorKey.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/CursorKey.cs index 522aaa939c9..4e0552c48fe 100644 --- a/src/GreenDonut/src/GreenDonut.Data/Cursors/CursorKey.cs +++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/CursorKey.cs @@ -44,7 +44,7 @@ public sealed class CursorKey( /// /// The span within the overall cursor that represents the key value. /// - /// + /// The parsed key value. public object? Parse(ReadOnlySpan cursorValue) => CursorKeySerializerHelper.Parse(cursorValue, serializer); diff --git a/src/GreenDonut/src/GreenDonut/PromiseCacheObserver.cs b/src/GreenDonut/src/GreenDonut/PromiseCacheObserver.cs index b396f569da3..c6e62938cef 100644 --- a/src/GreenDonut/src/GreenDonut/PromiseCacheObserver.cs +++ b/src/GreenDonut/src/GreenDonut/PromiseCacheObserver.cs @@ -96,8 +96,8 @@ public virtual void Accept(IPromiseCache cache, string? skipCacheKeyType) /// /// The method is called when a new task is added to the cache. /// - /// - /// + /// The promise cache that the new entry was added to. + /// The promise that was added to the cache. public abstract void OnNext(IPromiseCache cache, Promise promise); public void Dispose() diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/TestContext/Product.cs b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/TestContext/Product.cs index e121d8aa452..a90dcb14b03 100644 --- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/TestContext/Product.cs +++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/TestContext/Product.cs @@ -52,7 +52,9 @@ public class Product /// In this case, it is the responsibility of the client to determine if the amount that is returned is the same as quantityDesired. /// It is invalid to pass in a negative number. /// - /// + /// + /// The desired number of units to remove from stock. + /// /// int: Returns the number actually removed from stock. /// public int RemoveStock(int quantityDesired) @@ -76,7 +78,9 @@ public int RemoveStock(int quantityDesired) /// /// Increments the quantity of a particular item in inventory. - /// + /// + /// The number of units to add to stock. + /// /// int: Returns the quantity that has been added to stock /// public int AddStock(int quantity) diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Representation.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Representation.cs index fe8ae7cef56..397fa27dfe5 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Representation.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Representation.cs @@ -28,7 +28,6 @@ public Representation(string typeName, ObjectValueNode data) /// /// Gets the type name of the entity. /// - /// public string TypeName { get; } /// diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyDescriptorExtensions.cs index d2f6034b406..f2f7724f49a 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyDescriptorExtensions.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyDescriptorExtensions.cs @@ -33,7 +33,7 @@ public static class KeyDescriptorExtensions /// /// Boolean flag to indicate whether this entity is resolvable locally. /// - /// + /// The entity resolver descriptor. /// /// is null. /// @@ -74,7 +74,7 @@ public static IEntityResolverDescriptor Key( /// /// Boolean flag to indicate whether this entity is resolvable locally. /// - /// + /// The entity resolver descriptor. /// /// is null. /// @@ -125,7 +125,7 @@ public static IEntityResolverDescriptor Key( /// /// Boolean flag to indicate whether this entity is resolvable locally. /// - /// + /// The entity resolver descriptor. /// /// is null. /// @@ -166,7 +166,7 @@ public static IEntityResolverDescriptor Key( /// /// Boolean flag to indicate whether this entity is resolvable locally. /// - /// + /// The entity resolver descriptor. /// /// is null. /// diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/ServerFields.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/ServerFields.cs index 1309f6bbfd0..ec0e94ab1eb 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/ServerFields.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/ServerFields.cs @@ -15,7 +15,7 @@ internal static ObjectFieldConfiguration CreateServiceField(IDescriptorContext c descriptor.Type>>().Resolve(s_service); descriptor.Configuration.PureResolver = Resolve; - static _Service Resolve(IResolverContext ctx) + static _Service Resolve(IResolverContext _) => s_service; return descriptor.CreateConfiguration(); diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/TestHelper.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/TestHelper.cs index da482027868..c013f352fc0 100644 --- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/TestHelper.cs +++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/TestHelper.cs @@ -32,10 +32,7 @@ public static IResolverContext CreateResolverContext( mock.SetupGet(c => c.ObjectType).Returns(type); } - if (additionalMockSetup is not null) - { - additionalMockSetup(mock); - } + additionalMockSetup?.Invoke(mock); var context = mock.Object; context.ScopedContextData = ImmutableDictionary.Empty; diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/IOpaService.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/IOpaService.cs index 9840c7af544..c7bd909176f 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/IOpaService.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/IOpaService.cs @@ -11,7 +11,7 @@ public interface IOpaService /// The string parameter representing path of the evaluating policy. /// The instance . /// Cancellation token. - /// + /// The OPA policy decision response. Task QueryAsync( string policyPath, OpaQueryRequest request, diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreHostingBuilderExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreHostingBuilderExtensions.cs index e2534a8d9dc..0040b5c3108 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreHostingBuilderExtensions.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreHostingBuilderExtensions.cs @@ -24,7 +24,9 @@ public static class HotChocolateAspNetCoreHostingBuilderExtensions /// /// Defines if the default security policy should be disabled. /// - /// + /// + /// The for configuration chaining. + /// public static IRequestExecutorBuilder AddGraphQL( this IHostApplicationBuilder builder, string? schemaName = null, diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs index 28079dd2b4f..00ade90a40a 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Http.cs @@ -84,7 +84,9 @@ public static IRequestExecutorBuilder AddHttpRequestInterceptor( /// /// A delegate that allows to configure the GraphQL request. /// - /// + /// + /// The for configuration chaining. + /// public static IRequestExecutorBuilder AddHttpRequestInterceptor( this IRequestExecutorBuilder builder, Func handler) diff --git a/src/HotChocolate/AspNetCore/src/Transport.Abstractions/OperationBatchRequest.cs b/src/HotChocolate/AspNetCore/src/Transport.Abstractions/OperationBatchRequest.cs index d0c90cd9ca5..c0c48134cb8 100644 --- a/src/HotChocolate/AspNetCore/src/Transport.Abstractions/OperationBatchRequest.cs +++ b/src/HotChocolate/AspNetCore/src/Transport.Abstractions/OperationBatchRequest.cs @@ -173,7 +173,10 @@ public override int GetHashCode() /// /// The second to compare. /// - /// + /// + /// true if the two objects are equal; + /// otherwise, false. + /// public static bool operator ==(OperationBatchRequest left, OperationBatchRequest right) => left.Equals(right); @@ -186,7 +189,10 @@ public override int GetHashCode() /// /// The second to compare. /// - /// + /// + /// true if the two objects are not equal; + /// otherwise, false. + /// public static bool operator !=(OperationBatchRequest left, OperationBatchRequest right) => !left.Equals(right); } diff --git a/src/HotChocolate/AspNetCore/src/Transport.Http/DefaultGraphQLHttpClient.cs b/src/HotChocolate/AspNetCore/src/Transport.Http/DefaultGraphQLHttpClient.cs index 171926e6439..088782085bf 100644 --- a/src/HotChocolate/AspNetCore/src/Transport.Http/DefaultGraphQLHttpClient.cs +++ b/src/HotChocolate/AspNetCore/src/Transport.Http/DefaultGraphQLHttpClient.cs @@ -76,7 +76,7 @@ public DefaultGraphQLHttpClient(HttpClient httpClient) /// /// A cancellation token that can be used to cancel the HTTP request. /// - /// + /// The GraphQL HTTP response. /// /// is . /// diff --git a/src/HotChocolate/AspNetCore/src/Transport.Sockets/MessagePipeline.cs b/src/HotChocolate/AspNetCore/src/Transport.Sockets/MessagePipeline.cs index 076d980e54e..333a79b3413 100644 --- a/src/HotChocolate/AspNetCore/src/Transport.Sockets/MessagePipeline.cs +++ b/src/HotChocolate/AspNetCore/src/Transport.Sockets/MessagePipeline.cs @@ -55,8 +55,8 @@ public void OnCompleted(Action completed, T state) where T : class /// /// Run the pipeline and process messages. /// - /// - /// + /// The token to monitor for cancellation requests. + /// A task that represents the asynchronous operation. public async Task RunAsync(CancellationToken cancellationToken) { await Task.WhenAll( diff --git a/src/HotChocolate/Caching/src/Caching.Memory/Cache.cs b/src/HotChocolate/Caching/src/Caching.Memory/Cache.cs index 74ab4573b79..76cf309d02a 100644 --- a/src/HotChocolate/Caching/src/Caching.Memory/Cache.cs +++ b/src/HotChocolate/Caching/src/Caching.Memory/Cache.cs @@ -263,7 +263,7 @@ private CacheEntry InsertNew(string key, TValue value) /// /// Returns all keys in the cache. This method is for testing only. /// - /// + /// All keys currently in the cache. internal IEnumerable GetKeys() { foreach (var entry in _ring) diff --git a/src/HotChocolate/Core/src/Abstractions/Execution/Tasks/ExecutionTask.cs b/src/HotChocolate/Core/src/Abstractions/Execution/Tasks/ExecutionTask.cs index ffe1dd8f594..92595a11bcb 100644 --- a/src/HotChocolate/Core/src/Abstractions/Execution/Tasks/ExecutionTask.cs +++ b/src/HotChocolate/Core/src/Abstractions/Execution/Tasks/ExecutionTask.cs @@ -56,7 +56,7 @@ public void BeginExecute(CancellationToken cancellationToken) } /// - public Task WaitForCompletionAsync(CancellationToken cancellationToken) + public Task WaitForCompletionAsync(CancellationToken _) => _task ?? Task.CompletedTask; private async Task ExecuteInternalAsync(CancellationToken cancellationToken) diff --git a/src/HotChocolate/Core/src/Abstractions/ExtensionData.cs b/src/HotChocolate/Core/src/Abstractions/ExtensionData.cs index d7534464b72..8c09b7b7bfe 100644 --- a/src/HotChocolate/Core/src/Abstractions/ExtensionData.cs +++ b/src/HotChocolate/Core/src/Abstractions/ExtensionData.cs @@ -105,10 +105,7 @@ public bool ContainsKey(string key) public void CopyTo(KeyValuePair[] array, int arrayIndex) { - if (_dict is not null) - { - ((ICollection>)_dict).CopyTo(array, arrayIndex); - } + ((ICollection>?)_dict)?.CopyTo(array, arrayIndex); } public void Clear() diff --git a/src/HotChocolate/Core/src/Abstractions/IExecutable.cs b/src/HotChocolate/Core/src/Abstractions/IExecutable.cs index 1acf03b4159..94752e5daeb 100644 --- a/src/HotChocolate/Core/src/Abstractions/IExecutable.cs +++ b/src/HotChocolate/Core/src/Abstractions/IExecutable.cs @@ -44,8 +44,8 @@ public interface IExecutable /// Returns the only element of a default value if no such element exists. This method /// throws an exception if more than one element satisfies the condition. /// - /// - /// + /// A cancellation token that can be used to cancel the execution. + /// Returns the result, or a default value if no such element exists. ValueTask SingleOrDefaultAsync(CancellationToken cancellationToken = default); /// diff --git a/src/HotChocolate/Core/src/Abstractions/IQueryableExecutable.cs b/src/HotChocolate/Core/src/Abstractions/IQueryableExecutable.cs index 850a06bfb18..9c8ba2136f6 100644 --- a/src/HotChocolate/Core/src/Abstractions/IQueryableExecutable.cs +++ b/src/HotChocolate/Core/src/Abstractions/IQueryableExecutable.cs @@ -3,7 +3,7 @@ namespace HotChocolate; /// /// Represents an executable that has a queryable as its source. /// -/// +/// The element type. public interface IQueryableExecutable : IExecutable { /// diff --git a/src/HotChocolate/Core/src/Abstractions/ModuleAttribute.cs b/src/HotChocolate/Core/src/Abstractions/ModuleAttribute.cs index a9211805210..ae21f281151 100644 --- a/src/HotChocolate/Core/src/Abstractions/ModuleAttribute.cs +++ b/src/HotChocolate/Core/src/Abstractions/ModuleAttribute.cs @@ -25,7 +25,6 @@ public ModuleAttribute(string name, ModuleOptions options = ModuleOptions.Defaul /// /// Gets the module name. /// - /// public string Name { get; } /// diff --git a/src/HotChocolate/Core/src/Execution.Abstractions/Execution/IRequestExecutorManager.cs b/src/HotChocolate/Core/src/Execution.Abstractions/Execution/IRequestExecutorManager.cs index 4fb58074b76..a69fca0b231 100644 --- a/src/HotChocolate/Core/src/Execution.Abstractions/Execution/IRequestExecutorManager.cs +++ b/src/HotChocolate/Core/src/Execution.Abstractions/Execution/IRequestExecutorManager.cs @@ -8,6 +8,6 @@ public interface IRequestExecutorManager : IRequestExecutorProvider /// /// Evict the request executor and schema with the given name. /// - /// + /// The name of the schema whose executor shall be evicted. void EvictExecutor(string? schemaName = null); } diff --git a/src/HotChocolate/Core/src/Execution.Abstractions/Execution/OperationDocumentSourceText.cs b/src/HotChocolate/Core/src/Execution.Abstractions/Execution/OperationDocumentSourceText.cs index ff94f34045b..77b5de97401 100644 --- a/src/HotChocolate/Core/src/Execution.Abstractions/Execution/OperationDocumentSourceText.cs +++ b/src/HotChocolate/Core/src/Execution.Abstractions/Execution/OperationDocumentSourceText.cs @@ -5,7 +5,7 @@ namespace HotChocolate.Execution; /// /// Represents a GraphQL operation document source texts that needs parsing before it can be executed. /// -/// +/// The GraphQL operation document source text. public sealed class OperationDocumentSourceText(string sourceText) : IOperationDocument { /// diff --git a/src/HotChocolate/Core/src/Execution.Abstractions/Execution/OperationRequestBuilder.cs b/src/HotChocolate/Core/src/Execution.Abstractions/Execution/OperationRequestBuilder.cs index 3aa51005e04..e008689045e 100644 --- a/src/HotChocolate/Core/src/Execution.Abstractions/Execution/OperationRequestBuilder.cs +++ b/src/HotChocolate/Core/src/Execution.Abstractions/Execution/OperationRequestBuilder.cs @@ -571,7 +571,9 @@ public IOperationRequest Build() /// /// Creates a new instance of . /// - /// + /// + /// A new . + /// public static OperationRequestBuilder New() => new(); /// @@ -580,7 +582,10 @@ public IOperationRequest Build() /// /// The existing request from which the new builder is created. /// - /// + /// + /// A new initialized from + /// the request. + /// /// /// The request type is not supported. /// diff --git a/src/HotChocolate/Core/src/Execution.Abstractions/IVariableValueCollection.cs b/src/HotChocolate/Core/src/Execution.Abstractions/IVariableValueCollection.cs index 8e86f45b12a..4b1ad9854e1 100644 --- a/src/HotChocolate/Core/src/Execution.Abstractions/IVariableValueCollection.cs +++ b/src/HotChocolate/Core/src/Execution.Abstractions/IVariableValueCollection.cs @@ -17,7 +17,7 @@ public interface IVariableValueCollection : IEnumerable /// Gets a coerced variable value from the collection. /// /// The variable name. - /// + /// The coerced variable value. /// /// A GraphQL execution error is thrown when the /// requested variable cannot be found or cannot diff --git a/src/HotChocolate/Core/src/Features/IFeatureCollection.cs b/src/HotChocolate/Core/src/Features/IFeatureCollection.cs index ee319b7b841..c61d244d525 100644 --- a/src/HotChocolate/Core/src/Features/IFeatureCollection.cs +++ b/src/HotChocolate/Core/src/Features/IFeatureCollection.cs @@ -27,7 +27,7 @@ public interface IFeatureCollection : IEnumerable> /// /// Gets or sets a given feature. Setting a null value removes the feature. /// - /// + /// The type of the feature to get or set. /// The requested feature, or null if it is not present. object? this[Type key] { get; set; } diff --git a/src/HotChocolate/Core/src/Types.Abstractions/Types/ArgumentAssignment.cs b/src/HotChocolate/Core/src/Types.Abstractions/Types/ArgumentAssignment.cs index 2f894fd9703..04a9e2d052c 100644 --- a/src/HotChocolate/Core/src/Types.Abstractions/Types/ArgumentAssignment.cs +++ b/src/HotChocolate/Core/src/Types.Abstractions/Types/ArgumentAssignment.cs @@ -101,7 +101,9 @@ public ArgumentAssignment(string name, IValueNode value) /// /// Returns a string representation of the current argument assignment. /// - /// + /// + /// A string representation of this argument assignment. + /// public override string ToString() => ToSyntaxNode().ToString(indented: true); diff --git a/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ObjectTypeFileBuilder.cs b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ObjectTypeFileBuilder.cs index e0748e42a5f..517f935a361 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ObjectTypeFileBuilder.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/ObjectTypeFileBuilder.cs @@ -114,7 +114,7 @@ protected override void WriteResolversBindingInitialization(IOutputTypeInfo type { if (objectType.NodeResolver.RequiresParameterBindings) { - WriteResolverBindingInitialization(objectType.NodeResolver, typeLookup); + WriteResolverBindingInitialization(objectType.NodeResolver); } WriteIsSelectedInitialization(objectType.NodeResolver); diff --git a/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs index 35b6e9b8f6a..68a8c9ad73b 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs @@ -737,12 +737,12 @@ protected virtual void WriteResolversBindingInitialization(IOutputTypeInfo type, { foreach (var resolver in type.Resolvers) { - WriteResolverBindingInitialization(resolver, typeLookup); + WriteResolverBindingInitialization(resolver); WriteIsSelectedInitialization(resolver); } } - protected void WriteResolverBindingInitialization(Resolver resolver, ILocalTypeLookup typeLookup) + protected void WriteResolverBindingInitialization(Resolver resolver) { if (resolver.Member is not IMethodSymbol resolverMethod) { diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/GeneratorUtils.cs b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/GeneratorUtils.cs index 5aac09ff94c..4c118940cb0 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/GeneratorUtils.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/GeneratorUtils.cs @@ -184,12 +184,8 @@ public static string SanitizeIdentifier(string input) /// public static string? EscapeForStringLiteral(string? s) { - if (s == null) - { - return null; - } - - return s.Replace("\\", "\\\\") + return s? + .Replace("\\", "\\\\") .Replace("\"", "\\\"") .Replace("\n", "\\n") .Replace("\r", "\\r") diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs index 4d643df9fb5..0c649d1c6c8 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs @@ -611,12 +611,8 @@ static void MaterializeParamRefElements(XDocument xDocument) var methodName = qualifiedName.Substring(lastDotIndex + 1); var typeSymbol = ResolveTypeSymbol(typeName, compilation); - if (typeSymbol == null) - { - return null; - } - return typeSymbol + return typeSymbol? .GetMembers(methodName) .OfType() .FirstOrDefault(m => m.ToString() == documentationId); diff --git a/src/HotChocolate/Core/src/Types.Analyzers/IdAttributeOnRecordParameterCodeFixProvider.cs b/src/HotChocolate/Core/src/Types.Analyzers/IdAttributeOnRecordParameterCodeFixProvider.cs index 1fca7613e0a..bde26d67f08 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/IdAttributeOnRecordParameterCodeFixProvider.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/IdAttributeOnRecordParameterCodeFixProvider.cs @@ -31,13 +31,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) // Find the attribute var node = root.FindNode(diagnosticSpan); var attribute = node.AncestorsAndSelf().OfType().FirstOrDefault(); - if (attribute is null) - { - return; - } // Find the attribute list - var attributeList = attribute.Parent as AttributeListSyntax; + var attributeList = attribute?.Parent as AttributeListSyntax; if (attributeList is null) { return; diff --git a/src/HotChocolate/Core/src/Types.Analyzers/NodeResolverIdParameterCodeFixProvider.cs b/src/HotChocolate/Core/src/Types.Analyzers/NodeResolverIdParameterCodeFixProvider.cs index 60aa76aefe8..0f22b855c70 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/NodeResolverIdParameterCodeFixProvider.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/NodeResolverIdParameterCodeFixProvider.cs @@ -31,13 +31,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) // Find the parameter var node = root.FindNode(diagnosticSpan); var parameter = node.AncestorsAndSelf().OfType().FirstOrDefault(); - if (parameter is null) - { - return; - } // Find the method declaration - var methodDeclaration = parameter.AncestorsAndSelf().OfType().FirstOrDefault(); + var methodDeclaration = parameter?.AncestorsAndSelf().OfType().FirstOrDefault(); if (methodDeclaration is null) { return; diff --git a/src/HotChocolate/Core/src/Types.Analyzers/WrongAuthorizationAttributeCodeFixProvider.cs b/src/HotChocolate/Core/src/Types.Analyzers/WrongAuthorizationAttributeCodeFixProvider.cs index a99d0f05149..2f1469e0ae4 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/WrongAuthorizationAttributeCodeFixProvider.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/WrongAuthorizationAttributeCodeFixProvider.cs @@ -39,6 +39,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) // Get the semantic model var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + // ReSharper disable once UseNullPropagation - https://youtrack.jetbrains.com/issue/RSRP-503517 if (semanticModel is null) { return; diff --git a/src/HotChocolate/Core/src/Types.CursorPagination/ConnectionPageInfo.cs b/src/HotChocolate/Core/src/Types.CursorPagination/ConnectionPageInfo.cs index 9a16a87c74a..715cafd2b27 100644 --- a/src/HotChocolate/Core/src/Types.CursorPagination/ConnectionPageInfo.cs +++ b/src/HotChocolate/Core/src/Types.CursorPagination/ConnectionPageInfo.cs @@ -9,10 +9,10 @@ public class ConnectionPageInfo : IPageInfo /// /// Initializes . /// - /// - /// - /// - /// + /// Indicates whether more items exist after the current page. + /// Indicates whether more items exist before the current page. + /// The cursor of the first item in the current page. + /// The cursor of the last item in the current page. public ConnectionPageInfo( bool hasNextPage, bool hasPreviousPage, diff --git a/src/HotChocolate/Core/src/Types.CursorPagination/CursorPaginationAlgorithm.cs b/src/HotChocolate/Core/src/Types.CursorPagination/CursorPaginationAlgorithm.cs index 52fb725494e..15a9c0a6d97 100644 --- a/src/HotChocolate/Core/src/Types.CursorPagination/CursorPaginationAlgorithm.cs +++ b/src/HotChocolate/Core/src/Types.CursorPagination/CursorPaginationAlgorithm.cs @@ -19,7 +19,7 @@ public abstract class CursorPaginationAlgorithm where TQuery : /// /// The query builder. /// The paging arguments. - /// + /// The total number of items in the data set, or null if unknown. /// /// Returns the connection. /// diff --git a/src/HotChocolate/Core/src/Types.CursorPagination/Edge.cs b/src/HotChocolate/Core/src/Types.CursorPagination/Edge.cs index 7287f6ada57..39d7d2de72a 100644 --- a/src/HotChocolate/Core/src/Types.CursorPagination/Edge.cs +++ b/src/HotChocolate/Core/src/Types.CursorPagination/Edge.cs @@ -6,7 +6,7 @@ namespace HotChocolate.Types.Pagination; /// /// Represents an edge in a connection. /// -/// +/// The type of the node. public class Edge : IEdge { private readonly Func, string>? _resolveCursor; diff --git a/src/HotChocolate/Core/src/Types.OffsetPagination/Extensions/OffsetPaginationResolverContextExtensions.cs b/src/HotChocolate/Core/src/Types.OffsetPagination/Extensions/OffsetPaginationResolverContextExtensions.cs index 089dccdd442..d1125d115c4 100644 --- a/src/HotChocolate/Core/src/Types.OffsetPagination/Extensions/OffsetPaginationResolverContextExtensions.cs +++ b/src/HotChocolate/Core/src/Types.OffsetPagination/Extensions/OffsetPaginationResolverContextExtensions.cs @@ -12,8 +12,10 @@ internal static class OffsetPaginationResolverContextExtensions /// /// This method checks if the total count is selected /// - /// - /// + /// The resolver context to inspect. + /// + /// true if the total count field is included in the selection set; otherwise, false. + /// public static bool IsTotalCountSelected(this IResolverContext context) { // TotalCount is one of the heaviest operations. It is only necessary to load totalCount diff --git a/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPaginationAlgorithm.cs b/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPaginationAlgorithm.cs index 19c9114136f..94999c2ba93 100644 --- a/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPaginationAlgorithm.cs +++ b/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPaginationAlgorithm.cs @@ -21,7 +21,7 @@ public abstract class OffsetPaginationAlgorithm /// The paging arguments. /// Specifies if the total count is needed. /// The cancellation token. - /// + /// The paginated collection segment. public ValueTask> ApplyPaginationAsync( TQuery query, OffsetPagingArguments arguments, @@ -37,7 +37,7 @@ public ValueTask> ApplyPaginationAsync( /// Specify the total amount of elements. /// Specifies if the total count is needed. /// The cancellation token. - /// + /// The paginated collection segment. public async ValueTask> ApplyPaginationAsync( TQuery query, OffsetPagingArguments arguments, diff --git a/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPagingArguments.cs b/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPagingArguments.cs index c67312fd003..5670f5c2e64 100644 --- a/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPagingArguments.cs +++ b/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPagingArguments.cs @@ -23,12 +23,10 @@ public OffsetPagingArguments(int? skip, int? take) /// /// The items that shall be skipped. /// - /// public int? Skip { get; } /// /// The count of items that shall be included into the page. /// - /// public int? Take { get; } } diff --git a/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPagingHandler.cs b/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPagingHandler.cs index 926a8757035..f82c66220c9 100644 --- a/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPagingHandler.cs +++ b/src/HotChocolate/Core/src/Types.OffsetPagination/OffsetPagingHandler.cs @@ -32,7 +32,6 @@ protected OffsetPagingHandler(PagingOptions options) /// /// The maximum allowed page size configured for this handler. /// - /// protected int MaxPageSize { get; } /// diff --git a/src/HotChocolate/Core/src/Types.Validation/SchemaValidator.cs b/src/HotChocolate/Core/src/Types.Validation/SchemaValidator.cs index 78cee9437b0..e9da4f7ac0e 100644 --- a/src/HotChocolate/Core/src/Types.Validation/SchemaValidator.cs +++ b/src/HotChocolate/Core/src/Types.Validation/SchemaValidator.cs @@ -60,7 +60,9 @@ public void AddDefaultRules() /// /// The schema to validate. /// The log to which validation issues will be reported. - /// + /// + /// true if the schema is valid; otherwise, false. + /// public bool Validate(ISchemaDefinition schema, IValidationLog log) { ArgumentNullException.ThrowIfNull(schema); diff --git a/src/HotChocolate/Core/src/Types/Configuration/AggregateTypeInterceptor.cs b/src/HotChocolate/Core/src/Types/Configuration/AggregateTypeInterceptor.cs index af17435630f..db001294e34 100644 --- a/src/HotChocolate/Core/src/Types/Configuration/AggregateTypeInterceptor.cs +++ b/src/HotChocolate/Core/src/Types/Configuration/AggregateTypeInterceptor.cs @@ -66,10 +66,7 @@ internal override void OnBeforeCreateSchemaInternal( if (enabled) { - if (temp is not null) - { - temp[i++] = current; - } + temp?[i++] = current; if (_mutationAggregator is null && current.IsMutationAggregator(context)) { diff --git a/src/HotChocolate/Core/src/Types/Configuration/Contracts/ITypeCompletionContext.cs b/src/HotChocolate/Core/src/Types/Configuration/Contracts/ITypeCompletionContext.cs index 27204e37600..95a794b8cec 100644 --- a/src/HotChocolate/Core/src/Types/Configuration/Contracts/ITypeCompletionContext.cs +++ b/src/HotChocolate/Core/src/Types/Configuration/Contracts/ITypeCompletionContext.cs @@ -64,7 +64,10 @@ public interface ITypeCompletionContext : ITypeSystemObjectContext /// /// The resolved directive type. /// - /// + /// + /// true if the directive type was found; + /// otherwise, false. + /// bool TryGetDirectiveType( TypeReference directiveRef, [NotNullWhen(true)] out DirectiveType? directiveType); diff --git a/src/HotChocolate/Core/src/Types/Configuration/Contracts/ITypeSystemObjectContext.cs b/src/HotChocolate/Core/src/Types/Configuration/Contracts/ITypeSystemObjectContext.cs index 9fb005c9774..8d1d9929aec 100644 --- a/src/HotChocolate/Core/src/Types/Configuration/Contracts/ITypeSystemObjectContext.cs +++ b/src/HotChocolate/Core/src/Types/Configuration/Contracts/ITypeSystemObjectContext.cs @@ -17,7 +17,6 @@ public interface ITypeSystemObjectContext : IHasScope, IFeatureProvider /// /// A type reference that points to . /// - /// TypeReference TypeReference { get; } /// @@ -28,7 +27,6 @@ public interface ITypeSystemObjectContext : IHasScope, IFeatureProvider /// /// Defines if is an introspection type. /// - /// bool IsIntrospectionType { get; } /// @@ -81,7 +79,12 @@ public interface ITypeSystemObjectContext : IHasScope, IFeatureProvider /// /// The type reference. /// - /// - /// + /// + /// The predicted type kind. + /// + /// + /// true if the type kind could be predicted; + /// otherwise, false. + /// bool TryPredictTypeKind(TypeReference typeRef, out TypeKind kind); } diff --git a/src/HotChocolate/Core/src/Types/Configuration/OnCompleteType~1.cs b/src/HotChocolate/Core/src/Types/Configuration/OnCompleteType~1.cs index cb717e0d8ab..d9bcae5b7c3 100644 --- a/src/HotChocolate/Core/src/Types/Configuration/OnCompleteType~1.cs +++ b/src/HotChocolate/Core/src/Types/Configuration/OnCompleteType~1.cs @@ -5,7 +5,9 @@ namespace HotChocolate.Configuration; /// /// Represents a callback that is invoked when a type has been completed. /// -/// +/// +/// The type system configuration type. +/// public delegate void OnCompleteType( ITypeCompletionContext context, T? definition, diff --git a/src/HotChocolate/Core/src/Types/Configuration/OnInitializeType~1.cs b/src/HotChocolate/Core/src/Types/Configuration/OnInitializeType~1.cs index 7bd2885b374..9f95e17ac7b 100644 --- a/src/HotChocolate/Core/src/Types/Configuration/OnInitializeType~1.cs +++ b/src/HotChocolate/Core/src/Types/Configuration/OnInitializeType~1.cs @@ -5,7 +5,9 @@ namespace HotChocolate.Configuration; /// /// Represents a callback that is invoked when a type has been completed. /// -/// +/// +/// The type system configuration type. +/// public delegate void OnInitializeType( ITypeDiscoveryContext context, T? definition, diff --git a/src/HotChocolate/Core/src/Types/Configuration/TypeInterceptor.cs b/src/HotChocolate/Core/src/Types/Configuration/TypeInterceptor.cs index 03705cf9d1a..992dcb26069 100644 --- a/src/HotChocolate/Core/src/Types/Configuration/TypeInterceptor.cs +++ b/src/HotChocolate/Core/src/Types/Configuration/TypeInterceptor.cs @@ -435,7 +435,7 @@ public virtual void OnCreateSchemaError(IDescriptorContext context, Exception er /// The type discovery context. /// /// - /// + /// The type dependencies that define the scope. /// public virtual bool TryCreateScope( ITypeDiscoveryContext context, diff --git a/src/HotChocolate/Core/src/Types/Execution/DependencyInjection/RequestExecutorBuilderExtensions.Services.cs b/src/HotChocolate/Core/src/Types/Execution/DependencyInjection/RequestExecutorBuilderExtensions.Services.cs index 309dd46d478..d6e100e8e0e 100644 --- a/src/HotChocolate/Core/src/Types/Execution/DependencyInjection/RequestExecutorBuilderExtensions.Services.cs +++ b/src/HotChocolate/Core/src/Types/Execution/DependencyInjection/RequestExecutorBuilderExtensions.Services.cs @@ -19,7 +19,9 @@ public static partial class RequestExecutorBuilderExtensions /// /// The type of the service that shall be initialized. /// - /// + /// + /// The for configuration chaining. + /// /// /// The is null. /// diff --git a/src/HotChocolate/Core/src/Types/Execution/Extensions/ExecutionObjectFieldDescriptorExtensions.cs b/src/HotChocolate/Core/src/Types/Execution/Extensions/ExecutionObjectFieldDescriptorExtensions.cs index 8e0d3afc737..d9c6b3b339f 100644 --- a/src/HotChocolate/Core/src/Types/Execution/Extensions/ExecutionObjectFieldDescriptorExtensions.cs +++ b/src/HotChocolate/Core/src/Types/Execution/Extensions/ExecutionObjectFieldDescriptorExtensions.cs @@ -21,7 +21,9 @@ public static class ExecutionObjectFieldDescriptorExtensions /// /// The selection set optimizer. /// - /// + /// + /// The for configuration chaining. + /// /// public static IObjectFieldDescriptor UseOptimizer( this IObjectFieldDescriptor descriptor, diff --git a/src/HotChocolate/Core/src/Types/Execution/Extensions/ExecutionResultExtensions.cs b/src/HotChocolate/Core/src/Types/Execution/Extensions/ExecutionResultExtensions.cs index 5086de1e7d2..240fa338b01 100644 --- a/src/HotChocolate/Core/src/Types/Execution/Extensions/ExecutionResultExtensions.cs +++ b/src/HotChocolate/Core/src/Types/Execution/Extensions/ExecutionResultExtensions.cs @@ -40,7 +40,9 @@ public static void WriteTo( /// /// Defines if the JSON should be formatted with indentations. /// - /// + /// + /// The JSON string representation of the execution result. + /// /// /// is null. /// diff --git a/src/HotChocolate/Core/src/Types/Execution/Processing/OperationCompiler.cs b/src/HotChocolate/Core/src/Types/Execution/Processing/OperationCompiler.cs index f4050980bed..bda3fd10888 100644 --- a/src/HotChocolate/Core/src/Types/Execution/Processing/OperationCompiler.cs +++ b/src/HotChocolate/Core/src/Types/Execution/Processing/OperationCompiler.cs @@ -73,7 +73,9 @@ public Operation Compile( string hash, string? operationName, DocumentNode document, +#pragma warning disable RCS1163 // Unused parameter IFeatureProvider context) +#pragma warning restore RCS1163 // Unused parameter { ArgumentException.ThrowIfNullOrWhiteSpace(id); ArgumentNullException.ThrowIfNull(document); diff --git a/src/HotChocolate/Core/src/Types/Execution/Processing/OperationContext.Operation.cs b/src/HotChocolate/Core/src/Types/Execution/Processing/OperationContext.Operation.cs index 59fcdf97e96..029d9b17ced 100644 --- a/src/HotChocolate/Core/src/Types/Execution/Processing/OperationContext.Operation.cs +++ b/src/HotChocolate/Core/src/Types/Execution/Processing/OperationContext.Operation.cs @@ -74,7 +74,9 @@ public object? RootValue /// /// The type context. /// - /// + /// + /// The compiled selection set for the given selection and type. + /// public SelectionSet CollectFields(Selection selection, ObjectType typeContext) { AssertInitialized(); diff --git a/src/HotChocolate/Core/src/Types/Execution/Processing/OperationContext.Utilities.cs b/src/HotChocolate/Core/src/Types/Execution/Processing/OperationContext.Utilities.cs index 19e4d85e98b..6a0ad8813c4 100644 --- a/src/HotChocolate/Core/src/Types/Execution/Processing/OperationContext.Utilities.cs +++ b/src/HotChocolate/Core/src/Types/Execution/Processing/OperationContext.Utilities.cs @@ -33,6 +33,5 @@ public IExecutionDiagnosticEvents DiagnosticEvents /// /// Gets the type converter service. /// - /// public ITypeConverter Converter { get; } } diff --git a/src/HotChocolate/Core/src/Types/Execution/Processing/Tasks/ResolverTask.Pooling.cs b/src/HotChocolate/Core/src/Types/Execution/Processing/Tasks/ResolverTask.Pooling.cs index 9366b429b92..a3bd0dffebd 100644 --- a/src/HotChocolate/Core/src/Types/Execution/Processing/Tasks/ResolverTask.Pooling.cs +++ b/src/HotChocolate/Core/src/Types/Execution/Processing/Tasks/ResolverTask.Pooling.cs @@ -29,7 +29,7 @@ public void Initialize( /// /// Resets the resolver task before returning it to the pool. /// - /// + /// Always true. internal bool Reset() { _completionStatus = ExecutionTaskStatus.Completed; diff --git a/src/HotChocolate/Core/src/Types/Execution/Processing/VariableRewriter.cs b/src/HotChocolate/Core/src/Types/Execution/Processing/VariableRewriter.cs index 29972854d61..b557816d53c 100644 --- a/src/HotChocolate/Core/src/Types/Execution/Processing/VariableRewriter.cs +++ b/src/HotChocolate/Core/src/Types/Execution/Processing/VariableRewriter.cs @@ -99,9 +99,9 @@ private static ObjectValueNode Rewrite( } rewrittenItems[i] = rewritten; } - else if (rewrittenItems is not null) + else { - rewrittenItems[i] = node.Fields[i]; + rewrittenItems?[i] = node.Fields[i]; } } @@ -193,9 +193,9 @@ private static ListValueNode Rewrite( } rewrittenItems[i] = rewritten; } - else if (rewrittenItems is not null) + else { - rewrittenItems[i] = node.Items[i]; + rewrittenItems?[i] = node.Items[i]; } } diff --git a/src/HotChocolate/Core/src/Types/Execution/ThrowHelper.cs b/src/HotChocolate/Core/src/Types/Execution/ThrowHelper.cs index 4f28b3e933a..c13a1a08207 100644 --- a/src/HotChocolate/Core/src/Types/Execution/ThrowHelper.cs +++ b/src/HotChocolate/Core/src/Types/Execution/ThrowHelper.cs @@ -179,7 +179,9 @@ public static GraphQLException ResolverContext_ArgumentDoesNotExist( public static InvalidOperationException RequestExecutorResolver_SchemaNameDoesNotMatch( string configurationSchemaName, string schemaName) => - new("The schema name must align with the schema name expected by the configuration."); + new( + $"The schema name ({schemaName}) must align with the schema name expected by the " + + $"configuration ({configurationSchemaName})."); public static GraphQLException OperationResolverHelper_NoOperationFound( DocumentNode documentNode) => diff --git a/src/HotChocolate/Core/src/Types/Extensions/SchemaBuilderExtensions.Middleware.cs b/src/HotChocolate/Core/src/Types/Extensions/SchemaBuilderExtensions.Middleware.cs index 56d3dac5a62..5434ffda29d 100644 --- a/src/HotChocolate/Core/src/Types/Extensions/SchemaBuilderExtensions.Middleware.cs +++ b/src/HotChocolate/Core/src/Types/Extensions/SchemaBuilderExtensions.Middleware.cs @@ -28,7 +28,7 @@ public static ISchemaBuilder Map( { return builder.Use( FieldClassMiddlewareFactory.Create( - (s, n) => new MapMiddleware( + (_, n) => new MapMiddleware( n, fieldReference, middleware(n)))); } @@ -39,7 +39,7 @@ public static ISchemaBuilder Map( { return builder.Use( FieldClassMiddlewareFactory.Create( - (s, n) => + (_, n) => { var classMiddleware = FieldClassMiddlewareFactory.Create(); @@ -56,7 +56,7 @@ public static ISchemaBuilder Map( { return builder.Use( FieldClassMiddlewareFactory.Create( - (s, n) => + (_, n) => { var classMiddleware = FieldClassMiddlewareFactory diff --git a/src/HotChocolate/Core/src/Types/InvalidSchemaCoordinateException.cs b/src/HotChocolate/Core/src/Types/InvalidSchemaCoordinateException.cs index 29476b815b1..e8d76fb55b8 100644 --- a/src/HotChocolate/Core/src/Types/InvalidSchemaCoordinateException.cs +++ b/src/HotChocolate/Core/src/Types/InvalidSchemaCoordinateException.cs @@ -21,6 +21,5 @@ public InvalidSchemaCoordinateException(string message, SchemaCoordinate coordin /// /// The invalid schema coordinate. /// - /// public SchemaCoordinate Coordinate { get; } } diff --git a/src/HotChocolate/Core/src/Types/Resolvers/IMiddlewareContext.cs b/src/HotChocolate/Core/src/Types/Resolvers/IMiddlewareContext.cs index 3ad1043b3b4..2f9b31ea250 100644 --- a/src/HotChocolate/Core/src/Types/Resolvers/IMiddlewareContext.cs +++ b/src/HotChocolate/Core/src/Types/Resolvers/IMiddlewareContext.cs @@ -21,7 +21,6 @@ public interface IMiddlewareContext : IResolverContext /// /// Defines if at least one middleware has modified the result. /// - /// bool IsResultModified { get; } /// diff --git a/src/HotChocolate/Core/src/Types/Resolvers/IResolverContext.cs b/src/HotChocolate/Core/src/Types/Resolvers/IResolverContext.cs index 7a055e474f2..838678d732c 100644 --- a/src/HotChocolate/Core/src/Types/Resolvers/IResolverContext.cs +++ b/src/HotChocolate/Core/src/Types/Resolvers/IResolverContext.cs @@ -162,7 +162,6 @@ public interface IResolverContext : IHasContextData, IFeatureProvider /// /// Gets the name that the field will have in the response map. /// - /// string ResponseName { get; } /// diff --git a/src/HotChocolate/Core/src/Types/Schema.cs b/src/HotChocolate/Core/src/Types/Schema.cs index 61e1df3dc01..621750373ca 100644 --- a/src/HotChocolate/Core/src/Types/Schema.cs +++ b/src/HotChocolate/Core/src/Types/Schema.cs @@ -56,7 +56,6 @@ IReadOnlyDirectiveDefinitionCollection ISchemaDefinition.DirectiveDefinitions /// /// Gets the schema directives. /// - /// public DirectiveCollection Directives { get; private set; } = null!; IReadOnlyDirectiveCollection IDirectivesProvider.Directives diff --git a/src/HotChocolate/Core/src/Types/Text/Json/ResultElement.cs b/src/HotChocolate/Core/src/Types/Text/Json/ResultElement.cs index 478c38c6f23..057214c4e7e 100644 --- a/src/HotChocolate/Core/src/Types/Text/Json/ResultElement.cs +++ b/src/HotChocolate/Core/src/Types/Text/Json/ResultElement.cs @@ -771,7 +771,7 @@ internal ReadOnlySpan GetRawValue(bool includeQuotes = true) { CheckValidInstance(); - return _parent.GetRawValue(_cursor, includeQuotes: true); + return _parent.GetRawValue(_cursor, includeQuotes); } /// diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/IConfigurationFactory~1.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/IConfigurationFactory~1.cs index b75928f45f9..aaef95f22b1 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/IConfigurationFactory~1.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/IConfigurationFactory~1.cs @@ -11,6 +11,6 @@ public interface IConfigurationFactory : IConfigurationFactory where T : /// /// Creates a new type system configuration. /// - /// + /// The created type system configuration. new T CreateConfiguration(); } diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/ITypeSystemConfigurationTask.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/ITypeSystemConfigurationTask.cs index ca9d86b314c..64a9efdb003 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/ITypeSystemConfigurationTask.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/ITypeSystemConfigurationTask.cs @@ -9,7 +9,6 @@ public interface ITypeSystemConfigurationTask /// /// The definition of the type system member that shall be configured. /// - /// ITypeSystemConfiguration Owner { get; } /// diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/ObjectFieldBinding.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/ObjectFieldBinding.cs index 754f0f17347..9901d8324e7 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/ObjectFieldBinding.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Configurations/ObjectFieldBinding.cs @@ -42,6 +42,5 @@ public ObjectFieldBinding( /// /// Defines if the bound property shall be replaced. /// - /// public bool Replace { get; } } diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IArgumentDescriptor.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IArgumentDescriptor.cs index e2a7a64a814..40fead4fac8 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IArgumentDescriptor.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IArgumentDescriptor.cs @@ -79,7 +79,7 @@ public interface IArgumentDescriptor /// /// /// - /// + /// The input type. IArgumentDescriptor Type() where TInputType : IInputType; @@ -146,7 +146,9 @@ IArgumentDescriptor Type(TInputType inputType) /// /// /// - /// + /// + /// The GraphQL value literal representing the default value. + /// IArgumentDescriptor DefaultValue(IValueNode? value); /// @@ -163,7 +165,9 @@ IArgumentDescriptor Type(TInputType inputType) /// /// /// - /// + /// + /// The runtime default value. + /// IArgumentDescriptor DefaultValue(object? value); /// diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IInputFieldDescriptor.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IInputFieldDescriptor.cs index e7942158df4..cedb487695d 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IInputFieldDescriptor.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IInputFieldDescriptor.cs @@ -97,7 +97,7 @@ public interface IInputFieldDescriptor /// /// /// - /// + /// The input type. IInputFieldDescriptor Type() where TInputType : IInputType; @@ -115,7 +115,7 @@ IInputFieldDescriptor Type() /// /// /// - /// + /// The input type. IInputFieldDescriptor Type(TInputType inputType) where TInputType : class, IInputType; @@ -171,7 +171,9 @@ IInputFieldDescriptor Type(TInputType inputType) /// /// /// - /// + /// + /// The GraphQL value literal representing the default value. + /// IInputFieldDescriptor DefaultValue(IValueNode value); /// @@ -188,7 +190,9 @@ IInputFieldDescriptor Type(TInputType inputType) /// /// /// - /// + /// + /// The runtime default value. + /// IInputFieldDescriptor DefaultValue(object value); /// diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IObjectFieldDescriptor.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IObjectFieldDescriptor.cs index 301ee20057d..570f7e61c69 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IObjectFieldDescriptor.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Contracts/IObjectFieldDescriptor.cs @@ -141,7 +141,9 @@ IObjectFieldDescriptor Argument( /// ]]> /// /// - /// + /// + /// The for configuration chaining. + /// IObjectFieldDescriptor Resolve(FieldResolverDelegate fieldResolver); /// @@ -161,7 +163,9 @@ IObjectFieldDescriptor Argument( /// ]]> /// /// - /// + /// + /// The for configuration chaining. + /// IObjectFieldDescriptor Resolve( FieldResolverDelegate fieldResolver, Type? resultType); @@ -195,7 +199,9 @@ IObjectFieldDescriptor Resolve( /// ]]> /// /// - /// + /// + /// The for configuration chaining. + /// IObjectFieldDescriptor ResolveWith( Expression> propertyOrMethod); @@ -228,7 +234,9 @@ IObjectFieldDescriptor ResolveWith( /// ]]> /// /// - /// + /// + /// The for configuration chaining. + /// IObjectFieldDescriptor ResolveWith(MemberInfo propertyOrMethod); /// @@ -266,7 +274,9 @@ IObjectFieldDescriptor ResolveWith( /// Adds a subscription resolver to to the field /// /// The subscription resolver - /// + /// + /// The for configuration chaining. + /// IObjectFieldDescriptor Subscribe(SubscribeResolverDelegate subscribeResolver); /// diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/ConventionContext.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/ConventionContext.cs index 95aabb11c9d..51326a563cf 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/ConventionContext.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/ConventionContext.cs @@ -44,7 +44,7 @@ internal sealed class ConventionContext( /// /// The descriptor context. /// - /// + /// A new . public static ConventionContext Create( string? scope, IServiceProvider services, diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DescriptorContext.Services.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DescriptorContext.Services.cs index d59f86f05d1..cdf3650f2f8 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DescriptorContext.Services.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DescriptorContext.Services.cs @@ -141,17 +141,7 @@ public ObjectPool GetStringBuilderPool() public T? GetService() where T : class { - if (_appServices is not null) - { - var service = _appServices.GetService(); - - if (service is not null) - { - return service; - } - } - - return _schemaServices.GetService(); + return _appServices?.GetService() ?? _schemaServices.GetService(); } } } diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/ITypeInspector.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/ITypeInspector.cs index 2df7b88e23d..a37690f0d12 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/ITypeInspector.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/ITypeInspector.cs @@ -143,7 +143,7 @@ IExtendedType GetArgumentType( /// /// The type scope. /// - /// + /// The extended type reference. ExtendedTypeReference GetTypeRef( Type type, TypeContext context = TypeContext.None, diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/ObjectFieldDescriptor.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/ObjectFieldDescriptor.cs index e40555dc051..465b1dc46a9 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/ObjectFieldDescriptor.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/ObjectFieldDescriptor.cs @@ -639,7 +639,7 @@ public static ObjectFieldDescriptor New( /// The member this field represents /// The type of the member /// The resolved type - /// + /// A new . public static ObjectFieldDescriptor New( IDescriptorContext context, MemberInfo member, @@ -654,7 +654,7 @@ public static ObjectFieldDescriptor New( /// The expression this field is based on /// The type of the member /// The resolved type - /// + /// A new . public static ObjectFieldDescriptor New( IDescriptorContext context, LambdaExpression expression, @@ -667,7 +667,7 @@ public static ObjectFieldDescriptor New( /// /// The descriptor context /// The definition of the field - /// + /// A new . public static ObjectFieldDescriptor From( IDescriptorContext context, ObjectFieldConfiguration definition) diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/TypeReferences/SyntaxTypeReference.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/TypeReferences/SyntaxTypeReference.cs index 4b6e436ad5d..a1854da3895 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/TypeReferences/SyntaxTypeReference.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/TypeReferences/SyntaxTypeReference.cs @@ -125,7 +125,7 @@ public SyntaxTypeReference WithScope(string? scope = null) public SyntaxTypeReference WithFactory( Func? factory = null) - => new(Type, Context, Scope, Factory); + => new(Type, Context, Scope, factory); public SyntaxTypeReference With( Optional type = default, diff --git a/src/HotChocolate/Core/src/Types/Types/Directive.cs b/src/HotChocolate/Core/src/Types/Types/Directive.cs index f49248f03ef..f3edc8478ef 100644 --- a/src/HotChocolate/Core/src/Types/Types/Directive.cs +++ b/src/HotChocolate/Core/src/Types/Types/Directive.cs @@ -139,9 +139,9 @@ public DirectiveNode ToSyntaxNode(bool removeDefaults) } } } - else if (rewrittenArguments is not null) + else { - rewrittenArguments[index++] = arguments[i]; + rewrittenArguments?[index++] = arguments[i]; } } diff --git a/src/HotChocolate/Core/src/Types/Types/Directives/DeprecatedDirective.cs b/src/HotChocolate/Core/src/Types/Types/Directives/DeprecatedDirective.cs index c6f4a417725..d1baea2428a 100644 --- a/src/HotChocolate/Core/src/Types/Types/Directives/DeprecatedDirective.cs +++ b/src/HotChocolate/Core/src/Types/Types/Directives/DeprecatedDirective.cs @@ -40,7 +40,7 @@ public DeprecatedDirective(string? reason) /// /// Returns a deprecation directive syntax node representation. /// - /// + /// The directive syntax node representation. public DirectiveNode ToNode() => CreateNode(Reason); /// diff --git a/src/HotChocolate/Core/src/Types/Types/Extensions/ResolveWithObjectFieldDescriptorExtensions.cs b/src/HotChocolate/Core/src/Types/Types/Extensions/ResolveWithObjectFieldDescriptorExtensions.cs index 6c18cd3e6ce..8fffd1184e1 100644 --- a/src/HotChocolate/Core/src/Types/Types/Extensions/ResolveWithObjectFieldDescriptorExtensions.cs +++ b/src/HotChocolate/Core/src/Types/Types/Extensions/ResolveWithObjectFieldDescriptorExtensions.cs @@ -20,7 +20,9 @@ public static class ResolveWithObjectFieldDescriptorExtensions /// /// The result type. /// - /// + /// + /// The for configuration chaining. + /// public static IObjectFieldDescriptor ResolveWith( this IObjectFieldDescriptor descriptor, Expression> propertyOrMethod) @@ -51,7 +53,9 @@ public static IObjectFieldDescriptor ResolveWith( /// /// The result type. /// - /// + /// + /// The for configuration chaining. + /// public static IObjectFieldDescriptor ResolveWith( this IObjectFieldDescriptor descriptor, Expression>> propertyOrMethod) diff --git a/src/HotChocolate/Core/src/Types/Types/NamedTypeBase.cs b/src/HotChocolate/Core/src/Types/Types/NamedTypeBase.cs index 5e33c77bc76..549ba589998 100644 --- a/src/HotChocolate/Core/src/Types/Types/NamedTypeBase.cs +++ b/src/HotChocolate/Core/src/Types/Types/NamedTypeBase.cs @@ -148,7 +148,7 @@ public sealed override string ToString() /// /// Returns a from the named type. /// - /// + /// The type definition syntax node. public ITypeDefinitionNode ToSyntaxNode() => FormatType(); ISyntaxNode ISyntaxNodeProvider.ToSyntaxNode() => FormatType(); diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/NodeFieldResolvers.cs b/src/HotChocolate/Core/src/Types/Types/Relay/NodeFieldResolvers.cs index 3df7feb237f..bf3855a1538 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/NodeFieldResolvers.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/NodeFieldResolvers.cs @@ -38,12 +38,7 @@ public static async ValueTask ResolveSingleNodeAsync( } else { - context.ReportError( - ErrorHelper.Relay_NoNodeResolver( - typeName, - context.Path, - context.Selection)); - + context.ReportError(ErrorHelper.Relay_NoNodeResolver(typeName, context.Path)); context.Result = null; } } @@ -66,7 +61,6 @@ public static async ValueTask ResolveManyNodeAsync( { context.ReportError( ErrorHelper.FetchedToManyNodesAtOnce( - context.Selection, context.Path, maxAllowedNodes, list.Items.Count)); @@ -99,11 +93,7 @@ public static async ValueTask ResolveManyNodeAsync( { tasks[i] = s_nullTask; - context.ReportError( - ErrorHelper.Relay_NoNodeResolver( - typeName, - context.Path, - context.Selection)); + context.ReportError(ErrorHelper.Relay_NoNodeResolver(typeName, context.Path)); } } @@ -183,11 +173,7 @@ public static async ValueTask ResolveManyNodeAsync( { results[0] = null; - context.ReportError( - ErrorHelper.Relay_NoNodeResolver( - typeName, - context.Path, - context.Selection)); + context.ReportError(ErrorHelper.Relay_NoNodeResolver(typeName, context.Path)); } context.Result = results; diff --git a/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverInfo.cs b/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverInfo.cs index ab48df5e4b7..3e8f51b9821 100644 --- a/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverInfo.cs +++ b/src/HotChocolate/Core/src/Types/Types/Relay/NodeResolverInfo.cs @@ -30,7 +30,6 @@ internal sealed class NodeResolverInfo( /// /// Defines if the node resolver was inferred from a query field. /// - /// [MemberNotNullWhen(true, nameof(QueryField), nameof(Id))] public bool IsQueryFieldResolver { get; } = resolverField is not null; } diff --git a/src/HotChocolate/Core/src/Types/Utilities/ErrorHelper.cs b/src/HotChocolate/Core/src/Types/Utilities/ErrorHelper.cs index 15e2c1cc94e..26c9a6ac0d6 100644 --- a/src/HotChocolate/Core/src/Types/Utilities/ErrorHelper.cs +++ b/src/HotChocolate/Core/src/Types/Utilities/ErrorHelper.cs @@ -453,7 +453,7 @@ public static ISchemaError NoSchemaTypesAllowedAsRuntimeType( .SetTypeSystemObject(type) .Build(); - public static IError Relay_NoNodeResolver(string typeName, Path path, Selection selection) + public static IError Relay_NoNodeResolver(string typeName, Path path) => ErrorBuilder.New() .SetMessage(ErrorHelper_Relay_NoNodeResolver, typeName) .SetPath(path) @@ -494,7 +494,6 @@ public static ISchemaError NodeResolverMissing( .Build(); public static IError FetchedToManyNodesAtOnce( - Selection selection, Path path, int maxAllowedNodes, int requestNodes) diff --git a/src/HotChocolate/Core/test/Execution.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs index 07b64a243d6..0bd5f511671 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs @@ -1011,7 +1011,6 @@ await executor.ExecuteAsync( /// not lead to partial results. /// The result should consist of a single error stating the allowed depth. /// - /// [Fact] public async Task ExecutionDepthShouldNotLeadToEmptyObjects() { diff --git a/src/HotChocolate/Core/test/Types.Tests.Documentation/QueryWithDocumentation.cs b/src/HotChocolate/Core/test/Types.Tests.Documentation/QueryWithDocumentation.cs index 2f0adeb1c29..ef042d9f936 100644 --- a/src/HotChocolate/Core/test/Types.Tests.Documentation/QueryWithDocumentation.cs +++ b/src/HotChocolate/Core/test/Types.Tests.Documentation/QueryWithDocumentation.cs @@ -16,6 +16,7 @@ public class QueryWithDocumentation /// public string? Foo(string? bar) => bar; +#pragma warning disable RCS1228 /// /// This is a /// multiline summary @@ -24,6 +25,7 @@ public class QueryWithDocumentation /// /// Note: The returns is left intentionally empty /// +#pragma warning restore RCS1228 public string? Baz() => string.Empty; /// diff --git a/src/HotChocolate/Core/test/Types.Tests.Documentation/WithDictionaryArgs.cs b/src/HotChocolate/Core/test/Types.Tests.Documentation/WithDictionaryArgs.cs index 31b24e49656..0075bcb3859 100644 --- a/src/HotChocolate/Core/test/Types.Tests.Documentation/WithDictionaryArgs.cs +++ b/src/HotChocolate/Core/test/Types.Tests.Documentation/WithDictionaryArgs.cs @@ -2,10 +2,12 @@ namespace HotChocolate.Types.Descriptors; public class WithDictionaryArgs { +#pragma warning disable RCS1228 /// /// This is a method description /// /// Args description /// +#pragma warning restore RCS1228 public string Method(Dictionary? args = null) => string.Empty; } diff --git a/src/HotChocolate/Core/test/Types.Tests/SchemaBuilderTests.cs b/src/HotChocolate/Core/test/Types.Tests/SchemaBuilderTests.cs index eb0198ee072..1f2d3cc0065 100644 --- a/src/HotChocolate/Core/test/Types.Tests/SchemaBuilderTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/SchemaBuilderTests.cs @@ -1923,10 +1923,7 @@ public override void Merge(IConventionContext context, Convention convention) { if (convention is MockConvention mockConvention) { - if (mockConvention.Configuration != null) - { - mockConvention.Configuration.IsExtended = true; - } + mockConvention.Configuration?.IsExtended = true; } } } diff --git a/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListAllOperationHandler.cs b/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListAllOperationHandler.cs index 34b53098834..2934cf84a43 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListAllOperationHandler.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListAllOperationHandler.cs @@ -15,5 +15,5 @@ protected override Expression HandleListOperation( LambdaExpression lambda) => FilterExpressionBuilder.All(closureType, context.GetInstance(), lambda); - public static QueryableListAllOperationHandler Create(FilterProviderContext context) => new(); + public static QueryableListAllOperationHandler Create(FilterProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListNoneOperationHandler.cs b/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListNoneOperationHandler.cs index 672f314859b..8877c89e33f 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListNoneOperationHandler.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListNoneOperationHandler.cs @@ -16,5 +16,5 @@ protected override Expression HandleListOperation( FilterExpressionBuilder.Not( FilterExpressionBuilder.Any(closureType, context.GetInstance(), lambda)); - public static QueryableListNoneOperationHandler Create(FilterProviderContext context) => new(); + public static QueryableListNoneOperationHandler Create(FilterProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListOperationHandlerBase.cs b/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListOperationHandlerBase.cs index 34a2e2976dc..0b043f3a8b8 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListOperationHandlerBase.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListOperationHandlerBase.cs @@ -112,7 +112,7 @@ public override bool TryHandleLeave( /// The object field node /// The runtime type of the scope /// The expression of the nested operations - /// + /// The filter expression for the list operation. protected abstract Expression HandleListOperation( QueryableFilterContext context, IFilterField field, diff --git a/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListSomeOperationHandler.cs b/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListSomeOperationHandler.cs index dc3812363e5..fcdd45ff612 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListSomeOperationHandler.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/List/QueryableListSomeOperationHandler.cs @@ -15,5 +15,5 @@ protected override Expression HandleListOperation( LambdaExpression lambda) => FilterExpressionBuilder.Any(closureType, context.GetInstance(), lambda); - public static QueryableListSomeOperationHandler Create(FilterProviderContext context) => new(); + public static QueryableListSomeOperationHandler Create(FilterProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/QueryableDataOperationHandler.cs b/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/QueryableDataOperationHandler.cs index 0ac4857a713..2359ef518df 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/QueryableDataOperationHandler.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/QueryableDataOperationHandler.cs @@ -17,5 +17,5 @@ public override bool CanHandle( && def.Id == Operation; } - public static QueryableDataOperationHandler Create(FilterProviderContext context) => new(); + public static QueryableDataOperationHandler Create(FilterProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/QueryableDefaultFieldHandler.cs b/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/QueryableDefaultFieldHandler.cs index 72d1d84fa3b..fdfab0a0a1a 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/QueryableDefaultFieldHandler.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/QueryableDefaultFieldHandler.cs @@ -146,7 +146,7 @@ public override bool TryHandleLeave( return true; } - public static QueryableDefaultFieldHandler Create(FilterProviderContext context) => new(); + public static QueryableDefaultFieldHandler Create(FilterProviderContext _) => new(); private sealed class ReplaceVariableExpressionVisitor : ExpressionVisitor { diff --git a/src/HotChocolate/Data/src/Data/Filters/Extensions/FilterSchemaBuilderExtensions.cs b/src/HotChocolate/Data/src/Data/Filters/Extensions/FilterSchemaBuilderExtensions.cs index 5bc335eeae1..869f5f0294c 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Extensions/FilterSchemaBuilderExtensions.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Extensions/FilterSchemaBuilderExtensions.cs @@ -47,7 +47,7 @@ public static ISchemaBuilder AddFiltering( Action configure, string? name = null) => builder - .TryAddConvention(sp => new FilterConvention(configure), name) + .TryAddConvention(_ => new FilterConvention(configure), name) .TryAddTypeInterceptor() .TryAddTypeInterceptor(); diff --git a/src/HotChocolate/Data/src/Data/Filters/Visitor/IFilterProviderDescriptor.cs b/src/HotChocolate/Data/src/Data/Filters/Visitor/IFilterProviderDescriptor.cs index b784d38a09f..f6f913b195c 100644 --- a/src/HotChocolate/Data/src/Data/Filters/Visitor/IFilterProviderDescriptor.cs +++ b/src/HotChocolate/Data/src/Data/Filters/Visitor/IFilterProviderDescriptor.cs @@ -18,7 +18,9 @@ IFilterProviderDescriptor AddFieldHandler( /// Adds an instance of a to the provider /// This instance is directly used by the visitor for executing filters /// - /// + /// + /// The field handler instance. + /// /// The type of the field handler /// The descriptor that this methods was called on IFilterProviderDescriptor AddFieldHandler( diff --git a/src/HotChocolate/Data/src/Data/Projections/Convention/IProjectionProviderDescriptor.cs b/src/HotChocolate/Data/src/Data/Projections/Convention/IProjectionProviderDescriptor.cs index 973ecac6ab0..a0fac1945fb 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Convention/IProjectionProviderDescriptor.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Convention/IProjectionProviderDescriptor.cs @@ -64,9 +64,13 @@ IProjectionProviderDescriptor RegisterOptimizer( /// set. This can also be used to rewrite the resolver pipeline. /// This instance is directly used by the visitor for projection the selection set /// - /// - /// - /// + /// + /// The optimizer instance. + /// + /// + /// The type of the optimizer. + /// + /// The descriptor for configuration chaining. IProjectionProviderDescriptor RegisterOptimizer(THandler handler) where THandler : IProjectionOptimizer; } diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionFieldHandler.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionFieldHandler.cs index 0b32ac9b43e..38abddc3979 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionFieldHandler.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionFieldHandler.cs @@ -120,5 +120,5 @@ public override bool TryHandleLeave( return true; } - public static QueryableProjectionFieldHandler Create(ProjectionProviderContext context) => new(); + public static QueryableProjectionFieldHandler Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionListHandler.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionListHandler.cs index 389adbfd75c..9bd165df806 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionListHandler.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionListHandler.cs @@ -101,5 +101,5 @@ public override bool TryHandleLeave( return true; } - public static QueryableProjectionListHandler Create(ProjectionProviderContext context) => new(); + public static QueryableProjectionListHandler Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionScalarHandler.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionScalarHandler.cs index a91ef60c6e9..43745efc754 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionScalarHandler.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Handlers/QueryableProjectionScalarHandler.cs @@ -175,5 +175,5 @@ or ExpressionType.TypeAs } } - public static QueryableProjectionScalarHandler Create(ProjectionProviderContext context) => new(); + public static QueryableProjectionScalarHandler Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableFilterInterceptor.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableFilterInterceptor.cs index ce707b212db..7bbe6526716 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableFilterInterceptor.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableFilterInterceptor.cs @@ -67,5 +67,5 @@ public void AfterProjection(QueryableProjectionContext context, Selection select { } - public static QueryableFilterInterceptor Create(ProjectionProviderContext context) => new(); + public static QueryableFilterInterceptor Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableFirstOrDefaultInterceptor.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableFirstOrDefaultInterceptor.cs index 8fd3d0c6a8a..ddfa52cd34c 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableFirstOrDefaultInterceptor.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableFirstOrDefaultInterceptor.cs @@ -8,5 +8,5 @@ public QueryableFirstOrDefaultInterceptor() { } - public static QueryableFirstOrDefaultInterceptor Create(ProjectionProviderContext context) => new(); + public static QueryableFirstOrDefaultInterceptor Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableSingleOrDefaultInterceptor.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableSingleOrDefaultInterceptor.cs index 28e8c5982bc..fafb22e4864 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableSingleOrDefaultInterceptor.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableSingleOrDefaultInterceptor.cs @@ -9,5 +9,5 @@ public QueryableSingleOrDefaultInterceptor() { } - public static QueryableSingleOrDefaultInterceptor Create(ProjectionProviderContext context) => new(); + public static QueryableSingleOrDefaultInterceptor Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableSortInterceptor.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableSortInterceptor.cs index 3051f34d423..38e08e29177 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableSortInterceptor.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Interceptor/QueryableSortInterceptor.cs @@ -55,5 +55,5 @@ public void AfterProjection(QueryableProjectionContext context, Selection select { } - public static QueryableSortInterceptor Create(ProjectionProviderContext context) => new(); + public static QueryableSortInterceptor Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryableFilterProjectionOptimizer.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryableFilterProjectionOptimizer.cs index 19eec26c72d..d712e73951d 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryableFilterProjectionOptimizer.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryableFilterProjectionOptimizer.cs @@ -33,5 +33,5 @@ static FieldDelegate WrappedPipeline(FieldDelegate next) => return selection; } - public static QueryableFilterProjectionOptimizer Create(ProjectionProviderContext context) => new(); + public static QueryableFilterProjectionOptimizer Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryablePagingProjectionOptimizer.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryablePagingProjectionOptimizer.cs index acd94f0c25c..99af4c782e1 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryablePagingProjectionOptimizer.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryablePagingProjectionOptimizer.cs @@ -170,5 +170,5 @@ private static void CollectSelectionOfNodes( ? new SelectionSetNode(((SelectionSetNode)n).Selections) : n); - public static QueryablePagingProjectionOptimizer Create(ProjectionProviderContext context) => new(); + public static QueryablePagingProjectionOptimizer Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryableSortProjectionOptimizer.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryableSortProjectionOptimizer.cs index d8e2e96e6a1..65d801c31c0 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryableSortProjectionOptimizer.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/Optimizers/QueryableSortProjectionOptimizer.cs @@ -32,5 +32,5 @@ static FieldDelegate WrappedPipeline(FieldDelegate next) return selection; } - public static QueryableSortProjectionOptimizer Create(ProjectionProviderContext context) => new(); + public static QueryableSortProjectionOptimizer Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Projections/Expressions/QueryableProjectionScopeExtensions.cs b/src/HotChocolate/Data/src/Data/Projections/Expressions/QueryableProjectionScopeExtensions.cs index 9f71416f121..0acca1b7a79 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Expressions/QueryableProjectionScopeExtensions.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Expressions/QueryableProjectionScopeExtensions.cs @@ -22,7 +22,7 @@ public static Expression> Project(this QueryableProjectionScope sc /// The scope that contains the projection information /// The target type /// The target result type of the expression - /// + /// The projection expression. public static Expression> Project( this QueryableProjectionScope scope) where T : TTarget diff --git a/src/HotChocolate/Data/src/Data/Projections/Extensions/ProjectionsSchemaBuilderExtensions.cs b/src/HotChocolate/Data/src/Data/Projections/Extensions/ProjectionsSchemaBuilderExtensions.cs index 792f497b12a..88d93aefd2a 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Extensions/ProjectionsSchemaBuilderExtensions.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Extensions/ProjectionsSchemaBuilderExtensions.cs @@ -43,7 +43,7 @@ public static ISchemaBuilder AddProjections( builder .TryAddTypeInterceptor() .TryAddConvention( - sp => new ProjectionConvention(configure), + _ => new ProjectionConvention(configure), name); /// diff --git a/src/HotChocolate/Data/src/Data/Projections/Optimizers/IsProjectedProjectionOptimizer.cs b/src/HotChocolate/Data/src/Data/Projections/Optimizers/IsProjectedProjectionOptimizer.cs index 34160f1f113..8efdf4f47ac 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Optimizers/IsProjectedProjectionOptimizer.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Optimizers/IsProjectedProjectionOptimizer.cs @@ -74,5 +74,5 @@ [new FieldSelectionNode(fieldNode, 0)], return selection; } - public static IsProjectedProjectionOptimizer Create(ProjectionProviderContext context) => new(); + public static IsProjectedProjectionOptimizer Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Projections/Optimizers/QueryableRequirementsProjectionOptimizer.cs b/src/HotChocolate/Data/src/Data/Projections/Optimizers/QueryableRequirementsProjectionOptimizer.cs index cb045de0e26..deaa4c00529 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Optimizers/QueryableRequirementsProjectionOptimizer.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Optimizers/QueryableRequirementsProjectionOptimizer.cs @@ -258,5 +258,5 @@ private static string ToCamelCase(string value) return char.ToLowerInvariant(value[0]) + value[1..]; } - public static QueryableRequirementsProjectionOptimizer Create(ProjectionProviderContext context) => new(); + public static QueryableRequirementsProjectionOptimizer Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/src/Data/Projections/SelectionVisitor.cs b/src/HotChocolate/Data/src/Data/Projections/SelectionVisitor.cs index 3b8520b7461..e9ad141f48f 100644 --- a/src/HotChocolate/Data/src/Data/Projections/SelectionVisitor.cs +++ b/src/HotChocolate/Data/src/Data/Projections/SelectionVisitor.cs @@ -5,7 +5,6 @@ public abstract class SelectionVisitor /// /// The visitor default action. /// - /// protected ISelectionVisitorAction DefaultAction => Continue; /// diff --git a/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableAscendingSortOperationHandler.cs b/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableAscendingSortOperationHandler.cs index 627a78ad34e..2f54c41f575 100644 --- a/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableAscendingSortOperationHandler.cs +++ b/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableAscendingSortOperationHandler.cs @@ -15,7 +15,7 @@ protected override QueryableSortOperation HandleOperation( SortEnumValue? sortEnumValue) => AscendingSortOperation.From(fieldSelector); - public static QueryableAscendingSortOperationHandler Create(SortProviderContext context) => new(); + public static QueryableAscendingSortOperationHandler Create(SortProviderContext _) => new(); private sealed class AscendingSortOperation : QueryableSortOperation { diff --git a/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableDefaultSortFieldHandler.cs b/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableDefaultSortFieldHandler.cs index 74905f284d5..848a49d8278 100644 --- a/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableDefaultSortFieldHandler.cs +++ b/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableDefaultSortFieldHandler.cs @@ -102,7 +102,7 @@ public override bool TryHandleLeave( return true; } - public static QueryableDefaultSortFieldHandler Create(SortProviderContext context) => new(); + public static QueryableDefaultSortFieldHandler Create(SortProviderContext _) => new(); private sealed class ReplaceVariableExpressionVisitor : ExpressionVisitor { diff --git a/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableDescendingSortOperationHandler.cs b/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableDescendingSortOperationHandler.cs index 1b52a4c1ef0..fc35d84d30a 100644 --- a/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableDescendingSortOperationHandler.cs +++ b/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableDescendingSortOperationHandler.cs @@ -15,7 +15,7 @@ protected override QueryableSortOperation HandleOperation( SortEnumValue? sortEnumValue) => DescendingSortOperation.From(fieldSelector); - public static QueryableDescendingSortOperationHandler Create(SortProviderContext context) => new(); + public static QueryableDescendingSortOperationHandler Create(SortProviderContext _) => new(); private sealed class DescendingSortOperation : QueryableSortOperation { diff --git a/src/HotChocolate/Data/test/Data.EntityFramework.Pagination.Tests/TestContext/Product.cs b/src/HotChocolate/Data/test/Data.EntityFramework.Pagination.Tests/TestContext/Product.cs index 8c6ccfc890b..f069f18c7be 100644 --- a/src/HotChocolate/Data/test/Data.EntityFramework.Pagination.Tests/TestContext/Product.cs +++ b/src/HotChocolate/Data/test/Data.EntityFramework.Pagination.Tests/TestContext/Product.cs @@ -52,7 +52,9 @@ public class Product /// In this case, it is the responsibility of the client to determine if the amount that is returned is the same as quantityDesired. /// It is invalid to pass in a negative number. /// - /// + /// + /// The desired number of units to remove from stock. + /// /// int: Returns the number actually removed from stock. /// public int RemoveStock(int quantityDesired) @@ -76,7 +78,9 @@ public int RemoveStock(int quantityDesired) /// /// Increments the quantity of a particular item in inventory. - /// + /// + /// The number of units to add to stock. + /// /// int: Returns the quantity that has been added to stock /// public int AddStock(int quantity) diff --git a/src/HotChocolate/Data/test/Data.EntityFramework.Tests/TestContext/Product.cs b/src/HotChocolate/Data/test/Data.EntityFramework.Tests/TestContext/Product.cs index 8c6ccfc890b..f069f18c7be 100644 --- a/src/HotChocolate/Data/test/Data.EntityFramework.Tests/TestContext/Product.cs +++ b/src/HotChocolate/Data/test/Data.EntityFramework.Tests/TestContext/Product.cs @@ -52,7 +52,9 @@ public class Product /// In this case, it is the responsibility of the client to determine if the amount that is returned is the same as quantityDesired. /// It is invalid to pass in a negative number. /// - /// + /// + /// The desired number of units to remove from stock. + /// /// int: Returns the number actually removed from stock. /// public int RemoveStock(int quantityDesired) @@ -76,7 +78,9 @@ public int RemoveStock(int quantityDesired) /// /// Increments the quantity of a particular item in inventory. - /// + /// + /// The number of units to add to stock. + /// /// int: Returns the quantity that has been added to stock /// public int AddStock(int quantity) diff --git a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/TestContext/Product.cs b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/TestContext/Product.cs index 8c6ccfc890b..f069f18c7be 100644 --- a/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/TestContext/Product.cs +++ b/src/HotChocolate/Data/test/Data.Filters.SqlServer.Tests/TestContext/Product.cs @@ -52,7 +52,9 @@ public class Product /// In this case, it is the responsibility of the client to determine if the amount that is returned is the same as quantityDesired. /// It is invalid to pass in a negative number. /// - /// + /// + /// The desired number of units to remove from stock. + /// /// int: Returns the number actually removed from stock. /// public int RemoveStock(int quantityDesired) @@ -76,7 +78,9 @@ public int RemoveStock(int quantityDesired) /// /// Increments the quantity of a particular item in inventory. - /// + /// + /// The number of units to add to stock. + /// /// int: Returns the quantity that has been added to stock /// public int AddStock(int quantity) diff --git a/src/HotChocolate/Data/test/Data.Filters.Tests/Mock/MatchAnyFieldHandler.cs b/src/HotChocolate/Data/test/Data.Filters.Tests/Mock/MatchAnyFieldHandler.cs index dacd7851d3c..4106a60e7cb 100644 --- a/src/HotChocolate/Data/test/Data.Filters.Tests/Mock/MatchAnyFieldHandler.cs +++ b/src/HotChocolate/Data/test/Data.Filters.Tests/Mock/MatchAnyFieldHandler.cs @@ -11,5 +11,5 @@ public override bool CanHandle( IFilterInputTypeConfiguration typeConfiguration, IFilterFieldConfiguration fieldConfiguration) => true; - public static MatchAnyQueryableFieldHandler Create(FilterProviderContext context) => new(); + public static MatchAnyQueryableFieldHandler Create(FilterProviderContext _) => new(); } diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Models/Product.cs b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Models/Product.cs index 03039a590c7..6b7eadce729 100644 --- a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Models/Product.cs +++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/Models/Product.cs @@ -50,7 +50,9 @@ public sealed class Product /// It is invalid to pass in a negative number. /// /// - /// + /// + /// The desired number of units to remove from stock. + /// /// int: Returns the number actually removed from stock. /// public int RemoveStock(int quantityDesired) @@ -74,7 +76,9 @@ public int RemoveStock(int quantityDesired) /// /// Increments the quantity of a particular item in inventory. - /// + /// + /// The number of units to add to stock. + /// /// int: Returns the quantity that has been added to stock /// public int AddStock(int quantity) diff --git a/src/HotChocolate/Data/test/Data.Tests/TestContext1/Product.cs b/src/HotChocolate/Data/test/Data.Tests/TestContext1/Product.cs index a471b0f1297..c3a727c8c1d 100644 --- a/src/HotChocolate/Data/test/Data.Tests/TestContext1/Product.cs +++ b/src/HotChocolate/Data/test/Data.Tests/TestContext1/Product.cs @@ -48,7 +48,9 @@ public class Product /// In this case, it is the responsibility of the client to determine if the amount that is returned is the same as quantityDesired. /// It is invalid to pass in a negative number. /// - /// + /// + /// The desired number of units to remove from stock. + /// /// int: Returns the number actually removed from stock. /// public int RemoveStock(int quantityDesired) @@ -72,7 +74,9 @@ public int RemoveStock(int quantityDesired) /// /// Increments the quantity of a particular item in inventory. - /// + /// + /// The number of units to add to stock. + /// /// int: Returns the quantity that has been added to stock /// public int AddStock(int quantity) diff --git a/src/HotChocolate/Data/test/Data.Tests/TestContext2/Product.cs b/src/HotChocolate/Data/test/Data.Tests/TestContext2/Product.cs index db0ee0a6d3c..761ac0c2ce5 100644 --- a/src/HotChocolate/Data/test/Data.Tests/TestContext2/Product.cs +++ b/src/HotChocolate/Data/test/Data.Tests/TestContext2/Product.cs @@ -52,7 +52,9 @@ public class Product /// In this case, it is the responsibility of the client to determine if the amount that is returned is the same as quantityDesired. /// It is invalid to pass in a negative number. /// - /// + /// + /// The desired number of units to remove from stock. + /// /// int: Returns the number actually removed from stock. /// public int RemoveStock(int quantityDesired) @@ -76,7 +78,9 @@ public int RemoveStock(int quantityDesired) /// /// Increments the quantity of a particular item in inventory. - /// + /// + /// The number of units to add to stock. + /// /// int: Returns the quantity that has been added to stock /// public int AddStock(int quantity) diff --git a/src/HotChocolate/Fusion/benchmarks/k6/eShop.Inventory/ProductNode.cs b/src/HotChocolate/Fusion/benchmarks/k6/eShop.Inventory/ProductNode.cs index 8dfa3d37472..eb49640747d 100644 --- a/src/HotChocolate/Fusion/benchmarks/k6/eShop.Inventory/ProductNode.cs +++ b/src/HotChocolate/Fusion/benchmarks/k6/eShop.Inventory/ProductNode.cs @@ -9,7 +9,6 @@ namespace eShop.Inventory; public static partial class ProductNode { public static long? GetShippingEstimate( - [Parent] Product product, [Require] long weight, [Require] long price) => price > 1000 ? 0 : weight / 2; diff --git a/src/HotChocolate/Fusion/benchmarks/k6/eShop.Reviews/ReviewRepository.cs b/src/HotChocolate/Fusion/benchmarks/k6/eShop.Reviews/ReviewRepository.cs index 934fd386a4e..88d50584a68 100644 --- a/src/HotChocolate/Fusion/benchmarks/k6/eShop.Reviews/ReviewRepository.cs +++ b/src/HotChocolate/Fusion/benchmarks/k6/eShop.Reviews/ReviewRepository.cs @@ -19,7 +19,9 @@ public static class ReviewRepository ]; // Always returns reviews 0 and 1, ignoring the authorId parameter +#pragma warning disable RCS1163 public static IEnumerable GetByUserId(string authorId) +#pragma warning restore RCS1163 => s_reviews.Take(2); public static IEnumerable GetByProductUpc(string upc) diff --git a/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerAspNetCoreHostingBuilderExtensions.cs b/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerAspNetCoreHostingBuilderExtensions.cs index 20d6b3446cf..cb922e38094 100644 --- a/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerAspNetCoreHostingBuilderExtensions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerAspNetCoreHostingBuilderExtensions.cs @@ -24,7 +24,9 @@ public static class FusionServerAspNetCoreHostingBuilderExtensions /// /// Defines if the default security policy should be disabled. /// - /// + /// + /// The for configuration chaining. + /// public static IFusionGatewayBuilder AddGraphQLGateway( this IHostApplicationBuilder builder, string? name = null, diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/OperationExecutionNode.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/OperationExecutionNode.cs index 217408ffddf..23c1a52b292 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/OperationExecutionNode.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/OperationExecutionNode.cs @@ -191,9 +191,9 @@ protected override async ValueTask OnExecuteAsync( buffer.AsSpan(0, index).Clear(); ArrayPool.Shared.Return(buffer); } - else if (singleResult is not null) + else { - singleResult.Dispose(); + singleResult?.Dispose(); } context.AddErrors(exception, variables, _resultSelectionSet); diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/Serialization/JsonOperationPlanParser.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/Serialization/JsonOperationPlanParser.cs index 4cd9e7bec58..f00c3286ba2 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/Serialization/JsonOperationPlanParser.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Nodes/Serialization/JsonOperationPlanParser.cs @@ -404,7 +404,7 @@ private static (string? schemaName, OperationSourceText opSource, SelectionPath? List? requirements, string[]? forwardedVariables, SelectionSetNode? resultSelectionSet, int[]? dependencies, int? batchingGroupId, ExecutionNodeCondition[] conditions, bool requiresFileUpload) - ParseCommonOperationFields(JsonElement nodeElement, ISchemaDefinition schema) + ParseCommonOperationFields(JsonElement nodeElement, ISchemaDefinition _) { string? schemaName = null; diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/ISelectionSetIndex.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/ISelectionSetIndex.cs index c5cd2ba3cc5..2218e1c44c5 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/ISelectionSetIndex.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/ISelectionSetIndex.cs @@ -11,8 +11,8 @@ public interface ISelectionSetIndex /// /// Gets the ident /// - /// - /// + /// The selection set node to resolve the identifier for. + /// The unique identifier of the selection set. uint GetId(SelectionSetNode selectionSet); bool TryGetOriginalIdFromCloned(uint clonedId, out uint originalId); diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/Partitioners/SelectionSetPartitioner.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/Partitioners/SelectionSetPartitioner.cs index d308fc5b7c1..5794e7b140c 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/Partitioners/SelectionSetPartitioner.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Planning/Partitioners/SelectionSetPartitioner.cs @@ -233,19 +233,14 @@ void CompleteSelection(T original, T? resolvable, T? unresolvable, int index) static FieldNode? GetProvidedField(FieldNode fieldNode, SelectionSetNode? providedSelectionSetNode) { - if (providedSelectionSetNode is not null) - { - return providedSelectionSetNode.Selections - .OfType() - .FirstOrDefault(t => t.Name.Value.Equals(fieldNode.Name.Value)); - } - - return null; + return providedSelectionSetNode?.Selections + .OfType() + .FirstOrDefault(t => t.Name.Value.Equals(fieldNode.Name.Value)); } static SelectionSetNode? GetProvidedSelectionSet( - ITypeDefinition type, - FusionSchemaDefinition schema, + ITypeDefinition _1, + FusionSchemaDefinition _2, SelectionSetNode? providedSelectionSetNode) { // todo match correct inline fragment diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultElement.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultElement.cs index fe5b84fbe48..bd5841b804a 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultElement.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/CompositeResultElement.cs @@ -811,7 +811,7 @@ internal ReadOnlySpan GetRawValue(bool includeQuotes = true) { CheckValidInstance(); - return _parent.GetRawValue(_cursor, includeQuotes: true); + return _parent.GetRawValue(_cursor, includeQuotes); } /// diff --git a/src/HotChocolate/Fusion/src/Fusion.Utilities/Rewriters/DocumentRewriter.cs b/src/HotChocolate/Fusion/src/Fusion.Utilities/Rewriters/DocumentRewriter.cs index d4aea762a95..f5052b2bdbb 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Utilities/Rewriters/DocumentRewriter.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Utilities/Rewriters/DocumentRewriter.cs @@ -895,12 +895,7 @@ public bool TryGetConditionalContextsWithReferences( public void RemoveReferenceToConditionalContext(ISelectionNode selectionNode) { - if (ReferencesInConditionalContexts is null) - { - return; - } - - ReferencesInConditionalContexts.Remove(selectionNode); + ReferencesInConditionalContexts?.Remove(selectionNode); } public bool HasField(FieldNode fieldNode, out Context? fieldContext) diff --git a/src/HotChocolate/Json/src/Json/JsonWriter.WriteValues.String.cs b/src/HotChocolate/Json/src/Json/JsonWriter.WriteValues.String.cs index 10e3b187a62..d75bf0a24e8 100644 --- a/src/HotChocolate/Json/src/Json/JsonWriter.WriteValues.String.cs +++ b/src/HotChocolate/Json/src/Json/JsonWriter.WriteValues.String.cs @@ -393,7 +393,7 @@ private void WriteStringEscapeValue(ReadOnlySpan utf8Value, int firstEscap /// /// Writes a number as a JSON string. The string value is not escaped. /// - /// + /// The UTF-8 encoded number value to write as a string. internal void WriteNumberValueAsStringUnescaped(ReadOnlySpan utf8Value) { FlushDeferredPropertyName(); diff --git a/src/HotChocolate/Language/src/Language.SyntaxTree/ScalarTypeDefinitionNode.cs b/src/HotChocolate/Language/src/Language.SyntaxTree/ScalarTypeDefinitionNode.cs index 1e227364f11..939d885b9ff 100644 --- a/src/HotChocolate/Language/src/Language.SyntaxTree/ScalarTypeDefinitionNode.cs +++ b/src/HotChocolate/Language/src/Language.SyntaxTree/ScalarTypeDefinitionNode.cs @@ -65,7 +65,6 @@ public ScalarTypeDefinitionNode( /// /// Gets the scalar description. /// - /// public StringValueNode? Description { get; } /// diff --git a/src/HotChocolate/Language/src/Language.SyntaxTree/Utilities/SyntaxPrinter.cs b/src/HotChocolate/Language/src/Language.SyntaxTree/Utilities/SyntaxPrinter.cs index 7598220f5fa..4a68acf252b 100644 --- a/src/HotChocolate/Language/src/Language.SyntaxTree/Utilities/SyntaxPrinter.cs +++ b/src/HotChocolate/Language/src/Language.SyntaxTree/Utilities/SyntaxPrinter.cs @@ -55,7 +55,9 @@ public static async ValueTask PrintToAsync( this ISyntaxNode node, Stream stream, bool indented = true, +#pragma warning disable RCS1163 CancellationToken cancellationToken = default) +#pragma warning restore RCS1163 { #if NETSTANDARD2_0 using var streamWriter = new StreamWriter( diff --git a/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxNavigator.cs b/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxNavigator.cs index 76e763201fc..2e85e50470a 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxNavigator.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/Contracts/ISyntaxNavigator.cs @@ -86,7 +86,7 @@ IEnumerable GetAncestors() /// /// Creates a Schema Coordinate from the current path. /// - /// + /// The schema coordinate for the current path. /// /// If the path does not allow to create a Schema Coordinate. /// For instance, if traversing an executable document it is not possible to create a diff --git a/src/HotChocolate/Language/src/Language.Visitors/SyntaxVisitor~1.cs b/src/HotChocolate/Language/src/Language.Visitors/SyntaxVisitor~1.cs index a9bb3b4bc2e..e0ec1446eef 100644 --- a/src/HotChocolate/Language/src/Language.Visitors/SyntaxVisitor~1.cs +++ b/src/HotChocolate/Language/src/Language.Visitors/SyntaxVisitor~1.cs @@ -26,7 +26,6 @@ protected SyntaxVisitor( /// /// The visitor default action. /// - /// protected virtual ISyntaxVisitorAction DefaultAction { get; } /// diff --git a/src/HotChocolate/MongoDb/src/Data/Extensions/MongoDbDataRequestBuilderExtensions.cs b/src/HotChocolate/MongoDb/src/Data/Extensions/MongoDbDataRequestBuilderExtensions.cs index 31cd25d87b4..56ce49ee86f 100644 --- a/src/HotChocolate/MongoDb/src/Data/Extensions/MongoDbDataRequestBuilderExtensions.cs +++ b/src/HotChocolate/MongoDb/src/Data/Extensions/MongoDbDataRequestBuilderExtensions.cs @@ -18,7 +18,9 @@ public static class MongoDbDataRequestBuilderExtensions /// /// The . /// - /// + /// + /// The name of the filtering convention. + /// /// Uses the old behavior of naming the filters /// /// Returns the . @@ -35,7 +37,9 @@ public static IRequestExecutorBuilder AddMongoDbFiltering( /// /// The . /// - /// + /// + /// The name of the sorting convention. + /// /// /// Returns the . /// @@ -50,7 +54,9 @@ public static IRequestExecutorBuilder AddMongoDbSorting( /// /// The . /// - /// + /// + /// The name of the projections convention. + /// /// /// Returns the . /// diff --git a/src/HotChocolate/MongoDb/src/Data/Extensions/SchemaBuilderExtensions.cs b/src/HotChocolate/MongoDb/src/Data/Extensions/SchemaBuilderExtensions.cs index 9b00ac6a8f9..2d17c3a8314 100644 --- a/src/HotChocolate/MongoDb/src/Data/Extensions/SchemaBuilderExtensions.cs +++ b/src/HotChocolate/MongoDb/src/Data/Extensions/SchemaBuilderExtensions.cs @@ -13,7 +13,9 @@ public static class MongoDbSchemaBuilderExtensions /// /// The . /// - /// + /// + /// The name of the filtering convention. + /// /// Uses the old behavior of naming the filters /// /// Returns the . @@ -30,7 +32,9 @@ public static ISchemaBuilder AddMongoDbFiltering( /// /// The . /// - /// + /// + /// The name of the sorting convention. + /// /// /// Returns the . /// @@ -45,7 +49,9 @@ public static ISchemaBuilder AddMongoDbSorting( /// /// The . /// - /// + /// + /// The name of the projections convention. + /// /// /// Returns the . /// diff --git a/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListAllOperationHandler.cs b/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListAllOperationHandler.cs index bca8dab2304..fe345d78246 100644 --- a/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListAllOperationHandler.cs +++ b/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListAllOperationHandler.cs @@ -13,7 +13,7 @@ public class MongoDbListAllOperationHandler : MongoDbListOperationHandlerBase /// protected override int Operation => DefaultFilterOperations.All; - public static MongoDbListAllOperationHandler Create(FilterProviderContext context) + public static MongoDbListAllOperationHandler Create(FilterProviderContext _) => new(); /// diff --git a/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListNoneOperationHandler.cs b/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListNoneOperationHandler.cs index 7b319e5cc71..96c7c19da53 100644 --- a/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListNoneOperationHandler.cs +++ b/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListNoneOperationHandler.cs @@ -13,7 +13,7 @@ public class MongoDbListNoneOperationHandler : MongoDbListOperationHandlerBase /// protected override int Operation => DefaultFilterOperations.None; - public static MongoDbListNoneOperationHandler Create(FilterProviderContext context) + public static MongoDbListNoneOperationHandler Create(FilterProviderContext _) => new(); /// diff --git a/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListOperationHandlerBase.cs b/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListOperationHandlerBase.cs index d43cdfeb59f..b91e8d0fa92 100644 --- a/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListOperationHandlerBase.cs +++ b/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListOperationHandlerBase.cs @@ -98,7 +98,9 @@ public override bool TryHandleLeave( /// The currently visited filter field /// The current scope of the visitor /// The path that leads to this visitor - /// + /// + /// The MongoDB filter definition for the list operation. + /// protected abstract MongoDbFilterDefinition HandleListOperation( MongoDbFilterVisitorContext context, IFilterField field, diff --git a/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListSomeOperationHandler.cs b/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListSomeOperationHandler.cs index e1db82fcd51..40378160ed2 100644 --- a/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListSomeOperationHandler.cs +++ b/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/List/MongoDbListSomeOperationHandler.cs @@ -12,7 +12,7 @@ public class MongoDbListSomeOperationHandler : MongoDbListOperationHandlerBase /// protected override int Operation => DefaultFilterOperations.Some; - public static MongoDbListSomeOperationHandler Create(FilterProviderContext context) + public static MongoDbListSomeOperationHandler Create(FilterProviderContext _) => new(); /// diff --git a/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/MongoDbDefaultFieldHandler.cs b/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/MongoDbDefaultFieldHandler.cs index 5884680119a..b80ab2ffedc 100644 --- a/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/MongoDbDefaultFieldHandler.cs +++ b/src/HotChocolate/MongoDb/src/Data/Filters/Handlers/MongoDbDefaultFieldHandler.cs @@ -13,7 +13,7 @@ namespace HotChocolate.Data.MongoDb.Filters; public class MongoDbDefaultFieldHandler : FilterFieldHandler { - public static MongoDbDefaultFieldHandler Create(FilterProviderContext context) + public static MongoDbDefaultFieldHandler Create(FilterProviderContext _) => new(); /// diff --git a/src/HotChocolate/MongoDb/src/Data/Projections/Convention/Handlers/MongoDbProjectionFieldHandler.cs b/src/HotChocolate/MongoDb/src/Data/Projections/Convention/Handlers/MongoDbProjectionFieldHandler.cs index 92b3f6a017a..2c4e2cc4006 100644 --- a/src/HotChocolate/MongoDb/src/Data/Projections/Convention/Handlers/MongoDbProjectionFieldHandler.cs +++ b/src/HotChocolate/MongoDb/src/Data/Projections/Convention/Handlers/MongoDbProjectionFieldHandler.cs @@ -36,5 +36,5 @@ public override bool TryHandleLeave( return true; } - public static MongoDbProjectionFieldHandler Create(ProjectionProviderContext context) => new(); + public static MongoDbProjectionFieldHandler Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/MongoDb/src/Data/Projections/Convention/Handlers/MongoDbProjectionScalarHandler.cs b/src/HotChocolate/MongoDb/src/Data/Projections/Convention/Handlers/MongoDbProjectionScalarHandler.cs index 161bcd5aece..1ccbd01fd37 100644 --- a/src/HotChocolate/MongoDb/src/Data/Projections/Convention/Handlers/MongoDbProjectionScalarHandler.cs +++ b/src/HotChocolate/MongoDb/src/Data/Projections/Convention/Handlers/MongoDbProjectionScalarHandler.cs @@ -29,5 +29,5 @@ public override bool TryHandleEnter( return true; } - public static MongoDbProjectionScalarHandler Create(ProjectionProviderContext context) => new(); + public static MongoDbProjectionScalarHandler Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/MongoDb/src/Data/Sorting/Handlers/MongoDbAscendingSortOperationHandler.cs b/src/HotChocolate/MongoDb/src/Data/Sorting/Handlers/MongoDbAscendingSortOperationHandler.cs index 1c2325a9272..3901e833a6d 100644 --- a/src/HotChocolate/MongoDb/src/Data/Sorting/Handlers/MongoDbAscendingSortOperationHandler.cs +++ b/src/HotChocolate/MongoDb/src/Data/Sorting/Handlers/MongoDbAscendingSortOperationHandler.cs @@ -6,5 +6,5 @@ namespace HotChocolate.Data.MongoDb.Sorting; public class MongoDbAscendingSortOperationHandler() : MongoDbSortOperationHandlerBase(DefaultSortOperations.Ascending, SortDirection.Ascending) { - public static MongoDbAscendingSortOperationHandler Create(SortProviderContext context) => new(); + public static MongoDbAscendingSortOperationHandler Create(SortProviderContext _) => new(); } diff --git a/src/HotChocolate/MongoDb/src/Data/Sorting/Handlers/MongoDbDefaultSortFieldHandler.cs b/src/HotChocolate/MongoDb/src/Data/Sorting/Handlers/MongoDbDefaultSortFieldHandler.cs index 1d4ce2c4fba..a938b08bb8a 100644 --- a/src/HotChocolate/MongoDb/src/Data/Sorting/Handlers/MongoDbDefaultSortFieldHandler.cs +++ b/src/HotChocolate/MongoDb/src/Data/Sorting/Handlers/MongoDbDefaultSortFieldHandler.cs @@ -50,5 +50,5 @@ public override bool TryHandleLeave( return true; } - public static MongoDbDefaultSortFieldHandler Create(SortProviderContext context) => new(); + public static MongoDbDefaultSortFieldHandler Create(SortProviderContext _) => new(); } diff --git a/src/HotChocolate/MongoDb/src/Data/Sorting/Handlers/MongoDbDescendingSortOperationHandler.cs b/src/HotChocolate/MongoDb/src/Data/Sorting/Handlers/MongoDbDescendingSortOperationHandler.cs index 8b5fd6a8f53..5aa4e7a2789 100644 --- a/src/HotChocolate/MongoDb/src/Data/Sorting/Handlers/MongoDbDescendingSortOperationHandler.cs +++ b/src/HotChocolate/MongoDb/src/Data/Sorting/Handlers/MongoDbDescendingSortOperationHandler.cs @@ -6,5 +6,5 @@ namespace HotChocolate.Data.MongoDb.Sorting; public class MongoDbDescendingSortOperationHandler() : MongoDbSortOperationHandlerBase(DefaultSortOperations.Descending, SortDirection.Descending) { - public static MongoDbDescendingSortOperationHandler Create(SortProviderContext context) => new(); + public static MongoDbDescendingSortOperationHandler Create(SortProviderContext _) => new(); } diff --git a/src/HotChocolate/Mutable/src/Types.Mutable/Contracts/IMutableFieldDefinition.cs b/src/HotChocolate/Mutable/src/Types.Mutable/Contracts/IMutableFieldDefinition.cs index be06cf70dd2..b91180aa63d 100644 --- a/src/HotChocolate/Mutable/src/Types.Mutable/Contracts/IMutableFieldDefinition.cs +++ b/src/HotChocolate/Mutable/src/Types.Mutable/Contracts/IMutableFieldDefinition.cs @@ -24,7 +24,6 @@ public interface IMutableFieldDefinition : IFieldDefinition /// /// Gets or sets the type of the field. /// - /// new IType Type { get; set; } /// diff --git a/src/HotChocolate/Mutable/src/Types.Mutable/MutableInputFieldDefinition.cs b/src/HotChocolate/Mutable/src/Types.Mutable/MutableInputFieldDefinition.cs index 2300b85bbbd..bf145b86b94 100644 --- a/src/HotChocolate/Mutable/src/Types.Mutable/MutableInputFieldDefinition.cs +++ b/src/HotChocolate/Mutable/src/Types.Mutable/MutableInputFieldDefinition.cs @@ -96,7 +96,6 @@ public SchemaCoordinate Coordinate /// /// Gets or sets the default value for this input field. /// - /// public IValueNode? DefaultValue { get; set; } /// diff --git a/src/HotChocolate/Mutable/src/Types.Mutable/MutableSchemaDefinition.cs b/src/HotChocolate/Mutable/src/Types.Mutable/MutableSchemaDefinition.cs index cd7f1b7c0c5..9e7bcc2582d 100644 --- a/src/HotChocolate/Mutable/src/Types.Mutable/MutableSchemaDefinition.cs +++ b/src/HotChocolate/Mutable/src/Types.Mutable/MutableSchemaDefinition.cs @@ -324,7 +324,9 @@ IEnumerable ISchemaDefinition.GetPossibleTypes(ITypeDefin /// /// Gets the type and directive definitions that are defined in this schema in insert order. /// - /// + /// + /// The type and directive definitions in insert order. + /// public IEnumerable GetAllDefinitions() { foreach (var definition in _allDefinitionCoordinates) diff --git a/src/HotChocolate/Spatial/src/Data/Filters/Expressions/Handlers/QueryableSpatialBufferDataHandler.cs b/src/HotChocolate/Spatial/src/Data/Filters/Expressions/Handlers/QueryableSpatialBufferDataHandler.cs index 1eb7f4a18c4..89378457f3e 100644 --- a/src/HotChocolate/Spatial/src/Data/Filters/Expressions/Handlers/QueryableSpatialBufferDataHandler.cs +++ b/src/HotChocolate/Spatial/src/Data/Filters/Expressions/Handlers/QueryableSpatialBufferDataHandler.cs @@ -7,5 +7,5 @@ public class QueryableSpatialBufferDataHandler { protected override int Operation => SpatialFilterOperations.Buffer; - public static new QueryableSpatialBufferDataHandler Create(FilterProviderContext context) => new(); + public static new QueryableSpatialBufferDataHandler Create(FilterProviderContext _) => new(); } diff --git a/src/HotChocolate/Spatial/src/Data/Filters/Expressions/Handlers/QueryableSpatialGeometryDataHandler.cs b/src/HotChocolate/Spatial/src/Data/Filters/Expressions/Handlers/QueryableSpatialGeometryDataHandler.cs index 0aef4bd67af..0ef5416c742 100644 --- a/src/HotChocolate/Spatial/src/Data/Filters/Expressions/Handlers/QueryableSpatialGeometryDataHandler.cs +++ b/src/HotChocolate/Spatial/src/Data/Filters/Expressions/Handlers/QueryableSpatialGeometryDataHandler.cs @@ -6,5 +6,5 @@ public class QueryableSpatialGeometryDataHandler : QueryableDataOperationHandler { protected override int Operation => SpatialFilterOperations.Geometry; - public static new QueryableSpatialGeometryDataHandler Create(FilterProviderContext context) => new(); + public static new QueryableSpatialGeometryDataHandler Create(FilterProviderContext _) => new(); } diff --git a/src/HotChocolate/Spatial/src/Data/Projections/Extensions/Extensions/QueryableSpatialProjectionScalarHandler.cs b/src/HotChocolate/Spatial/src/Data/Projections/Extensions/Extensions/QueryableSpatialProjectionScalarHandler.cs index 9b0808e3d1e..bed9485c9eb 100644 --- a/src/HotChocolate/Spatial/src/Data/Projections/Extensions/Extensions/QueryableSpatialProjectionScalarHandler.cs +++ b/src/HotChocolate/Spatial/src/Data/Projections/Extensions/Extensions/QueryableSpatialProjectionScalarHandler.cs @@ -12,5 +12,5 @@ public override bool CanHandle(Selection selection) => selection.Field.Member is not null && typeof(Geometry).IsAssignableFrom(selection.Field.Member.GetReturnType()); - public static new QueryableSpatialProjectionScalarHandler Create(ProjectionProviderContext context) => new(); + public static new QueryableSpatialProjectionScalarHandler Create(ProjectionProviderContext _) => new(); } diff --git a/src/HotChocolate/Spatial/src/Types/Configuration/SpatialConventionDescriptor.cs b/src/HotChocolate/Spatial/src/Types/Configuration/SpatialConventionDescriptor.cs index 73f791db9fd..ea5903e5b7d 100644 --- a/src/HotChocolate/Spatial/src/Types/Configuration/SpatialConventionDescriptor.cs +++ b/src/HotChocolate/Spatial/src/Types/Configuration/SpatialConventionDescriptor.cs @@ -15,7 +15,7 @@ public class SpatialConventionDescriptor : ISpatialConventionDescriptor /// /// Creates the definition of this descriptor /// - /// + /// The spatial convention configuration. public SpatialConventionConfiguration CreateConfiguration() => Configuration; /// diff --git a/src/HotChocolate/Spatial/src/Types/Serialization/GeoJsonGeometrySerializer.cs b/src/HotChocolate/Spatial/src/Types/Serialization/GeoJsonGeometrySerializer.cs index a09bc4e64a7..b5583149927 100644 --- a/src/HotChocolate/Spatial/src/Types/Serialization/GeoJsonGeometrySerializer.cs +++ b/src/HotChocolate/Spatial/src/Types/Serialization/GeoJsonGeometrySerializer.cs @@ -44,7 +44,7 @@ public bool IsValueCompatible(IType type, JsonElement inputValue) return false; } - if (!TryGetGeometryKindFromJson(type, inputValue, out var geometryType)) + if (!TryGetGeometryKindFromJson(inputValue, out var geometryType)) { return false; } @@ -89,7 +89,7 @@ public bool IsValueCompatible(IType type, JsonElement inputValue) throw Serializer_Parse_ValueKindInvalid(type, SyntaxKind.ObjectValue); } - if (!TryGetGeometryKindFromJson(type, inputValue, out var geometryType)) + if (!TryGetGeometryKindFromJson(inputValue, out var geometryType)) { throw Geometry_Parse_InvalidType(type); } @@ -211,7 +211,6 @@ private bool TryGetGeometryKind( } private bool TryGetGeometryKindFromJson( - IType type, JsonElement inputValue, out GeoJsonGeometryType geometryType) { diff --git a/src/HotChocolate/Spatial/src/Types/ThrowHelper.cs b/src/HotChocolate/Spatial/src/Types/ThrowHelper.cs index 434ddbfa1be..d206641c6c4 100644 --- a/src/HotChocolate/Spatial/src/Types/ThrowHelper.cs +++ b/src/HotChocolate/Spatial/src/Types/ThrowHelper.cs @@ -32,7 +32,7 @@ public static LeafCoercionException Serializer_Parse_TypeIsInvalid(IType type) public static LeafCoercionException Serializer_Parse_ValueKindInvalid( IType type, - SyntaxKind syntaxKind) + SyntaxKind _) => new("Resources.Serializer_Parse_TypeIsInvalid", type); public static LeafCoercionException Serializer_CoordinatesIsMissing(IType type) diff --git a/src/Mocha/src/Mocha.Abstractions/IFeatureCollection.cs b/src/Mocha/src/Mocha.Abstractions/IFeatureCollection.cs index 698a2862b21..7a168f8ee4c 100644 --- a/src/Mocha/src/Mocha.Abstractions/IFeatureCollection.cs +++ b/src/Mocha/src/Mocha.Abstractions/IFeatureCollection.cs @@ -25,7 +25,7 @@ public interface IFeatureCollection : IEnumerable> /// /// Gets or sets a given feature. Setting a null value removes the feature. /// - /// + /// The type of the feature to get or set. /// The requested feature, or null if it is not present. object? this[Type key] { get; set; } diff --git a/src/Mocha/src/Mocha.Mediator.Abstractions/Unit.cs b/src/Mocha/src/Mocha.Mediator.Abstractions/Unit.cs index 7d458030fd4..13e1cf7441c 100644 --- a/src/Mocha/src/Mocha.Mediator.Abstractions/Unit.cs +++ b/src/Mocha/src/Mocha.Mediator.Abstractions/Unit.cs @@ -34,7 +34,9 @@ namespace Mocha.Mediator; /// The left operand. /// The right operand. /// Always . +#pragma warning disable RCS1163 public static bool operator ==(Unit left, Unit right) => true; +#pragma warning restore RCS1163 /// /// Determines whether two values are not equal. Always returns . @@ -42,7 +44,9 @@ namespace Mocha.Mediator; /// The left operand. /// The right operand. /// Always . +#pragma warning disable RCS1163 public static bool operator !=(Unit left, Unit right) => false; +#pragma warning restore RCS1163 /// /// Gets the default and only value of . diff --git a/src/Mocha/src/Mocha.Transport.RabbitMQ/Middlewares/Receive/RabbitMQAcknowledgementMiddleware.cs b/src/Mocha/src/Mocha.Transport.RabbitMQ/Middlewares/Receive/RabbitMQAcknowledgementMiddleware.cs index 73c1d948bc9..a4ac41192a5 100644 --- a/src/Mocha/src/Mocha.Transport.RabbitMQ/Middlewares/Receive/RabbitMQAcknowledgementMiddleware.cs +++ b/src/Mocha/src/Mocha.Transport.RabbitMQ/Middlewares/Receive/RabbitMQAcknowledgementMiddleware.cs @@ -50,6 +50,6 @@ public async ValueTask InvokeAsync(IReceiveContext context, ReceiveDelegate next /// A middleware configuration keyed as "RabbitMQAcknowledgement". public static ReceiveMiddlewareConfiguration Create() => new( - static (context, next) => ctx => s_instance.InvokeAsync(ctx, next), + static (_, next) => ctx => s_instance.InvokeAsync(ctx, next), "RabbitMQAcknowledgement"); } diff --git a/src/Mocha/src/Mocha/Endpoints/EndpointRouter.cs b/src/Mocha/src/Mocha/Endpoints/EndpointRouter.cs index 58a3c699420..8f68d5aabcd 100644 --- a/src/Mocha/src/Mocha/Endpoints/EndpointRouter.cs +++ b/src/Mocha/src/Mocha/Endpoints/EndpointRouter.cs @@ -179,6 +179,7 @@ private void AddOrUpdateInternal(DispatchEndpoint endpoint, Uri? resolvedAddress // Build new set of addresses var newAddresses = ImmutableHashSet.Empty; + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (endpointAddress is not null) { newAddresses = newAddresses.Add(endpointAddress); @@ -222,6 +223,7 @@ private void AddOrUpdateInternal(DispatchEndpoint endpoint, Uri? resolvedAddress // New endpoint var addresses = ImmutableHashSet.Empty; + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (endpointAddress is not null) { addresses = addresses.Add(endpointAddress); diff --git a/src/Mocha/src/Mocha/Instrumentation/ActivityMessagingDiagnosticListener.cs b/src/Mocha/src/Mocha/Instrumentation/ActivityMessagingDiagnosticListener.cs index 4c95c85ad6e..2179eb3b907 100644 --- a/src/Mocha/src/Mocha/Instrumentation/ActivityMessagingDiagnosticListener.cs +++ b/src/Mocha/src/Mocha/Instrumentation/ActivityMessagingDiagnosticListener.cs @@ -72,13 +72,10 @@ private ReceiveActivity(IReceiveContext context) public void Dispose() { // Enrich activity with context state after all middlewares have run - if (_activity is not null) - { - _activity - .EnrichMessageDefault() - .SetMessageId(_context.MessageId ?? string.Empty) - .SetConversationId(_context.CorrelationId ?? string.Empty); - } + _activity? + .EnrichMessageDefault() + .SetMessageId(_context.MessageId ?? string.Empty) + .SetConversationId(_context.CorrelationId ?? string.Empty); _activity?.Dispose(); } @@ -151,15 +148,12 @@ public void Dispose() var transportName = _context.Transport.Name; // Enrich activity with context state after all middlewares have run - if (_activity is not null) - { - _activity - .SetMessageId(_context.MessageId ?? string.Empty) - .SetConversationId(_context.ConversationId ?? string.Empty) - .SetInstanceId(_context.Host.InstanceId) - .SetDestinationTemporary(false) - .SetDestinationAddress(destination); - } + _activity? + .SetMessageId(_context.MessageId ?? string.Empty) + .SetConversationId(_context.ConversationId ?? string.Empty) + .SetInstanceId(_context.Host.InstanceId) + .SetDestinationTemporary(false) + .SetDestinationAddress(destination); _activity?.Dispose(); diff --git a/src/Mocha/src/Mocha/MessageTypes/InboundRoute.cs b/src/Mocha/src/Mocha/MessageTypes/InboundRoute.cs index 9c4d8154d6b..1c6de1b4dba 100644 --- a/src/Mocha/src/Mocha/MessageTypes/InboundRoute.cs +++ b/src/Mocha/src/Mocha/MessageTypes/InboundRoute.cs @@ -92,7 +92,9 @@ public void ConnectEndpoint(IMessagingConfigurationContext context, ReceiveEndpo /// Completes the route initialization, verifying that an endpoint has been connected. /// /// The messaging configuration context. +#pragma warning disable RCS1163 // Unused parameter public void Complete(IMessagingConfigurationContext context) +#pragma warning restore RCS1163 // Unused parameter { AssertInitialized(); AssertNotCompleted(); diff --git a/src/Mocha/src/Mocha/MessageTypes/OutboundRoute.cs b/src/Mocha/src/Mocha/MessageTypes/OutboundRoute.cs index ca39b990319..73ab1af4598 100644 --- a/src/Mocha/src/Mocha/MessageTypes/OutboundRoute.cs +++ b/src/Mocha/src/Mocha/MessageTypes/OutboundRoute.cs @@ -139,6 +139,7 @@ public OutboundRouteDescription Describe() Kind, MessageType.Identity, Destination?.ToString(), + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract Endpoint is not null ? new EndpointReferenceDescription(Endpoint.Name, Endpoint.Address?.ToString(), Endpoint.Transport.Name) : null); diff --git a/src/Mocha/src/Mocha/Middlewares/ReceiveContext.cs b/src/Mocha/src/Mocha/Middlewares/ReceiveContext.cs index 2f7715a5f3e..7f9a6e1921f 100644 --- a/src/Mocha/src/Mocha/Middlewares/ReceiveContext.cs +++ b/src/Mocha/src/Mocha/Middlewares/ReceiveContext.cs @@ -233,7 +233,9 @@ public void Initialize( IServiceProvider services, ReceiveEndpoint endpoint, IMessagingRuntime runtime, +#pragma warning disable RCS1163 // Unused parameter CancellationToken cancellationToken) +#pragma warning restore RCS1163 // Unused parameter { Services = services; Endpoint = endpoint; diff --git a/src/Mocha/src/Mocha/Transport/MessagingTransport.Lifecyle.cs b/src/Mocha/src/Mocha/Transport/MessagingTransport.Lifecyle.cs index af9480d1963..e443d3daae9 100644 --- a/src/Mocha/src/Mocha/Transport/MessagingTransport.Lifecyle.cs +++ b/src/Mocha/src/Mocha/Transport/MessagingTransport.Lifecyle.cs @@ -96,6 +96,7 @@ internal void Initialize(IMessagingSetupContext context) // in case we have found a matching route that has no endpoint and no destination, // we need to connect it to the endpoint if (route is not null + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract && route.Endpoint is null && route.Destination is not null) { @@ -202,6 +203,7 @@ internal void DiscoverEndpoints(IMessagingSetupContext context) // TODO i am not sure if this is correct. foreach (var route in router.OutboundRoutes) { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (route.Endpoint is null) { ConnectRoute(context, route); @@ -246,6 +248,7 @@ private void CreateMatchingOutboundRoute(IMessagingSetupContext context, Inbound outboundRoute.Initialize(context, outboundRouteConfiguration); } + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (outboundRoute.Endpoint is null) { var outboundEndpoint = ConnectRoute(context, outboundRoute); @@ -280,7 +283,7 @@ internal void Complete(IMessagingSetupContext context) } } - internal void Finalize(IMessagingSetupContext context) + internal void Finalize(IMessagingSetupContext _) { _features = _features?.ToReadOnly() ?? FeatureCollection.Empty; Configuration = null!; diff --git a/src/Mocha/src/Mocha/Transport/MessagingTransport.cs b/src/Mocha/src/Mocha/Transport/MessagingTransport.cs index 6e2961c7e46..715f059dd75 100644 --- a/src/Mocha/src/Mocha/Transport/MessagingTransport.cs +++ b/src/Mocha/src/Mocha/Transport/MessagingTransport.cs @@ -101,6 +101,7 @@ public virtual TransportDescription Describe() foreach (var endpoint in ReceiveEndpoints) { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (endpoint.Source is not null) { outboundResources.Add(endpoint.Source); @@ -109,6 +110,7 @@ public virtual TransportDescription Describe() foreach (var endpoint in DispatchEndpoints) { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (endpoint.Destination is not null) { inboundResources.Add(endpoint.Destination); diff --git a/src/Nitro/CommandLine/src/ChilliCream.Nitro.Client/Extensions/NitroClientServiceCollectionExtensions.cs b/src/Nitro/CommandLine/src/ChilliCream.Nitro.Client/Extensions/NitroClientServiceCollectionExtensions.cs index f49852f2c79..8ec17671292 100644 --- a/src/Nitro/CommandLine/src/ChilliCream.Nitro.Client/Extensions/NitroClientServiceCollectionExtensions.cs +++ b/src/Nitro/CommandLine/src/ChilliCream.Nitro.Client/Extensions/NitroClientServiceCollectionExtensions.cs @@ -206,10 +206,6 @@ private static void ConfigureApiHttpClient(IServiceProvider sp, HttpClient clien client.DefaultRequestHeaders.Remove("Authorization"); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken.AccessToken}"); break; - - case null: - throw new InvalidOperationException( - "You are not authenticated. Either specify --api-key or run 'nitro login'."); } } diff --git a/src/Nitro/CommandLine/src/CommandLine.FusionCompatibility/FusionGraphPackage.cs b/src/Nitro/CommandLine/src/CommandLine.FusionCompatibility/FusionGraphPackage.cs index b37c2f37f33..fb22285763b 100644 --- a/src/Nitro/CommandLine/src/CommandLine.FusionCompatibility/FusionGraphPackage.cs +++ b/src/Nitro/CommandLine/src/CommandLine.FusionCompatibility/FusionGraphPackage.cs @@ -46,7 +46,7 @@ private FusionGraphPackage(Package package) /// /// The access mode for the Fusion graph package. /// - /// + /// The opened Fusion graph package. /// /// is null. /// @@ -78,7 +78,7 @@ public static FusionGraphPackage Open( /// /// The access mode for the Fusion graph package. /// - /// + /// The opened Fusion graph package. /// /// is null. /// @@ -433,7 +433,9 @@ public async Task SetSubgraphConfigurationAsync( /// public Task RemoveSubgraphConfigurationAsync( string subgraphName, +#pragma warning disable RCS1163 // Unused parameter CancellationToken cancellationToken = default) +#pragma warning restore RCS1163 // Unused parameter { ArgumentNullException.ThrowIfNull(subgraphName); diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/ApiKeys/CreateApiKeyCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/ApiKeys/CreateApiKeyCommand.cs index ad8d267bad1..c4758e088dc 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/ApiKeys/CreateApiKeyCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/ApiKeys/CreateApiKeyCommand.cs @@ -96,7 +96,7 @@ private async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in data.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/ApiKeys/DeleteApiKeyCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/ApiKeys/DeleteApiKeyCommand.cs index 63d6b7751b6..44fd6e254b5 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/ApiKeys/DeleteApiKeyCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/ApiKeys/DeleteApiKeyCommand.cs @@ -60,7 +60,7 @@ private static async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in data.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Apis/CreateApiCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Apis/CreateApiCommand.cs index fcb4b9a628c..3db6d225766 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Apis/CreateApiCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Apis/CreateApiCommand.cs @@ -48,7 +48,7 @@ private static async Task ExecuteAsync( var name = await console.PromptAsync("Name", defaultValue: null, parseResult, Opt.Instance, ct); var pathResult = await console .PromptAsync( - "Path [dim](e.g. /foo/bar)[/]", + $"Path {"(e.g. /foo/bar)".Dim()}", defaultValue: "/", parseResult, Opt.Instance, @@ -66,7 +66,7 @@ private static async Task ExecuteAsync( if (payload.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var mutationError in payload.Errors) { @@ -89,7 +89,7 @@ private static async Task ExecuteAsync( if (changeResult.Error is IError error) { - activity.Fail(); + await activity.FailAllAsync(); console.Error.WriteErrorLine(error.Message); return ExitCodes.Error; } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Apis/DeleteApiCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Apis/DeleteApiCommand.cs index 92522ec5f53..5cc4949a0a3 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Apis/DeleteApiCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Apis/DeleteApiCommand.cs @@ -65,7 +65,7 @@ private static async Task ExecuteAsync( var data = await client.DeleteApiAsync(apiId, cancellationToken); if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var mutationError in data.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Apis/SetApiSettingsCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Apis/SetApiSettingsCommand.cs index 50c03f1d6a8..da3b1e326f9 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Apis/SetApiSettingsCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Apis/SetApiSettingsCommand.cs @@ -72,7 +72,7 @@ private static async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var mutationError in data.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/Components/SelectClientPrompt.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/Components/SelectClientPrompt.cs index 3f7ff61b57f..20e002bc397 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/Components/SelectClientPrompt.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/Components/SelectClientPrompt.cs @@ -20,7 +20,7 @@ public SelectClientPrompt Title(string title) { var paginationContainer = PaginationContainer.CreateConnectionData( async (after, first, ct) => await client.ListClientsAsync(apiId, after, first, ct) - ?? throw ThrowHelper.ThereWasAnIssueWithTheRequest("The API was not found.")); + ?? throw new ExitException("The API was not found.")); return await PagedSelectionPrompt .New(paginationContainer) diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/CreateClientCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/CreateClientCommand.cs index eb73863de58..28c7ef7efea 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/CreateClientCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/CreateClientCommand.cs @@ -62,7 +62,7 @@ private static async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in data.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/DeleteClientCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/DeleteClientCommand.cs index 16cb9440953..3833002716a 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/DeleteClientCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/DeleteClientCommand.cs @@ -42,23 +42,17 @@ private static async Task ExecuteAsync( const string clientMessage = "Which client do you want to delete?"; - var clientId = parseResult.GetValue(Opt.Instance); + var clientId = parseResult.GetRequiredValueIfNotInteractive(Opt.Instance, console); if (clientId is null) { - if (!console.IsInteractive) - { - throw MissingRequiredOption("id"); - } - var workspaceId = parseResult.GetWorkspaceId(sessionService); - var selectedApi = await SelectApiPrompt - .New(apisClient, workspaceId) - .Title("For which API do you want to delete a client?") - .RenderAsync(console, cancellationToken) ?? throw NoApiSelected(); - - var apiId = selectedApi.Id; + var apiId = await console.PromptForApiIdAsync( + apisClient, + workspaceId, + "For which API do you want to delete a client?", + cancellationToken); var selectedClient = await SelectClientPrompt .New(client, apiId) @@ -96,7 +90,7 @@ private static async Task ExecuteAsync( if (deletedClient.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in deletedClient.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ListClientCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ListClientCommand.cs index 7adfc39c27a..6f7660e6d26 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ListClientCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ListClientCommand.cs @@ -4,8 +4,6 @@ using ChilliCream.Nitro.CommandLine.Helpers; using ChilliCream.Nitro.CommandLine.Results; using ChilliCream.Nitro.CommandLine.Services.Sessions; -using static ChilliCream.Nitro.CommandLine.ThrowHelper; - namespace ChilliCream.Nitro.CommandLine.Commands.Clients; internal sealed class ListClientCommand : Command @@ -41,32 +39,33 @@ private static async Task ExecuteAsync( parseResult.AssertHasAuthentication(sessionService); var cursor = parseResult.GetValue(Opt.Instance); + var apiId = await console.GetOrPromptForApiIdAsync( + "For which API do you want to list the clients?", + parseResult, + apisClient, + sessionService, + ct); if (console.IsInteractive) { - return await RenderInteractiveAsync(parseResult, console, client, apisClient, sessionService, resultHolder, cursor, ct); + return await RenderInteractiveAsync(console, client, resultHolder, apiId, cursor, ct); } - return await RenderNonInteractiveAsync(parseResult, client, resultHolder, cursor, ct); + return await RenderNonInteractiveAsync(client, resultHolder, apiId, cursor, ct); } private static async Task RenderInteractiveAsync( - ParseResult parseResult, INitroConsole console, IClientsClient client, - IApisClient apisClient, - ISessionService sessionService, IResultHolder resultHolder, + string apiId, string? cursor, CancellationToken ct) { - const string apiMessage = "For which API do you want to list the clients?"; - var apiId = await console.GetOrPromptForApiIdAsync(apiMessage, parseResult, apisClient, sessionService, ct); - var container = PaginationContainer .CreateConnectionData(async (after, first, cancellationToken) => await client.ListClientsAsync(apiId, after ?? cursor, first, cancellationToken) - ?? throw ThereWasAnIssueWithTheRequest("The API was not found.")) + ?? throw new ExitException("The API was not found.")) .PageSize(10); var selectedClient = await PagedTable @@ -85,20 +84,14 @@ await client.ListClientsAsync(apiId, after ?? cursor, first, cancellationToken) } private static async Task RenderNonInteractiveAsync( - ParseResult parseResult, IClientsClient client, IResultHolder resultHolder, + string apiId, string? cursor, CancellationToken ct) { - var apiId = parseResult.GetValue(Opt.Instance); - if (apiId is null) - { - throw MissingRequiredOption(ApiIdOption.OptionName); - } - var page = await client.ListClientsAsync(apiId, cursor, 10, ct) - ?? throw ThereWasAnIssueWithTheRequest("The API was not found."); + ?? throw new ExitException("The API was not found."); var items = page.Items .Select(x => ClientDetailPrompt.From(x).ToObject()) diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ListClientPublishedVersionsCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ListClientPublishedVersionsCommand.cs index b3a03724df1..1e9370cd7f0 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ListClientPublishedVersionsCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ListClientPublishedVersionsCommand.cs @@ -78,7 +78,7 @@ private static async Task RenderInteractiveAsync( { var page = await client.ListClientVersionsAsync( clientId, after ?? cursor, first, cancellationToken) - ?? throw ThereWasAnIssueWithTheRequest("The client was not found."); + ?? throw new ExitException("The client was not found."); var mappedItems = page.Items .Select(ToResult) @@ -115,14 +115,10 @@ private static async Task RenderNonInteractiveAsync( string? cursor, CancellationToken ct) { - var clientId = parseResult.GetValue(Opt.Instance); - if (clientId is null) - { - throw MissingRequiredOption("--client-id"); - } + var clientId = parseResult.GetRequiredOptionalValue(Opt.Instance); var page = await client.ListClientVersionsAsync(clientId, cursor, 10, ct) - ?? throw ThereWasAnIssueWithTheRequest("The client was not found."); + ?? throw new ExitException("The client was not found."); var items = page.Items .Select(ToResult) diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ListClientVersionsCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ListClientVersionsCommand.cs index 66a572b04b5..7004ce0d619 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ListClientVersionsCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ListClientVersionsCommand.cs @@ -78,7 +78,7 @@ private static async Task RenderInteractiveAsync( { var page = await client.ListClientVersionsAsync( clientId, after ?? cursor, first, cancellationToken) - ?? throw ThereWasAnIssueWithTheRequest("The client was not found."); + ?? throw new ExitException("The client was not found."); return new ConnectionPage( page.Items.Select(ToResult).ToArray(), @@ -110,14 +110,10 @@ private static async Task RenderNonInteractiveAsync( string? cursor, CancellationToken ct) { - var clientId = parseResult.GetValue(Opt.Instance); - if (clientId is null) - { - throw MissingRequiredOption("--client-id"); - } + var clientId = parseResult.GetRequiredOptionalValue(Opt.Instance); var page = await client.ListClientVersionsAsync(clientId, cursor, 10, ct) - ?? throw ThereWasAnIssueWithTheRequest("The client was not found."); + ?? throw new ExitException("The client was not found."); var items = page.Items .Select(ToResult) diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/PublishClientCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/PublishClientCommand.cs index 286d932fc8d..976e2c543c7 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/PublishClientCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/PublishClientCommand.cs @@ -64,155 +64,139 @@ private static async Task ExecuteAsync( var source = SourceMetadataParser.Parse(sourceMetadataJson); - await using (var rootActivity = console.StartActivity( - $"Publishing new client version '{tag.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}' of client '{clientId.EscapeMarkup()}'", + await using (var activity = console.StartActivity( + $"Publishing new version '{tag.EscapeMarkup()}' of client '{clientId.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'", "Failed to publish a new client version.")) { if (force) { - rootActivity.Update(Messages.ForcePushEnabled, ActivityUpdateKind.Warning); + activity.Update(Messages.ForcePushEnabled, ActivityUpdateKind.Warning); } - string requestId; + var publishRequest = await client.StartClientPublishAsync( + clientId, + stage, + tag, + force, + waitForApproval, + source, + ct); - await using (var child = rootActivity.StartChildActivity( - Messages.StartingPublishRequest, - Messages.FailedToStartPublishRequest)) + if (publishRequest.Errors?.Count > 0) { - var publishRequest = await client.StartClientPublishAsync( - clientId, - stage, - tag, - force, - waitForApproval, - source, - ct); - - if (publishRequest.Errors?.Count > 0) - { - await child.FailAllAsync(); + await activity.FailAllAsync(); - foreach (var error in publishRequest.Errors) + foreach (var error in publishRequest.Errors) + { + var errorMessage = error switch { - var errorMessage = error switch - { - IPublishClientVersion_PublishClient_Errors_UnauthorizedOperation err => err.Message, - IPublishClientVersion_PublishClient_Errors_ClientNotFoundError err => err.Message, - IPublishClientVersion_PublishClient_Errors_StageNotFoundError err => err.Message, - IPublishClientVersion_PublishClient_Errors_ClientVersionNotFoundError err => err.Message, - IPublishClientVersion_PublishClient_Errors_InvalidSourceMetadataInputError err => err.Message, - IError err => Messages.UnexpectedMutationError(err), - _ => Messages.UnexpectedMutationError() - }; - - console.Error.WriteErrorLine(errorMessage); - } - - return ExitCodes.Error; + IPublishClientVersion_PublishClient_Errors_UnauthorizedOperation err => err.Message, + IPublishClientVersion_PublishClient_Errors_ClientNotFoundError err => err.Message, + IPublishClientVersion_PublishClient_Errors_StageNotFoundError err => err.Message, + IPublishClientVersion_PublishClient_Errors_ClientVersionNotFoundError err => err.Message, + IPublishClientVersion_PublishClient_Errors_InvalidSourceMetadataInputError err => err.Message, + IError err => Messages.UnexpectedMutationError(err), + _ => Messages.UnexpectedMutationError() + }; + + console.Error.WriteErrorLine(errorMessage); } - if (publishRequest.Id is not { } id) - { - throw MutationReturnedNoData(); - } + return ExitCodes.Error; + } - requestId = id; - child.Success($"Publish request created (ID: {requestId.EscapeMarkup()})."); + if (publishRequest.Id is not { } id) + { + throw MutationReturnedNoData(); } - await using (var child = rootActivity.StartChildActivity( - Messages.ProcessingActivity, - Messages.ProcessingFailed)) + activity.Update($"Publication request created. {$"(ID: {id})".Dim()}"); + + await foreach (var update in client.SubscribeToClientPublishAsync(id, ct)) { - await foreach (var update in client.SubscribeToClientPublishAsync(requestId, ct)) + switch (update) { - switch (update) - { - case IProcessingTaskIsQueued v: - child.Update(Messages.QueuedAtPosition(v.QueuePosition), ActivityUpdateKind.Waiting); - break; + case IProcessingTaskIsQueued v: + activity.Update(Messages.QueuedAtPosition(v.QueuePosition), ActivityUpdateKind.Waiting); + break; - case IClientVersionPublishFailed { Errors: var errors }: - var errorTree = new Tree(""); + case IClientVersionPublishFailed { Errors: var errors }: + var errorTree = new Tree(""); - foreach (var error in errors) + foreach (var error in errors) + { + switch (error) { - switch (error) - { - case IPersistedQueryValidationError e: - errorTree.AddPersistedQueryValidationErrors(e); - break; - case IConcurrentOperationError e: - errorTree.AddErrorMessage(e.Message); - break; - case IProcessingTimeoutError e: - errorTree.AddErrorMessage(e.Message); - break; - case IUnexpectedProcessingError e: - errorTree.AddErrorMessage(e.Message); - break; - } + case IPersistedQueryValidationError e: + errorTree.AddPersistedQueryValidationErrors(e); + break; + case IConcurrentOperationError e: + errorTree.AddErrorMessage(e.Message); + break; + case IProcessingTimeoutError e: + errorTree.AddErrorMessage(e.Message); + break; + case IUnexpectedProcessingError e: + errorTree.AddErrorMessage(e.Message); + break; } + } - child.Fail(errorTree); + await activity.FailAllAsync(errorTree); - await child.FailAllAsync(); + throw new ExitException("Client publish failed."); - throw new ExitException("Client publish failed."); + case IClientVersionPublishSuccess: + activity.Success($"Published new client version '{tag.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'."); - case IClientVersionPublishSuccess: - child.Success(Messages.PublishedSuccessfully); - rootActivity.Success($"Published new client version '{tag.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'."); + return ExitCodes.Success; - return ExitCodes.Success; + case IProcessingTaskIsReady: + activity.Update(Messages.RequestReadyForProcessing); + break; - case IProcessingTaskIsReady: - child.Update(Messages.RequestReadyForProcessing); - break; + case IOperationInProgress: + activity.Update(Messages.RequestBeingProcessed); + break; - case IOperationInProgress: - child.Update(Messages.RequestBeingProcessed); - break; + case IWaitForApproval waitForApprovalEvent: + if (waitForApprovalEvent.Deployment is IClientDeployment deployment) + { + var approvalErrorTree = new Tree(""); - case IWaitForApproval waitForApprovalEvent: - if (waitForApprovalEvent.Deployment is IClientDeployment deployment) + foreach (var error in deployment.Errors) { - var approvalErrorTree = new Tree(""); - - foreach (var error in deployment.Errors) + switch (error) { - switch (error) - { - case IPersistedQueryValidationError e: - approvalErrorTree.AddPersistedQueryValidationErrors(e); - break; - } + case IPersistedQueryValidationError e: + approvalErrorTree.AddPersistedQueryValidationErrors(e); + break; } - - child.Update( - Messages.ValidationFailed, - ActivityUpdateKind.Warning, - approvalErrorTree); } - child.Update( - Messages.WaitingForApproval, - ActivityUpdateKind.Waiting); - break; - - case IProcessingTaskApproved: - child.Update(Messages.RequestApproved); - break; - - default: - child.Update( - Messages.UnknownServerResponse, ActivityUpdateKind.Warning); - break; - } + activity.Update( + Messages.ValidationFailed, + ActivityUpdateKind.Warning, + approvalErrorTree); + } + + activity.Update( + Messages.WaitingForApproval, + ActivityUpdateKind.Waiting); + break; + + case IProcessingTaskApproved: + activity.Update(Messages.RequestApproved); + break; + + default: + activity.Update( + Messages.UnknownServerResponse, ActivityUpdateKind.Warning); + break; } - - child.Fail(); } + + await activity.FailAllAsync(); } return ExitCodes.Error; diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/UploadClientCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/UploadClientCommand.cs index dfcf9f6a284..0b33068efc2 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/UploadClientCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/UploadClientCommand.cs @@ -60,51 +60,50 @@ private static async Task ExecuteAsync( throw new ExitException(Messages.OperationsFileDoesNotExist(operationsFilePath)); } - await using (var activity = console.StartActivity( - $"Uploading new client version '{tag.EscapeMarkup()}' for client '{clientId.EscapeMarkup()}'", - "Failed to upload a new client version.")) - { - await using var stream = fileSystem.OpenReadStream(operationsFilePath); + await using var activity = console.StartActivity( + $"Uploading new version '{tag.EscapeMarkup()}' for client '{clientId.EscapeMarkup()}'", + "Failed to upload a new client version."); - var data = await client.UploadClientVersionAsync( - clientId, - tag, - stream, - source, - cancellationToken); + await using var stream = fileSystem.OpenReadStream(operationsFilePath); - if (data.Errors?.Count > 0) - { - activity.Fail(); + var data = await client.UploadClientVersionAsync( + clientId, + tag, + stream, + source, + cancellationToken); - foreach (var error in data.Errors) - { - var errorMessage = error switch - { - IUploadClient_UploadClient_Errors_UnauthorizedOperation err => err.Message, - IUploadClient_UploadClient_Errors_ClientNotFoundError err => err.Message, - IUploadClient_UploadClient_Errors_DuplicatedTagError err => err.Message, - IUploadClient_UploadClient_Errors_ConcurrentOperationError err => err.Message, - IUploadClient_UploadClient_Errors_InvalidPersistedQueryError err => err.Message, - IUploadClient_UploadClient_Errors_InvalidSourceMetadataInputError err => err.Message, - IError err => Messages.UnexpectedMutationError(err), - _ => Messages.UnexpectedMutationError() - }; - - console.Error.WriteErrorLine(errorMessage); - } - - return ExitCodes.Error; - } + if (data.Errors?.Count > 0) + { + await activity.FailAllAsync(); - if (data.ClientVersion is null) + foreach (var error in data.Errors) { - throw Exit("Could not upload client."); + var errorMessage = error switch + { + IUploadClient_UploadClient_Errors_UnauthorizedOperation err => err.Message, + IUploadClient_UploadClient_Errors_ClientNotFoundError err => err.Message, + IUploadClient_UploadClient_Errors_DuplicatedTagError err => err.Message, + IUploadClient_UploadClient_Errors_ConcurrentOperationError err => err.Message, + IUploadClient_UploadClient_Errors_InvalidPersistedQueryError err => err.Message, + IUploadClient_UploadClient_Errors_InvalidSourceMetadataInputError err => err.Message, + IError err => Messages.UnexpectedMutationError(err), + _ => Messages.UnexpectedMutationError() + }; + + console.Error.WriteErrorLine(errorMessage); } - activity.Success($"Uploaded new client version '{tag.EscapeMarkup()}'."); + return ExitCodes.Error; + } - return ExitCodes.Success; + if (data.ClientVersion is null) + { + throw Exit("Could not upload client."); } + + activity.Success($"Uploaded new client version '{tag.EscapeMarkup()}'."); + + return ExitCodes.Success; } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ValidateClientCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ValidateClientCommand.cs index e374f29bf2c..502bfe1f42e 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ValidateClientCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Clients/ValidateClientCommand.cs @@ -59,108 +59,92 @@ private static async Task ExecuteAsync( throw new ExitException(Messages.OperationsFileDoesNotExist(operationsFilePath)); } - await using (var rootActivity = console.StartActivity( - $"Validating client against stage '{stage.EscapeMarkup()}' of client '{clientId.EscapeMarkup()}'", + await using (var activity = console.StartActivity( + $"Validating client '{clientId.EscapeMarkup()}' against stage '{stage.EscapeMarkup()}'", "Failed to validate the client.")) { - string requestId; + await using var stream = fileSystem.OpenReadStream(operationsFilePath); - await using (var child = rootActivity.StartChildActivity( - Messages.StartingValidationRequest, - Messages.FailedToStartValidationRequest)) - { - await using var stream = fileSystem.OpenReadStream(operationsFilePath); + var validationRequest = await client.StartClientValidationAsync( + clientId, + stage, + stream, + source, + ct); - var validationRequest = await client.StartClientValidationAsync( - clientId, - stage, - stream, - source, - ct); + if (validationRequest.Errors?.Count > 0) + { + await activity.FailAllAsync(); - if (validationRequest.Errors?.Count > 0) + foreach (var error in validationRequest.Errors) { - await child.FailAllAsync(); - - foreach (var error in validationRequest.Errors) + var errorMessage = error switch { - var errorMessage = error switch - { - IValidateClientVersion_ValidateClient_Errors_UnauthorizedOperation err => err.Message, - IValidateClientVersion_ValidateClient_Errors_ClientNotFoundError err => err.Message, - IValidateClientVersion_ValidateClient_Errors_StageNotFoundError err => err.Message, - IValidateClientVersion_ValidateClient_Errors_InvalidSourceMetadataInputError err => err.Message, - IError err => Messages.UnexpectedMutationError(err), - _ => Messages.UnexpectedMutationError() - }; - - console.Error.WriteErrorLine(errorMessage); - } - - return ExitCodes.Error; + IValidateClientVersion_ValidateClient_Errors_UnauthorizedOperation err => err.Message, + IValidateClientVersion_ValidateClient_Errors_ClientNotFoundError err => err.Message, + IValidateClientVersion_ValidateClient_Errors_StageNotFoundError err => err.Message, + IValidateClientVersion_ValidateClient_Errors_InvalidSourceMetadataInputError err => err.Message, + IError err => Messages.UnexpectedMutationError(err), + _ => Messages.UnexpectedMutationError() + }; + + console.Error.WriteErrorLine(errorMessage); } - if (validationRequest.Id is not { } id) - { - throw new ExitException("Could not create client validation request."); - } + return ExitCodes.Error; + } - requestId = id; - child.Success($"Validation request created (ID: {requestId.EscapeMarkup()})."); + if (validationRequest.Id is not { } id) + { + throw new ExitException("Could not create client validation request."); } - await using (var child = rootActivity.StartChildActivity( - Messages.ValidatingActivity, - Messages.ValidationFailed)) + activity.Update($"Validation request created. {$"(ID: {id})".Dim()}"); + + await foreach (var update in client.SubscribeToClientValidationAsync(id, ct)) { - await foreach (var update in client.SubscribeToClientValidationAsync(requestId, ct)) + switch (update) { - switch (update) - { - case IClientVersionValidationFailed { Errors: var errors }: - var errorTree = new Tree(""); + case IClientVersionValidationFailed { Errors: var errors }: + var errorTree = new Tree(""); - foreach (var error in errors) + foreach (var error in errors) + { + switch (error) { - switch (error) - { - case IPersistedQueryValidationError e: - errorTree.AddPersistedQueryValidationErrors(e); - break; - case IProcessingTimeoutError e: - errorTree.AddErrorMessage(e.Message); - break; - case IUnexpectedProcessingError e: - errorTree.AddErrorMessage(e.Message); - break; - } + case IPersistedQueryValidationError e: + errorTree.AddPersistedQueryValidationErrors(e); + break; + case IProcessingTimeoutError e: + errorTree.AddErrorMessage(e.Message); + break; + case IUnexpectedProcessingError e: + errorTree.AddErrorMessage(e.Message); + break; } + } - child.Fail(errorTree); + await activity.FailAllAsync(errorTree); - await child.FailAllAsync(); + throw new ExitException("Client validation failed."); - throw new ExitException("Client validation failed."); + case IClientVersionValidationSuccess: + activity.Success($"Validated client against stage '{stage.EscapeMarkup()}'."); - case IClientVersionValidationSuccess: - child.Success(Messages.ValidationPassed); - rootActivity.Success($"Validated client against stage '{stage.EscapeMarkup()}'."); + return ExitCodes.Success; - return ExitCodes.Success; + case IOperationInProgress: + case IValidationInProgress: + activity.Update(Messages.Validating); + break; - case IOperationInProgress: - case IValidationInProgress: - child.Update(Messages.Validating); - break; - - default: - child.Update(Messages.UnknownServerResponse, ActivityUpdateKind.Warning); - break; - } + default: + activity.Update(Messages.UnknownServerResponse, ActivityUpdateKind.Warning); + break; } - - child.Fail(); } + + await activity.FailAllAsync(); } return ExitCodes.Error; diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Environments/CreateEnvironmentCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Environments/CreateEnvironmentCommand.cs index d58af0dd998..ea0e74e765e 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Environments/CreateEnvironmentCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Environments/CreateEnvironmentCommand.cs @@ -54,7 +54,7 @@ private static async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in data.Errors) { @@ -80,7 +80,7 @@ private static async Task ExecuteAsync( if (changeResult.Error is IError changeError) { - activity.Fail(); + await activity.FailAllAsync(); console.Error.WriteErrorLine(changeError.Message); return ExitCodes.Error; } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionComposeCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionComposeCommand.cs index 205e58bde1c..228308e5b31 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionComposeCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionComposeCommand.cs @@ -450,7 +450,7 @@ private static async Task ComposeAsync( } catch (Exception e) { - console.Error.WriteErrorLine(e.Message); + console.Error.WriteErrorLine(e.Message.EscapeMarkup()); return 1; } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionPublishCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionPublishCommand.cs index a854793bdfc..9b9b00d4ac6 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionPublishCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionPublishCommand.cs @@ -144,7 +144,7 @@ async Task PublishFusionConfigurationWithArchiveAsync() throw new ExitException(Messages.ArchiveFileDoesNotExist(archiveFile)); } - await using var activity = StartPublishActivity(console, stageName, apiId, force); + await using var activity = StartPublishActivity(console, stageName, apiId, tag, force); await using var archiveStream = fileSystem.OpenReadStream(archiveFile); return await ExecutePublishAsync( @@ -170,7 +170,7 @@ async Task PublishFusionConfigurationWithSourceSchemaFilesAsync() } } - await using var activity = StartPublishActivity(console, stageName, apiId, force); + await using var activity = StartPublishActivity(console, stageName, apiId, tag, force); var newSourceSchemas = await FusionComposeCommand.ReadSourceSchemasAsync( fileSystem, @@ -189,7 +189,7 @@ async Task PublishFusionConfigurationWithSourceSchemasAsync() .Select(i => ParseSourceSchemaVersion(i, tag)) .ToArray(); - await using var activity = StartPublishActivity(console, stageName, apiId, force); + await using var activity = StartPublishActivity(console, stageName, apiId, tag, force); var newSourceSchemas = new Dictionary(); @@ -371,7 +371,11 @@ await FusionPublishHelpers.ClaimDeploymentSlotAsync( } else if (!force) { - throw new ExitException("Failed to validate configuration."); + // Write directly instead of throwing so the release-slot fallback + // in the outer catch is not triggered — the publish hasn't actually + // reserved any remote state that needs tearing down here. + console.Error.WriteErrorLine("Fusion configuration validation failed."); + return ExitCodes.Error; } } @@ -438,7 +442,7 @@ await FusionPublishHelpers.ClaimDeploymentSlotAsync( { console.Error.WriteErrorLine( "Encountered an unexpected exception while trying to release the deployment slot after an error during the publishing process:"); - console.Error.WriteErrorLine(exception.Message); + console.Error.WriteErrorLine(exception.Message.EscapeMarkup()); console.Error.WriteErrorLine("This is the error that caused the publishing process to fail in the first place:"); } } @@ -477,11 +481,12 @@ private static INitroConsoleActivity StartPublishActivity( INitroConsole console, string stageName, string apiId, + string tag, bool force) { var activity = console.StartActivity( - $"Publishing Fusion configuration to stage '{stageName}' of API '{apiId.EscapeMarkup()}'", - "Failed to publish Fusion configuration."); + $"Publishing new Fusion configuration version '{tag.EscapeMarkup()}' of API '{apiId.EscapeMarkup()}' to stage '{stageName.EscapeMarkup()}'", + "Failed to publish a new Fusion configuration version."); if (force) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionPublishHelpers.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionPublishHelpers.cs index 119b8d93d22..51f7ad82807 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionPublishHelpers.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionPublishHelpers.cs @@ -1,6 +1,7 @@ using System.Text.Json; using ChilliCream.Nitro.Client; using ChilliCream.Nitro.Client.FusionConfiguration; +using ChilliCream.Nitro.CommandLine.Helpers; using HotChocolate.Fusion; using HotChocolate.Fusion.Logging; using HotChocolate.Fusion.Packaging; @@ -68,7 +69,7 @@ public static async Task RequestDeploymentSlotAsync( throw MutationReturnedNoData(); } - // activity.Update($"Request ID: {requestId.EscapeMarkup()}"); + activity.Update($"Publication request created. {$"(ID: {requestId.EscapeMarkup()})".Dim()}"); using var subscriptionCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); @@ -103,8 +104,9 @@ public static async Task RequestDeploymentSlotAsync( } } - activity.Fail(errorTree); - throw Exit("Your request has failed."); + await activity.FailAllAsync(errorTree); + + throw new ExitException("Your request has failed."); case IFusionConfigurationPublishingSuccess: await subscriptionCancellation.CancelAsync(); @@ -225,7 +227,7 @@ public static async Task UploadFusionConfigurationAsync( } } - activity.Fail(publishErrorTree); + await activity.FailAllAsync(publishErrorTree); throw new ExitException("Failed to publish the new configuration."); case IFusionConfigurationPublishingSuccess: @@ -310,7 +312,7 @@ public static async Task ValidateFusionConfigurationAsync( if (result.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in result.Errors) { @@ -336,17 +338,17 @@ public static async Task ValidateFusionConfigurationAsync( { case IProcessingTaskIsQueued: throw Exit( - "Your request is in the queued state. Try to run `fusion-configuration publish start` once the request is ready "); + "Your request is in the queued state. Try to run `fusion-configuration publish start` once the request is ready."); case IFusionConfigurationPublishingFailed: - throw Exit("Your request has already failed"); + throw Exit("Your request has already failed."); case IFusionConfigurationPublishingSuccess: - throw Exit("You request is already published"); + throw Exit("Your request is already published."); case IProcessingTaskIsReady: throw Exit( - "Your request is ready for the composition. Run `fusion-configuration publish start`"); + "Your request is ready for the composition. Run `fusion-configuration publish start`."); case IFusionConfigurationValidationFailed { Errors: var errors }: var errorTree = new Tree(""); @@ -377,6 +379,7 @@ public static async Task ValidateFusionConfigurationAsync( } activity.Fail(errorTree); + return false; case IFusionConfigurationValidationSuccess: @@ -386,7 +389,7 @@ public static async Task ValidateFusionConfigurationAsync( case IValidationInProgress: case IWaitForApproval: case IProcessingTaskApproved: - // activity.Update(Messages.Validating); + activity.Update(Messages.Validating); break; default: diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionUploadCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionUploadCommand.cs index 5b4ff0ae774..101c8aca1e7 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionUploadCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionUploadCommand.cs @@ -69,58 +69,57 @@ private static async Task ExecuteAsync( throw new ExitException(Messages.SchemaFileDoesNotExist(sourceSchemaFile)); } - var (_, sourceText, settings) = await FusionComposeCommand.ReadSourceSchemaAsync( + var (sourceSchemaName, sourceText, settings) = await FusionComposeCommand.ReadSourceSchemaAsync( fileSystem, sourceSchemaFile, cancellationToken); - await using (var activity = console.StartActivity( - $"Uploading new source schema version '{tag.EscapeMarkup()}' to API '{apiId.EscapeMarkup()}'", - "Failed to upload a new source schema version.")) - { - await using var archiveStream = await FusionSourceSchemaArchiveHelper.CreateArchiveStreamAsync( - Encoding.UTF8.GetBytes(sourceText.SourceText), - settings, - cancellationToken); - - var result = await fusionConfigurationClient.UploadFusionSubgraphAsync( - apiId, - tag, - archiveStream, - source, - cancellationToken); - - if (result.Errors?.Count > 0) - { - activity.Fail(); + await using var activity = console.StartActivity( + $"Uploading new version '{tag.EscapeMarkup()}' for source schema '{sourceSchemaName.EscapeMarkup()}' of API '{apiId.EscapeMarkup()}'", + "Failed to upload a new source schema version."); - foreach (var error in result.Errors) - { - var errorMessage = error switch - { - IUnauthorizedOperation err => err.Message, - IInvalidSourceMetadataInputError err => err.Message, - IDuplicatedTagError err => err.Message, - IConcurrentOperationError err => err.Message, - IInvalidFusionSourceSchemaArchiveError err => Messages.InvalidArchive(err.Message), - IError err => Messages.UnexpectedMutationError(err), - _ => Messages.UnexpectedMutationError() - }; - - console.Error.WriteErrorLine(errorMessage); - } - - return ExitCodes.Error; - } + await using var archiveStream = await FusionSourceSchemaArchiveHelper.CreateArchiveStreamAsync( + Encoding.UTF8.GetBytes(sourceText.SourceText), + settings, + cancellationToken); + + var result = await fusionConfigurationClient.UploadFusionSubgraphAsync( + apiId, + tag, + archiveStream, + source, + cancellationToken); - if (string.IsNullOrWhiteSpace(result.FusionSubgraphVersion?.Id)) + if (result.Errors?.Count > 0) + { + await activity.FailAllAsync(); + + foreach (var error in result.Errors) { - throw Exit("Upload of source schema failed."); + var errorMessage = error switch + { + IUnauthorizedOperation err => err.Message, + IInvalidSourceMetadataInputError err => err.Message, + IDuplicatedTagError err => err.Message, + IConcurrentOperationError err => err.Message, + IInvalidFusionSourceSchemaArchiveError err => Messages.InvalidArchive(err.Message), + IError err => Messages.UnexpectedMutationError(err), + _ => Messages.UnexpectedMutationError() + }; + + console.Error.WriteErrorLine(errorMessage); } - activity.Success($"Uploaded new source schema version '{tag.EscapeMarkup()}'."); + return ExitCodes.Error; + } - return ExitCodes.Success; + if (string.IsNullOrWhiteSpace(result.FusionSubgraphVersion?.Id)) + { + throw Exit("Upload of source schema failed."); } + + activity.Success($"Uploaded new source schema version '{tag.EscapeMarkup()}'."); + + return ExitCodes.Success; } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionValidateCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionValidateCommand.cs index 8d03e6aef95..6641b8c618d 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionValidateCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/FusionValidateCommand.cs @@ -228,7 +228,12 @@ async Task ValidateAsync(INitroConsoleActivity activity, Stream archiveStre source: null, ct); - return isValid ? ExitCodes.Success : ExitCodes.Error; + if (!isValid) + { + throw new ExitException("Schema validation failed."); + } + + return ExitCodes.Success; } finally { @@ -239,7 +244,7 @@ async Task ValidateAsync(INitroConsoleActivity activity, Stream archiveStre INitroConsoleActivity StartActivity() { return console.StartActivity( - $"Validating Fusion configuration against stage '{stageName.EscapeMarkup()}' of API '{apiId.EscapeMarkup()}'", + $"Validating Fusion configuration of API '{apiId.EscapeMarkup()}' against stage '{stageName.EscapeMarkup()}'", "Failed to validate the Fusion configuration."); } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/PublishCommand/FusionConfigurationPublishBeginCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/PublishCommand/FusionConfigurationPublishBeginCommand.cs index 276e62f714c..e80624d4f09 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/PublishCommand/FusionConfigurationPublishBeginCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/PublishCommand/FusionConfigurationPublishBeginCommand.cs @@ -49,10 +49,8 @@ private static async Task ExecuteAsync( var stageName = parseResult.GetRequiredValue(Opt.Instance); var apiId = parseResult.GetRequiredValue(Opt.Instance); var tag = parseResult.GetRequiredValue(Opt.Instance); - var subgraphId = - parseResult.GetRequiredValue(Opt.Instance); - var subgraphName = - parseResult.GetRequiredValue(Opt.Instance); + var subgraphId = parseResult.GetValue(Opt.Instance); + var subgraphName = parseResult.GetValue(Opt.Instance); var waitForApproval = parseResult.GetValue(Opt.Instance); var sourceMetadataJson = @@ -84,6 +82,8 @@ async Task RequestDeploymentSlotAsync(INitroConsoleActivity activity) fusionConfigurationClient, cancellationToken); + activity.Success("Deployment slot ready."); + resultHolder.SetResult(new ObjectResult(new FusionConfigurationPublishBeginCommandResult { RequestId = requestId })); await FusionConfigurationPublishingState.SetRequestId( diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/PublishCommand/FusionConfigurationPublishCommitCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/PublishCommand/FusionConfigurationPublishCommitCommand.cs index 2baed587f33..73e85b28717 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/PublishCommand/FusionConfigurationPublishCommitCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/PublishCommand/FusionConfigurationPublishCommitCommand.cs @@ -74,7 +74,7 @@ await FusionConfigurationPublishingState.GetRequestId(fileSystem, ct) ?? } else { - activity.Fail(); + await activity.FailAllAsync(); } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/PublishCommand/FusionConfigurationPublishValidateCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/PublishCommand/FusionConfigurationPublishValidateCommand.cs index 3435bb39a0f..daf5870d761 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/PublishCommand/FusionConfigurationPublishValidateCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Fusion/PublishCommand/FusionConfigurationPublishValidateCommand.cs @@ -61,7 +61,14 @@ await FusionConfigurationPublishingState.GetRequestId(fileSystem, cancellationTo fusionConfigurationClient, cancellationToken); - return isValidArchive ? ExitCodes.Success : ExitCodes.Error; + if (!isValidArchive) + { + throw new ExitException("Fusion configuration validation failed."); + } + + activity.Success("Validated configuration."); + + return ExitCodes.Success; } } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Login/LoginCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Login/LoginCommand.cs index cdc8384419d..ec2fb60d75b 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Login/LoginCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Login/LoginCommand.cs @@ -1,6 +1,6 @@ using ChilliCream.Nitro.Client; using ChilliCream.Nitro.Client.Workspaces; -using ChilliCream.Nitro.CommandLine.Commands.Workspaces; +using ChilliCream.Nitro.CommandLine.Helpers; using ChilliCream.Nitro.CommandLine.Services; using ChilliCream.Nitro.CommandLine.Services.Sessions; @@ -34,33 +34,73 @@ private static async Task ExecuteAsync( if (!console.IsInteractive) { throw new ExitException( - "'nitro login' requires an interactive console. " + "`nitro login` requires an interactive console. " + $"Use '{OptionalApiKeyOption.OptionName}' to authenticate command invocations in non-interactive environments."); } - var cloudUrl = parseResult.GetRequiredValue(Opt.Instance); + var cloudUrl = parseResult.GetValue(Opt.Instance); var url = parseResult.GetValue(Opt.Instance); url ??= cloudUrl; - Session? session; await using (var activity = console.StartActivity("Logging in via browser", "Failed to log in.")) { activity.Update($"Browser opened at {url.EscapeMarkup()}. Continue login there."); - session = await sessionService.LoginAsync(url, cancellationToken); - + var session = await sessionService.LoginAsync(url, cancellationToken); if (session is null) { throw new ExitException("There was a failure and Nitro could not log you in."); } - activity.Success($"Logged in as '{session.Email.EscapeMarkup()}'."); + clientContext.Configure( + session.ApiUrl, + new NitroClientAccessTokenAuthorization(session.Tokens!.AccessToken)); + + var page = await client.SelectWorkspacesAsync(null, 5, cancellationToken); + var email = session.Email.EscapeMarkup(); + + if (page.Items.Count == 0) + { + activity.Update( + "You do not have any workspaces. Run `nitro launch` and create one.", + ActivityUpdateKind.Warning); + activity.Success($"Logged in as [green]{email}[/]"); + return ExitCodes.Success; + } + + if (page.Items.Count == 1) + { + var only = page.Items[0]; + await sessionService.SelectWorkspaceAsync( + new Workspace(only.Id, only.Name), + cancellationToken); + activity.Success( + $"Logged in as [green]{email}[/] (Workspace: [green]{only.Name.EscapeMarkup()}[/])"); + return ExitCodes.Success; + } + + activity.Success($"Logged in as [green]{email}[/]"); } - clientContext.Configure(session.ApiUrl, new NitroClientAccessTokenAuthorization(session.Tokens!.AccessToken)); + var paginationContainer = PaginationContainer.CreateConnectionData(client.SelectWorkspacesAsync); + var selected = await PagedSelectionPrompt + .New(paginationContainer) + .Title("Which workspace do you want to use as your default?".AsQuestion()) + .UseConverter(x => x.Name) + .RenderAsync(console, cancellationToken); + + if (selected is null) + { + throw new ExitException("No workspace was selected as default."); + } + + await sessionService.SelectWorkspaceAsync( + new Workspace(selected.Id, selected.Name), + cancellationToken); + + console.MarkupLine($"(Workspace: [green]{selected.Name.EscapeMarkup()}[/])"); - return await SetDefaultWorkspaceCommand - .ExecuteAsync(forceSelection: false, console, client, sessionService, cancellationToken); + return ExitCodes.Success; } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/Component/SelectMcpFeatureCollectionPrompt.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/Component/SelectMcpFeatureCollectionPrompt.cs index 3c7a582e053..7f88c59fcbb 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/Component/SelectMcpFeatureCollectionPrompt.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/Component/SelectMcpFeatureCollectionPrompt.cs @@ -20,7 +20,7 @@ public SelectMcpFeatureCollectionPrompt Title(string title) { var paginationContainer = PaginationContainer.CreateConnectionData( async (after, first, ct) => await client.ListMcpFeatureCollectionsAsync(apiId, after, first, ct) - ?? throw ThrowHelper.ThereWasAnIssueWithTheRequest("The API was not found.")); + ?? throw new ExitException("The API was not found.")); return await PagedSelectionPrompt .New(paginationContainer) diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/CreateMcpFeatureCollectionCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/CreateMcpFeatureCollectionCommand.cs index 58ef4a42209..7c940f5ff00 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/CreateMcpFeatureCollectionCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/CreateMcpFeatureCollectionCommand.cs @@ -44,8 +44,7 @@ private static async Task ExecuteAsync( parseResult.AssertHasAuthentication(sessionService); - const string apiMessage = "For which API do you want to create an MCP Feature Collection?"; - var apiId = await console.GetOrPromptForApiIdAsync(apiMessage, parseResult, apisClient, sessionService, cancellationToken); + var apiId = await console.GetOrPromptForApiIdAsync("For which API do you want to create an MCP Feature Collection?", parseResult, apisClient, sessionService, cancellationToken); var name = await console .PromptAsync("Name", defaultValue: null, parseResult, Opt.Instance, cancellationToken); @@ -61,7 +60,7 @@ private static async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in data.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/DeleteMcpFeatureCollectionCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/DeleteMcpFeatureCollectionCommand.cs index 7db45bd9990..fbd21295459 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/DeleteMcpFeatureCollectionCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/DeleteMcpFeatureCollectionCommand.cs @@ -40,31 +40,21 @@ private static async Task ExecuteAsync( parseResult.AssertHasAuthentication(sessionService); - var mcpFeatureCollectionId = parseResult.GetValue(Opt.Instance); + var mcpFeatureCollectionId = parseResult.GetRequiredValueIfNotInteractive(Opt.Instance, console); if (mcpFeatureCollectionId is null) { - if (!console.IsInteractive) - { - throw MissingRequiredOption("id"); - } - - const string apiMessage = "For which API do you want to delete an MCP Feature Collection?"; - const string mcpFeatureCollectionMessage = "Which MCP Feature Collection do you want to delete?"; - var workspaceId = parseResult.GetWorkspaceId(sessionService); - - var selectedApi = await SelectApiPrompt - .New(apisClient, workspaceId) - .Title(apiMessage) - .RenderAsync(console, cancellationToken) ?? throw NoApiSelected(); - - var apiId = selectedApi.Id; + var apiId = await console.PromptForApiIdAsync( + apisClient, + workspaceId, + "For which API do you want to delete an MCP Feature Collection?", + cancellationToken); var selectedMcpFeatureCollection = await SelectMcpFeatureCollectionPrompt .New(client, apiId) - .Title(mcpFeatureCollectionMessage) - .RenderAsync(console, cancellationToken) ?? throw NoMcpFeatureCollectionSelected(); + .Title("Which MCP Feature Collection do you want to delete?") + .RenderAsync(console, cancellationToken) ?? throw new ExitException("You did not select an MCP Feature Collection!"); mcpFeatureCollectionId = selectedMcpFeatureCollection.Id; } @@ -93,7 +83,7 @@ private static async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in data.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/ListMcpFeatureCollectionCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/ListMcpFeatureCollectionCommand.cs index 23842372bff..1be3e31dffc 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/ListMcpFeatureCollectionCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/ListMcpFeatureCollectionCommand.cs @@ -4,8 +4,6 @@ using ChilliCream.Nitro.CommandLine.Helpers; using ChilliCream.Nitro.CommandLine.Results; using ChilliCream.Nitro.CommandLine.Services.Sessions; -using static ChilliCream.Nitro.CommandLine.ThrowHelper; - namespace ChilliCream.Nitro.CommandLine.Commands.Mcp; internal sealed class ListMcpFeatureCollectionCommand : Command @@ -57,13 +55,12 @@ private static async Task RenderInteractiveAsync( string? cursor, CancellationToken ct) { - const string apiMessage = "For which API do you want to list the MCP Feature Collections?"; - var apiId = await console.GetOrPromptForApiIdAsync(apiMessage, parseResult, apisClient, sessionService, ct); + var apiId = await console.GetOrPromptForApiIdAsync("For which API do you want to list the MCP Feature Collections?", parseResult, apisClient, sessionService, ct); var container = PaginationContainer .CreateConnectionData(async (after, first, token) => await client.ListMcpFeatureCollectionsAsync(apiId, after ?? cursor, first, token) - ?? throw ThereWasAnIssueWithTheRequest("The API was not found.")) + ?? throw new ExitException("The API was not found.")) .PageSize(10); var api = await PagedTable @@ -88,14 +85,10 @@ private static async Task RenderNonInteractiveAsync( string? cursor, CancellationToken ct) { - var apiId = parseResult.GetValue(Opt.Instance); - if (apiId is null) - { - throw MissingRequiredOption(ApiIdOption.OptionName); - } + var apiId = parseResult.GetRequiredOptionalValue(Opt.Instance); var data = await client.ListMcpFeatureCollectionsAsync(apiId, cursor, 10, ct) - ?? throw ThereWasAnIssueWithTheRequest("The API was not found."); + ?? throw new ExitException("The API was not found."); var items = data.Items .Select(McpFeatureCollectionDetailPrompt.From) .Select(x => x.ToObject()) diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/PublishMcpFeatureCollectionCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/PublishMcpFeatureCollectionCommand.cs index 56c5063dd47..e066924d425 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/PublishMcpFeatureCollectionCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/PublishMcpFeatureCollectionCommand.cs @@ -65,148 +65,132 @@ private static async Task ExecuteAsync( var source = SourceMetadataParser.Parse(sourceMetadataJson); - await using (var rootActivity = console.StartActivity( - $"Publishing new MCP feature collection version '{tag.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'", + await using (var activity = console.StartActivity( + $"Publishing new version '{tag.EscapeMarkup()}' of MCP feature collection '{mcpFeatureCollectionId.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'", "Failed to publish a new MCP feature collection version.")) { if (force) { - rootActivity.Update(Messages.ForcePushEnabled, ActivityUpdateKind.Warning); + activity.Update(Messages.ForcePushEnabled, ActivityUpdateKind.Warning); } - string requestId; + var publishRequest = await client.StartMcpFeatureCollectionPublishAsync( + mcpFeatureCollectionId, + stage, + tag, + force, + waitForApproval, + source, + ct); - await using (var child = rootActivity.StartChildActivity( - Messages.StartingPublishRequest, - Messages.FailedToStartPublishRequest)) + if (publishRequest.Errors?.Count > 0) { - var publishRequest = await client.StartMcpFeatureCollectionPublishAsync( - mcpFeatureCollectionId, - stage, - tag, - force, - waitForApproval, - source, - ct); - - if (publishRequest.Errors?.Count > 0) - { - await child.FailAllAsync(); + await activity.FailAllAsync(); - foreach (var error in publishRequest.Errors) + foreach (var error in publishRequest.Errors) + { + var errorMessage = error switch { - var errorMessage = error switch - { - IUnauthorizedOperation err => err.Message, - IPublishMcpFeatureCollectionCommandMutation_PublishMcpFeatureCollection_Errors_InvalidSourceMetadataInputError err => err.Message, - IStageNotFoundError err => err.Message, - IMcpFeatureCollectionNotFoundError err => err.Message, - IMcpFeatureCollectionVersionNotFoundError err => err.Message, - IError err => Messages.UnexpectedMutationError(err), - _ => Messages.UnexpectedMutationError() - }; - - console.Error.WriteErrorLine(errorMessage); - } - - return ExitCodes.Error; + IUnauthorizedOperation err => err.Message, + IPublishMcpFeatureCollectionCommandMutation_PublishMcpFeatureCollection_Errors_InvalidSourceMetadataInputError err => err.Message, + IStageNotFoundError err => err.Message, + IMcpFeatureCollectionNotFoundError err => err.Message, + IMcpFeatureCollectionVersionNotFoundError err => err.Message, + IError err => Messages.UnexpectedMutationError(err), + _ => Messages.UnexpectedMutationError() + }; + + console.Error.WriteErrorLine(errorMessage); } - if (publishRequest.Id is not { } id) - { - throw MutationReturnedNoData(); - } + return ExitCodes.Error; + } - requestId = id; - child.Success($"Publish request created (ID: {requestId.EscapeMarkup()})."); + if (publishRequest.Id is not { } id) + { + throw MutationReturnedNoData(); } - await using (var child = rootActivity.StartChildActivity( - Messages.ProcessingActivity, - Messages.ProcessingFailed)) + activity.Update($"Publication request created. {$"(ID: {id})".Dim()}"); + + await foreach (var update in client.SubscribeToMcpFeatureCollectionPublishAsync(id, ct)) { - await foreach (var update in client.SubscribeToMcpFeatureCollectionPublishAsync(requestId, ct)) + switch (update) { - switch (update) - { - case IProcessingTaskIsQueued v: - child.Update(Messages.QueuedAtPosition(v.QueuePosition), ActivityUpdateKind.Waiting); - break; + case IProcessingTaskIsQueued v: + activity.Update(Messages.QueuedAtPosition(v.QueuePosition), ActivityUpdateKind.Waiting); + break; - case IMcpFeatureCollectionVersionPublishFailed { Errors: var errors }: - var errorTree = new Tree(""); + case IMcpFeatureCollectionVersionPublishFailed { Errors: var errors }: + var errorTree = new Tree(""); - foreach (var error in errors) + foreach (var error in errors) + { + switch (error) { - switch (error) - { - case IMcpFeatureCollectionValidationError e: - errorTree.AddMcpFeatureCollectionValidationErrors(e); - break; - case IConcurrentOperationError e: - errorTree.AddErrorMessage(e.Message); - break; - case IUnexpectedProcessingError e: - errorTree.AddErrorMessage(e.Message); - break; - case IProcessingTimeoutError e: - errorTree.AddErrorMessage(e.Message); - break; - } + case IMcpFeatureCollectionValidationError e: + errorTree.AddMcpFeatureCollectionValidationErrors(e); + break; + case IConcurrentOperationError e: + errorTree.AddErrorMessage(e.Message); + break; + case IUnexpectedProcessingError e: + errorTree.AddErrorMessage(e.Message); + break; + case IProcessingTimeoutError e: + errorTree.AddErrorMessage(e.Message); + break; } + } - child.Fail(errorTree); + await activity.FailAllAsync(errorTree); - await child.FailAllAsync(); + throw new ExitException("MCP feature collection publish failed."); - throw new ExitException("MCP feature collection publish failed."); + case IMcpFeatureCollectionVersionPublishSuccess: + activity.Success($"Published new version '{tag.EscapeMarkup()}' of MCP feature collection '{mcpFeatureCollectionId.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'."); + return ExitCodes.Success; - case IMcpFeatureCollectionVersionPublishSuccess: - child.Success(Messages.PublishedSuccessfully); - rootActivity.Success($"Published new MCP feature collection version '{tag.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'."); - return ExitCodes.Success; + case IProcessingTaskIsReady: + activity.Update(Messages.RequestReadyForProcessing); + break; - case IProcessingTaskIsReady: - child.Update(Messages.RequestReadyForProcessing); - break; + case IOperationInProgress: + activity.Update(Messages.RequestBeingProcessed); + break; - case IOperationInProgress: - child.Update(Messages.RequestBeingProcessed); - break; + case IWaitForApproval waitForApprovalEvent: + if (waitForApprovalEvent.Deployment is IMcpFeatureCollectionDeployment deployment) + { + var approvalErrorTree = new Tree(""); - case IWaitForApproval waitForApprovalEvent: - if (waitForApprovalEvent.Deployment is IMcpFeatureCollectionDeployment deployment) + foreach (var error in deployment.Errors) { - var approvalErrorTree = new Tree(""); - - foreach (var error in deployment.Errors) + switch (error) { - switch (error) - { - case IMcpFeatureCollectionValidationError e: - approvalErrorTree.AddMcpFeatureCollectionValidationErrors(e); - break; - } + case IMcpFeatureCollectionValidationError e: + approvalErrorTree.AddMcpFeatureCollectionValidationErrors(e); + break; } - - child.Update(Messages.ValidationFailed, ActivityUpdateKind.Warning, approvalErrorTree); } - child.Update(Messages.WaitingForApproval, ActivityUpdateKind.Waiting); - break; + activity.Update(Messages.ValidationFailed, ActivityUpdateKind.Warning, approvalErrorTree); + } - case IProcessingTaskApproved: - child.Update(Messages.RequestApproved); - break; + activity.Update(Messages.WaitingForApproval, ActivityUpdateKind.Waiting); + break; - default: - child.Update(Messages.UnknownServerResponse, ActivityUpdateKind.Warning); - break; - } - } + case IProcessingTaskApproved: + activity.Update(Messages.RequestApproved); + break; - child.Fail(); + default: + activity.Update(Messages.UnknownServerResponse, ActivityUpdateKind.Warning); + break; + } } + + await activity.FailAllAsync(); } return ExitCodes.Error; diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/UploadMcpFeatureCollectionCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/UploadMcpFeatureCollectionCommand.cs index 276cacbd9a3..3ae8806174c 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/UploadMcpFeatureCollectionCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/UploadMcpFeatureCollectionCommand.cs @@ -69,54 +69,53 @@ await McpFeatureCollectionHelpers.BuildMcpFeatureCollectionArchive( toolFiles, cancellationToken); - await using (var activity = console.StartActivity( - $"Uploading new MCP feature collection version '{tag.EscapeMarkup()}' for collection '{mcpFeatureCollectionId.EscapeMarkup()}'", - "Failed to upload a new MCP feature collection version.")) - { - activity.Update($"Found {promptFiles.Length} prompt(s) and {toolFiles.Length} tool(s)."); + await using var activity = console.StartActivity( + $"Uploading new version '{tag.EscapeMarkup()}' for MCP feature collection '{mcpFeatureCollectionId.EscapeMarkup()}'", + "Failed to upload a new MCP feature collection version."); - var data = await client.UploadMcpFeatureCollectionVersionAsync( - mcpFeatureCollectionId, - tag, - archiveStream, - source, - cancellationToken); + activity.Update($"Found {promptFiles.Length} prompt(s) and {toolFiles.Length} tool(s)."); - if (data.Errors?.Count > 0) - { - activity.Fail(); + var data = await client.UploadMcpFeatureCollectionVersionAsync( + mcpFeatureCollectionId, + tag, + archiveStream, + source, + cancellationToken); - foreach (var error in data.Errors) - { - var errorMessage = error switch - { - IMcpFeatureCollectionNotFoundError err => err.Message, - IUnauthorizedOperation err => err.Message, - IInvalidSourceMetadataInputError err => err.Message, - IDuplicatedTagError err => err.Message, - IConcurrentOperationError err => err.Message, - IInvalidMcpFeatureCollectionArchiveError err => - Messages.InvalidArchive(err.Message), - IError err => Messages.UnexpectedMutationError(err), - _ => Messages.UnexpectedMutationError() - }; - - console.Error.WriteErrorLine(errorMessage); - } - - return ExitCodes.Error; - } + if (data.Errors?.Count > 0) + { + await activity.FailAllAsync(); - if (data.McpFeatureCollectionVersion is null) + foreach (var error in data.Errors) { - activity.Fail(); - console.Error.WriteErrorLine("Could not upload MCP Feature Collection version."); - return ExitCodes.Error; + var errorMessage = error switch + { + IMcpFeatureCollectionNotFoundError err => err.Message, + IUnauthorizedOperation err => err.Message, + IInvalidSourceMetadataInputError err => err.Message, + IDuplicatedTagError err => err.Message, + IConcurrentOperationError err => err.Message, + IInvalidMcpFeatureCollectionArchiveError err => + Messages.InvalidArchive(err.Message), + IError err => Messages.UnexpectedMutationError(err), + _ => Messages.UnexpectedMutationError() + }; + + console.Error.WriteErrorLine(errorMessage); } - activity.Success($"Uploaded new MCP feature collection version '{tag.EscapeMarkup()}'."); + return ExitCodes.Error; + } - return ExitCodes.Success; + if (data.McpFeatureCollectionVersion is null) + { + await activity.FailAllAsync(); + console.Error.WriteErrorLine("Could not upload MCP Feature Collection version."); + return ExitCodes.Error; } + + activity.Success($"Uploaded new MCP feature collection version '{tag.EscapeMarkup()}'."); + + return ExitCodes.Success; } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/ValidateMcpFeatureCollectionCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/ValidateMcpFeatureCollectionCommand.cs index 7578b132f3c..ebfe5359b86 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/ValidateMcpFeatureCollectionCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Mcp/ValidateMcpFeatureCollectionCommand.cs @@ -53,18 +53,18 @@ private static async Task ExecuteAsync( var source = SourceMetadataParser.Parse(sourceMetadataJson); - await using (var rootActivity = console.StartActivity( - $"Validating MCP feature collection against stage '{stage.EscapeMarkup()}'", + await using (var activity = console.StartActivity( + $"Validating MCP feature collection '{mcpFeatureCollectionId.EscapeMarkup()}' against stage '{stage.EscapeMarkup()}'", "Failed to validate the MCP feature collection.")) { var promptFiles = fileSystem.GlobMatch(promptPatterns, ["**/bin/**", "**/obj/**"]).ToArray(); var toolFiles = fileSystem.GlobMatch(toolPatterns, ["**/bin/**", "**/obj/**"]).ToArray(); - rootActivity.Update($"Found {promptFiles.Length} prompt(s) and {toolFiles.Length} tool(s)."); + activity.Update($"Found {promptFiles.Length} prompt(s) and {toolFiles.Length} tool(s)."); if (promptFiles.Length < 1 && toolFiles.Length < 1) { - rootActivity.Fail(); + await activity.FailAllAsync(); console.Error.WriteErrorLine( "Could not find any MCP prompt or tool definition files with the provided patterns."); return ExitCodes.Error; @@ -77,104 +77,88 @@ await McpFeatureCollectionHelpers.BuildMcpFeatureCollectionArchive( toolFiles, ct); - string requestId; + var validationRequest = await client.StartMcpFeatureCollectionValidationAsync( + mcpFeatureCollectionId, + stage, + archiveStream, + source, + ct); - await using (var child = rootActivity.StartChildActivity( - Messages.StartingValidationRequest, - Messages.FailedToStartValidationRequest)) + if (validationRequest.Errors?.Count > 0) { - var validationRequest = await client.StartMcpFeatureCollectionValidationAsync( - mcpFeatureCollectionId, - stage, - archiveStream, - source, - ct); + await activity.FailAllAsync(); - if (validationRequest.Errors?.Count > 0) + foreach (var error in validationRequest.Errors) { - await child.FailAllAsync(); - - foreach (var error in validationRequest.Errors) + var errorMessage = error switch { - var errorMessage = error switch - { - IUnauthorizedOperation err => err.Message, - IInvalidSourceMetadataInputError err => err.Message, - IStageNotFoundError err => err.Message, - IMcpFeatureCollectionNotFoundError err => err.Message, - IError err => Messages.UnexpectedMutationError(err), - _ => Messages.UnexpectedMutationError() - }; - - console.Error.WriteErrorLine(errorMessage); - } - - return ExitCodes.Error; + IUnauthorizedOperation err => err.Message, + IInvalidSourceMetadataInputError err => err.Message, + IStageNotFoundError err => err.Message, + IMcpFeatureCollectionNotFoundError err => err.Message, + IError err => Messages.UnexpectedMutationError(err), + _ => Messages.UnexpectedMutationError() + }; + + console.Error.WriteErrorLine(errorMessage); } - if (validationRequest.Id is not { } id) - { - throw new ExitException("Could not create validation request!"); - } + return ExitCodes.Error; + } - requestId = id; - child.Success($"Validation request created (ID: {requestId.EscapeMarkup()})."); + if (validationRequest.Id is not { } id) + { + throw new ExitException("Could not create validation request!"); } - await using (var child = rootActivity.StartChildActivity( - Messages.ValidatingActivity, - Messages.ValidationFailed)) + activity.Update($"Validation request created. {$"(ID: {id})".Dim()}"); + + await foreach (var update in client.SubscribeToMcpFeatureCollectionValidationAsync(id, ct)) { - await foreach (var update in client.SubscribeToMcpFeatureCollectionValidationAsync(requestId, ct)) + switch (update) { - switch (update) - { - case IMcpFeatureCollectionVersionValidationFailed { Errors: var errors }: - var errorTree = new Tree(""); + case IMcpFeatureCollectionVersionValidationFailed { Errors: var errors }: + var errorTree = new Tree(""); - foreach (var error in errors) + foreach (var error in errors) + { + switch (error) { - switch (error) - { - case IMcpFeatureCollectionValidationError e: - errorTree.AddMcpFeatureCollectionValidationErrors(e); - break; - case IMcpFeatureCollectionValidationArchiveError e: - errorTree.AddErrorMessage(Messages.InvalidArchive(e.Message)); - break; - case IUnexpectedProcessingError e: - errorTree.AddErrorMessage(e.Message); - break; - case IProcessingTimeoutError e: - errorTree.AddErrorMessage(e.Message); - break; - } + case IMcpFeatureCollectionValidationError e: + errorTree.AddMcpFeatureCollectionValidationErrors(e); + break; + case IMcpFeatureCollectionValidationArchiveError e: + errorTree.AddErrorMessage(Messages.InvalidArchive(e.Message)); + break; + case IUnexpectedProcessingError e: + errorTree.AddErrorMessage(e.Message); + break; + case IProcessingTimeoutError e: + errorTree.AddErrorMessage(e.Message); + break; } + } - child.Fail(errorTree); + await activity.FailAllAsync(errorTree); - await child.FailAllAsync(); + throw new ExitException("MCP feature collection validation failed."); - throw new ExitException("MCP feature collection validation failed."); + case IMcpFeatureCollectionVersionValidationSuccess: + activity.Success($"Validated MCP feature collection against stage '{stage.EscapeMarkup()}'."); + return ExitCodes.Success; - case IMcpFeatureCollectionVersionValidationSuccess: - child.Success(Messages.ValidationPassed); - rootActivity.Success($"Validated MCP feature collection against stage '{stage.EscapeMarkup()}'."); - return ExitCodes.Success; + case IOperationInProgress: + case IValidationInProgress: + activity.Update(Messages.Validating); + break; - case IOperationInProgress: - case IValidationInProgress: - child.Update(Messages.Validating); - break; - - default: - child.Update(Messages.UnknownServerResponse, ActivityUpdateKind.Warning); - break; - } + default: + activity.Update(Messages.UnknownServerResponse, ActivityUpdateKind.Warning); + break; } - - child.Fail(); } + + await activity.FailAllAsync(); } return ExitCodes.Error; diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Mocks/CreateMockCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Mocks/CreateMockCommand.cs index c4cfcc92cef..b8b636ea2c6 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Mocks/CreateMockCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Mocks/CreateMockCommand.cs @@ -104,7 +104,7 @@ private static async Task ExecuteAsync( if (createdMock.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in createdMock.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Mocks/ListMockCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Mocks/ListMockCommand.cs index 2e59a819416..6b1db0bd0e1 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Mocks/ListMockCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Mocks/ListMockCommand.cs @@ -4,8 +4,6 @@ using ChilliCream.Nitro.CommandLine.Helpers; using ChilliCream.Nitro.CommandLine.Results; using ChilliCream.Nitro.CommandLine.Services.Sessions; -using static ChilliCream.Nitro.CommandLine.ThrowHelper; - namespace ChilliCream.Nitro.CommandLine.Commands.Mocks; internal sealed class ListMockCommand : Command @@ -57,8 +55,7 @@ private static async Task RenderInteractiveAsync( string? cursor, CancellationToken ct) { - const string apiMessage = "For which API do you want to list the mock schemas?"; - var apiId = await console.GetOrPromptForApiIdAsync(apiMessage, parseResult, apisClient, sessionService, ct); + var apiId = await console.GetOrPromptForApiIdAsync("For which API do you want to list the mock schemas?", parseResult, apisClient, sessionService, ct); var container = PaginationContainer .CreateConnectionData((after, first, token) => @@ -87,11 +84,7 @@ private static async Task RenderNonInteractiveAsync( string? cursor, CancellationToken ct) { - var apiId = parseResult.GetValue(Opt.Instance); - if (apiId is null) - { - throw MissingRequiredOption(ApiIdOption.OptionName); - } + var apiId = parseResult.GetRequiredOptionalValue(Opt.Instance); var data = await client.ListMockSchemasAsync(apiId, cursor, 10, ct); var items = data.Items diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Mocks/UpdateMockCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Mocks/UpdateMockCommand.cs index 0d227d6a37d..8284534a87e 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Mocks/UpdateMockCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Mocks/UpdateMockCommand.cs @@ -65,17 +65,10 @@ private static async Task ExecuteAsync( var workspaceId = parseResult.GetWorkspaceId(sessionService); - var selectedApi = await SelectApiPrompt - .New(apisClient, workspaceId) - .RenderAsync(console, cancellationToken); - - if (selectedApi?.Id is null) - { - throw new ExitException("No API selected."); - } + var apiId = await console.PromptForApiIdAsync(apisClient, workspaceId, null, cancellationToken); var selectedMock = await SelectMockSchemaPrompt - .New(client, selectedApi.Id) + .New(client, apiId) .RenderAsync(console, cancellationToken); mockSchemaId = selectedMock?.Id ?? throw new ExitException("No mock schema selected."); @@ -128,7 +121,7 @@ private static async Task ExecuteAsync( if (updatedMock.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in updatedMock.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/Component/SelectOpenApiCollectionPrompt.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/Component/SelectOpenApiCollectionPrompt.cs index 8e9b4907b50..0516c58436e 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/Component/SelectOpenApiCollectionPrompt.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/Component/SelectOpenApiCollectionPrompt.cs @@ -20,7 +20,7 @@ public SelectOpenApiCollectionPrompt Title(string title) { var paginationContainer = PaginationContainer.CreateConnectionData( async (after, first, ct) => await client.ListOpenApiCollectionsAsync(apiId, after, first, ct) - ?? throw ThrowHelper.ThereWasAnIssueWithTheRequest("The API was not found.")); + ?? throw new ExitException("The API was not found.")); return await PagedSelectionPrompt .New(paginationContainer) diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/CreateOpenApiCollectionCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/CreateOpenApiCollectionCommand.cs index 22369ba6acc..5cfe692fac1 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/CreateOpenApiCollectionCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/CreateOpenApiCollectionCommand.cs @@ -44,8 +44,7 @@ private static async Task ExecuteAsync( parseResult.AssertHasAuthentication(sessionService); - const string apiMessage = "For which API do you want to create an OpenAPI collection?"; - var apiId = await console.GetOrPromptForApiIdAsync(apiMessage, parseResult, apisClient, sessionService, cancellationToken); + var apiId = await console.GetOrPromptForApiIdAsync("For which API do you want to create an OpenAPI collection?", parseResult, apisClient, sessionService, cancellationToken); var name = await console .PromptAsync("Name", defaultValue: null, parseResult, Opt.Instance, cancellationToken); @@ -61,7 +60,7 @@ private static async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in data.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/DeleteOpenApiCollectionCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/DeleteOpenApiCollectionCommand.cs index e079ec89dfc..79c11868dec 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/DeleteOpenApiCollectionCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/DeleteOpenApiCollectionCommand.cs @@ -40,31 +40,21 @@ private static async Task ExecuteAsync( parseResult.AssertHasAuthentication(sessionService); - var openApiCollectionId = parseResult.GetValue(Opt.Instance); + var openApiCollectionId = parseResult.GetRequiredValueIfNotInteractive(Opt.Instance, console); if (openApiCollectionId is null) { - if (!console.IsInteractive) - { - throw MissingRequiredOption("id"); - } - - const string apiMessage = "For which API do you want to delete an OpenAPI collection?"; - const string openApiCollectionMessage = "Which OpenAPI collection do you want to delete?"; - var workspaceId = parseResult.GetWorkspaceId(sessionService); - - var selectedApi = await SelectApiPrompt - .New(apisClient, workspaceId) - .Title(apiMessage) - .RenderAsync(console, cancellationToken) ?? throw NoApiSelected(); - - var apiId = selectedApi.Id; + var apiId = await console.PromptForApiIdAsync( + apisClient, + workspaceId, + "For which API do you want to delete an OpenAPI collection?", + cancellationToken); var selectedOpenApiCollection = await SelectOpenApiCollectionPrompt .New(client, apiId) - .Title(openApiCollectionMessage) - .RenderAsync(console, cancellationToken) ?? throw NoOpenApiCollectionSelected(); + .Title("Which OpenAPI collection do you want to delete?") + .RenderAsync(console, cancellationToken) ?? throw new ExitException("You did not select an OpenAPI collection!"); openApiCollectionId = selectedOpenApiCollection.Id; } @@ -93,7 +83,7 @@ private static async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in data.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/ListOpenApiCollectionCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/ListOpenApiCollectionCommand.cs index 0bb40165fb0..2a067aff50e 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/ListOpenApiCollectionCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/ListOpenApiCollectionCommand.cs @@ -4,8 +4,6 @@ using ChilliCream.Nitro.CommandLine.Helpers; using ChilliCream.Nitro.CommandLine.Results; using ChilliCream.Nitro.CommandLine.Services.Sessions; -using static ChilliCream.Nitro.CommandLine.ThrowHelper; - namespace ChilliCream.Nitro.CommandLine.Commands.OpenApi; internal sealed class ListOpenApiCollectionCommand : Command @@ -39,7 +37,15 @@ private static async Task ExecuteAsync( if (console.IsInteractive) { - return await RenderInteractiveAsync(parseResult, console, client, apisClient, sessionService, resultHolder, cursor, ct); + return await RenderInteractiveAsync( + parseResult, + console, + client, + apisClient, + sessionService, + resultHolder, + cursor, + ct); } return await RenderNonInteractiveAsync(parseResult, client, resultHolder, cursor, ct); @@ -55,13 +61,12 @@ private static async Task RenderInteractiveAsync( string? cursor, CancellationToken ct) { - const string apiMessage = "For which API do you want to list the OpenAPI collections?"; - var apiId = await parseResult.GetOrPromptForApiIdAsync(apiMessage, console, apisClient, sessionService, ct); + var apiId = await console.GetOrPromptForApiIdAsync("For which API do you want to list the OpenAPI collections?", parseResult, apisClient, sessionService, ct); var container = PaginationContainer .CreateConnectionData(async (after, first, token) => - await client.ListOpenApiCollectionsAsync(apiId, after, first, token) - ?? throw ThereWasAnIssueWithTheRequest("The API was not found.")) + await client.ListOpenApiCollectionsAsync(apiId, after ?? cursor, first, token) + ?? throw new ExitException("The API was not found.")) .PageSize(10); var api = await PagedTable @@ -86,14 +91,10 @@ private static async Task RenderNonInteractiveAsync( string? cursor, CancellationToken ct) { - var apiId = parseResult.GetValue(Opt.Instance); - if (apiId is null) - { - throw MissingRequiredOption(ApiIdOption.OptionName); - } + var apiId = parseResult.GetRequiredOptionalValue(Opt.Instance); var data = await client.ListOpenApiCollectionsAsync(apiId, cursor, 10, ct) - ?? throw ThereWasAnIssueWithTheRequest("The API was not found."); + ?? throw new ExitException("The API was not found."); var items = data.Items .Select(OpenApiCollectionDetailPrompt.From) .Select(x => x.ToObject()) diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/PublishOpenApiCollectionCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/PublishOpenApiCollectionCommand.cs index 3fae6fb57df..2244166759c 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/PublishOpenApiCollectionCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/PublishOpenApiCollectionCommand.cs @@ -64,148 +64,132 @@ private static async Task ExecuteAsync( var sourceMetadataJson = parseResult.GetValue(Opt.Instance); var source = SourceMetadataParser.Parse(sourceMetadataJson); - await using (var rootActivity = console.StartActivity( - $"Publishing new OpenAPI collection version '{tag.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'", + await using (var activity = console.StartActivity( + $"Publishing new version '{tag.EscapeMarkup()}' of OpenAPI collection '{openApiCollectionId.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'", "Failed to publish a new OpenAPI collection version.")) { if (force) { - rootActivity.Update(Messages.ForcePushEnabled, ActivityUpdateKind.Warning); + activity.Update(Messages.ForcePushEnabled, ActivityUpdateKind.Warning); } - string requestId; + var publishRequest = await client.StartOpenApiCollectionPublishAsync( + openApiCollectionId, + stage, + tag, + force, + waitForApproval, + source, + ct); - await using (var child = rootActivity.StartChildActivity( - Messages.StartingPublishRequest, - Messages.FailedToStartPublishRequest)) + if (publishRequest.Errors?.Count > 0) { - var publishRequest = await client.StartOpenApiCollectionPublishAsync( - openApiCollectionId, - stage, - tag, - force, - waitForApproval, - source, - ct); - - if (publishRequest.Errors?.Count > 0) - { - await child.FailAllAsync(); + await activity.FailAllAsync(); - foreach (var error in publishRequest.Errors) + foreach (var error in publishRequest.Errors) + { + var errorMessage = error switch { - var errorMessage = error switch - { - IUnauthorizedOperation err => err.Message, - IInvalidSourceMetadataInputError err => err.Message, - IStageNotFoundError err => err.Message, - IOpenApiCollectionNotFoundError err => err.Message, - IOpenApiCollectionVersionNotFoundError err => err.Message, - IError err => Messages.UnexpectedMutationError(err), - _ => Messages.UnexpectedMutationError() - }; - - console.Error.WriteErrorLine(errorMessage); - } - - return ExitCodes.Error; + IUnauthorizedOperation err => err.Message, + IInvalidSourceMetadataInputError err => err.Message, + IStageNotFoundError err => err.Message, + IOpenApiCollectionNotFoundError err => err.Message, + IOpenApiCollectionVersionNotFoundError err => err.Message, + IError err => Messages.UnexpectedMutationError(err), + _ => Messages.UnexpectedMutationError() + }; + + console.Error.WriteErrorLine(errorMessage); } - if (publishRequest.Id is not { } id) - { - throw MutationReturnedNoData(); - } + return ExitCodes.Error; + } - requestId = id; - child.Success($"Publish request created (ID: {requestId.EscapeMarkup()})."); + if (publishRequest.Id is not { } id) + { + throw MutationReturnedNoData(); } - await using (var child = rootActivity.StartChildActivity( - Messages.ProcessingActivity, - Messages.ProcessingFailed)) + activity.Update($"Publication request created. {$"(ID: {id})".Dim()}"); + + await foreach (var update in client.SubscribeToOpenApiCollectionPublishAsync(id, ct)) { - await foreach (var update in client.SubscribeToOpenApiCollectionPublishAsync(requestId, ct)) + switch (update) { - switch (update) - { - case IProcessingTaskIsQueued v: - child.Update(Messages.QueuedAtPosition(v.QueuePosition), ActivityUpdateKind.Waiting); - break; + case IProcessingTaskIsQueued v: + activity.Update(Messages.QueuedAtPosition(v.QueuePosition), ActivityUpdateKind.Waiting); + break; - case IOpenApiCollectionVersionPublishFailed { Errors: var errors }: - var errorTree = new Tree(""); + case IOpenApiCollectionVersionPublishFailed { Errors: var errors }: + var errorTree = new Tree(""); - foreach (var error in errors) + foreach (var error in errors) + { + switch (error) { - switch (error) - { - case IOpenApiCollectionValidationError e: - errorTree.AddOpenApiCollectionValidationErrors(e); - break; - case IConcurrentOperationError e: - errorTree.AddErrorMessage(e.Message); - break; - case IUnexpectedProcessingError e: - errorTree.AddErrorMessage(e.Message); - break; - case IProcessingTimeoutError e: - errorTree.AddErrorMessage(e.Message); - break; - } + case IOpenApiCollectionValidationError e: + errorTree.AddOpenApiCollectionValidationErrors(e); + break; + case IConcurrentOperationError e: + errorTree.AddErrorMessage(e.Message); + break; + case IUnexpectedProcessingError e: + errorTree.AddErrorMessage(e.Message); + break; + case IProcessingTimeoutError e: + errorTree.AddErrorMessage(e.Message); + break; } + } - child.Fail(errorTree); + await activity.FailAllAsync(errorTree); - await child.FailAllAsync(); + throw new ExitException("OpenAPI collection publish failed."); - throw new ExitException("OpenAPI collection publish failed."); + case IOpenApiCollectionVersionPublishSuccess: + activity.Success($"Published new version '{tag.EscapeMarkup()}' of OpenAPI collection '{openApiCollectionId.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'."); + return ExitCodes.Success; - case IOpenApiCollectionVersionPublishSuccess: - child.Success(Messages.PublishedSuccessfully); - rootActivity.Success($"Published new OpenAPI collection version '{tag.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'."); - return ExitCodes.Success; + case IProcessingTaskIsReady: + activity.Update(Messages.RequestReadyForProcessing); + break; - case IProcessingTaskIsReady: - child.Update(Messages.RequestReadyForProcessing); - break; + case IOperationInProgress: + activity.Update(Messages.RequestBeingProcessed); + break; - case IOperationInProgress: - child.Update(Messages.RequestBeingProcessed); - break; + case IWaitForApproval waitForApprovalEvent: + if (waitForApprovalEvent.Deployment is IOpenApiCollectionDeployment deployment) + { + var approvalErrorTree = new Tree(""); - case IWaitForApproval waitForApprovalEvent: - if (waitForApprovalEvent.Deployment is IOpenApiCollectionDeployment deployment) + foreach (var error in deployment.Errors) { - var approvalErrorTree = new Tree(""); - - foreach (var error in deployment.Errors) + switch (error) { - switch (error) - { - case IOpenApiCollectionValidationError e: - approvalErrorTree.AddOpenApiCollectionValidationErrors(e); - break; - } + case IOpenApiCollectionValidationError e: + approvalErrorTree.AddOpenApiCollectionValidationErrors(e); + break; } - - child.Update(Messages.ValidationFailed, ActivityUpdateKind.Warning, approvalErrorTree); } - child.Update(Messages.WaitingForApproval, ActivityUpdateKind.Waiting); - break; + activity.Update(Messages.ValidationFailed, ActivityUpdateKind.Warning, approvalErrorTree); + } - case IProcessingTaskApproved: - child.Update(Messages.RequestApproved); - break; + activity.Update(Messages.WaitingForApproval, ActivityUpdateKind.Waiting); + break; - default: - child.Update(Messages.UnknownServerResponse, ActivityUpdateKind.Warning); - break; - } - } + case IProcessingTaskApproved: + activity.Update(Messages.RequestApproved); + break; - child.Fail(); + default: + activity.Update(Messages.UnknownServerResponse, ActivityUpdateKind.Warning); + break; + } } + + await activity.FailAllAsync(); } return ExitCodes.Error; diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/UploadOpenApiCollectionCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/UploadOpenApiCollectionCommand.cs index 27c221a6b4a..209c7f4573e 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/UploadOpenApiCollectionCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/UploadOpenApiCollectionCommand.cs @@ -63,51 +63,50 @@ await OpenApiCollectionHelpers.BuildOpenApiCollectionArchive( files, cancellationToken); - await using (var activity = console.StartActivity( - $"Uploading new OpenAPI collection version '{tag.EscapeMarkup()}' for collection '{openApiCollectionId.EscapeMarkup()}'", - "Failed to upload a new OpenAPI collection version.")) + await using var activity = console.StartActivity( + $"Uploading new version '{tag.EscapeMarkup()}' for OpenAPI collection '{openApiCollectionId.EscapeMarkup()}'", + "Failed to upload a new OpenAPI collection version."); + + var data = await client.UploadOpenApiCollectionVersionAsync( + openApiCollectionId, + tag, + archiveStream, + source, + cancellationToken); + + if (data.Errors?.Count > 0) { - var data = await client.UploadOpenApiCollectionVersionAsync( - openApiCollectionId, - tag, - archiveStream, - source, - cancellationToken); + await activity.FailAllAsync(); - if (data.Errors?.Count > 0) + foreach (var error in data.Errors) { - activity.Fail(); - - foreach (var error in data.Errors) + var errorMessage = error switch { - var errorMessage = error switch - { - IOpenApiCollectionNotFoundError err => err.Message, - IUnauthorizedOperation err => err.Message, - IUploadOpenApiCollectionCommandMutation_UploadOpenApiCollection_Errors_InvalidSourceMetadataInputError err => err.Message, - IDuplicatedTagError err => err.Message, - IConcurrentOperationError err => err.Message, - IInvalidOpenApiCollectionArchiveError err => Messages.InvalidArchive(err.Message), - IError err => Messages.UnexpectedMutationError(err), - _ => Messages.UnexpectedMutationError() - }; - - console.Error.WriteErrorLine(errorMessage); - } - - return ExitCodes.Error; - } - - if (data.OpenApiCollectionVersion is null) - { - activity.Fail(); - console.Error.WriteErrorLine("Could not upload OpenAPI collection version."); - return ExitCodes.Error; + IOpenApiCollectionNotFoundError err => err.Message, + IUnauthorizedOperation err => err.Message, + IUploadOpenApiCollectionCommandMutation_UploadOpenApiCollection_Errors_InvalidSourceMetadataInputError err => err.Message, + IDuplicatedTagError err => err.Message, + IConcurrentOperationError err => err.Message, + IInvalidOpenApiCollectionArchiveError err => Messages.InvalidArchive(err.Message), + IError err => Messages.UnexpectedMutationError(err), + _ => Messages.UnexpectedMutationError() + }; + + console.Error.WriteErrorLine(errorMessage); } - activity.Success($"Uploaded new OpenAPI collection version '{tag.EscapeMarkup()}'."); + return ExitCodes.Error; + } - return ExitCodes.Success; + if (data.OpenApiCollectionVersion is null) + { + await activity.FailAllAsync(); + console.Error.WriteErrorLine("Could not upload OpenAPI collection version."); + return ExitCodes.Error; } + + activity.Success($"Uploaded new OpenAPI collection version '{tag.EscapeMarkup()}'."); + + return ExitCodes.Success; } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/ValidateOpenApiCollectionCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/ValidateOpenApiCollectionCommand.cs index d5824d71d55..4e5f9d09fc8 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/ValidateOpenApiCollectionCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/OpenApi/ValidateOpenApiCollectionCommand.cs @@ -49,17 +49,17 @@ private static async Task ExecuteAsync( var sourceMetadataJson = parseResult.GetValue(Opt.Instance); var source = SourceMetadataParser.Parse(sourceMetadataJson); - await using (var rootActivity = console.StartActivity( - $"Validating OpenAPI collection against stage '{stage.EscapeMarkup()}'", + await using (var activity = console.StartActivity( + $"Validating OpenAPI collection '{openApiCollectionId.EscapeMarkup()}' against stage '{stage.EscapeMarkup()}'", "Failed to validate the OpenAPI collection.")) { var files = fileSystem.GlobMatch(patterns, ["**/bin/**", "**/obj/**"]).ToArray(); - rootActivity.Update($"Found {files.Length} document(s)."); + activity.Update($"Found {files.Length} document(s)."); if (files.Length < 1) { - rootActivity.Fail(); + await activity.FailAllAsync(); throw new ExitException("Could not find any OpenAPI documents with the provided pattern."); } @@ -69,104 +69,88 @@ await OpenApiCollectionHelpers.BuildOpenApiCollectionArchive( files, ct); - string requestId; + var validationRequest = await client.StartOpenApiCollectionValidationAsync( + openApiCollectionId, + stage, + archiveStream, + source, + ct); - await using (var child = rootActivity.StartChildActivity( - Messages.StartingValidationRequest, - Messages.FailedToStartValidationRequest)) + if (validationRequest.Errors?.Count > 0) { - var validationRequest = await client.StartOpenApiCollectionValidationAsync( - openApiCollectionId, - stage, - archiveStream, - source, - ct); + await activity.FailAllAsync(); - if (validationRequest.Errors?.Count > 0) + foreach (var error in validationRequest.Errors) { - await child.FailAllAsync(); - - foreach (var error in validationRequest.Errors) + var errorMessage = error switch { - var errorMessage = error switch - { - IUnauthorizedOperation err => err.Message, - IInvalidSourceMetadataInputError err => err.Message, - IStageNotFoundError err => err.Message, - IOpenApiCollectionNotFoundError err => err.Message, - IError err => Messages.UnexpectedMutationError(err), - _ => Messages.UnexpectedMutationError() - }; - - console.Error.WriteErrorLine(errorMessage); - } - - return ExitCodes.Error; + IUnauthorizedOperation err => err.Message, + IInvalidSourceMetadataInputError err => err.Message, + IStageNotFoundError err => err.Message, + IOpenApiCollectionNotFoundError err => err.Message, + IError err => Messages.UnexpectedMutationError(err), + _ => Messages.UnexpectedMutationError() + }; + + console.Error.WriteErrorLine(errorMessage); } - if (validationRequest.Id is not { } id) - { - throw new ExitException("Could not create validation request!"); - } + return ExitCodes.Error; + } - requestId = id; - child.Success($"Validation request created (ID: {requestId.EscapeMarkup()})."); + if (validationRequest.Id is not { } id) + { + throw new ExitException("Could not create validation request!"); } - await using (var child = rootActivity.StartChildActivity( - Messages.ValidatingActivity, - Messages.ValidationFailed)) + activity.Update($"Validation request created. {$"(ID: {id})".Dim()}"); + + await foreach (var update in client.SubscribeToOpenApiCollectionValidationAsync(id, ct)) { - await foreach (var update in client.SubscribeToOpenApiCollectionValidationAsync(requestId, ct)) + switch (update) { - switch (update) - { - case IOpenApiCollectionVersionValidationFailed { Errors: var errors }: - var errorTree = new Tree(""); + case IOpenApiCollectionVersionValidationFailed { Errors: var errors }: + var errorTree = new Tree(""); - foreach (var error in errors) + foreach (var error in errors) + { + switch (error) { - switch (error) - { - case IOpenApiCollectionValidationError e: - errorTree.AddOpenApiCollectionValidationErrors(e); - break; - case IOpenApiCollectionValidationArchiveError e: - errorTree.AddErrorMessage(Messages.InvalidArchive(e.Message)); - break; - case IUnexpectedProcessingError e: - errorTree.AddErrorMessage(e.Message); - break; - case IProcessingTimeoutError e: - errorTree.AddErrorMessage(e.Message); - break; - } + case IOpenApiCollectionValidationError e: + errorTree.AddOpenApiCollectionValidationErrors(e); + break; + case IOpenApiCollectionValidationArchiveError e: + errorTree.AddErrorMessage(Messages.InvalidArchive(e.Message)); + break; + case IUnexpectedProcessingError e: + errorTree.AddErrorMessage(e.Message); + break; + case IProcessingTimeoutError e: + errorTree.AddErrorMessage(e.Message); + break; } + } - child.Fail(errorTree); + await activity.FailAllAsync(errorTree); - await child.FailAllAsync(); + throw new ExitException("OpenAPI collection validation failed."); - throw new ExitException("OpenAPI collection validation failed."); + case IOpenApiCollectionVersionValidationSuccess: + activity.Success($"Validated OpenAPI collection against stage '{stage.EscapeMarkup()}'."); + return ExitCodes.Success; - case IOpenApiCollectionVersionValidationSuccess: - child.Success(Messages.ValidationPassed); - rootActivity.Success($"Validated OpenAPI collection against stage '{stage.EscapeMarkup()}'."); - return ExitCodes.Success; + case IOperationInProgress: + case IValidationInProgress: + activity.Update(Messages.Validating); + break; - case IOperationInProgress: - case IValidationInProgress: - child.Update(Messages.Validating); - break; - - default: - child.Update(Messages.UnknownServerResponse, ActivityUpdateKind.Warning); - break; - } + default: + activity.Update(Messages.UnknownServerResponse, ActivityUpdateKind.Warning); + break; } - - child.Fail(); } + + await activity.FailAllAsync(); } return ExitCodes.Error; diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/PersonalAccessTokens/CreatePersonalAccessTokenCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/PersonalAccessTokens/CreatePersonalAccessTokenCommand.cs index 4f2ffd6ee27..4c09139c648 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/PersonalAccessTokens/CreatePersonalAccessTokenCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/PersonalAccessTokens/CreatePersonalAccessTokenCommand.cs @@ -62,7 +62,7 @@ private static async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in data.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/PersonalAccessTokens/RevokePersonalAccessTokenCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/PersonalAccessTokens/RevokePersonalAccessTokenCommand.cs index a83f88401a7..a7fde5255d3 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/PersonalAccessTokens/RevokePersonalAccessTokenCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/PersonalAccessTokens/RevokePersonalAccessTokenCommand.cs @@ -59,7 +59,7 @@ private static async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in data.Errors) { @@ -78,7 +78,7 @@ private static async Task ExecuteAsync( if (data.PersonalAccessToken is not IPersonalAccessTokenDetailPrompt_PersonalAccessToken token) { - activity.Fail(); + await activity.FailAllAsync(); console.Error.WriteErrorLine("Could not revoke personal access token."); return ExitCodes.Error; } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/PublishSchemaCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/PublishSchemaCommand.cs index 6c982316fb0..2784472f88c 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/PublishSchemaCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/PublishSchemaCommand.cs @@ -65,194 +65,178 @@ private static async Task ExecuteAsync( var source = SourceMetadataParser.Parse(sourceMetadataJson); - await using (var rootActivity = console.StartActivity( - $"Publishing new schema version '{tag.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}' of API '{apiId.EscapeMarkup()}'", + await using (var activity = console.StartActivity( + $"Publishing new schema version '{tag.EscapeMarkup()}' of API '{apiId.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'", "Failed to publish a new schema version.")) { if (force) { - rootActivity.Update(Messages.ForcePushEnabled, ActivityUpdateKind.Warning); + activity.Update(Messages.ForcePushEnabled, ActivityUpdateKind.Warning); } - string requestId; + var publishRequest = await client.StartSchemaPublishAsync( + apiId, + stage, + tag, + force, + waitForApproval, + source, + ct); - await using (var child = rootActivity.StartChildActivity( - Messages.StartingPublishRequest, - Messages.FailedToStartPublishRequest)) + if (publishRequest.Errors?.Count > 0) { - var publishRequest = await client.StartSchemaPublishAsync( - apiId, - stage, - tag, - force, - waitForApproval, - source, - ct); - - if (publishRequest.Errors?.Count > 0) - { - await child.FailAllAsync(); + await activity.FailAllAsync(); - foreach (var error in publishRequest.Errors) + foreach (var error in publishRequest.Errors) + { + var errorMessage = error switch { - var errorMessage = error switch - { - IUnauthorizedOperation err => err.Message, - IInvalidSourceMetadataInputError err => err.Message, - IApiNotFoundError err => err.Message, - IStageNotFoundError err => err.Message, - ISchemaNotFoundError err => err.Message, - IError err => Messages.UnexpectedMutationError(err), - _ => Messages.UnexpectedMutationError() - }; - - console.Error.WriteErrorLine(errorMessage); - } - - return ExitCodes.Error; + IUnauthorizedOperation err => err.Message, + IInvalidSourceMetadataInputError err => err.Message, + IApiNotFoundError err => err.Message, + IStageNotFoundError err => err.Message, + ISchemaNotFoundError err => err.Message, + IError err => Messages.UnexpectedMutationError(err), + _ => Messages.UnexpectedMutationError() + }; + + console.Error.WriteErrorLine(errorMessage); } - if (publishRequest.Id is not { } id) - { - throw MutationReturnedNoData(); - } + return ExitCodes.Error; + } - requestId = id; - child.Success($"Publish request created (ID: {requestId.EscapeMarkup()})."); + if (publishRequest.Id is not { } id) + { + throw MutationReturnedNoData(); } - await using (var child = rootActivity.StartChildActivity( - Messages.ProcessingActivity, - Messages.ProcessingFailed)) + activity.Update($"Publication request created. {$"(ID: {id})".Dim()}"); + + await foreach (var update in client.SubscribeToSchemaPublishAsync(id, ct)) { - await foreach (var update in client.SubscribeToSchemaPublishAsync(requestId, ct)) + switch (update) { - switch (update) - { - case IProcessingTaskIsQueued v: - child.Update(Messages.QueuedAtPosition(v.QueuePosition), ActivityUpdateKind.Waiting); - break; + case IProcessingTaskIsQueued v: + activity.Update(Messages.QueuedAtPosition(v.QueuePosition), ActivityUpdateKind.Waiting); + break; - case ISchemaVersionPublishFailed { Errors: var schemaErrors }: - var errorTree = new Tree(""); + case ISchemaVersionPublishFailed { Errors: var schemaErrors }: + var errorTree = new Tree(""); - foreach (var error in schemaErrors) + foreach (var error in schemaErrors) + { + switch (error) { - switch (error) - { - case ISchemaVersionChangeViolationError e: - errorTree.AddSchemaVersionChangeViolations(e); - break; - case IInvalidGraphQLSchemaError e: - errorTree.AddGraphQLSchemaErrors(e); - break; - case IPersistedQueryValidationError e: - errorTree.AddPersistedQueryValidationErrorsWithClients(e); - break; - case IOpenApiCollectionValidationError e: - errorTree.AddOpenApiCollectionValidationErrors(e); - break; - case IMcpFeatureCollectionValidationError e: - errorTree.AddMcpFeatureCollectionValidationErrors(e); - break; - case IConcurrentOperationError e: - errorTree.AddErrorMessage(e.Message); - break; - case IOperationsAreNotAllowedError e: - errorTree.AddErrorMessage(e.Message); - break; - case ISchemaVersionSyntaxError e: - errorTree.AddErrorMessage(e.Message); - break; - case IProcessingTimeoutError e: - errorTree.AddErrorMessage(e.Message); - break; - case IUnexpectedProcessingError e: - errorTree.AddErrorMessage(e.Message); - break; - case IError e: - errorTree.AddErrorMessage("Unexpected error: " + e.Message); - break; - } + case ISchemaVersionChangeViolationError e: + errorTree.AddSchemaVersionChangeViolations(e); + break; + case IInvalidGraphQLSchemaError e: + errorTree.AddGraphQLSchemaErrors(e); + break; + case IPersistedQueryValidationError e: + errorTree.AddPersistedQueryValidationErrorsWithClients(e); + break; + case IOpenApiCollectionValidationError e: + errorTree.AddOpenApiCollectionValidationErrors(e); + break; + case IMcpFeatureCollectionValidationError e: + errorTree.AddMcpFeatureCollectionValidationErrors(e); + break; + case IConcurrentOperationError e: + errorTree.AddErrorMessage(e.Message); + break; + case IOperationsAreNotAllowedError e: + errorTree.AddErrorMessage(e.Message); + break; + case ISchemaVersionSyntaxError e: + errorTree.AddErrorMessage(e.Message); + break; + case IProcessingTimeoutError e: + errorTree.AddErrorMessage(e.Message); + break; + case IUnexpectedProcessingError e: + errorTree.AddErrorMessage(e.Message); + break; + case IError e: + errorTree.AddErrorMessage("Unexpected error: " + e.Message); + break; } + } - child.Fail(errorTree); + await activity.FailAllAsync(errorTree); - await child.FailAllAsync(); + throw new ExitException("Schema publish failed."); - throw new ExitException("Schema publish failed."); + case ISchemaVersionPublishSuccess: + activity.Success($"Published new schema version '{tag.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'."); - case ISchemaVersionPublishSuccess: - child.Success(Messages.PublishedSuccessfully); - rootActivity.Success($"Published new schema version '{tag.EscapeMarkup()}' to stage '{stage.EscapeMarkup()}'."); + return ExitCodes.Success; - return ExitCodes.Success; + case IProcessingTaskIsReady: + activity.Update(Messages.RequestReadyForProcessing); + break; - case IProcessingTaskIsReady: - child.Update(Messages.RequestReadyForProcessing); - break; + case IOperationInProgress: + activity.Update(Messages.RequestBeingProcessed); + break; - case IOperationInProgress: - child.Update(Messages.RequestBeingProcessed); - break; + case IWaitForApproval waitForApprovalEvent: + if (waitForApprovalEvent.Deployment is ISchemaDeployment deployment) + { + var deploymentErrorTree = new Tree(""); - case IWaitForApproval waitForApprovalEvent: - if (waitForApprovalEvent.Deployment is ISchemaDeployment deployment) + foreach (var error in deployment.Errors) { - var deploymentErrorTree = new Tree(""); - - foreach (var error in deployment.Errors) + switch (error) { - switch (error) - { - case IOperationsAreNotAllowedError e: - deploymentErrorTree.AddNode(e.Message); - break; - case ISchemaVersionSyntaxError e: - deploymentErrorTree.AddNode(e.Message); - break; - case ISchemaChangeViolationError e: - deploymentErrorTree.AddSchemaVersionChangeViolations(e); - break; - case IInvalidGraphQLSchemaError e: - deploymentErrorTree.AddGraphQLSchemaErrors(e); - break; - case IPersistedQueryValidationError e: - deploymentErrorTree.AddPersistedQueryValidationErrorsWithClients(e); - break; - case IOpenApiCollectionValidationError e: - deploymentErrorTree.AddOpenApiCollectionValidationErrors(e); - break; - case IMcpFeatureCollectionValidationError e: - deploymentErrorTree.AddMcpFeatureCollectionValidationErrors(e); - break; - } + case IOperationsAreNotAllowedError e: + deploymentErrorTree.AddNode(e.Message); + break; + case ISchemaVersionSyntaxError e: + deploymentErrorTree.AddNode(e.Message); + break; + case ISchemaChangeViolationError e: + deploymentErrorTree.AddSchemaVersionChangeViolations(e); + break; + case IInvalidGraphQLSchemaError e: + deploymentErrorTree.AddGraphQLSchemaErrors(e); + break; + case IPersistedQueryValidationError e: + deploymentErrorTree.AddPersistedQueryValidationErrorsWithClients(e); + break; + case IOpenApiCollectionValidationError e: + deploymentErrorTree.AddOpenApiCollectionValidationErrors(e); + break; + case IMcpFeatureCollectionValidationError e: + deploymentErrorTree.AddMcpFeatureCollectionValidationErrors(e); + break; } - - child.Update( - Messages.ValidationFailed, - ActivityUpdateKind.Warning, - deploymentErrorTree); } - child.Update( - Messages.WaitingForApproval, - ActivityUpdateKind.Waiting); - break; - - case IProcessingTaskApproved: - child.Update(Messages.RequestApproved); - break; - - default: - child.Update( - Messages.UnknownServerResponse, ActivityUpdateKind.Warning); - break; - } + activity.Update( + Messages.ValidationFailed, + ActivityUpdateKind.Warning, + deploymentErrorTree); + } + + activity.Update( + Messages.WaitingForApproval, + ActivityUpdateKind.Waiting); + break; + + case IProcessingTaskApproved: + activity.Update(Messages.RequestApproved); + break; + + default: + activity.Update( + Messages.UnknownServerResponse, ActivityUpdateKind.Warning); + break; } - - child.Fail(); } + + await activity.FailAllAsync(); } return ExitCodes.Error; diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/SchemaHelpers.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/SchemaHelpers.cs index 13d98055e4e..59a132f0875 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/SchemaHelpers.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/SchemaHelpers.cs @@ -1,5 +1,6 @@ using ChilliCream.Nitro.Client; using ChilliCream.Nitro.Client.Schemas; +using ChilliCream.Nitro.CommandLine.Helpers; namespace ChilliCream.Nitro.CommandLine.Commands.Schemas; @@ -52,7 +53,7 @@ public static async Task ValidateSchemaAsync( throw new ExitException("Could not create validation request!"); } - activity.Update($"Validation request created (ID: {requestId.EscapeMarkup()})."); + activity.Update($"Validation request created. {$"(ID: {requestId})".Dim()}"); await foreach (var @event in client.SubscribeToSchemaValidationAsync(requestId, ct)) { @@ -95,16 +96,12 @@ public static async Task ValidateSchemaAsync( } } - activity.Fail(errorTree); - - await activity.FailAllAsync(); - - console.Error.WriteErrorLine(Messages.ValidationFailed); + await activity.FailAllAsync(errorTree); return false; case ISchemaVersionValidationSuccess: - activity.Success(Messages.ValidationPassed); + activity.Success("Schema validation successful."); return true; diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/UploadSchemaCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/UploadSchemaCommand.cs index 074679e4907..d3891e74f1f 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/UploadSchemaCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/UploadSchemaCommand.cs @@ -61,7 +61,7 @@ private static async Task ExecuteAsync( } await using (var activity = console.StartActivity( - $"Uploading new schema version '{tag.EscapeMarkup()}' to API '{apiId.EscapeMarkup()}'", + $"Uploading new schema version '{tag.EscapeMarkup()}' of API '{apiId.EscapeMarkup()}'", "Failed to upload a new schema version.")) { await using var stream = fileSystem.OpenReadStream(schemaFilePath); @@ -75,7 +75,7 @@ private static async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in data.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/ValidateSchemaCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/ValidateSchemaCommand.cs index ef50691bc55..0a9732183b7 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/ValidateSchemaCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Schemas/ValidateSchemaCommand.cs @@ -59,7 +59,7 @@ private static async Task ExecuteAsync( } await using var activity = console.StartActivity( - $"Validating schema against stage '{stage.EscapeMarkup()}' of API '{apiId.EscapeMarkup()}'", + $"Validating schema of API '{apiId.EscapeMarkup()}' against stage '{stage.EscapeMarkup()}'", "Failed to validate the schema."); await using var stream = fileSystem.OpenReadStream(schemaFilePath); @@ -74,6 +74,11 @@ private static async Task ExecuteAsync( source, ct); - return isValid ? ExitCodes.Success : ExitCodes.Error; + if (!isValid) + { + throw new ExitException("Schema validation failed."); + } + + return ExitCodes.Success; } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Stages/DeleteStageCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Stages/DeleteStageCommand.cs index 7889e221fd0..8ed84454226 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Stages/DeleteStageCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Stages/DeleteStageCommand.cs @@ -44,10 +44,9 @@ private static async Task ExecuteAsync( parseResult.AssertHasAuthentication(sessionService); - const string apiMessage = "For which API do you want to force delete a stage?"; - var apiId = await parseResult.GetOrPromptForApiIdAsync( - apiMessage, - console, + var apiId = await console.GetOrPromptForApiIdAsync( + "For which API do you want to force delete a stage?", + parseResult, apisClient, sessionService, cancellationToken); @@ -75,7 +74,7 @@ private static async Task ExecuteAsync( if (data.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in data.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Stages/EditStagesCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Stages/EditStagesCommand.cs index 7f46ac387d0..e9dff895029 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Stages/EditStagesCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Stages/EditStagesCommand.cs @@ -50,11 +50,9 @@ private static async Task ExecuteAsync( parseResult.AssertHasAuthentication(sessionService); - const string apiMessage = "For which API do you want to edit the stages?"; - - var apiId = await parseResult.GetOrPromptForApiIdAsync( - apiMessage, - console, + var apiId = await console.GetOrPromptForApiIdAsync( + "For which API do you want to edit the stages?", + parseResult, apisClient, sessionService, cancellationToken); @@ -221,7 +219,7 @@ public static async Task UpdateStagesAsync( } } - activity.Fail(errorTree); + await activity.FailAllAsync(errorTree); throw new ExitException("Stage update failed."); } diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Stages/ListStagesCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Stages/ListStagesCommand.cs index 5077609f6c5..f70118fc705 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Stages/ListStagesCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Stages/ListStagesCommand.cs @@ -53,8 +53,7 @@ private static async Task RenderInteractiveAsync( IResultHolder resultHolder, CancellationToken ct) { - const string apiMessage = "For which API do you want to display the clients?"; - var apiId = await parseResult.GetOrPromptForApiIdAsync(apiMessage, console, apisClient, sessionService, ct); + var apiId = await console.GetOrPromptForApiIdAsync("For which API do you want to display the clients?", parseResult, apisClient, sessionService, ct); var stages = await client.ListStagesAsync(apiId, ct) ?? []; @@ -85,11 +84,7 @@ private static async Task RenderNonInteractiveAsync( IResultHolder resultHolder, CancellationToken ct) { - var apiId = parseResult.GetValue(Opt.Instance); - if (apiId is null) - { - throw ThrowHelper.MissingRequiredOption(ApiIdOption.OptionName); - } + var apiId = parseResult.GetRequiredOptionalValue(Opt.Instance); var data = await client.ListStagesAsync(apiId, ct) ?? []; var items = data diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Status/StatusCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Status/StatusCommand.cs index 2dc51d12c06..5b9dc781746 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Status/StatusCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Status/StatusCommand.cs @@ -27,7 +27,7 @@ private static Task ExecuteAsync( if (session is null) { throw new ExitException( - "Not logged in. Run 'nitro login' first."); + "Not logged in. Run `nitro login` first."); } var defaultApiUrl = Constants.ApiUrl["https://".Length..]; @@ -43,7 +43,7 @@ private static Task ExecuteAsync( if (session.Workspace is { } workspace) { - message += $" ([green]{workspace.Name.EscapeMarkup()}[/] workspace)"; + message += $" (Workspace: [green]{workspace.Name.EscapeMarkup()}[/])"; } console.MarkupLine(message); diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Workspaces/CreateWorkspaceCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Workspaces/CreateWorkspaceCommand.cs index 4d328fe8a5b..84a171e3510 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Workspaces/CreateWorkspaceCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Workspaces/CreateWorkspaceCommand.cs @@ -56,7 +56,7 @@ private static async Task ExecuteAsync( if (createdWorkspace.Errors?.Count > 0) { - activity.Fail(); + await activity.FailAllAsync(); foreach (var error in createdWorkspace.Errors) { diff --git a/src/Nitro/CommandLine/src/CommandLine/Commands/Workspaces/SetDefaultWorkspaceCommand.cs b/src/Nitro/CommandLine/src/CommandLine/Commands/Workspaces/SetDefaultWorkspaceCommand.cs index d00aca5ee51..2ac02e1ef92 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Commands/Workspaces/SetDefaultWorkspaceCommand.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Commands/Workspaces/SetDefaultWorkspaceCommand.cs @@ -35,7 +35,7 @@ private static async Task ExecuteAsync( parseResult.AssertHasAuthentication(sessionService); - var workspaceId = parseResult.GetValue(Opt.Instance); + var workspaceId = parseResult.GetRequiredValueIfNotInteractive(Opt.Instance, console); if (workspaceId is not null) { @@ -51,64 +51,29 @@ private static async Task ExecuteAsync( return ExitCodes.Success; } - if (!console.IsInteractive) - { - throw MissingRequiredOption(OptionalWorkspaceIdOption.OptionName); - } - - return await ExecuteAsync(true, console, client, sessionService, cancellationToken); - } - - public static async Task ExecuteAsync( - bool forceSelection, - INitroConsole console, - IWorkspacesClient client, - ISessionService sessionService, - CancellationToken cancellationToken) - { - const string message = "Which workspace do you want to use as your default?"; - var paginationContainer = PaginationContainer.CreateConnectionData(client.SelectWorkspacesAsync); var current = await paginationContainer.GetCurrentAsync(cancellationToken); if (current.Count == 0) { - throw new ExitException( - $"You do not have any workspaces. Run {"nitro launch".AsCommand()} and create one."); + throw new ExitException("You do not have any workspaces. Run `nitro launch` and create one."); } - Workspace? workspace; - var wasPrompted = false; + var selected = await PagedSelectionPrompt + .New(paginationContainer) + .Title("Which workspace do you want to use as your default?".AsQuestion()) + .UseConverter(x => x.Name) + .RenderAsync(console, cancellationToken); - if (current.Count == 1 && !forceSelection) + if (selected is null) { - var firstWorkspace = current[0]; - workspace = new Workspace(firstWorkspace.Id, firstWorkspace.Name); + throw Exit("No workspace was selected as default."); } - else - { - var selectedWorkspace = await PagedSelectionPrompt - .New(paginationContainer) - .Title(message.AsQuestion()) - .UseConverter(x => x.Name) - .RenderAsync(console, cancellationToken); - if (selectedWorkspace is null) - { - throw Exit("No workspace was selected as default."); - } - - workspace = new Workspace(selectedWorkspace.Id, selectedWorkspace.Name); - - wasPrompted = true; - } - - await sessionService.SelectWorkspaceAsync(workspace, cancellationToken); + var selectedWorkspace = new Workspace(selected.Id, selected.Name); + await sessionService.SelectWorkspaceAsync(selectedWorkspace, cancellationToken); - if (wasPrompted) - { - console.OkQuestion(message, workspace.Name); - } + console.OkQuestion("Which workspace do you want to use as your default?", selectedWorkspace.Name); return ExitCodes.Success; } diff --git a/src/Nitro/CommandLine/src/CommandLine/Extensions/AnsiConsoleExtensions.cs b/src/Nitro/CommandLine/src/CommandLine/Extensions/AnsiConsoleExtensions.cs index ffd18b5640c..3b07e30a8cd 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Extensions/AnsiConsoleExtensions.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Extensions/AnsiConsoleExtensions.cs @@ -4,6 +4,6 @@ internal static class AnsiConsoleExtensions { public static void WriteErrorLine(this IAnsiConsole console, string message) { - console.MarkupLine($"[red]{message.EscapeMarkup()}[/]"); + console.MarkupLine($"[red]{message}[/]"); } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Extensions/ConsoleRenderingExtensions.cs b/src/Nitro/CommandLine/src/CommandLine/Extensions/ConsoleRenderingExtensions.cs index aade948e893..2ece83c6134 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Extensions/ConsoleRenderingExtensions.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Extensions/ConsoleRenderingExtensions.cs @@ -37,6 +37,11 @@ public static string AsHighlight(this string str) return $"[bold blue]{str.EscapeMarkup()}[/]"; } + public static string Dim(this string str) + { + return $"[dim]{str.EscapeMarkup()}[/]"; + } + public static string AsCommand(this string str) => $"`{str.AsHighlight()}`"; public static string AsIcon(this bool value) diff --git a/src/Nitro/CommandLine/src/CommandLine/Extensions/NitroConsoleExtensions.cs b/src/Nitro/CommandLine/src/CommandLine/Extensions/NitroConsoleExtensions.cs index 5f4306fcd11..7e8754c6066 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Extensions/NitroConsoleExtensions.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Extensions/NitroConsoleExtensions.cs @@ -7,19 +7,6 @@ namespace ChilliCream.Nitro.CommandLine; internal static class NitroConsoleExtensions { - public static INitroConsoleActivity StartActivity( - this INitroConsole console, - string title, - string failureMessage) - { - if (!console.IsInteractive) - { - return NitroConsoleActivity.Start(console, title, failureMessage); - } - - return InteractiveNitroConsoleActivity.Start(console, title, failureMessage); - } - public static async Task PromptAsync( this INitroConsole console, string question, @@ -37,70 +24,12 @@ public static async Task PromptAsync( if (!console.IsInteractive) { - throw new ExitException($"Missing required option '{option.Name}'."); + throw ThrowHelper.MissingRequiredOption(option.Name); } return await console.PromptAsync(question, defaultValue, cancellationToken); } - public static async Task ConfirmAsync( - this INitroConsole console, - ParseResult parseResult, - Option option, - string question, - CancellationToken cancellationToken) - { - var value = parseResult.GetValue(option); - - if (value is not null) - { - return value.Value; - } - - if (!console.IsInteractive) - { - throw new ExitException($"Missing required option '{option.Name}'."); - } - - return await console.ConfirmAsync(question, cancellationToken); - } - - public static async Task GetOrPromptForApiIdAsync( - this INitroConsole console, - string message, - ParseResult parseResult, - IApisClient apisClient, - ISessionService sessionService, - CancellationToken cancellationToken) - { - var apiId = parseResult.GetValue(Opt.Instance); - - if (!string.IsNullOrEmpty(apiId)) - { - return apiId; - } - - var workspaceId = parseResult.GetWorkspaceId(sessionService); - - return await console.PromptForApiIdAsync(apisClient, workspaceId, message, cancellationToken); - } - - public static async Task PromptForApiIdAsync( - this INitroConsole console, - IApisClient apisClient, - string workspaceId, - string message, - CancellationToken cancellationToken) - { - var selectedApi = await SelectApiPrompt - .New(apisClient, workspaceId) - .Title(message) - .RenderAsync(console, cancellationToken) ?? - throw ThrowHelper.NoApiSelected(); - - return selectedApi.Id; - } - public static async Task PromptAsync( this INitroConsole console, string question, @@ -143,6 +72,28 @@ public static async Task PromptAsync( return await prompt.ShowAsync(console, cancellationToken); } + public static async Task ConfirmAsync( + this INitroConsole console, + ParseResult parseResult, + Option option, + string question, + CancellationToken cancellationToken) + { + var value = parseResult.GetValue(option); + + if (value is not null) + { + return value.Value; + } + + if (!console.IsInteractive) + { + throw ThrowHelper.MissingRequiredOption(option.Name); + } + + return await console.ConfirmAsync(question, cancellationToken); + } + public static async Task ConfirmAsync( this INitroConsole console, string question, @@ -158,6 +109,56 @@ public static async Task ConfirmAsync( .ShowAsync(console, cancellationToken); } + public static async Task GetOrPromptForApiIdAsync( + this INitroConsole console, + string message, + ParseResult parseResult, + IApisClient apisClient, + ISessionService sessionService, + CancellationToken cancellationToken) + { + var option = Opt.Instance; + var apiId = parseResult.GetValue(option); + + if (!string.IsNullOrEmpty(apiId)) + { + return apiId; + } + + if (!console.IsInteractive) + { + throw ThrowHelper.MissingRequiredOption(option.Name); + } + + var workspaceId = parseResult.GetWorkspaceId(sessionService); + + return await console.PromptForApiIdAsync(apisClient, workspaceId, message, cancellationToken); + } + + public static async Task PromptForApiIdAsync( + this INitroConsole console, + IApisClient apisClient, + string workspaceId, + string? title = null, + CancellationToken cancellationToken = default) + { + var prompt = SelectApiPrompt.New(apisClient, workspaceId); + + if (!string.IsNullOrEmpty(title)) + { + prompt = prompt.Title(title); + } + var selectedApi = await prompt.RenderAsync(console, cancellationToken); + var apiId = selectedApi?.Id; + + if (string.IsNullOrEmpty(apiId)) + { + throw new ExitException("You did not select an API!"); + } + + return apiId; + } + public static void Success(this INitroConsole console, string message) { console.MarkupLine($"[green]{message}[/]"); diff --git a/src/Nitro/CommandLine/src/CommandLine/Extensions/OptionExtensions.cs b/src/Nitro/CommandLine/src/CommandLine/Extensions/OptionExtensions.cs index af8e862861c..f3b2cb6b945 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Extensions/OptionExtensions.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Extensions/OptionExtensions.cs @@ -68,7 +68,7 @@ public static Option DefaultFromEnvironmentValue( string name, T? defaultValue = default) { - option.DefaultValueFactory = r => + option.DefaultValueFactory = _ => { var provider = CommandExecutionContext.s_services.Value! .GetRequiredService(); diff --git a/src/Nitro/CommandLine/src/CommandLine/Extensions/ParseResultExtensions.cs b/src/Nitro/CommandLine/src/CommandLine/Extensions/ParseResultExtensions.cs index 97cb47822b1..5a8039f13ec 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Extensions/ParseResultExtensions.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Extensions/ParseResultExtensions.cs @@ -1,5 +1,4 @@ -using ChilliCream.Nitro.Client.Apis; -using ChilliCream.Nitro.CommandLine.Commands.Apis.Components; +using ChilliCream.Nitro.CommandLine.Helpers; namespace ChilliCream.Nitro.CommandLine.Services.Sessions; @@ -18,7 +17,7 @@ public static void AssertHasAuthentication( throw new ExitException( "This command requires an authenticated user. " - + $"Either specify '{OptionalApiKeyOption.OptionName}' or run 'nitro login'."); + + $"Either specify '{OptionalApiKeyOption.OptionName}' or run `nitro login`."); } public static string GetWorkspaceId( @@ -27,31 +26,50 @@ public static string GetWorkspaceId( { return sessionService.Session?.Workspace?.Id ?? parseResult.GetValue(Opt.Instance) - ?? throw ThrowHelper.NoDefaultWorkspace(); + ?? throw new ExitException($"Could not determine workspace. Either login via `nitro login` or specify the '{OptionalWorkspaceIdOption.OptionName}' option."); } - public static async Task GetOrPromptForApiIdAsync( + public static T? GetRequiredValueIfNotInteractive( this ParseResult parseResult, - string message, - INitroConsole console, - IApisClient apisClient, - ISessionService sessionService, - CancellationToken cancellationToken) + Option option, + INitroConsole console) { - var apiId = parseResult.GetValue(Opt.Instance); + var value = parseResult.GetValue(option); - if (apiId is null) + if (value is null && !console.IsInteractive) { - var workspaceId = parseResult.GetWorkspaceId(sessionService); - var selectedApi = await SelectApiPrompt - .New(apisClient, workspaceId) - .Title(message) - .RenderAsync(console, cancellationToken) ?? throw ThrowHelper.NoApiSelected(); - apiId = selectedApi.Id; + throw ThrowHelper.MissingRequiredOption(option.Name); } - console.OkQuestion(message, apiId); + return value; + } + + public static T? GetRequiredValueIfNotInteractive( + this ParseResult parseResult, + Argument argument, + INitroConsole console) + { + var value = parseResult.GetValue(argument); + + if (value is null && !console.IsInteractive) + { + throw ThrowHelper.MissingRequiredArgument(argument.Name); + } + + return value; + } + + public static T GetRequiredOptionalValue( + this ParseResult parseResult, + Option option) + { + var value = parseResult.GetValue(option); + + if (value is null) + { + throw ThrowHelper.MissingRequiredOption(option.Name); + } - return apiId; + return value; } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Extensions/TreeNodeExtensions.cs b/src/Nitro/CommandLine/src/CommandLine/Extensions/TreeNodeExtensions.cs index 18f6bb2697a..01ee1781065 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Extensions/TreeNodeExtensions.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Extensions/TreeNodeExtensions.cs @@ -36,7 +36,14 @@ public static IHasTreeNodes AddGraphQLSchemaErrors( foreach (var schemaError in error.Errors) { - schemaErrorsNode.AddNode($"{schemaError.Message.AsError()} [dim]{schemaError.Code}[/]"); + var message = schemaError.Message.AsError(); + + if (!string.IsNullOrEmpty(schemaError.Code)) + { + message += $" {$"({schemaError.Code})".Dim()}"; + } + + schemaErrorsNode.AddNode(message); } return node; @@ -47,7 +54,7 @@ public static IHasTreeNodes AddPersistedQueryValidationErrorsWithClients( IPersistedQueryValidationError error) { var client = error.Client; - var clientNode = node.AddNode($"Client '{client?.Name.EscapeMarkup()}' [dim](ID: {client?.Id})[/]"); + var clientNode = node.AddNode($"Client '{client?.Name.EscapeMarkup()}' {$"(ID: {client?.Id})".Dim()}"); AddPersistedQueryValidationErrors(clientNode, error); @@ -61,7 +68,7 @@ public static IHasTreeNodes AddPersistedQueryValidationErrors( foreach (var operation in error.Queries) { var publishingInfo = operation.DeployedTags.Count > 0 - ? $" [dim](Deployed tags: {string.Join(",", operation.DeployedTags)})[/]" + ? $" {$"(Deployed tags: {string.Join(",", operation.DeployedTags)})".Dim()}" : ""; var operationNode = node.AddNode($"Operation '{operation.Hash}'{publishingInfo}"); @@ -71,7 +78,7 @@ public static IHasTreeNodes AddPersistedQueryValidationErrors( var errorLocation = string.Empty; if (err.Locations is { Count: > 0 } locations) { - errorLocation = $" [dim]({locations[0].Line}:{locations[0].Column})[/]"; + errorLocation = $" {$"({locations[0].Line}:{locations[0].Column})".Dim()}"; } operationNode.AddNode(err.Message.AsError() + errorLocation); @@ -89,7 +96,7 @@ public static IHasTreeNodes AddOpenApiCollectionValidationErrors( { var openApiCollection = collectionError.OpenApiCollection; var collectionNode = node.AddNode( - $"OpenAPI collection '{openApiCollection?.Name.EscapeMarkup()}' [dim](ID: {openApiCollection?.Id})[/]"); + $"OpenAPI collection '{openApiCollection?.Name.EscapeMarkup()}' {$"(ID: {openApiCollection?.Id})".Dim()}"); foreach (var entity in collectionError.Entities) { @@ -102,7 +109,7 @@ public static IHasTreeNodes AddOpenApiCollectionValidationErrors( var errorLocation = string.Empty; if (documentError.Locations is { Count: > 0 } locations) { - errorLocation = $" [dim]({locations[0].Line}:{locations[0].Column})[/]"; + errorLocation = $" {$"({locations[0].Line}:{locations[0].Column})".Dim()}"; } entityNode.AddNode(documentError.Message.AsError() + errorLocation); @@ -140,7 +147,7 @@ public static IHasTreeNodes AddMcpFeatureCollectionValidationErrors( { var mcpFeatureCollection = collectionError.McpFeatureCollection; var collectionNode = node.AddNode( - $"MCP Feature Collection '{mcpFeatureCollection?.Name.EscapeMarkup()}' [dim](ID: {mcpFeatureCollection?.Id})[/]"); + $"MCP Feature Collection '{mcpFeatureCollection?.Name.EscapeMarkup()}' {$"(ID: {mcpFeatureCollection?.Id})".Dim()}"); foreach (var entity in collectionError.Entities) { @@ -153,7 +160,7 @@ public static IHasTreeNodes AddMcpFeatureCollectionValidationErrors( var errorLocation = string.Empty; if (documentError.Locations is { Count: > 0 } locations) { - errorLocation = $" [dim]({locations[0].Line}:{locations[0].Column})[/]"; + errorLocation = $" {$"({locations[0].Line}:{locations[0].Column})".Dim()}"; } entityNode.AddNode(documentError.Message.AsError() + errorLocation); diff --git a/src/Nitro/CommandLine/src/CommandLine/Helpers/PaginationContainer.cs b/src/Nitro/CommandLine/src/CommandLine/Helpers/PaginationContainer.cs index e9174a40e34..232a7689cca 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Helpers/PaginationContainer.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Helpers/PaginationContainer.cs @@ -133,7 +133,15 @@ public async ValueTask> FetchNextAsync(CancellationToken ca var result = await _fetchAsync(_latestPageInfo?.EndCursor, _pageSize, cancellationToken); - var data = result.EnsureData(); + if (result.Data is not { } data) + { + if (result.Errors.FirstOrDefault() is { } error) + { + throw new ExitException(error.Message); + } + + throw new ExitException("There was an issue with the request to the server."); + } _latestPageInfo = _pageInfoSelector(data) ?? throw NoPageInfoFound(); var edges = _selectEdgesSelector(data)?.ToArray() ?? throw CouldNotSelectEdges(); diff --git a/src/Nitro/CommandLine/src/CommandLine/Helpers/StrawberryShakeExtensions.cs b/src/Nitro/CommandLine/src/CommandLine/Helpers/StrawberryShakeExtensions.cs deleted file mode 100644 index 35548818c54..00000000000 --- a/src/Nitro/CommandLine/src/CommandLine/Helpers/StrawberryShakeExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using StrawberryShake; -using static ChilliCream.Nitro.CommandLine.ThrowHelper; - -namespace ChilliCream.Nitro.CommandLine.Helpers; - -public static class StrawberryShakeExtensions -{ - public static T EnsureData(this IOperationResult result) where T : class - { - if (result.Data is not { } data) - { - if (result.Errors.FirstOrDefault() is { } error) - { - throw ThereWasAnIssueWithTheRequest(error.Message); - } - - throw ThereWasAnIssueWithTheRequest(); - } - - return data; - } -} diff --git a/src/Nitro/CommandLine/src/CommandLine/Messages.cs b/src/Nitro/CommandLine/src/CommandLine/Messages.cs index ad85b54fe05..9d293443d7b 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Messages.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Messages.cs @@ -33,28 +33,12 @@ public static string UnexpectedMutationError(IError error) public const string ForcePushEnabled = "Force push is enabled."; - public const string StartingValidationRequest = "Starting validation request"; - - public const string FailedToStartValidationRequest = - "Failed to start the validation request."; - - public const string ValidatingActivity = "Validating"; - public const string Validating = "Validating..."; - public const string ValidationPassed = "Validation passed."; + public const string ValidationPassed = "Validation successful."; public const string ValidationFailed = "Validation failed."; - public const string StartingPublishRequest = "Starting publish request"; - - public const string FailedToStartPublishRequest = - "Failed to start publish request."; - - public const string ProcessingActivity = "Processing"; - - public const string ProcessingFailed = "Processing failed."; - public const string RequestReadyForProcessing = "Your request is ready for processing."; @@ -67,8 +51,6 @@ public static string UnexpectedMutationError(IError error) public const string RequestApproved = "Your request has been approved."; - public const string PublishedSuccessfully = "Published successfully."; - public static string QueuedAtPosition(int position) => $"Your request is queued at position {position}."; } diff --git a/src/Nitro/CommandLine/src/CommandLine/Program.cs b/src/Nitro/CommandLine/src/CommandLine/Program.cs index 6847bef849f..25291beb3f3 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Program.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Program.cs @@ -55,7 +55,8 @@ public static async Task Main(string[] args) new NitroConsole( outConsole, errorConsole, - sp.GetRequiredService())); + sp.GetRequiredService(), + new ActivitySinkFactory())); await using var provider = services.BuildServiceProvider(); diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/ActivityEntry.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/ActivityEntry.cs index 08b97b04e66..9030b97e8c5 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Services/Console/ActivityEntry.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Console/ActivityEntry.cs @@ -22,11 +22,13 @@ public ActivityEntry(string text, ActivityState state = ActivityState.Active) public IRenderable? Details { get; set; } + public bool IsTerminator { get; init; } + public TimeSpan Elapsed => _stopwatch.Elapsed; - public ActivityEntry AddChild(string text, ActivityState state) + public ActivityEntry AddChild(string text, ActivityState state, bool isTerminator = false) { - var child = new ActivityEntry(text, state); + var child = new ActivityEntry(text, state) { IsTerminator = isTerminator }; _children.Add(child); return child; } diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/ActivitySinkFactory.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/ActivitySinkFactory.cs new file mode 100644 index 00000000000..5704feeea1a --- /dev/null +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Console/ActivitySinkFactory.cs @@ -0,0 +1,14 @@ +namespace ChilliCream.Nitro.CommandLine; + +internal sealed class ActivitySinkFactory : IActivitySinkFactory +{ + public IActivitySink Create(INitroConsole console, bool isInteractive) + { + if (isInteractive) + { + return new LiveActivitySink(console); + } + + return new StreamingActivitySink(console); + } +} diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/ActivityTree.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/ActivityTree.cs index e7b2e4a9e25..952cf03782a 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Services/Console/ActivityTree.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Console/ActivityTree.cs @@ -4,6 +4,11 @@ namespace ChilliCream.Nitro.CommandLine; internal sealed class ActivityTree : Renderable { + private const string BranchConnector = "├── "; + private const string LastConnector = "└── "; + private const string BranchLane = "│ "; + private const string LastLane = " "; + #if NET9_0_OR_GREATER private readonly Lock _lock = new(); #else @@ -22,11 +27,11 @@ public ActivityEntry AddRoot(string text) } } - public ActivityEntry AddChild(ActivityEntry parent, string text, ActivityState state) + public ActivityEntry AddChild(ActivityEntry parent, string text, ActivityState state, bool isTerminator = false) { lock (_lock) { - return parent.AddChild(text, state); + return parent.AddChild(text, state, isTerminator); } } @@ -38,28 +43,41 @@ public void SetEntryState(ActivityEntry entry, ActivityState state) } } - public void SetEntryText(ActivityEntry entry, string text) + public void SetEntryTextAndState(ActivityEntry entry, string text, ActivityState state) { lock (_lock) { entry.Text = text; + entry.State = state; } } - public void SetEntryTextAndState(ActivityEntry entry, string text, ActivityState state) + public void SetEntryDetails(ActivityEntry entry, IRenderable details) { lock (_lock) { - entry.Text = text; - entry.State = state; + entry.Details = details; } } - public void SetEntryDetails(ActivityEntry entry, IRenderable details) + public void FailActiveDescendants(ActivityEntry entry) { lock (_lock) { - entry.Details = details; + FailActiveDescendantsCore(entry); + } + } + + private static void FailActiveDescendantsCore(ActivityEntry entry) + { + foreach (var child in entry.Children) + { + if (child.State == ActivityState.Active) + { + child.State = ActivityState.Failed; + } + + FailActiveDescendantsCore(child); } } @@ -71,9 +89,17 @@ protected override IEnumerable Render(RenderOptions options, int maxWid foreach (var root in _rootEntries) { - RenderEntry(segments, root, prefix: "", connector: "", options, maxWidth); + RenderEntry(segments, root, parentPrefix: "", NodePosition.Root, options, maxWidth); } + // Workaround for a Spectre.Console bug: LiveRenderable.PositionCursor emits + // `CSI 0 A` when the rendered shape is one line tall, and most terminals treat + // that as `CSI 1 A` (move cursor up one row) per ECMA-48 default-parameter + // handling. The result is that a single-line tree drifts up one row on every + // refresh and overwrites previously-printed output. Forcing the shape to be at + // least two lines tall keeps Spectre on the `CursorUp(n>=1)` path. + segments.Add(Segment.LineBreak); + return segments; } } @@ -81,44 +107,62 @@ protected override IEnumerable Render(RenderOptions options, int maxWid private void RenderEntry( List segments, ActivityEntry entry, - string prefix, - string connector, + string parentPrefix, + NodePosition position, RenderOptions options, int maxWidth) { - segments.Add(new Segment(prefix)); + var connector = ConnectorFor(position); + var lane = LaneFor(position); + var icon = IconFor(entry); + var textStyle = TextStyleFor(entry.State); + + // When a failed entry's own terminator child duplicates a preceding failed + // child, suppress the terminator — the preceding child already conveys the + // failure. Only the explicitly marked terminator can be hidden; real children + // are never dropped. + var visibleChildCount = entry.Children.Count; + if (entry.State == ActivityState.Failed + && visibleChildCount > 1 + && entry.Children[visibleChildCount - 1].IsTerminator + && entry.Children[visibleChildCount - 1].State == ActivityState.Failed + && entry.Children[visibleChildCount - 2].State == ActivityState.Failed) + { + visibleChildCount--; + } + + var hasChildren = visibleChildCount > 0; + var hasDetails = entry.Details is not null; + var continuationPrefix = BuildContinuationPrefix( + parentPrefix, + lane, + position, + icon.Width, + hasChildren, + hasDetails); + + segments.Add(new Segment(parentPrefix)); segments.Add(new Segment(connector)); + icon.Write(segments); - RenderIcon(segments, entry); + var availableWidth = Math.Max(1, maxWidth - parentPrefix.Length - connector.Length - icon.Width); + var markup = new Markup(entry.Text, textStyle); + var textSegments = ((IRenderable)markup).Render(options, availableWidth); - var textStyle = entry.State switch - { - ActivityState.Failed => new Style(Color.Red), - ActivityState.Warning => new Style(Color.Yellow), - _ => Style.Plain - }; - segments.Add(new Segment(entry.Text, textStyle)); + WritePrefixed(segments, textSegments, continuationPrefix, prefixFirstLine: false); segments.Add(Segment.LineBreak); - for (var i = 0; i < entry.Children.Count; i++) + var childPrefix = parentPrefix + lane; + for (var i = 0; i < visibleChildCount; i++) { - var child = entry.Children[i]; - var isLast = i == entry.Children.Count - 1 && entry.Details is null; - var childConnector = isLast ? "└── " : "├── "; - var childPrefix = prefix + (connector.Length > 0 - ? (connector == "└── " ? " " : "│ ") - : ""); - - RenderEntry(segments, child, childPrefix, childConnector, options, maxWidth); + var isLastChild = i == visibleChildCount - 1 && !hasDetails; + var childPosition = isLastChild ? NodePosition.Last : NodePosition.Middle; + RenderEntry(segments, entry.Children[i], childPrefix, childPosition, options, maxWidth); } if (entry.Details is not null) { - var detailsPrefix = prefix + (connector.Length > 0 - ? (connector == "└── " ? " " : "│ ") - : ""); - - RenderDetails(segments, entry.Details, detailsPrefix, options, maxWidth); + RenderDetails(segments, entry.Details, childPrefix, options, maxWidth); } } @@ -137,9 +181,23 @@ private static void RenderDetails( } var detailSegments = details.Render(options, availableWidth); - var atLineStart = true; + var endedAtLineStart = WritePrefixed(segments, detailSegments, prefix, prefixFirstLine: true); - foreach (var segment in detailSegments) + if (!endedAtLineStart) + { + segments.Add(Segment.LineBreak); + } + } + + private static bool WritePrefixed( + List segments, + IEnumerable rendered, + string continuationPrefix, + bool prefixFirstLine) + { + var atLineStart = prefixFirstLine; + + foreach (var segment in rendered) { if (segment.IsLineBreak) { @@ -150,7 +208,7 @@ private static void RenderDetails( { if (atLineStart) { - segments.Add(new Segment(prefix)); + segments.Add(new Segment(continuationPrefix)); atLineStart = false; } @@ -158,46 +216,92 @@ private static void RenderDetails( } } - if (!atLineStart) - { - segments.Add(Segment.LineBreak); - } + return atLineStart; + } + + private static string BuildContinuationPrefix( + string parentPrefix, + string lane, + NodePosition position, + int iconWidth, + bool hasChildren, + bool hasDetails) + { + // Root entries use (children || details) to decide whether to draw a vertical + // guide under the icon; non-root entries use children only. Preserved as-is + // from the original implementation — changing it would shift layout. + var drawGuide = position == NodePosition.Root + ? hasChildren || hasDetails + : hasChildren; + + var underIcon = drawGuide + ? "│" + Spaces(iconWidth - 1) + : Spaces(iconWidth); + + return parentPrefix + lane + underIcon; } - private void RenderIcon(List segments, ActivityEntry entry) + private static string ConnectorFor(NodePosition position) => position switch + { + NodePosition.Middle => BranchConnector, + NodePosition.Last => LastConnector, + _ => "" + }; + + private static string LaneFor(NodePosition position) => position switch + { + NodePosition.Middle => BranchLane, + NodePosition.Last => LastLane, + _ => "" + }; + + private static Style TextStyleFor(ActivityState state) => state switch { - switch (entry.State) + ActivityState.Failed => new Style(Color.Red), + ActivityState.Warning => new Style(Color.Yellow), + _ => Style.Plain + }; + + private ActivityIcon IconFor(ActivityEntry entry) => entry.State switch + { + ActivityState.Active => new ActivityIcon(CurrentSpinnerFrame(entry), new Style(Color.DeepPink1_1, decoration: Decoration.Bold)), + ActivityState.Completed => new ActivityIcon("✓", new Style(Color.Green, decoration: Decoration.Bold)), + ActivityState.Failed => new ActivityIcon("✕", new Style(Color.Red, decoration: Decoration.Bold)), + ActivityState.Warning => new ActivityIcon("!", new Style(Color.Yellow, decoration: Decoration.Bold)), + ActivityState.Waiting => new ActivityIcon("⏳", new Style(Color.Blue, decoration: Decoration.Bold)), + _ => ActivityIcon.None + }; + + private string CurrentSpinnerFrame(ActivityEntry entry) + { + var frameIndex = + (int)(entry.Elapsed.TotalMilliseconds / _spinner.Interval.TotalMilliseconds) + % _spinner.Frames.Count; + return _spinner.Frames[frameIndex]; + } + + private static string Spaces(int count) => count <= 0 ? "" : new string(' ', count); + + private enum NodePosition + { + Root, + Middle, + Last + } + + private readonly record struct ActivityIcon(string Glyph, Style Style, int Width = 2) + { + public static ActivityIcon None { get; } = new("", Spectre.Console.Style.Plain, Width: 0); + + public void Write(List segments) { - case ActivityState.Active: - var frame = _spinner.Frames[ - (int)(entry.Elapsed.TotalMilliseconds / _spinner.Interval.TotalMilliseconds) - % _spinner.Frames.Count]; - segments.Add(new Segment(frame, new Style(Color.DeepPink1_1, decoration: Decoration.Bold))); - segments.Add(new Segment(" ")); - break; - - case ActivityState.Completed: - segments.Add(new Segment("✓", new Style(Color.Green, decoration: Decoration.Bold))); - segments.Add(new Segment(" ")); - break; - - case ActivityState.Failed: - segments.Add(new Segment("✕", new Style(Color.Red, decoration: Decoration.Bold))); - segments.Add(new Segment(" ")); - break; - - case ActivityState.Warning: - segments.Add(new Segment("!", new Style(Color.Yellow, decoration: Decoration.Bold))); - segments.Add(new Segment(" ")); - break; - - case ActivityState.Waiting: - segments.Add(new Segment("⏳", new Style(Color.Blue, decoration: Decoration.Bold))); - segments.Add(new Segment(" ")); - break; - - case ActivityState.Info: - break; + if (Width == 0) + { + return; + } + + segments.Add(new Segment(Glyph, Style)); + segments.Add(new Segment(" ")); } } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/IActivitySink.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/IActivitySink.cs new file mode 100644 index 00000000000..973f8bce90b --- /dev/null +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Console/IActivitySink.cs @@ -0,0 +1,24 @@ +using Spectre.Console.Rendering; + +namespace ChilliCream.Nitro.CommandLine; + +internal interface IActivitySink +{ + Task Completion { get; } + + ActivityEntry AddRoot(string text); + + ActivityEntry AddChild(ActivityEntry parent, string text, ActivityState state); + + ActivityEntry CompleteChild(ActivityEntry parent, string text, ActivityState state); + + void SetState(ActivityEntry entry, ActivityState state); + + void SetTextAndState(ActivityEntry entry, string text, ActivityState state); + + void SetDetails(ActivityEntry entry, IRenderable details); + + void FailActiveDescendants(ActivityEntry entry); + + void Stop(); +} diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/IActivitySinkFactory.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/IActivitySinkFactory.cs new file mode 100644 index 00000000000..9de6d480e86 --- /dev/null +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Console/IActivitySinkFactory.cs @@ -0,0 +1,6 @@ +namespace ChilliCream.Nitro.CommandLine; + +internal interface IActivitySinkFactory +{ + IActivitySink Create(INitroConsole console, bool isInteractive); +} diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/INitroConsole.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/INitroConsole.cs index 059c1478654..5011ff7e068 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Services/Console/INitroConsole.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Console/INitroConsole.cs @@ -15,4 +15,6 @@ internal interface INitroConsole : IAnsiConsole IAnsiConsole Error { get; } void SetOutputFormat(OutputFormat format); + + INitroConsoleActivity StartActivity(string title, string failureMessage); } diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/INitroConsoleActivity.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/INitroConsoleActivity.cs index 0b92d38129f..e5e0d909c81 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Services/Console/INitroConsoleActivity.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Console/INitroConsoleActivity.cs @@ -16,7 +16,7 @@ internal interface INitroConsoleActivity : IAsyncDisposable void Fail(IRenderable details); - ValueTask FailAllAsync(); + ValueTask FailAllAsync(IRenderable? details = null); INitroConsoleActivity StartChildActivity(string title, string failureMessage); } diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/InteractiveNitroConsoleActivity.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/InteractiveNitroConsoleActivity.cs deleted file mode 100644 index 84a4b629aea..00000000000 --- a/src/Nitro/CommandLine/src/CommandLine/Services/Console/InteractiveNitroConsoleActivity.cs +++ /dev/null @@ -1,169 +0,0 @@ -using Spectre.Console.Rendering; - -namespace ChilliCream.Nitro.CommandLine; - -internal sealed class InteractiveNitroConsoleActivity : INitroConsoleActivity -{ - private readonly ActivityTree _tree; - private readonly ActivityEntry _rootEntry; - private readonly string _failureMessage; - private readonly Task _liveTask; - private readonly PeriodicTimer _refreshTimer; - private bool _completed; - - private InteractiveNitroConsoleActivity( - ActivityTree tree, - ActivityEntry rootEntry, - string failureMessage, - Task liveTask, - PeriodicTimer refreshTimer) - { - _tree = tree; - _rootEntry = rootEntry; - _failureMessage = failureMessage; - _liveTask = liveTask; - _refreshTimer = refreshTimer; - } - - public void Update( - string message, - ActivityUpdateKind kind = ActivityUpdateKind.Regular, - IRenderable? details = null) - { - var state = kind switch - { - ActivityUpdateKind.Warning => ActivityState.Warning, - ActivityUpdateKind.Waiting => ActivityState.Waiting, - ActivityUpdateKind.Success => ActivityState.Completed, - _ => ActivityState.Info - }; - var child = _tree.AddChild(_rootEntry, message, state); - if (details is not null) - { - _tree.SetEntryDetails(child, details); - } - } - - public void Warning(string message) - { - if (_completed) - { - return; - } - - _tree.SetEntryState(_rootEntry, ActivityState.Warning); - _tree.AddChild(_rootEntry, message, ActivityState.Warning); - _completed = true; - _refreshTimer.Dispose(); - } - - public void Success(string message) - { - if (_completed) - { - return; - } - - _tree.SetEntryState(_rootEntry, ActivityState.Completed); - _tree.AddChild(_rootEntry, message, ActivityState.Completed); - _completed = true; - _refreshTimer.Dispose(); - } - - public void Fail(string message) - { - if (_completed) - { - return; - } - - _tree.SetEntryState(_rootEntry, ActivityState.Failed); - _tree.AddChild(_rootEntry, message, ActivityState.Failed); - _completed = true; - _refreshTimer.Dispose(); - } - - public void Fail(IRenderable details) - { - if (_completed) - { - return; - } - - _tree.SetEntryState(_rootEntry, ActivityState.Failed); - var failChild = _tree.AddChild(_rootEntry, _failureMessage, ActivityState.Failed); - _tree.SetEntryDetails(failChild, details); - _completed = true; - _refreshTimer.Dispose(); - } - - public void Fail() - { - Fail(_failureMessage); - } - - public async ValueTask FailAllAsync() - { - FailSilent(); - await _liveTask; - } - - public INitroConsoleActivity StartChildActivity(string title, string failureMessage) - { - var childEntry = _tree.AddChild(_rootEntry, title, ActivityState.Active); - return new InteractiveNitroConsoleChildActivity(_tree, childEntry, failureMessage, this); - } - - private void FailSilent() - { - if (_completed) - { - return; - } - - _tree.SetEntryState(_rootEntry, ActivityState.Failed); - _completed = true; - _refreshTimer.Dispose(); - } - - public async ValueTask DisposeAsync() - { - if (!_completed) - { - Fail(); - } - - await _liveTask; - } - - public static INitroConsoleActivity Start( - INitroConsole console, - string title, - string failureMessage) - { - var tree = new ActivityTree(); - var rootEntry = tree.AddRoot(title); - var refreshTimer = new PeriodicTimer(TimeSpan.FromMilliseconds(100)); - - var liveTask = console - .Live(tree) - .AutoClear(false) - .Overflow(VerticalOverflow.Visible) - .StartAsync(async ctx => - { - while (await refreshTimer.WaitForNextTickAsync()) - { - ctx.Refresh(); - } - - ctx.Refresh(); - }); - - return new InteractiveNitroConsoleActivity( - tree, - rootEntry, - failureMessage, - liveTask, - refreshTimer); - } -} diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/InteractiveNitroConsoleChildActivity.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/InteractiveNitroConsoleChildActivity.cs deleted file mode 100644 index aed50026b1c..00000000000 --- a/src/Nitro/CommandLine/src/CommandLine/Services/Console/InteractiveNitroConsoleChildActivity.cs +++ /dev/null @@ -1,141 +0,0 @@ -using Spectre.Console.Rendering; - -namespace ChilliCream.Nitro.CommandLine; - -internal sealed class InteractiveNitroConsoleChildActivity( - ActivityTree tree, - ActivityEntry entry, - string failureMessage, - INitroConsoleActivity parent) - : INitroConsoleActivity -{ - private bool _completed; - - public void Update( - string message, - ActivityUpdateKind kind = ActivityUpdateKind.Regular, - IRenderable? details = null) - { - var state = kind switch - { - ActivityUpdateKind.Warning => ActivityState.Warning, - ActivityUpdateKind.Waiting => ActivityState.Waiting, - ActivityUpdateKind.Success => ActivityState.Completed, - _ => ActivityState.Info - }; - var child = tree.AddChild(entry, message, state); - if (details is not null) - { - tree.SetEntryDetails(child, details); - } - } - - public void Warning(string message) - { - if (_completed) - { - return; - } - - if (entry.Children.Count > 0) - { - tree.SetEntryState(entry, ActivityState.Warning); - tree.AddChild(entry, message, ActivityState.Warning); - } - else - { - tree.SetEntryTextAndState(entry, message, ActivityState.Warning); - } - - _completed = true; - } - - public void Success(string message) - { - if (_completed) - { - return; - } - - if (entry.Children.Count > 0) - { - tree.SetEntryState(entry, ActivityState.Completed); - tree.AddChild(entry, message, ActivityState.Completed); - } - else - { - tree.SetEntryTextAndState(entry, message, ActivityState.Completed); - } - - _completed = true; - } - - public void Fail(string message) - { - if (_completed) - { - return; - } - - if (entry.Children.Count > 0) - { - tree.SetEntryState(entry, ActivityState.Failed); - tree.AddChild(entry, message, ActivityState.Failed); - } - else - { - tree.SetEntryTextAndState(entry, message, ActivityState.Failed); - } - - _completed = true; - } - - public void Fail(IRenderable details) - { - if (_completed) - { - return; - } - - ActivityEntry failEntry; - - if (entry.Children.Count > 0) - { - tree.SetEntryState(entry, ActivityState.Failed); - failEntry = tree.AddChild(entry, failureMessage, ActivityState.Failed); - } - else - { - tree.SetEntryTextAndState(entry, failureMessage, ActivityState.Failed); - failEntry = entry; - } - - tree.SetEntryDetails(failEntry, details); - _completed = true; - } - - public void Fail() - { - Fail(failureMessage); - } - - public async ValueTask FailAllAsync() - { - Fail(); - await parent.FailAllAsync(); - } - - public INitroConsoleActivity StartChildActivity(string title, string failureMessage) - { - var childEntry = tree.AddChild(entry, title, ActivityState.Active); - return new InteractiveNitroConsoleChildActivity(tree, childEntry, failureMessage, this); - } - - public async ValueTask DisposeAsync() - { - if (!_completed) - { - await FailAllAsync(); - } - } -} diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/LiveActivitySink.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/LiveActivitySink.cs new file mode 100644 index 00000000000..02b8712e85e --- /dev/null +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Console/LiveActivitySink.cs @@ -0,0 +1,82 @@ +using Spectre.Console.Rendering; + +namespace ChilliCream.Nitro.CommandLine; + +internal sealed class LiveActivitySink : IActivitySink +{ + private readonly INitroConsole _console; + private readonly ActivityTree _tree = new(); + private readonly PeriodicTimer _timer = new(TimeSpan.FromMilliseconds(100)); + private readonly Task _liveTask; + + public LiveActivitySink(INitroConsole console) + { + _console = console; + _liveTask = RunAsync(); + } + + public Task Completion => _liveTask; + + public ActivityEntry AddRoot(string text) + { + return _tree.AddRoot(text); + } + + public ActivityEntry AddChild(ActivityEntry parent, string text, ActivityState state) + { + return _tree.AddChild(parent, text, state); + } + + public ActivityEntry CompleteChild(ActivityEntry parent, string text, ActivityState state) + { + return _tree.AddChild(parent, text, state, isTerminator: true); + } + + public void SetState(ActivityEntry entry, ActivityState state) + { + _tree.SetEntryState(entry, state); + } + + public void SetTextAndState(ActivityEntry entry, string text, ActivityState state) + { + _tree.SetEntryTextAndState(entry, text, state); + } + + public void SetDetails(ActivityEntry entry, IRenderable details) + { + _tree.SetEntryDetails(entry, details); + } + + public void FailActiveDescendants(ActivityEntry entry) + { + _tree.FailActiveDescendants(entry); + } + + public void Stop() + { + _timer.Dispose(); + } + + private async Task RunAsync() + { + await _console + .Live(_tree) + .AutoClear(false) + .Overflow(VerticalOverflow.Visible) + .StartAsync(async ctx => + { + while (await _timer.WaitForNextTickAsync()) + { + ctx.Refresh(); + } + + ctx.Refresh(); + }); + + // ActivityTree pads its output with a trailing blank line to work around a + // Spectre.Console bug (see ActivityTree.Render). After Live completes, Spectre + // has left the cursor one row below that padding. Step back onto the padded + // row so subsequent output overwrites it instead of leaving a visible gap. + _console.Cursor.Move(CursorDirection.Up, 1); + } +} diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/NitroConsole.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/NitroConsole.cs index 46a38aac68c..1df8d052dbb 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Services/Console/NitroConsole.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Console/NitroConsole.cs @@ -7,7 +7,8 @@ namespace ChilliCream.Nitro.CommandLine; internal sealed class NitroConsole( IAnsiConsole outConsole, IAnsiConsole errorConsole, - IEnvironmentVariableProvider environmentVariables) + IEnvironmentVariableProvider environmentVariables, + IActivitySinkFactory activitySinkFactory) : INitroConsole { private OutputFormat? _outputFormat; @@ -30,6 +31,12 @@ public void SetOutputFormat(OutputFormat format) _outputFormat = format; } + public INitroConsoleActivity StartActivity(string title, string failureMessage) + { + var sink = activitySinkFactory.Create(this, IsInteractive); + return NitroConsoleActivity.Start(sink, title, failureMessage); + } + public void Clear(bool home) { if (IsHumanReadable) diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/NitroConsoleActivity.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/NitroConsoleActivity.cs index b2f8d9f7f93..90e1b6dab9c 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Services/Console/NitroConsoleActivity.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Console/NitroConsoleActivity.cs @@ -1,149 +1,165 @@ -using ChilliCream.Nitro.CommandLine.Helpers; using Spectre.Console.Rendering; namespace ChilliCream.Nitro.CommandLine; -internal sealed class NitroConsoleActivity( - INitroConsole console, - string failureMessage, - string prefix, - INitroConsoleActivity? parent) - : INitroConsoleActivity +internal sealed class NitroConsoleActivity : INitroConsoleActivity { + private readonly IActivitySink _sink; + private readonly ActivityEntry _entry; + private readonly string _failureMessage; + private readonly NitroConsoleActivity? _parent; private bool _completed; + private NitroConsoleActivity( + IActivitySink sink, + ActivityEntry entry, + string failureMessage, + NitroConsoleActivity? parent) + { + _sink = sink; + _entry = entry; + _failureMessage = failureMessage; + _parent = parent; + } + + public static INitroConsoleActivity Start( + IActivitySink sink, + string title, + string failureMessage) + { + var root = sink.AddRoot(title); + return new NitroConsoleActivity(sink, root, failureMessage, parent: null); + } + public void Update( string message, ActivityUpdateKind kind = ActivityUpdateKind.Regular, IRenderable? details = null) { - var glyph = kind switch - { - ActivityUpdateKind.Warning => Glyphs.ExclamationMark.Space(), - ActivityUpdateKind.Waiting => Glyphs.Clock.Space(), - ActivityUpdateKind.Success => Glyphs.Check.Space(), - _ => "" - }; - if (kind == ActivityUpdateKind.Warning) + if (_completed) { - message = message.AsWarning(); + return; } - console.MarkupLine(prefix + "├── " + glyph + message); + + var state = MapState(kind); + var child = _sink.AddChild(_entry, message, state); + if (details is not null) { - WriteIndented(details, prefix + "│ "); + _sink.SetDetails(child, details); } } public void Warning(string message) { - Complete(Glyphs.ExclamationMark.Space() + message.AsWarning()); + Complete(message, ActivityState.Warning, details: null); } public void Success(string message) { - Complete(Glyphs.Check.Space() + message); + Complete(message, ActivityState.Completed, details: null); } public void Fail(string message) { - Complete(Glyphs.Cross.Space() + message.AsError()); + Complete(message, ActivityState.Failed, details: null); } - public void Fail(IRenderable details) + public void Fail() { - if (_completed) - { - return; - } - - console.MarkupLine(prefix + "└── " + Glyphs.Cross.Space() + failureMessage.AsError()); - WriteIndented(details, prefix + " "); - _completed = true; + Fail(_failureMessage); } - public void Fail() + public void Fail(IRenderable details) { - Fail(failureMessage); + Complete(_failureMessage, ActivityState.Failed, details); } - public async ValueTask FailAllAsync() + public async ValueTask FailAllAsync(IRenderable? details = null) { - Fail(); + Complete(_failureMessage, ActivityState.Failed, details); - if (parent is not null) + if (_parent is not null) + { + await _parent.FailAllAsync(); + } + else { - await parent.FailAllAsync(); + await _sink.Completion; } } public INitroConsoleActivity StartChildActivity(string title, string failureMessage) { - console.MarkupLine(prefix + "├── " + title); - return new NitroConsoleActivity(console, failureMessage, prefix + "│ ", this); + var child = _sink.AddChild(_entry, title, ActivityState.Active); + return new NitroConsoleActivity(_sink, child, failureMessage, parent: this); } public async ValueTask DisposeAsync() { if (!_completed) { - await FailAllAsync(); + if (IsRoot) + { + Fail(); + } + else + { + await FailAllAsync(); + return; + } } - } - private void Complete(string message) - { - if (_completed) + if (IsRoot) { - return; + await _sink.Completion; } - - console.MarkupLine(prefix + "└── " + message); - _completed = true; } - public static INitroConsoleActivity Start( - INitroConsole console, - string title, - string failureMessage) - { - console.MarkupLine(title); - return new NitroConsoleActivity(console, failureMessage, "", null); - } + private bool IsRoot => _parent is null; - private void WriteIndented(IRenderable renderable, string linePrefix) + private void Complete(string message, ActivityState state, IRenderable? details) { - var availableWidth = console.Profile.Width - linePrefix.Length; - - if (availableWidth <= 0) + if (_completed) { return; } - var options = RenderOptions.Create(console, console.Profile.Capabilities); - var segments = renderable.Render(options, availableWidth); + if (!IsRoot || state is ActivityState.Failed) + { + _sink.FailActiveDescendants(_entry); + } - var lineBuffer = new System.Text.StringBuilder(); + ActivityEntry target; + if (IsRoot || _entry.Children.Count > 0) + { + _sink.SetState(_entry, state); + target = _sink.CompleteChild(_entry, message, state); + } + else + { + _sink.SetTextAndState(_entry, message, state); + target = _entry; + } - foreach (var segment in segments) + if (details is not null) { - if (segment.IsLineBreak) - { - if (lineBuffer.Length > 0) - { - console.MarkupLine(linePrefix + lineBuffer.ToString().EscapeMarkup()); - lineBuffer.Clear(); - } - } - else - { - lineBuffer.Append(segment.Text); - } + _sink.SetDetails(target, details); } - if (lineBuffer.Length > 0) + _completed = true; + + if (IsRoot) { - console.MarkupLine(linePrefix + lineBuffer.ToString().EscapeMarkup()); + _sink.Stop(); } } + + private static ActivityState MapState(ActivityUpdateKind kind) => kind switch + { + ActivityUpdateKind.Warning => ActivityState.Warning, + ActivityUpdateKind.Waiting => ActivityState.Waiting, + ActivityUpdateKind.Success => ActivityState.Completed, + _ => ActivityState.Info + }; } diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/StreamingActivitySink.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/StreamingActivitySink.cs new file mode 100644 index 00000000000..5e9d17e2e08 --- /dev/null +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Console/StreamingActivitySink.cs @@ -0,0 +1,119 @@ +using ChilliCream.Nitro.CommandLine.Helpers; +using Spectre.Console.Rendering; + +namespace ChilliCream.Nitro.CommandLine; + +internal sealed class StreamingActivitySink : IActivitySink +{ + private readonly INitroConsole _console; + private readonly Dictionary _meta = []; + + public StreamingActivitySink(INitroConsole console) + { + _console = console; + } + + public Task Completion => Task.CompletedTask; + + public ActivityEntry AddRoot(string text) + { + TreeLineWriter.WriteWrapped(_console, "", "│ ", text); + + var entry = new ActivityEntry(text); + _meta[entry] = new EntryMeta(Prefix: "", DetailsPrefix: ""); + return entry; + } + + public ActivityEntry AddChild(ActivityEntry parent, string text, ActivityState state) + { + var parentMeta = _meta[parent]; + var (markupText, wrapPadding) = FormatChild(text, state); + var linePrefix = parentMeta.Prefix + "├── "; + var continuationPrefix = parentMeta.Prefix + "│ " + wrapPadding; + + TreeLineWriter.WriteWrapped(_console, linePrefix, continuationPrefix, markupText); + + var entry = parent.AddChild(text, state); + var childPrefix = parentMeta.Prefix + "│ "; + _meta[entry] = new EntryMeta(Prefix: childPrefix, DetailsPrefix: childPrefix); + return entry; + } + + public ActivityEntry CompleteChild(ActivityEntry parent, string text, ActivityState state) + { + var parentMeta = _meta[parent]; + var markupText = FormatTerminator(text, state); + var linePrefix = parentMeta.Prefix + "└── "; + var continuationPrefix = parentMeta.Prefix + " " + " "; + + TreeLineWriter.WriteWrapped(_console, linePrefix, continuationPrefix, markupText); + + var entry = parent.AddChild(text, state, isTerminator: true); + _meta[entry] = new EntryMeta( + Prefix: parentMeta.Prefix + " ", + DetailsPrefix: parentMeta.Prefix + " "); + return entry; + } + + public void SetState(ActivityEntry entry, ActivityState state) + { + // Streaming is append-only; state changes are reflected only when + // a terminator line is written for this entry. + } + + public void SetTextAndState(ActivityEntry entry, string text, ActivityState state) + { + var entryMeta = _meta[entry]; + var markupText = FormatTerminator(text, state); + var linePrefix = entryMeta.Prefix + "└── "; + var continuationPrefix = entryMeta.Prefix + " " + " "; + + TreeLineWriter.WriteWrapped(_console, linePrefix, continuationPrefix, markupText); + + entry.Text = text; + entry.State = state; + _meta[entry] = entryMeta with { DetailsPrefix = entryMeta.Prefix + " " }; + } + + public void SetDetails(ActivityEntry entry, IRenderable details) + { + TreeLineWriter.WriteIndented(_console, details, _meta[entry].DetailsPrefix); + entry.Details = details; + } + + public void FailActiveDescendants(ActivityEntry entry) + { + // Streaming is append-only; descendant state does not affect already-written output. + } + + public void Stop() + { + } + + private static (string MarkupText, string WrapPadding) FormatChild(string text, ActivityState state) + { + return state switch + { + ActivityState.Active => (text, ""), + ActivityState.Info => (text, ""), + ActivityState.Warning => (Glyphs.ExclamationMark.Space() + text.AsWarning(), " "), + ActivityState.Waiting => (Glyphs.Clock.Space() + text, " "), + ActivityState.Completed => (Glyphs.Check.Space() + text, " "), + ActivityState.Failed => (Glyphs.Cross.Space() + text.AsError(), " "), + _ => (text, "") + }; + } + + private static string FormatTerminator(string text, ActivityState state) + { + return state switch + { + ActivityState.Completed => Glyphs.Check.Space() + text, + ActivityState.Failed => Glyphs.Cross.Space() + text.AsError(), + ActivityState.Warning => Glyphs.ExclamationMark.Space() + text.AsWarning(), + _ => text + }; + } + + private readonly record struct EntryMeta(string Prefix, string DetailsPrefix); +} diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Console/TreeLineWriter.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Console/TreeLineWriter.cs new file mode 100644 index 00000000000..e287bd004b6 --- /dev/null +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Console/TreeLineWriter.cs @@ -0,0 +1,127 @@ +using Spectre.Console.Rendering; + +namespace ChilliCream.Nitro.CommandLine; + +internal static class TreeLineWriter +{ + public static void WriteWrapped( + INitroConsole console, + string linePrefix, + string continuationPrefix, + string markupText) + { + var prefixWidth = Math.Max(linePrefix.Length, continuationPrefix.Length); + var textWidth = console.Profile.Width - prefixWidth; + + if (textWidth <= 0) + { + console.MarkupLine(linePrefix + markupText); + return; + } + + var markup = new Markup(markupText); + var options = RenderOptions.Create(console, console.Profile.Capabilities); + var segments = ((IRenderable)markup).Render(options, textWidth); + + var hasLineBreaks = false; + + foreach (var seg in segments) + { + if (seg.IsLineBreak) + { + hasLineBreaks = true; + break; + } + } + + if (!hasLineBreaks) + { + console.MarkupLine(linePrefix + markupText); + return; + } + + var output = new List(); + var atLineStart = true; + var isFirstLine = true; + + foreach (var segment in segments) + { + if (segment.IsLineBreak) + { + output.Add(Segment.LineBreak); + atLineStart = true; + isFirstLine = false; + } + else + { + if (atLineStart) + { + output.Add(new Segment(isFirstLine ? linePrefix : continuationPrefix)); + atLineStart = false; + } + + output.Add(segment); + } + } + + if (!atLineStart) + { + output.Add(Segment.LineBreak); + } + + console.Write(new SegmentRenderable(output)); + } + + public static void WriteIndented( + INitroConsole console, + IRenderable renderable, + string linePrefix) + { + var availableWidth = console.Profile.Width - linePrefix.Length; + + if (availableWidth <= 0) + { + return; + } + + var options = RenderOptions.Create(console, console.Profile.Capabilities); + var segments = renderable.Render(options, availableWidth); + + var output = new List(); + var atLineStart = true; + + foreach (var segment in segments) + { + if (segment.IsLineBreak) + { + output.Add(Segment.LineBreak); + atLineStart = true; + } + else + { + if (atLineStart) + { + output.Add(new Segment(linePrefix)); + atLineStart = false; + } + + output.Add(segment); + } + } + + if (!atLineStart) + { + output.Add(Segment.LineBreak); + } + + console.Write(new SegmentRenderable(output)); + } + + private sealed class SegmentRenderable(IReadOnlyList segments) : Renderable + { + protected override IEnumerable Render(RenderOptions options, int maxWidth) + { + return segments; + } + } +} diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/NitroClientContext.cs b/src/Nitro/CommandLine/src/CommandLine/Services/NitroClientContext.cs index 4d358c78003..e884528e0fc 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Services/NitroClientContext.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Services/NitroClientContext.cs @@ -5,15 +5,38 @@ namespace ChilliCream.Nitro.CommandLine.Services; internal sealed class NitroClientContext : INitroClientContextProvider { - public Uri Url { get; private set; } = new Uri(Constants.ApiUrl); + public Uri Url + { + get + { + return field ?? throw new InvalidOperationException($"{nameof(NitroClientContext)} hasn't been initialized."); + } + private set; + } public INitroClientAuthorization? Authorization { get; private set; } public void Configure(string? apiUrl, INitroClientAuthorization? authorization) { - Url = apiUrl is not null - ? new Uri($"https://{apiUrl}/graphql") - : new Uri(Constants.ApiUrl + "/graphql"); + if (string.IsNullOrWhiteSpace(apiUrl)) + { + apiUrl = Constants.ApiUrl; + } + else if (!apiUrl.StartsWith("https://") && !apiUrl.StartsWith("http://")) + { + apiUrl = $"https://{apiUrl}"; + } + + var uriBuilder = new UriBuilder(apiUrl) + { + Path = "/graphql", + Query = string.Empty, + Fragment = string.Empty, + UserName = string.Empty, + Password = string.Empty + }; + + Url = uriBuilder.Uri; Authorization = authorization; } } diff --git a/src/Nitro/CommandLine/src/CommandLine/Services/Sessions/SessionService.cs b/src/Nitro/CommandLine/src/CommandLine/Services/Sessions/SessionService.cs index 9a303aeb90b..bebcffeca69 100644 --- a/src/Nitro/CommandLine/src/CommandLine/Services/Sessions/SessionService.cs +++ b/src/Nitro/CommandLine/src/CommandLine/Services/Sessions/SessionService.cs @@ -149,8 +149,7 @@ private async Task RefreshTokenAsync( private async Task EnsureSessionAsync(CancellationToken cancellationToken) { Session ??= await _configurationService.GetAsync(cancellationToken) - ?? throw new ExitException( - $"User session could not be loaded, run {"nitro login".AsCommand()} first."); + ?? throw new ExitException("User session could not be loaded, run `nitro login` first."); } private OidcClient CreateClient(Action? configure = null) @@ -255,14 +254,9 @@ public static Session ToSession(this LoginResult result) } } - if (userId is null - || sessionId is null - || email is null - || tenant is null - || issuer is null - || apiUrl is null) + if (userId is null || sessionId is null || email is null || tenant is null || issuer is null || apiUrl is null) { - throw new ExitException("The session"); + throw new ExitException("The user session could not be constructed."); } return new Session( diff --git a/src/Nitro/CommandLine/src/CommandLine/ThrowHelper.cs b/src/Nitro/CommandLine/src/CommandLine/ThrowHelper.cs index 72e50e3b3f4..68f5f9ea758 100644 --- a/src/Nitro/CommandLine/src/CommandLine/ThrowHelper.cs +++ b/src/Nitro/CommandLine/src/CommandLine/ThrowHelper.cs @@ -1,5 +1,3 @@ -using ChilliCream.Nitro.CommandLine.Helpers; - namespace ChilliCream.Nitro.CommandLine; internal static class ThrowHelper @@ -10,32 +8,19 @@ public static ExitException Exit(string message) } public static ExitException MissingRequiredOption(string optionName) - => Exit($"The '{optionName}' option is required in non-interactive mode."); + => Exit($"Missing required option '{optionName}'."); public static ExitException MissingRequiredArgument(string argumentName) - => Exit($"The '{argumentName}' argument is required."); - - public static ExitException NoDefaultWorkspace() => new( - $"You are not logged in. Run {"nitro login".AsCommand()} to sign in or manually specify the '{OptionalWorkspaceIdOption.OptionName}' option (if available)."); + => Exit($"Missing required argument '{argumentName}'."); public static Exception NoPageInfoFound() - => ThereWasAnIssueWithTheRequest("No page info found in the response."); + => new ExitException("No page info found in the response."); public static Exception CouldNotSelectEdges() - => ThereWasAnIssueWithTheRequest("Could not select edges."); - - public static Exception NoApiSelected() => Exit("You did not select an API!"); + => new ExitException("Could not select edges."); public static Exception NoClientSelected() => Exit("You did not select a client!"); - public static Exception NoOpenApiCollectionSelected() => Exit("You did not select an OpenAPI collection!"); - - public static Exception NoMcpFeatureCollectionSelected() => Exit("You did not select an MCP Feature Collection!"); - public static ExitException MutationReturnedNoData() => Exit("The GraphQL mutation completed without errors, but the server did not return the expected data."); - - public static Exception ThereWasAnIssueWithTheRequest(string? additional = null) - => new ExitException( - $"There was an issue with the request to the server.\n{additional ?? ""}"); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/ApiKeys/CreateApiKeyCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/ApiKeys/CreateApiKeyCommandTests.cs index b853102fae0..4cd663508b2 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/ApiKeys/CreateApiKeyCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/ApiKeys/CreateApiKeyCommandTests.cs @@ -62,7 +62,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -104,7 +104,7 @@ public async Task MissingWorkspaceAndApi_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - The '--workspace-id' or '--api-id' option is required in non-interactive mode. + Missing required option '--workspace-id' or '--api-id'. """); } @@ -129,7 +129,7 @@ public async Task WithApiId_NoWorkspaceIdOption_NoWorkspaceInSession_ReturnsErro // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Could not determine workspace. Either login via `nitro login` or specify the '--workspace-id' option. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/ApiKeys/DeleteApiKeyCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/ApiKeys/DeleteApiKeyCommandTests.cs index 4390b549136..8a135903898 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/ApiKeys/DeleteApiKeyCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/ApiKeys/DeleteApiKeyCommandTests.cs @@ -58,7 +58,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/ApiKeys/ListApiKeyCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/ApiKeys/ListApiKeyCommandTests.cs index 3270b7ca84c..7d20ff1a3c7 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/ApiKeys/ListApiKeyCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/ApiKeys/ListApiKeyCommandTests.cs @@ -50,7 +50,7 @@ public async Task NoSession_And_NoWorkspaceId_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Could not determine workspace. Either login via `nitro login` or specify the '--workspace-id' option. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/CreateApiCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/CreateApiCommandTests.cs index 133027464f3..e09724f8988 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/CreateApiCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/CreateApiCommandTests.cs @@ -62,7 +62,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -87,7 +87,7 @@ public async Task NoWorkspaceInSession_And_NoWorkspaceOption_ReturnsError(Intera // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Could not determine workspace. Either login via `nitro login` or specify the '--workspace-id' option. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/DeleteApiCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/DeleteApiCommandTests.cs index 8dd0a40a794..27079ac8963 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/DeleteApiCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/DeleteApiCommandTests.cs @@ -57,7 +57,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/ListApiCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/ListApiCommandTests.cs index 60a6a765df7..2398732da3e 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/ListApiCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/ListApiCommandTests.cs @@ -51,7 +51,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -73,7 +73,7 @@ public async Task NoWorkspaceInSession_And_NoWorkspaceOption_ReturnsError(Intera // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Could not determine workspace. Either login via `nitro login` or specify the '--workspace-id' option. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/SetApiSettingsCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/SetApiSettingsCommandTests.cs index 3aeef7a68df..1ab0ae35d37 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/SetApiSettingsCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/SetApiSettingsCommandTests.cs @@ -63,7 +63,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/ShowApiCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/ShowApiCommandTests.cs index 03743efaec0..ef445561880 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/ShowApiCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Apis/ShowApiCommandTests.cs @@ -53,7 +53,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/CreateClientCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/CreateClientCommandTests.cs index 542cf74f000..e08e9ecc9f3 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/CreateClientCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/CreateClientCommandTests.cs @@ -59,12 +59,31 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. + """); + } + + [Fact] + public async Task NoWorkspaceInSession_And_NoApiId_ReturnsError_Interactive() + { + // arrange & act + SetupSession(); + SetupInteractionMode(InteractionMode.Interactive); + + var result = await ExecuteCommandAsync( + "client", + "create", + "--name", + ClientName); + + // assert + result.AssertError( + """ + Could not determine workspace. Either login via `nitro login` or specify the '--workspace-id' option. """); } [Theory] - [InlineData(InteractionMode.Interactive)] [InlineData(InteractionMode.NonInteractive)] [InlineData(InteractionMode.JsonOutput)] public async Task NoWorkspaceInSession_And_NoApiId_ReturnsError(InteractionMode mode) @@ -82,7 +101,7 @@ public async Task NoWorkspaceInSession_And_NoApiId_ReturnsError(InteractionMode // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Missing required option '--api-id'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/DeleteClientCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/DeleteClientCommandTests.cs index bed59deb141..e34fe4e394a 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/DeleteClientCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/DeleteClientCommandTests.cs @@ -57,7 +57,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -121,7 +121,7 @@ public async Task MissingClientId_NonInteractive_ReturnsError(InteractionMode mo // assert result.AssertError( """ - The 'id' option is required in non-interactive mode. + Missing required argument 'id'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/DownloadClientCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/DownloadClientCommandTests.cs index f523bfdf59c..80203b6e5a2 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/DownloadClientCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/DownloadClientCommandTests.cs @@ -67,7 +67,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ListClientCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ListClientCommandTests.cs index 2b3e6236dba..3ff0942efc5 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ListClientCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ListClientCommandTests.cs @@ -54,7 +54,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -72,7 +72,7 @@ public async Task NoWorkspaceInSession_And_NoApiId_ReturnsError_Interactive() // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Could not determine workspace. Either login via `nitro login` or specify the '--workspace-id' option. """); } @@ -91,7 +91,7 @@ public async Task MissingApiId_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - The '--api-id' option is required in non-interactive mode. + Missing required option '--api-id'. """); } @@ -278,7 +278,6 @@ public async Task List_Should_ReturnError_When_ApiNotFound() // assert result.AssertError( """ - There was an issue with the request to the server. The API was not found. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ListClientPublishedVersionsCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ListClientPublishedVersionsCommandTests.cs index 049ea01c135..e26f48e1a07 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ListClientPublishedVersionsCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ListClientPublishedVersionsCommandTests.cs @@ -53,7 +53,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -73,7 +73,7 @@ public async Task MissingClientId_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - The '--client-id' option is required in non-interactive mode. + Missing required option '--client-id'. """); } @@ -343,7 +343,6 @@ public async Task ListPublished_Should_ReturnError_When_ClientNotFound() // assert result.AssertError( """ - There was an issue with the request to the server. The client was not found. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ListClientVersionsCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ListClientVersionsCommandTests.cs index 795f3fd77e3..991092bf225 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ListClientVersionsCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ListClientVersionsCommandTests.cs @@ -52,7 +52,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -72,7 +72,7 @@ public async Task MissingClientId_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - The '--client-id' option is required in non-interactive mode. + Missing required option '--client-id'. """); } @@ -303,7 +303,6 @@ public async Task ListVersions_Should_ReturnError_When_ClientNotFound() // assert result.AssertError( """ - There was an issue with the request to the server. The client was not found. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/PublishClientCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/PublishClientCommandTests.cs index 385fe40c94a..5ede31e0511 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/PublishClientCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/PublishClientCommandTests.cs @@ -94,7 +94,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -122,9 +122,7 @@ public async Task PublishThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing new client version 'v1' to stage 'dev' of client 'client-1' - ├── Starting publish request - │ └── ✕ Failed to start publish request. + Publishing new version 'v1' of client 'client-1' to stage 'dev' └── ✕ Failed to publish a new client version. """); Assert.Equal(1, result.ExitCode); @@ -154,9 +152,7 @@ public async Task MutationReturnsTypedError_ReturnsError( result.StdErr.MatchInlineSnapshot(expectedStdErr); result.StdOut.MatchInlineSnapshot( """ - Publishing new client version 'v1' to stage 'dev' of client 'client-1' - ├── Starting publish request - │ └── ✕ Failed to start publish request. + Publishing new version 'v1' of client 'client-1' to stage 'dev' └── ✕ Failed to publish a new client version. """); Assert.Equal(1, result.ExitCode); @@ -186,9 +182,7 @@ public async Task MutationReturnsNullRequestId_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing new client version 'v1' to stage 'dev' of client 'client-1' - ├── Starting publish request - │ └── ✕ Failed to start publish request. + Publishing new version 'v1' of client 'client-1' to stage 'dev' └── ✕ Failed to publish a new client version. """); Assert.Equal(1, result.ExitCode); @@ -216,11 +210,8 @@ public async Task ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new client version 'v1' to stage 'dev' of client 'client-1' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✓ Published successfully. + Publishing new version 'v1' of client 'client-1' to stage 'dev' + ├── Publication request created. (ID: request-1) └── ✓ Published new client version 'v1' to stage 'dev'. """); } @@ -248,11 +239,8 @@ public async Task WaitForApproval_NoBreakingChanges_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new client version 'v1' to stage 'dev' of client 'client-1' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✓ Published successfully. + Publishing new version 'v1' of client 'client-1' to stage 'dev' + ├── Publication request created. (ID: request-1) └── ✓ Published new client version 'v1' to stage 'dev'. """); } @@ -279,14 +267,11 @@ public async Task BreakingChanges_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Publishing new client version 'v1' to stage 'dev' of client 'client-1' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✕ Processing failed. - │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) - │ └── foo (10:10) + Publishing new version 'v1' of client 'client-1' to stage 'dev' + ├── Publication request created. (ID: request-1) └── ✕ Failed to publish a new client version. + └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) + └── foo (10:10) """); result.StdErr.MatchInlineSnapshot( """ @@ -318,12 +303,9 @@ public async Task BreakingChanges_Force_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new client version 'v1' to stage 'dev' of client 'client-1' + Publishing new version 'v1' of client 'client-1' to stage 'dev' ├── ! Force push is enabled. - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✓ Published successfully. + ├── Publication request created. (ID: request-1) └── ✓ Published new client version 'v1' to stage 'dev'. """); } @@ -353,16 +335,13 @@ public async Task WaitForApproval_BreakingChanges_Approved_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new client version 'v1' to stage 'dev' of client 'client-1' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ ├── ! Validation failed. - │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) - │ │ └── foo (10:10) - │ ├── ⏳ Waiting for approval. Approve in Nitro to continue. - │ ├── Your request has been approved. - │ └── ✓ Published successfully. + Publishing new version 'v1' of client 'client-1' to stage 'dev' + ├── Publication request created. (ID: request-1) + ├── ! Validation failed. + │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) + │ └── foo (10:10) + ├── ⏳ Waiting for approval. Approve in Nitro to continue. + ├── Your request has been approved. └── ✓ Published new client version 'v1' to stage 'dev'. """); } @@ -395,15 +374,12 @@ Client publish failed. """); result.StdOut.MatchInlineSnapshot( """ - Publishing new client version 'v1' to stage 'dev' of client 'client-1' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ ├── ! Validation failed. - │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) - │ │ └── foo (10:10) - │ ├── ⏳ Waiting for approval. Approve in Nitro to continue. - │ └── ✕ Processing failed. + Publishing new version 'v1' of client 'client-1' to stage 'dev' + ├── Publication request created. (ID: request-1) + ├── ! Validation failed. + │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) + │ └── foo (10:10) + ├── ⏳ Waiting for approval. Approve in Nitro to continue. └── ✕ Failed to publish a new client version. """); Assert.Equal(1, result.ExitCode); @@ -429,11 +405,8 @@ public async Task WithEnvVars_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new client version 'v1' to stage 'dev' of client 'client-1' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✓ Published successfully. + Publishing new version 'v1' of client 'client-1' to stage 'dev' + ├── Publication request created. (ID: request-1) └── ✓ Published new client version 'v1' to stage 'dev'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ShowClientCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ShowClientCommandTests.cs index 0ee23526343..fe06b30dbf9 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ShowClientCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ShowClientCommandTests.cs @@ -55,7 +55,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/UnpublishClientCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/UnpublishClientCommandTests.cs index 7d3a03aba5f..cbd8170ff1d 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/UnpublishClientCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/UnpublishClientCommandTests.cs @@ -62,7 +62,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/UploadClientCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/UploadClientCommandTests.cs index 738e05cc852..575895d8bf6 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/UploadClientCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/UploadClientCommandTests.cs @@ -62,7 +62,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -118,7 +118,7 @@ public async Task UploadClientThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Uploading new client version 'v1' for client 'client-1' + Uploading new version 'v1' for client 'client-1' └── ✕ Failed to upload a new client version. """); Assert.Equal(1, result.ExitCode); @@ -149,7 +149,7 @@ public async Task UploadClientHasErrors_ReturnsError( result.StdErr.MatchInlineSnapshot(expectedErrorMessage); result.StdOut.MatchInlineSnapshot( """ - Uploading new client version 'v1' for client 'client-1' + Uploading new version 'v1' for client 'client-1' └── ✕ Failed to upload a new client version. """); Assert.Equal(1, result.ExitCode); @@ -180,7 +180,7 @@ Could not upload client. """); result.StdOut.MatchInlineSnapshot( """ - Uploading new client version 'v1' for client 'client-1' + Uploading new version 'v1' for client 'client-1' └── ✕ Failed to upload a new client version. """); Assert.Equal(1, result.ExitCode); @@ -209,7 +209,7 @@ public async Task UploadsClient_ReturnsSuccess() System.Text.Encoding.UTF8.GetString(capturedStream.ToArray())); result.AssertSuccess( """ - Uploading new client version 'v1' for client 'client-1' + Uploading new version 'v1' for client 'client-1' └── ✓ Uploaded new client version 'v1'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ValidateClientCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ValidateClientCommandTests.cs index b71715c0f5c..7f3a90b9298 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ValidateClientCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Clients/ValidateClientCommandTests.cs @@ -62,7 +62,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -118,9 +118,7 @@ public async Task StartClientValidationThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Validating client against stage 'dev' of client 'client-1' - ├── Starting validation request - │ └── ✕ Failed to start the validation request. + Validating client 'client-1' against stage 'dev' └── ✕ Failed to validate the client. """); Assert.Equal(1, result.ExitCode); @@ -151,9 +149,7 @@ public async Task StartClientValidationHasErrors_ReturnsError( result.StdErr.MatchInlineSnapshot(expectedErrorMessage); result.StdOut.MatchInlineSnapshot( """ - Validating client against stage 'dev' of client 'client-1' - ├── Starting validation request - │ └── ✕ Failed to start the validation request. + Validating client 'client-1' against stage 'dev' └── ✕ Failed to validate the client. """); Assert.Equal(1, result.ExitCode); @@ -184,9 +180,7 @@ Could not create client validation request. """); result.StdOut.MatchInlineSnapshot( """ - Validating client against stage 'dev' of client 'client-1' - ├── Starting validation request - │ └── ✕ Failed to start the validation request. + Validating client 'client-1' against stage 'dev' └── ✕ Failed to validate the client. """); Assert.Equal(1, result.ExitCode); @@ -216,11 +210,8 @@ public async Task ReturnsSuccess() System.Text.Encoding.UTF8.GetString(capturedStream.ToArray())); result.AssertSuccess( """ - Validating client against stage 'dev' of client 'client-1' - ├── Starting validation request - │ └── ✓ Validation request created (ID: request-1). - ├── Validating - │ └── ✓ Validation passed. + Validating client 'client-1' against stage 'dev' + ├── Validation request created. (ID: request-1) └── ✓ Validated client against stage 'dev'. """); } @@ -245,11 +236,8 @@ public async Task WithEnvVars_ReturnsSuccess() // assert result.AssertSuccess( """ - Validating client against stage 'dev' of client 'client-1' - ├── Starting validation request - │ └── ✓ Validation request created (ID: request-1). - ├── Validating - │ └── ✓ Validation passed. + Validating client 'client-1' against stage 'dev' + ├── Validation request created. (ID: request-1) └── ✓ Validated client against stage 'dev'. """); } @@ -277,14 +265,11 @@ public async Task BreakingChanges_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Validating client against stage 'dev' of client 'client-1' - ├── Starting validation request - │ └── ✓ Validation request created (ID: request-1). - ├── Validating - │ └── ✕ Validation failed. - │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) - │ └── foo (10:10) + Validating client 'client-1' against stage 'dev' + ├── Validation request created. (ID: request-1) └── ✕ Failed to validate the client. + └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) + └── foo (10:10) """); result.StdErr.MatchInlineSnapshot( """ diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/CommandTestBase.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/CommandTestBase.cs index 887d92e251c..d6f381d4053 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/CommandTestBase.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/CommandTestBase.cs @@ -15,6 +15,7 @@ using ChilliCream.Nitro.Client.Workspaces; using ChilliCream.Nitro.CommandLine.Helpers; using ChilliCream.Nitro.CommandLine.Services; +using ChilliCream.Nitro.CommandLine.Tests.Console; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Moq; @@ -116,7 +117,11 @@ protected async Task ExecuteCommandAsync(params string[] args) outConsole.Profile.Capabilities.Interactive = true; } - var console = new NitroConsole(outConsole, errConsole, _environmentVariableProviderMock.Object); + var console = new NitroConsole( + outConsole, + errConsole, + _environmentVariableProviderMock.Object, + new SnapshotActivitySinkFactory()); var services = BuildServices(console); var rootCommand = _fixture.RootCommand; @@ -161,7 +166,11 @@ internal InteractiveCommand StartInteractiveCommand(params string[] args) errConsole.Profile.Out = new AnsiConsoleOutput(stdErrWriter); errConsole.Profile.Width = Constants.DefaultPrintWidth; - var console = new NitroConsole(outConsole, errConsole, _environmentVariableProviderMock.Object); + var console = new NitroConsole( + outConsole, + errConsole, + _environmentVariableProviderMock.Object, + new SnapshotActivitySinkFactory()); var services = BuildServices(console); var rootCommand = _fixture.RootCommand; @@ -369,6 +378,24 @@ protected void SetupSelectApisPrompt( nodes, null, false)); } + protected void VerifyWorkspaceSelected(string workspaceId, string workspaceName) + { + _sessionServiceMock.Verify( + x => x.SelectWorkspaceAsync( + It.Is(w => w.Id == workspaceId && w.Name == workspaceName), + It.IsAny()), + Times.Once); + } + + protected void VerifyNoWorkspaceSelected() + { + _sessionServiceMock.Verify( + x => x.SelectWorkspaceAsync( + It.IsAny(), + It.IsAny()), + Times.Never); + } + public async ValueTask DisposeAsync() { foreach (var file in _files) diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Environments/CreateEnvironmentCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Environments/CreateEnvironmentCommandTests.cs index 4d6fa109282..fa503863cce 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Environments/CreateEnvironmentCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Environments/CreateEnvironmentCommandTests.cs @@ -59,7 +59,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -83,7 +83,7 @@ public async Task NoWorkspaceInSession_And_NoWorkspaceOption_ReturnsError(Intera // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Could not determine workspace. Either login via `nitro login` or specify the '--workspace-id' option. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Environments/ListEnvironmentCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Environments/ListEnvironmentCommandTests.cs index 1d5828f93f1..4b741ceb6c1 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Environments/ListEnvironmentCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Environments/ListEnvironmentCommandTests.cs @@ -52,7 +52,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -74,7 +74,7 @@ public async Task NoWorkspaceInSession_And_NoWorkspaceOption_ReturnsError(Intera // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Could not determine workspace. Either login via `nitro login` or specify the '--workspace-id' option. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Environments/ShowEnvironmentCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Environments/ShowEnvironmentCommandTests.cs index 77c405782dc..e3d90dd595c 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Environments/ShowEnvironmentCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Environments/ShowEnvironmentCommandTests.cs @@ -54,7 +54,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishBeginCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishBeginCommandTests.cs index 4f5252f7602..a4475c34cd7 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishBeginCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishBeginCommandTests.cs @@ -68,7 +68,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -178,7 +178,7 @@ public async Task MutationReturnsNullRequestId_ReturnsError() } [Fact] - public async Task Success_DeploymentSlotReady() + public async Task Success_DeploymentSlotReady_ReturnsSuccess() { // arrange SetupRequestDeploymentSlotMutation(); @@ -200,7 +200,8 @@ public async Task Success_DeploymentSlotReady() result.AssertSuccess( """ Requesting deployment slot for stage 'dev' of API 'api-1' - └── ✕ Failed to request a deployment slot. + ├── Publication request created. (ID: request-id) + └── ✓ Deployment slot ready. { "requestId": "request-id" @@ -209,7 +210,7 @@ public async Task Success_DeploymentSlotReady() } [Fact] - public async Task Success_DeploymentSlotReady_JsonOutput() + public async Task Success_DeploymentSlotReady_ReturnsSuccess_JsonOutput() { // arrange SetupInteractionMode(InteractionMode.JsonOutput); @@ -238,7 +239,7 @@ public async Task Success_DeploymentSlotReady_JsonOutput() } [Fact] - public async Task Begin_Should_HandleQueuePosition_When_ProcessingTaskIsQueued() + public async Task Begin_Should_HandleQueuePosition_When_ProcessingTaskIsQueued_ReturnsSuccess() { // arrange SetupRequestDeploymentSlotMutation(); @@ -262,8 +263,9 @@ public async Task Begin_Should_HandleQueuePosition_When_ProcessingTaskIsQueued() result.AssertSuccess( """ Requesting deployment slot for stage 'dev' of API 'api-1' + ├── Publication request created. (ID: request-id) ├── ⏳ Your request is queued at position 3. - └── ✕ Failed to request a deployment slot. + └── ✓ Deployment slot ready. { "requestId": "request-id" @@ -272,7 +274,7 @@ public async Task Begin_Should_HandleQueuePosition_When_ProcessingTaskIsQueued() } [Fact] - public async Task Begin_Should_PassSubgraphId_When_Provided() + public async Task Begin_Should_PassSubgraphId_When_Provided_ReturnsSuccess() { // arrange SetupRequestDeploymentSlotMutation(subgraphId: "subgraph-1"); @@ -297,7 +299,7 @@ public async Task Begin_Should_PassSubgraphId_When_Provided() } [Fact] - public async Task Begin_Should_PassSubgraphName_When_Provided() + public async Task Begin_Should_PassSubgraphName_When_Provided_ReturnsSuccess() { // arrange SetupRequestDeploymentSlotMutation(subgraphName: "subgraph-1"); @@ -322,7 +324,7 @@ public async Task Begin_Should_PassSubgraphName_When_Provided() } [Fact] - public async Task Begin_Should_PassWaitForApproval_When_Provided() + public async Task Begin_Should_PassWaitForApproval_When_Provided_ReturnsSuccess() { // arrange SetupRequestDeploymentSlotMutation(waitForApproval: true); diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishCancelCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishCancelCommandTests.cs index 256dbb36a56..e45c72c10d9 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishCancelCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishCancelCommandTests.cs @@ -53,7 +53,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -127,7 +127,7 @@ Canceling publication } [Fact] - public async Task RequestIdFromArg_Success() + public async Task RequestIdFromArg_Success_ReturnsSuccess() { // arrange SetupFusionPublishingStateCache(RequestId); @@ -145,7 +145,7 @@ Canceling publication } [Fact] - public async Task RequestIdFromStateFile_Success() + public async Task RequestIdFromStateFile_Success_ReturnsSuccess() { // arrange SetupFusionPublishingStateCache(RequestId); diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishCommitCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishCommitCommandTests.cs index 367182c0893..c6372d43f17 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishCommitCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishCommitCommandTests.cs @@ -57,7 +57,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -165,7 +165,7 @@ Publishing Fusion configuration } [Fact] - public async Task Success_CommitsArchive_NonInteractive() + public async Task Success_CommitsArchive_ReturnsSuccess() { // arrange SetupArchiveFile(); @@ -193,33 +193,7 @@ Publishing Fusion configuration } [Fact] - public async Task Success_CommitsArchive_Interactive() - { - // arrange - SetupArchiveFile(); - var capturedStream = SetupFusionConfigurationUploadMutation(); - SetupFusionConfigurationUploadSubscription(); - SetupInteractionMode(InteractionMode.Interactive); - - // act - var result = await ExecuteCommandAsync( - "fusion", - "publish", - "commit", - "--request-id", - RequestId, - "--archive", - ArchiveFile); - - // assert - Assert.Empty(result.StdErr); - Assert.Equal(0, result.ExitCode); - var schema = await GetFusionSchemaAsync(capturedStream); - AssertFusionSchema(schema); - } - - [Fact] - public async Task Success_CommitsArchive_JsonOutput() + public async Task Success_CommitsArchive_ReturnsSuccess_JsonOutput() { // arrange SetupArchiveFile(); @@ -248,7 +222,7 @@ public async Task Success_CommitsArchive_JsonOutput() [InlineData(InteractionMode.Interactive)] [InlineData(InteractionMode.NonInteractive)] [InlineData(InteractionMode.JsonOutput)] - public async Task RequestIdFromStateFile_Success(InteractionMode mode) + public async Task RequestIdFromStateFile_Success_ReturnsSuccess(InteractionMode mode) { // arrange SetupFusionPublishingStateCache(RequestId); @@ -273,7 +247,7 @@ public async Task RequestIdFromStateFile_Success(InteractionMode mode) } [Fact] - public async Task Commit_Should_ReturnError_When_CommitFails() + public async Task Commit_Should_ReturnError_When_CommitFails_ReturnsError() { // arrange SetupArchiveFile(); @@ -309,7 +283,7 @@ The commit has failed. } [Fact] - public async Task Commit_Should_HandleSubscriptionEvents_When_PublishFails() + public async Task Commit_Should_HandleSubscriptionEvents_When_PublishFails_ReturnsError() { // arrange SetupArchiveFile(); @@ -342,7 +316,7 @@ Publishing Fusion configuration } [Fact] - public async Task Commit_Should_HandleSubscriptionEvents_When_Queued() + public async Task Commit_Should_HandleSubscriptionEvents_When_Queued_ReturnsSuccess() { // arrange SetupArchiveFile(); diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishStartCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishStartCommandTests.cs index 8b127db1358..9e914ba312a 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishStartCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishStartCommandTests.cs @@ -51,7 +51,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -79,7 +79,7 @@ No request ID was provided and no request ID was found in the cache. Please prov [InlineData(InteractionMode.Interactive)] [InlineData(InteractionMode.NonInteractive)] [InlineData(InteractionMode.JsonOutput)] - public async Task RequestIdFromStateFile_Success(InteractionMode mode) + public async Task RequestIdFromStateFile_Success_ReturnsSuccess(InteractionMode mode) { // arrange SetupFusionPublishingStateCache(RequestId); @@ -118,7 +118,7 @@ Starting composition } [Fact] - public async Task Success_WithRequestIdOption() + public async Task Success_WithRequestIdOption_ReturnsSuccess() { // arrange SetupClaimDeploymentSlotMutation(); diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishValidateCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishValidateCommandTests.cs index b052f9c4fb4..6872f9c17f7 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishValidateCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionConfigurationPublishValidateCommandTests.cs @@ -109,7 +109,7 @@ Validating Fusion configuration } [Fact] - public async Task Subscription_ValidationSuccess_ReturnsSuccess_NonInteractive() + public async Task Subscription_ValidationSuccess_ReturnsSuccess() { // arrange SetupArchiveFile(); @@ -131,36 +131,12 @@ public async Task Subscription_ValidationSuccess_ReturnsSuccess_NonInteractive() result.AssertSuccess( """ Validating Fusion configuration - └── ✕ Failed to validate the Fusion configuration. + └── ✓ Validated configuration. """); } [Fact] - public async Task Subscription_ValidationSuccess_ReturnsSuccess_Interactive() - { - // arrange - SetupInteractionMode(InteractionMode.Interactive); - SetupArchiveFile(); - SetupFusionConfigurationValidationMutation(); - SetupFusionConfigurationValidationSubscription( - CreateValidationSuccessEvent()); - - // act - var result = await ExecuteCommandAsync( - "fusion", - "publish", - "validate", - "--archive", - ArchiveFile, - "--request-id", - RequestId); - - // assert - result.AssertSuccess(); - } - - [Fact] - public async Task Subscription_ValidationFailed_ReturnsError_NonInteractive() + public async Task Subscription_ValidationFailed_ReturnsError() { // arrange var errorMock = new Mock(MockBehavior.Strict); @@ -196,12 +172,15 @@ Validating Fusion configuration └── ✕ Failed to validate the Fusion configuration. └── Something went wrong. """); - result.StdErr.MatchInlineSnapshot(""); + result.StdErr.MatchInlineSnapshot( + """ + Fusion configuration validation failed. + """); Assert.Equal(1, result.ExitCode); } [Fact] - public async Task Subscription_Queued_ThrowsExitException() + public async Task Subscription_Queued_ReturnsError() { // arrange SetupArchiveFile(); @@ -226,13 +205,13 @@ Validating Fusion configuration """); result.StdErr.MatchInlineSnapshot( """ - Your request is in the queued state. Try to run `fusion-configuration publish start` once the request is ready + Your request is in the queued state. Try to run `fusion-configuration publish start` once the request is ready. """); Assert.Equal(1, result.ExitCode); } [Fact] - public async Task Subscription_AlreadyFailed_ThrowsExitException() + public async Task Subscription_AlreadyFailed_ReturnsError() { // arrange SetupArchiveFile(); @@ -257,13 +236,13 @@ Validating Fusion configuration """); result.StdErr.MatchInlineSnapshot( """ - Your request has already failed + Your request has already failed. """); Assert.Equal(1, result.ExitCode); } [Fact] - public async Task Subscription_AlreadyPublished_ThrowsExitException() + public async Task Subscription_AlreadyPublished_ReturnsError() { // arrange SetupArchiveFile(); @@ -288,13 +267,13 @@ Validating Fusion configuration """); result.StdErr.MatchInlineSnapshot( """ - You request is already published + Your request is already published. """); Assert.Equal(1, result.ExitCode); } [Fact] - public async Task Subscription_Ready_ThrowsExitException() + public async Task Subscription_Ready_ReturnsError() { // arrange SetupArchiveFile(); @@ -319,13 +298,13 @@ Validating Fusion configuration """); result.StdErr.MatchInlineSnapshot( """ - Your request is ready for the composition. Run `fusion-configuration publish start` + Your request is ready for the composition. Run `fusion-configuration publish start`. """); Assert.Equal(1, result.ExitCode); } [Fact] - public async Task Subscription_InProgressThenSuccess_ReturnsSuccess_NonInteractive() + public async Task Subscription_InProgressThenSuccess_ReturnsSuccess() { // arrange SetupArchiveFile(); @@ -349,12 +328,14 @@ public async Task Subscription_InProgressThenSuccess_ReturnsSuccess_NonInteracti result.AssertSuccess( """ Validating Fusion configuration - └── ✕ Failed to validate the Fusion configuration. + ├── Validating... + ├── Validating... + └── ✓ Validated configuration. """); } [Fact] - public async Task Subscription_UnknownEvent_ThrowsExitException() + public async Task Subscription_UnknownEvent_ReturnsError() { // arrange var unknownEvent = new Mock( @@ -382,37 +363,11 @@ Validating Fusion configuration ├── ! Unknown server response. Consider updating the CLI. └── ✕ Failed to validate the Fusion configuration. """); - Assert.Empty(result.StdErr); - Assert.Equal(1, result.ExitCode); - } - - [Fact] - public async Task Validate_Should_HandleApprovalEvents_When_WaitForApproval() - { - // arrange - SetupArchiveFile(); - SetupFusionConfigurationValidationMutation(); - SetupFusionConfigurationValidationSubscription( - CreateWaitForApprovalEvent(), - CreateProcessingTaskApprovedEvent(), - CreateValidationSuccessEvent()); - - // act - var result = await ExecuteCommandAsync( - "fusion", - "publish", - "validate", - "--archive", - ArchiveFile, - "--request-id", - RequestId); - - // assert - result.AssertSuccess( + result.StdErr.MatchInlineSnapshot( """ - Validating Fusion configuration - └── ✕ Failed to validate the Fusion configuration. + Fusion configuration validation failed. """); + Assert.Equal(1, result.ExitCode); } #region Theory Data diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionDownloadCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionDownloadCommandTests.cs index 0555a4b2643..5d8d4ca858b 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionDownloadCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionDownloadCommandTests.cs @@ -62,7 +62,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionPublishCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionPublishCommandTests.cs index 6ef6a64d090..4b7f5564981 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionPublishCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionPublishCommandTests.cs @@ -78,7 +78,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -267,12 +267,14 @@ public async Task WithArchive_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✓ Uploaded configuration. @@ -306,12 +308,14 @@ public async Task WithArchive_WithEnvVars_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✓ Uploaded configuration. @@ -351,10 +355,10 @@ public async Task WithArchive_RequestDeploymentSlotHasErrors_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot │ └── ✕ Failed to request a deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -386,10 +390,10 @@ public async Task WithArchive_RequestDeploymentSlotThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot │ └── ✕ Failed to request a deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -427,12 +431,13 @@ public async Task WithArchive_ClaimDeploymentSlotHasError_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✕ Failed to claim the deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -467,12 +472,13 @@ public async Task WithArchive_ClaimDeploymentSlotThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✕ Failed to claim the deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -511,14 +517,15 @@ public async Task WithArchive_ValidationHasErrors_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Validating configuration against 'dev' │ └── ✕ Failed to validate the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -554,14 +561,15 @@ public async Task WithArchive_ValidationThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Validating configuration against 'dev' │ └── ✕ Failed to validate the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -595,8 +603,9 @@ public async Task WithArchive_WaitForApproval_NoBreakingChanges_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -638,16 +647,18 @@ public async Task WithArchive_BreakingChanges_ReturnsError() // assert result.StdErr.MatchInlineSnapshot( """ - Failed to validate configuration. + Fusion configuration validation failed. """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✕ Failed to validate the new configuration. │ ├── GraphQL schema changes │ │ ├── ✕ Directive foo was modified @@ -662,7 +673,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ │ ├── ✓ Type system member NewType was added. │ │ └── ✕ Type system member OldType was removed. │ ├── Invalid GraphQL schema - │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) │ ├── Client 'TestClient' (ID: client-1) │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ │ └── foo (10:10) @@ -673,7 +684,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ │ └── Tool 'Fail' │ │ └── The field `person` does not exist on the type `Query`. (1:14) │ └── An unexpected error occurred. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -710,13 +721,15 @@ public async Task WithArchive_BreakingChanges_Force_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── ! Force push is enabled. ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✕ Failed to validate the new configuration. │ ├── GraphQL schema changes │ │ ├── ✕ Directive foo was modified @@ -731,7 +744,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ │ ├── ✓ Type system member NewType was added. │ │ └── ✕ Type system member OldType was removed. │ ├── Invalid GraphQL schema - │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) │ ├── Client 'TestClient' (ID: client-1) │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ │ └── foo (10:10) @@ -781,15 +794,16 @@ public async Task WithArchive_WaitForApproval_BreakingChanges_Approved_ReturnsSu // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Uploading configuration to 'dev' │ ├── ! Validation failed. │ │ ├── Invalid GraphQL schema - │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) │ │ ├── Client 'TestClient' (ID: client-1) │ │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ │ │ └── foo (10:10) @@ -843,15 +857,16 @@ public async Task WithArchive_WaitForApproval_BreakingChanges_NotApproved_Return """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Uploading configuration to 'dev' │ ├── ! Validation failed. │ │ ├── Invalid GraphQL schema - │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) │ │ ├── Client 'TestClient' (ID: client-1) │ │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ │ │ └── foo (10:10) @@ -863,7 +878,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ │ └── The field `person` does not exist on the type `Query`. (1:14) │ ├── ⏳ Waiting for approval. Approve in Nitro to continue. │ └── ✕ Failed to upload the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -904,16 +919,18 @@ public async Task WithArchive_UploadHasErrors_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✕ Failed to upload the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -951,16 +968,18 @@ public async Task WithArchive_UploadThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✕ Failed to upload the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -999,19 +1018,21 @@ public async Task WithArchive_PublishingFailedWithErrors_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✕ Failed to upload the new configuration. │ ├── Invalid GraphQL schema - │ │ └── Field 'Query.foo' has no type. SCHEMA_ERROR + │ │ └── Field 'Query.foo' has no type. (SCHEMA_ERROR) │ └── An error occurred. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -1050,12 +1071,13 @@ Something unexpected happened. """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✕ Failed to claim the deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -1097,12 +1119,13 @@ public async Task WithArchive_ReleaseDeploymentSlotHasErrors_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✕ Failed to claim the deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -1172,8 +1195,9 @@ public async Task WithSourceSchemaFile_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -1182,6 +1206,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✓ Uploaded configuration. @@ -1219,8 +1244,9 @@ public async Task WithSourceSchemaFile_WithEnvVars_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -1229,6 +1255,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✓ Uploaded configuration. @@ -1268,10 +1295,10 @@ public async Task WithSourceSchemaFile_RequestDeploymentSlotHasErrors_ReturnsErr """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot │ └── ✕ Failed to request a deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -1303,10 +1330,10 @@ public async Task WithSourceSchemaFile_RequestDeploymentSlotThrows_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot │ └── ✕ Failed to request a deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -1344,12 +1371,13 @@ public async Task WithSourceSchemaFile_ClaimDeploymentSlotHasError_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✕ Failed to claim the deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -1384,12 +1412,13 @@ public async Task WithSourceSchemaFile_ClaimDeploymentSlotThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✕ Failed to claim the deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -1425,14 +1454,15 @@ public async Task WithSourceSchemaFile_ConfigurationDownloadThrows_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Downloading existing configuration from 'dev' │ └── ✕ Failed to download the existing Fusion configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -1468,8 +1498,9 @@ Source schema validation failed. """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -1477,7 +1508,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ └── ✓ Downloaded existing configuration from 'dev'. ├── Composing new configuration │ └── ✕ Failed to compose new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. ## Composition log @@ -1521,8 +1552,9 @@ public async Task WithSourceSchemaFile_ValidationHasErrors_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -1532,7 +1564,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' │ └── ✕ Failed to validate the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -1569,8 +1601,9 @@ public async Task WithSourceSchemaFile_ValidationThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -1580,7 +1613,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' │ └── ✕ Failed to validate the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -1617,12 +1650,13 @@ public async Task WithSourceSchemaFile_BreakingChanges_ReturnsError() // assert result.StdErr.MatchInlineSnapshot( """ - Failed to validate configuration. + Fusion configuration validation failed. """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -1631,6 +1665,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✕ Failed to validate the new configuration. │ ├── GraphQL schema changes │ │ ├── ✕ Directive foo was modified @@ -1645,7 +1680,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ │ ├── ✓ Type system member NewType was added. │ │ └── ✕ Type system member OldType was removed. │ ├── Invalid GraphQL schema - │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) │ ├── Client 'TestClient' (ID: client-1) │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ │ └── foo (10:10) @@ -1656,7 +1691,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ │ └── Tool 'Fail' │ │ └── The field `person` does not exist on the type `Query`. (1:14) │ └── An unexpected error occurred. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -1694,9 +1729,10 @@ public async Task WithSourceSchemaFile_BreakingChanges_Force_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── ! Force push is enabled. ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -1705,6 +1741,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✕ Failed to validate the new configuration. │ ├── GraphQL schema changes │ │ ├── ✕ Directive foo was modified @@ -1719,7 +1756,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ │ ├── ✓ Type system member NewType was added. │ │ └── ✕ Type system member OldType was removed. │ ├── Invalid GraphQL schema - │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) │ ├── Client 'TestClient' (ID: client-1) │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ │ └── foo (10:10) @@ -1768,8 +1805,9 @@ public async Task WithSourceSchemaFile_WaitForApproval_NoBreakingChanges_Returns // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -1817,8 +1855,9 @@ public async Task WithSourceSchemaFile_WaitForApproval_BreakingChanges_Approved_ // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -1829,7 +1868,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Uploading configuration to 'dev' │ ├── ! Validation failed. │ │ ├── Invalid GraphQL schema - │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) │ │ ├── Client 'TestClient' (ID: client-1) │ │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ │ │ └── foo (10:10) @@ -1884,8 +1923,9 @@ public async Task WithSourceSchemaFile_WaitForApproval_BreakingChanges_NotApprov """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -1896,7 +1936,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Uploading configuration to 'dev' │ ├── ! Validation failed. │ │ ├── Invalid GraphQL schema - │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) │ │ ├── Client 'TestClient' (ID: client-1) │ │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ │ │ └── foo (10:10) @@ -1908,7 +1948,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ │ └── The field `person` does not exist on the type `Query`. (1:14) │ ├── ⏳ Waiting for approval. Approve in Nitro to continue. │ └── ✕ Failed to upload the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -1950,8 +1990,9 @@ public async Task WithSourceSchemaFile_UploadHasErrors_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -1960,10 +2001,11 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✕ Failed to upload the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2002,8 +2044,9 @@ public async Task WithSourceSchemaFile_UploadThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -2012,10 +2055,11 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✕ Failed to upload the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2055,8 +2099,9 @@ public async Task WithSourceSchemaFile_PublishingFailedWithErrors_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -2065,13 +2110,14 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✕ Failed to upload the new configuration. │ ├── Invalid GraphQL schema - │ │ └── Field 'Query.foo' has no type. SCHEMA_ERROR + │ │ └── Field 'Query.foo' has no type. (SCHEMA_ERROR) │ └── An error occurred. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2110,12 +2156,13 @@ Something unexpected happened. """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✕ Failed to claim the deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2157,12 +2204,13 @@ public async Task WithSourceSchemaFile_ReleaseDeploymentSlotHasErrors_ReturnsErr """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✕ Failed to claim the deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2197,10 +2245,10 @@ Could not find source schema 'products' with version 'v1'. """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✕ Could not find source schema 'products' with version 'v1'. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2231,10 +2279,10 @@ public async Task WithSourceSchema_SourceSchemaDownloadThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✕ Failed to download source schemas. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2269,10 +2317,11 @@ public async Task WithSourceSchema_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -2281,6 +2330,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✓ Uploaded configuration. @@ -2318,10 +2368,11 @@ public async Task WithSourceSchema_WithEnvVars_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -2330,6 +2381,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✓ Uploaded configuration. @@ -2371,10 +2423,11 @@ public async Task WithSourceSchema_WithExplicitSourceSchemaVersion_ReturnsSucces // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -2383,6 +2436,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✓ Uploaded configuration. @@ -2422,12 +2476,12 @@ public async Task WithSourceSchema_RequestDeploymentSlotHasErrors_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot │ └── ✕ Failed to request a deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2459,12 +2513,12 @@ public async Task WithSourceSchema_RequestDeploymentSlotThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot │ └── ✕ Failed to request a deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2502,14 +2556,15 @@ public async Task WithSourceSchema_ClaimDeploymentSlotHasError_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✕ Failed to claim the deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2544,14 +2599,15 @@ public async Task WithSourceSchema_ClaimDeploymentSlotThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✕ Failed to claim the deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2587,10 +2643,11 @@ Source schema validation failed. """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -2598,7 +2655,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ └── ✓ Downloaded existing configuration from 'dev'. ├── Composing new configuration │ └── ✕ Failed to compose new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. ## Composition log @@ -2638,16 +2695,17 @@ public async Task WithSourceSchema_ConfigurationDownloadThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. ├── Downloading existing configuration from 'dev' │ └── ✕ Failed to download the existing Fusion configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2687,10 +2745,11 @@ public async Task WithSourceSchema_ValidationHasErrors_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -2700,7 +2759,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' │ └── ✕ Failed to validate the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2738,10 +2797,11 @@ public async Task WithSourceSchema_ValidationThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -2751,7 +2811,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' │ └── ✕ Failed to validate the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2787,14 +2847,15 @@ public async Task WithSourceSchema_BreakingChanges_ReturnsError() // assert result.StdErr.MatchInlineSnapshot( """ - Failed to validate configuration. + Fusion configuration validation failed. """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -2803,6 +2864,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✕ Failed to validate the new configuration. │ ├── GraphQL schema changes │ │ ├── ✕ Directive foo was modified @@ -2817,7 +2879,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ │ ├── ✓ Type system member NewType was added. │ │ └── ✕ Type system member OldType was removed. │ ├── Invalid GraphQL schema - │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) │ ├── Client 'TestClient' (ID: client-1) │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ │ └── foo (10:10) @@ -2828,7 +2890,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ │ └── Tool 'Fail' │ │ └── The field `person` does not exist on the type `Query`. (1:14) │ └── An unexpected error occurred. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -2866,11 +2928,12 @@ public async Task WithSourceSchema_BreakingChanges_Force_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── ! Force push is enabled. ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -2879,6 +2942,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✕ Failed to validate the new configuration. │ ├── GraphQL schema changes │ │ ├── ✕ Directive foo was modified @@ -2893,7 +2957,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ │ ├── ✓ Type system member NewType was added. │ │ └── ✕ Type system member OldType was removed. │ ├── Invalid GraphQL schema - │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) │ ├── Client 'TestClient' (ID: client-1) │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ │ └── foo (10:10) @@ -2942,10 +3006,11 @@ public async Task WithSourceSchema_WaitForApproval_NoBreakingChanges_ReturnsSucc // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -2993,10 +3058,11 @@ public async Task WithSourceSchema_WaitForApproval_BreakingChanges_Approved_Retu // assert result.AssertSuccess( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -3007,7 +3073,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Uploading configuration to 'dev' │ ├── ! Validation failed. │ │ ├── Invalid GraphQL schema - │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) │ │ ├── Client 'TestClient' (ID: client-1) │ │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ │ │ └── foo (10:10) @@ -3062,10 +3128,11 @@ public async Task WithSourceSchema_WaitForApproval_BreakingChanges_NotApproved_R """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -3076,7 +3143,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Uploading configuration to 'dev' │ ├── ! Validation failed. │ │ ├── Invalid GraphQL schema - │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) │ │ ├── Client 'TestClient' (ID: client-1) │ │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ │ │ └── foo (10:10) @@ -3088,7 +3155,7 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' │ │ └── The field `person` does not exist on the type `Query`. (1:14) │ ├── ⏳ Waiting for approval. Approve in Nitro to continue. │ └── ✕ Failed to upload the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -3130,10 +3197,11 @@ public async Task WithSourceSchema_UploadHasErrors_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -3142,10 +3210,11 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✕ Failed to upload the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -3184,10 +3253,11 @@ public async Task WithSourceSchema_UploadThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -3196,10 +3266,11 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✕ Failed to upload the new configuration. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -3239,10 +3310,11 @@ public async Task WithSourceSchema_PublishingFailedWithErrors_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✓ Claimed deployment slot. @@ -3251,13 +3323,14 @@ Publishing Fusion configuration to stage 'dev' of API 'api-1' ├── Composing new configuration │ └── ✓ Composed new configuration. ├── Validating configuration against 'dev' + │ ├── Validating... │ └── ✓ Validated configuration. ├── Uploading configuration to 'dev' │ └── ✕ Failed to upload the new configuration. │ ├── Invalid GraphQL schema - │ │ └── Field 'Query.foo' has no type. SCHEMA_ERROR + │ │ └── Field 'Query.foo' has no type. (SCHEMA_ERROR) │ └── An error occurred. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -3296,14 +3369,15 @@ Something unexpected happened. """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✕ Failed to claim the deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } @@ -3345,14 +3419,15 @@ public async Task WithSourceSchema_ReleaseDeploymentSlotHasErrors_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Publishing Fusion configuration to stage 'dev' of API 'api-1' + Publishing new Fusion configuration version 'v1' of API 'api-1' to stage 'dev' ├── Downloading 1 source schema(s) │ └── ✓ Downloaded 1 source schema(s). ├── Requesting deployment slot + │ ├── Publication request created. (ID: request-id) │ └── ✓ Deployment slot ready. ├── Claiming deployment slot │ └── ✕ Failed to claim the deployment slot. - └── ✕ Failed to publish Fusion configuration. + └── ✕ Failed to publish a new Fusion configuration version. """); Assert.Equal(1, result.ExitCode); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionUploadCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionUploadCommandTests.cs index 4eba861a454..66717e67d8e 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionUploadCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionUploadCommandTests.cs @@ -64,7 +64,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -120,7 +120,7 @@ public async Task UploadSourceSchemaMutationHasErrors_ReturnsError( result.StdErr.MatchInlineSnapshot(expectedErrorMessage); result.StdOut.MatchInlineSnapshot( """ - Uploading new source schema version 'v1' to API 'api-1' + Uploading new version 'v1' for source schema 'products' of API 'api-1' └── ✕ Failed to upload a new source schema version. """); Assert.Equal(1, result.ExitCode); @@ -148,7 +148,7 @@ public async Task UploadSourceSchema_ReturnsSuccess() await AssertFusionSourceSchemaArchive(capturedStream); result.AssertSuccess( """ - Uploading new source schema version 'v1' to API 'api-1' + Uploading new version 'v1' for source schema 'products' of API 'api-1' └── ✓ Uploaded new source schema version 'v1'. """); } @@ -174,7 +174,7 @@ public async Task UploadSourceSchemaThrows_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Uploading new source schema version 'v1' to API 'api-1' + Uploading new version 'v1' for source schema 'products' of API 'api-1' └── ✕ Failed to upload a new source schema version. """); result.StdErr.MatchInlineSnapshot( diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionValidateCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionValidateCommandTests.cs index 617ad136169..3e18b0d5487 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionValidateCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Fusion/FusionValidateCommandTests.cs @@ -63,7 +63,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -204,9 +204,9 @@ public async Task WithArchive_ReturnsSuccess() // assert result.AssertSuccess( """ - Validating Fusion configuration against stage 'dev' of API 'api-1' - ├── Validation request created (ID: request-id). - └── ✓ Validation passed. + Validating Fusion configuration of API 'api-1' against stage 'dev' + ├── Validation request created. (ID: request-id) + └── ✓ Schema validation successful. """); AssertSchemaUpload(capturedStream); } @@ -231,9 +231,9 @@ public async Task WithArchive_WithEnvVars_ReturnsSuccess() // assert result.AssertSuccess( """ - Validating Fusion configuration against stage 'dev' of API 'api-1' - ├── Validation request created (ID: request-id). - └── ✓ Validation passed. + Validating Fusion configuration of API 'api-1' against stage 'dev' + ├── Validation request created. (ID: request-id) + └── ✓ Schema validation successful. """); AssertSchemaUpload(capturedStream); } @@ -263,7 +263,7 @@ public async Task WithArchive_ValidateSchemaVersionHasErrors_ReturnsError( result.StdErr.MatchInlineSnapshot(expectedErrorMessage); result.StdOut.MatchInlineSnapshot( """ - Validating Fusion configuration against stage 'dev' of API 'api-1' + Validating Fusion configuration of API 'api-1' against stage 'dev' └── ✕ Failed to validate the Fusion configuration. """); Assert.Equal(1, result.ExitCode); @@ -294,7 +294,7 @@ public async Task WithArchive_ValidateSchemaVersionThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Validating Fusion configuration against stage 'dev' of API 'api-1' + Validating Fusion configuration of API 'api-1' against stage 'dev' └── ✕ Failed to validate the Fusion configuration. """); Assert.Equal(1, result.ExitCode); @@ -326,12 +326,12 @@ public async Task WithArchive_BreakingChanges_ReturnsError() // assert result.StdErr.MatchInlineSnapshot( """ - Validation failed. + Schema validation failed. """); result.StdOut.MatchInlineSnapshot( """ - Validating Fusion configuration against stage 'dev' of API 'api-1' - ├── Validation request created (ID: request-id). + Validating Fusion configuration of API 'api-1' against stage 'dev' + ├── Validation request created. (ID: request-id) ├── Validating... ├── Validating... └── ✕ Failed to validate the Fusion configuration. @@ -348,7 +348,7 @@ Validating Fusion configuration against stage 'dev' of API 'api-1' │ ├── ✓ Type system member NewType was added. │ └── ✕ Type system member OldType was removed. ├── Invalid GraphQL schema - │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) ├── Client 'TestClient' (ID: client-1) │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ └── foo (10:10) @@ -418,13 +418,13 @@ public async Task WithSourceSchemaFile_ReturnsSuccess() // assert result.AssertSuccess( """ - Validating Fusion configuration against stage 'dev' of API 'api-1' + Validating Fusion configuration of API 'api-1' against stage 'dev' ├── Downloading existing Fusion configuration │ └── ✓ Downloaded existing configuration from 'dev'. ├── Composing new Fusion configuration │ └── ✓ Composed new configuration. - ├── Validation request created (ID: request-id). - └── ✓ Validation passed. + ├── Validation request created. (ID: request-id) + └── ✓ Schema validation successful. """); AssertSchemaUploadAfterCompose(capturedStream); } @@ -451,13 +451,13 @@ public async Task WithSourceSchemaFile_WithEnvVars_ReturnsSuccess() // assert result.AssertSuccess( """ - Validating Fusion configuration against stage 'dev' of API 'api-1' + Validating Fusion configuration of API 'api-1' against stage 'dev' ├── Downloading existing Fusion configuration │ └── ✓ Downloaded existing configuration from 'dev'. ├── Composing new Fusion configuration │ └── ✓ Composed new configuration. - ├── Validation request created (ID: request-id). - └── ✓ Validation passed. + ├── Validation request created. (ID: request-id) + └── ✓ Schema validation successful. """); AssertSchemaUploadAfterCompose(capturedStream); } @@ -488,7 +488,7 @@ public async Task WithSourceSchemaFile_ValidateSchemaVersionHasErrors_ReturnsErr result.StdErr.MatchInlineSnapshot(expectedErrorMessage); result.StdOut.MatchInlineSnapshot( """ - Validating Fusion configuration against stage 'dev' of API 'api-1' + Validating Fusion configuration of API 'api-1' against stage 'dev' ├── Downloading existing Fusion configuration │ └── ✓ Downloaded existing configuration from 'dev'. ├── Composing new Fusion configuration @@ -524,7 +524,7 @@ public async Task WithSourceSchemaFile_ValidateSchemaVersionThrows_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Validating Fusion configuration against stage 'dev' of API 'api-1' + Validating Fusion configuration of API 'api-1' against stage 'dev' ├── Downloading existing Fusion configuration │ └── ✓ Downloaded existing configuration from 'dev'. ├── Composing new Fusion configuration @@ -560,16 +560,16 @@ public async Task WithSourceSchemaFile_BreakingChanges_ReturnsError() // assert result.StdErr.MatchInlineSnapshot( """ - Validation failed. + Schema validation failed. """); result.StdOut.MatchInlineSnapshot( """ - Validating Fusion configuration against stage 'dev' of API 'api-1' + Validating Fusion configuration of API 'api-1' against stage 'dev' ├── Downloading existing Fusion configuration │ └── ✓ Downloaded existing configuration from 'dev'. ├── Composing new Fusion configuration │ └── ✓ Composed new configuration. - ├── Validation request created (ID: request-id). + ├── Validation request created. (ID: request-id) ├── Validating... ├── Validating... └── ✕ Failed to validate the Fusion configuration. @@ -586,7 +586,7 @@ Validating Fusion configuration against stage 'dev' of API 'api-1' │ ├── ✓ Type system member NewType was added. │ └── ✕ Type system member OldType was removed. ├── Invalid GraphQL schema - │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) ├── Client 'TestClient' (ID: client-1) │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ └── foo (10:10) @@ -626,7 +626,7 @@ public async Task WithSourceSchemaFile_ConfigurationDownloadThrows_ReturnsError( """); result.StdOut.MatchInlineSnapshot( """ - Validating Fusion configuration against stage 'dev' of API 'api-1' + Validating Fusion configuration of API 'api-1' against stage 'dev' ├── Downloading existing Fusion configuration │ └── ✕ Failed to download existing Fusion configuration. └── ✕ Failed to validate the Fusion configuration. @@ -659,7 +659,7 @@ Source schema validation failed. """); result.StdOut.MatchInlineSnapshot( """ - Validating Fusion configuration against stage 'dev' of API 'api-1' + Validating Fusion configuration of API 'api-1' against stage 'dev' ├── Downloading existing Fusion configuration │ └── ✓ Downloaded existing configuration from 'dev'. ├── Composing new Fusion configuration diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/CreateMcpFeatureCollectionCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/CreateMcpFeatureCollectionCommandTests.cs index 77ae7a09891..431f7c95d11 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/CreateMcpFeatureCollectionCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/CreateMcpFeatureCollectionCommandTests.cs @@ -59,7 +59,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -104,7 +104,7 @@ public async Task NoWorkspaceInSession_And_NoApiId_ReturnsError(InteractionMode // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Missing required option '--api-id'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/DeleteMcpFeatureCollectionCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/DeleteMcpFeatureCollectionCommandTests.cs index 764be94f46c..9a777a578f9 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/DeleteMcpFeatureCollectionCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/DeleteMcpFeatureCollectionCommandTests.cs @@ -57,7 +57,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -78,7 +78,7 @@ public async Task MissingRequiredId_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - The 'id' option is required in non-interactive mode. + Missing required argument 'id'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/ListMcpFeatureCollectionCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/ListMcpFeatureCollectionCommandTests.cs index e083993e51e..0e7b0432083 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/ListMcpFeatureCollectionCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/ListMcpFeatureCollectionCommandTests.cs @@ -50,7 +50,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -68,7 +68,7 @@ public async Task NoWorkspaceInSession_And_NoApiId_ReturnsError_Interactive() // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Could not determine workspace. Either login via `nitro login` or specify the '--workspace-id' option. """); } @@ -87,7 +87,7 @@ public async Task MissingApiId_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - The '--api-id' option is required in non-interactive mode. + Missing required option '--api-id'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/PublishMcpFeatureCollectionCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/PublishMcpFeatureCollectionCommandTests.cs index 3c8b09eaa96..22aaec91411 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/PublishMcpFeatureCollectionCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/PublishMcpFeatureCollectionCommandTests.cs @@ -94,7 +94,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -122,9 +122,7 @@ public async Task StartMcpFeatureCollectionPublishThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing new MCP feature collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✕ Failed to start publish request. + Publishing new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev' └── ✕ Failed to publish a new MCP feature collection version. """); Assert.Equal(1, result.ExitCode); @@ -154,9 +152,7 @@ public async Task StartMcpFeatureCollectionPublishHasErrors_ReturnsError( result.StdErr.MatchInlineSnapshot(expectedErrorMessage); result.StdOut.MatchInlineSnapshot( """ - Publishing new MCP feature collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✕ Failed to start publish request. + Publishing new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev' └── ✕ Failed to publish a new MCP feature collection version. """); Assert.Equal(1, result.ExitCode); @@ -186,9 +182,7 @@ public async Task StartMcpFeatureCollectionPublishReturnsNullRequestId_ReturnsEr """); result.StdOut.MatchInlineSnapshot( """ - Publishing new MCP feature collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✕ Failed to start publish request. + Publishing new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev' └── ✕ Failed to publish a new MCP feature collection version. """); Assert.Equal(1, result.ExitCode); @@ -216,12 +210,9 @@ public async Task ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new MCP feature collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✓ Published successfully. - └── ✓ Published new MCP feature collection version 'v1' to stage 'dev'. + Publishing new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev' + ├── Publication request created. (ID: request-1) + └── ✓ Published new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev'. """); } @@ -248,12 +239,9 @@ public async Task WaitForApproval_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new MCP feature collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✓ Published successfully. - └── ✓ Published new MCP feature collection version 'v1' to stage 'dev'. + Publishing new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev' + ├── Publication request created. (ID: request-1) + └── ✓ Published new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev'. """); } @@ -279,13 +267,10 @@ public async Task BreakingChanges_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Publishing new MCP feature collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✕ Processing failed. - │ └── Something went wrong during publish. + Publishing new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev' + ├── Publication request created. (ID: request-1) └── ✕ Failed to publish a new MCP feature collection version. + └── Something went wrong during publish. """); result.StdErr.MatchInlineSnapshot( """ @@ -317,13 +302,10 @@ public async Task BreakingChanges_Force_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new MCP feature collection version 'v1' to stage 'dev' + Publishing new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev' ├── ! Force push is enabled. - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✓ Published successfully. - └── ✓ Published new MCP feature collection version 'v1' to stage 'dev'. + ├── Publication request created. (ID: request-1) + └── ✓ Published new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev'. """); } @@ -352,18 +334,15 @@ public async Task WaitForApproval_BreakingChanges_Approved_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new MCP feature collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ ├── ! Validation failed. - │ │ └── MCP Feature Collection 'mcp-collection' (ID: mcp-1) - │ │ └── Tool 'Fail' - │ │ └── The field `person` does not exist on the type `Query`. (1:14) - │ ├── ⏳ Waiting for approval. Approve in Nitro to continue. - │ ├── Your request has been approved. - │ └── ✓ Published successfully. - └── ✓ Published new MCP feature collection version 'v1' to stage 'dev'. + Publishing new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev' + ├── Publication request created. (ID: request-1) + ├── ! Validation failed. + │ └── MCP Feature Collection 'mcp-collection' (ID: mcp-1) + │ └── Tool 'Fail' + │ └── The field `person` does not exist on the type `Query`. (1:14) + ├── ⏳ Waiting for approval. Approve in Nitro to continue. + ├── Your request has been approved. + └── ✓ Published new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev'. """); } @@ -395,16 +374,13 @@ MCP feature collection publish failed. """); result.StdOut.MatchInlineSnapshot( """ - Publishing new MCP feature collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ ├── ! Validation failed. - │ │ └── MCP Feature Collection 'mcp-collection' (ID: mcp-1) - │ │ └── Tool 'Fail' - │ │ └── The field `person` does not exist on the type `Query`. (1:14) - │ ├── ⏳ Waiting for approval. Approve in Nitro to continue. - │ └── ✕ Processing failed. + Publishing new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev' + ├── Publication request created. (ID: request-1) + ├── ! Validation failed. + │ └── MCP Feature Collection 'mcp-collection' (ID: mcp-1) + │ └── Tool 'Fail' + │ └── The field `person` does not exist on the type `Query`. (1:14) + ├── ⏳ Waiting for approval. Approve in Nitro to continue. └── ✕ Failed to publish a new MCP feature collection version. """); Assert.Equal(1, result.ExitCode); @@ -430,12 +406,9 @@ public async Task WithEnvVars_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new MCP feature collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✓ Published successfully. - └── ✓ Published new MCP feature collection version 'v1' to stage 'dev'. + Publishing new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev' + ├── Publication request created. (ID: request-1) + └── ✓ Published new version 'v1' of MCP feature collection 'mcp-1' to stage 'dev'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/UploadMcpFeatureCollectionCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/UploadMcpFeatureCollectionCommandTests.cs index b2eb585a3cd..db3cd422fdc 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/UploadMcpFeatureCollectionCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/UploadMcpFeatureCollectionCommandTests.cs @@ -67,7 +67,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -141,7 +141,7 @@ public async Task UploadMcpFeatureCollectionThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Uploading new MCP feature collection version 'v1' for collection 'mcp-1' + Uploading new version 'v1' for MCP feature collection 'mcp-1' ├── Found 1 prompt(s) and 1 tool(s). └── ✕ Failed to upload a new MCP feature collection version. """); @@ -175,7 +175,7 @@ public async Task UploadMcpFeatureCollectionHasErrors_ReturnsError( result.StdErr.MatchInlineSnapshot(expectedErrorMessage); result.StdOut.MatchInlineSnapshot( """ - Uploading new MCP feature collection version 'v1' for collection 'mcp-1' + Uploading new version 'v1' for MCP feature collection 'mcp-1' ├── Found 1 prompt(s) and 1 tool(s). └── ✕ Failed to upload a new MCP feature collection version. """); @@ -209,7 +209,7 @@ Could not upload MCP Feature Collection version. """); result.StdOut.MatchInlineSnapshot( """ - Uploading new MCP feature collection version 'v1' for collection 'mcp-1' + Uploading new version 'v1' for MCP feature collection 'mcp-1' ├── Found 1 prompt(s) and 1 tool(s). └── ✕ Failed to upload a new MCP feature collection version. """); @@ -240,7 +240,7 @@ public async Task UploadsMcpFeatureCollection_ReturnsSuccess() await AssertMcpFeatureCollectionArchive(capturedStream); result.AssertSuccess( """ - Uploading new MCP feature collection version 'v1' for collection 'mcp-1' + Uploading new version 'v1' for MCP feature collection 'mcp-1' ├── Found 1 prompt(s) and 1 tool(s). └── ✓ Uploaded new MCP feature collection version 'v1'. """); diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/ValidateMcpFeatureCollectionCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/ValidateMcpFeatureCollectionCommandTests.cs index 1b69ca68534..58e99f346f1 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/ValidateMcpFeatureCollectionCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mcp/ValidateMcpFeatureCollectionCommandTests.cs @@ -68,7 +68,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -99,10 +99,8 @@ public async Task StartMcpFeatureCollectionValidationThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Validating MCP feature collection against stage 'dev' + Validating MCP feature collection 'mcp-1' against stage 'dev' ├── Found 1 prompt(s) and 1 tool(s). - ├── Starting validation request - │ └── ✕ Failed to start the validation request. └── ✕ Failed to validate the MCP feature collection. """); Assert.Equal(1, result.ExitCode); @@ -135,10 +133,8 @@ public async Task StartMcpFeatureCollectionValidationHasErrors_ReturnsError( result.StdErr.MatchInlineSnapshot(expectedErrorMessage); result.StdOut.MatchInlineSnapshot( """ - Validating MCP feature collection against stage 'dev' + Validating MCP feature collection 'mcp-1' against stage 'dev' ├── Found 1 prompt(s) and 1 tool(s). - ├── Starting validation request - │ └── ✕ Failed to start the validation request. └── ✕ Failed to validate the MCP feature collection. """); Assert.Equal(1, result.ExitCode); @@ -171,10 +167,8 @@ Could not create validation request! """); result.StdOut.MatchInlineSnapshot( """ - Validating MCP feature collection against stage 'dev' + Validating MCP feature collection 'mcp-1' against stage 'dev' ├── Found 1 prompt(s) and 1 tool(s). - ├── Starting validation request - │ └── ✕ Failed to start the validation request. └── ✕ Failed to validate the MCP feature collection. """); Assert.Equal(1, result.ExitCode); @@ -205,12 +199,9 @@ public async Task ReturnsSuccess() await AssertMcpFeatureCollectionArchive(capturedStream); result.AssertSuccess( """ - Validating MCP feature collection against stage 'dev' + Validating MCP feature collection 'mcp-1' against stage 'dev' ├── Found 1 prompt(s) and 1 tool(s). - ├── Starting validation request - │ └── ✓ Validation request created (ID: request-1). - ├── Validating - │ └── ✓ Validation passed. + ├── Validation request created. (ID: request-1) └── ✓ Validated MCP feature collection against stage 'dev'. """); } @@ -239,12 +230,9 @@ public async Task WithEnvVars_ReturnsSuccess() await AssertMcpFeatureCollectionArchive(capturedStream); result.AssertSuccess( """ - Validating MCP feature collection against stage 'dev' + Validating MCP feature collection 'mcp-1' against stage 'dev' ├── Found 1 prompt(s) and 1 tool(s). - ├── Starting validation request - │ └── ✓ Validation request created (ID: request-1). - ├── Validating - │ └── ✓ Validation passed. + ├── Validation request created. (ID: request-1) └── ✓ Validated MCP feature collection against stage 'dev'. """); } @@ -274,16 +262,13 @@ public async Task BreakingChanges_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Validating MCP feature collection against stage 'dev' + Validating MCP feature collection 'mcp-1' against stage 'dev' ├── Found 1 prompt(s) and 1 tool(s). - ├── Starting validation request - │ └── ✓ Validation request created (ID: request-1). - ├── Validating - │ └── ✕ Validation failed. - │ └── MCP Feature Collection 'mcp-collection' (ID: mcp-1) - │ └── Tool 'Fail' - │ └── Invalid tool definition. (1:14) + ├── Validation request created. (ID: request-1) └── ✕ Failed to validate the MCP feature collection. + └── MCP Feature Collection 'mcp-collection' (ID: mcp-1) + └── Tool 'Fail' + └── Invalid tool definition. (1:14) """); result.StdErr.MatchInlineSnapshot( """ diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mocks/CreateMockCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mocks/CreateMockCommandTests.cs index 53a12fbb0ab..50ee2c077ec 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mocks/CreateMockCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mocks/CreateMockCommandTests.cs @@ -71,7 +71,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mocks/ListMockCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mocks/ListMockCommandTests.cs index e082ab51191..b30fc427563 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mocks/ListMockCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mocks/ListMockCommandTests.cs @@ -51,7 +51,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -70,7 +70,7 @@ public async Task NoWorkspaceInSession_And_NoApiId_ReturnsError_Interactive() // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Could not determine workspace. Either login via `nitro login` or specify the '--workspace-id' option. """); } @@ -90,7 +90,7 @@ public async Task MissingApiId_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - The '--api-id' option is required in non-interactive mode. + Missing required option '--api-id'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mocks/UpdateMockCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mocks/UpdateMockCommandTests.cs index e60ed41975e..f9633c6b8ff 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mocks/UpdateMockCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Mocks/UpdateMockCommandTests.cs @@ -61,7 +61,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -117,7 +117,7 @@ public async Task MissingId_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - The 'id' option is required in non-interactive mode. + Missing required option 'id'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/CreateOpenApiCollectionCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/CreateOpenApiCollectionCommandTests.cs index ca19b7acc9c..638aefbe087 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/CreateOpenApiCollectionCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/CreateOpenApiCollectionCommandTests.cs @@ -59,7 +59,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -101,10 +101,10 @@ public async Task NoWorkspaceInSession_And_NoApiId_ReturnsError(InteractionMode "--name", OpenApiCollectionName); - // assertÎ + // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Missing required option '--api-id'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/DeleteOpenApiCollectionCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/DeleteOpenApiCollectionCommandTests.cs index ef9ab2fb92f..9521893abaa 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/DeleteOpenApiCollectionCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/DeleteOpenApiCollectionCommandTests.cs @@ -56,7 +56,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -76,7 +76,7 @@ public async Task MissingRequiredId_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - The 'id' option is required in non-interactive mode. + Missing required argument 'id'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/ListOpenApiCollectionCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/ListOpenApiCollectionCommandTests.cs index 28356211642..cd8a94119bd 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/ListOpenApiCollectionCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/ListOpenApiCollectionCommandTests.cs @@ -47,7 +47,7 @@ public async Task NoApiKey_NoApiId_ReturnsError_Interactive() // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Could not determine workspace. Either login via `nitro login` or specify the '--workspace-id' option. """); } @@ -66,7 +66,7 @@ public async Task MissingApiId_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - The '--api-id' option is required in non-interactive mode. + Missing required option '--api-id'. """); } @@ -84,7 +84,7 @@ public async Task NoWorkspaceInSession_And_NoApiId_ReturnsError_Interactive() // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Could not determine workspace. Either login via `nitro login` or specify the '--workspace-id' option. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/PublishOpenApiCollectionCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/PublishOpenApiCollectionCommandTests.cs index 822809e8d62..f2a2e9383ed 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/PublishOpenApiCollectionCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/PublishOpenApiCollectionCommandTests.cs @@ -94,7 +94,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -122,9 +122,7 @@ public async Task PublishOpenApiCollectionThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing new OpenAPI collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✕ Failed to start publish request. + Publishing new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev' └── ✕ Failed to publish a new OpenAPI collection version. """); Assert.Equal(1, result.ExitCode); @@ -153,9 +151,7 @@ public async Task PublishOpenApiCollectionHasErrors_ReturnsError( // assert result.StdOut.MatchInlineSnapshot( """ - Publishing new OpenAPI collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✕ Failed to start publish request. + Publishing new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev' └── ✕ Failed to publish a new OpenAPI collection version. """); result.StdErr.MatchInlineSnapshot(expectedStdErr); @@ -182,9 +178,7 @@ public async Task PublishOpenApiCollectionReturnsNullRequestId_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Publishing new OpenAPI collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✕ Failed to start publish request. + Publishing new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev' └── ✕ Failed to publish a new OpenAPI collection version. """); result.StdErr.MatchInlineSnapshot( @@ -216,12 +210,9 @@ public async Task ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new OpenAPI collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✓ Published successfully. - └── ✓ Published new OpenAPI collection version 'v1' to stage 'dev'. + Publishing new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev' + ├── Publication request created. (ID: request-1) + └── ✓ Published new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev'. """); } @@ -248,12 +239,9 @@ public async Task WaitForApproval_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new OpenAPI collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✓ Published successfully. - └── ✓ Published new OpenAPI collection version 'v1' to stage 'dev'. + Publishing new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev' + ├── Publication request created. (ID: request-1) + └── ✓ Published new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev'. """); } @@ -279,13 +267,10 @@ public async Task BreakingChanges_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Publishing new OpenAPI collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✕ Processing failed. - │ └── Something went wrong during publish. + Publishing new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev' + ├── Publication request created. (ID: request-1) └── ✕ Failed to publish a new OpenAPI collection version. + └── Something went wrong during publish. """); result.StdErr.MatchInlineSnapshot( """ @@ -317,13 +302,10 @@ public async Task BreakingChanges_Force_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new OpenAPI collection version 'v1' to stage 'dev' + Publishing new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev' ├── ! Force push is enabled. - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✓ Published successfully. - └── ✓ Published new OpenAPI collection version 'v1' to stage 'dev'. + ├── Publication request created. (ID: request-1) + └── ✓ Published new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev'. """); } @@ -352,18 +334,15 @@ public async Task WaitForApproval_BreakingChanges_Approved_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new OpenAPI collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ ├── ! Validation failed. - │ │ └── OpenAPI collection 'petstore' (ID: collection-1) - │ │ └── Endpoint 'GET /fail' - │ │ └── The field `person` does not exist on the type `Query`. (1:14) - │ ├── ⏳ Waiting for approval. Approve in Nitro to continue. - │ ├── Your request has been approved. - │ └── ✓ Published successfully. - └── ✓ Published new OpenAPI collection version 'v1' to stage 'dev'. + Publishing new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev' + ├── Publication request created. (ID: request-1) + ├── ! Validation failed. + │ └── OpenAPI collection 'petstore' (ID: collection-1) + │ └── Endpoint 'GET /fail' + │ └── The field `person` does not exist on the type `Query`. (1:14) + ├── ⏳ Waiting for approval. Approve in Nitro to continue. + ├── Your request has been approved. + └── ✓ Published new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev'. """); } @@ -395,16 +374,13 @@ OpenAPI collection publish failed. """); result.StdOut.MatchInlineSnapshot( """ - Publishing new OpenAPI collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ ├── ! Validation failed. - │ │ └── OpenAPI collection 'petstore' (ID: collection-1) - │ │ └── Endpoint 'GET /fail' - │ │ └── The field `person` does not exist on the type `Query`. (1:14) - │ ├── ⏳ Waiting for approval. Approve in Nitro to continue. - │ └── ✕ Processing failed. + Publishing new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev' + ├── Publication request created. (ID: request-1) + ├── ! Validation failed. + │ └── OpenAPI collection 'petstore' (ID: collection-1) + │ └── Endpoint 'GET /fail' + │ └── The field `person` does not exist on the type `Query`. (1:14) + ├── ⏳ Waiting for approval. Approve in Nitro to continue. └── ✕ Failed to publish a new OpenAPI collection version. """); Assert.Equal(1, result.ExitCode); @@ -430,12 +406,9 @@ public async Task WithEnvVars_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new OpenAPI collection version 'v1' to stage 'dev' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-1). - ├── Processing - │ └── ✓ Published successfully. - └── ✓ Published new OpenAPI collection version 'v1' to stage 'dev'. + Publishing new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev' + ├── Publication request created. (ID: request-1) + └── ✓ Published new version 'v1' of OpenAPI collection 'oa-1' to stage 'dev'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/UploadOpenApiCollectionCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/UploadOpenApiCollectionCommandTests.cs index 66ea9da2d20..0c2d3b7ce64 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/UploadOpenApiCollectionCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/UploadOpenApiCollectionCommandTests.cs @@ -63,7 +63,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -138,7 +138,7 @@ public async Task UploadOpenApiCollectionThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Uploading new OpenAPI collection version 'v1' for collection 'oa-1' + Uploading new version 'v1' for OpenAPI collection 'oa-1' └── ✕ Failed to upload a new OpenAPI collection version. """); Assert.Equal(1, result.ExitCode); @@ -168,7 +168,7 @@ public async Task UploadOpenApiCollectionHasErrors_ReturnsError( // assert result.StdOut.MatchInlineSnapshot( """ - Uploading new OpenAPI collection version 'v1' for collection 'oa-1' + Uploading new version 'v1' for OpenAPI collection 'oa-1' └── ✕ Failed to upload a new OpenAPI collection version. """); result.StdErr.MatchInlineSnapshot(expectedStdErr); @@ -196,7 +196,7 @@ public async Task UploadOpenApiCollectionReturnsNullVersion_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Uploading new OpenAPI collection version 'v1' for collection 'oa-1' + Uploading new version 'v1' for OpenAPI collection 'oa-1' └── ✕ Failed to upload a new OpenAPI collection version. """); result.StdErr.MatchInlineSnapshot( @@ -228,7 +228,7 @@ public async Task UploadsCollection_ReturnsSuccess() await AssertOpenApiCollectionArchive(capturedStream); result.AssertSuccess( """ - Uploading new OpenAPI collection version 'v1' for collection 'oa-1' + Uploading new version 'v1' for OpenAPI collection 'oa-1' └── ✓ Uploaded new OpenAPI collection version 'v1'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/ValidateOpenApiCollectionCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/ValidateOpenApiCollectionCommandTests.cs index 79bffc80abe..95b1a7440b6 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/ValidateOpenApiCollectionCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/OpenApi/ValidateOpenApiCollectionCommandTests.cs @@ -63,7 +63,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -92,10 +92,8 @@ public async Task ValidateOpenApiCollectionThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Validating OpenAPI collection against stage 'dev' + Validating OpenAPI collection 'oa-1' against stage 'dev' ├── Found 1 document(s). - ├── Starting validation request - │ └── ✕ Failed to start the validation request. └── ✕ Failed to validate the OpenAPI collection. """); Assert.Equal(1, result.ExitCode); @@ -125,10 +123,8 @@ public async Task ValidateOpenApiCollectionHasErrors_ReturnsError( // assert result.StdOut.MatchInlineSnapshot( """ - Validating OpenAPI collection against stage 'dev' + Validating OpenAPI collection 'oa-1' against stage 'dev' ├── Found 1 document(s). - ├── Starting validation request - │ └── ✕ Failed to start the validation request. └── ✕ Failed to validate the OpenAPI collection. """); result.StdErr.MatchInlineSnapshot(expectedStdErr); @@ -156,10 +152,8 @@ public async Task ValidateOpenApiCollectionReturnsNullRequestId_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Validating OpenAPI collection against stage 'dev' + Validating OpenAPI collection 'oa-1' against stage 'dev' ├── Found 1 document(s). - ├── Starting validation request - │ └── ✕ Failed to start the validation request. └── ✕ Failed to validate the OpenAPI collection. """); result.StdErr.MatchInlineSnapshot( @@ -192,12 +186,9 @@ public async Task ReturnsSuccess() await AssertOpenApiCollectionArchive(capturedStream); result.AssertSuccess( """ - Validating OpenAPI collection against stage 'dev' + Validating OpenAPI collection 'oa-1' against stage 'dev' ├── Found 1 document(s). - ├── Starting validation request - │ └── ✓ Validation request created (ID: request-1). - ├── Validating - │ └── ✓ Validation passed. + ├── Validation request created. (ID: request-1) └── ✓ Validated OpenAPI collection against stage 'dev'. """); } @@ -224,12 +215,9 @@ public async Task WithEnvVars_ReturnsSuccess() await AssertOpenApiCollectionArchive(capturedStream); result.AssertSuccess( """ - Validating OpenAPI collection against stage 'dev' + Validating OpenAPI collection 'oa-1' against stage 'dev' ├── Found 1 document(s). - ├── Starting validation request - │ └── ✓ Validation request created (ID: request-1). - ├── Validating - │ └── ✓ Validation passed. + ├── Validation request created. (ID: request-1) └── ✓ Validated OpenAPI collection against stage 'dev'. """); } @@ -257,16 +245,13 @@ public async Task BreakingChanges_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Validating OpenAPI collection against stage 'dev' + Validating OpenAPI collection 'oa-1' against stage 'dev' ├── Found 1 document(s). - ├── Starting validation request - │ └── ✓ Validation request created (ID: request-1). - ├── Validating - │ └── ✕ Validation failed. - │ └── OpenAPI collection 'petstore' (ID: collection-1) - │ └── Endpoint 'GET /fail' - │ └── The field `person` does not exist on the type `Query`. (1:14) + ├── Validation request created. (ID: request-1) └── ✕ Failed to validate the OpenAPI collection. + └── OpenAPI collection 'petstore' (ID: collection-1) + └── Endpoint 'GET /fail' + └── The field `person` does not exist on the type `Query`. (1:14) """); result.StdErr.MatchInlineSnapshot( """ diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/PersonalAccessTokens/CreatePersonalAccessTokenCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/PersonalAccessTokens/CreatePersonalAccessTokenCommandTests.cs index 3e4fe9bde24..974f45a81c7 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/PersonalAccessTokens/CreatePersonalAccessTokenCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/PersonalAccessTokens/CreatePersonalAccessTokenCommandTests.cs @@ -58,7 +58,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/PersonalAccessTokens/RevokePersonalAccessTokenCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/PersonalAccessTokens/RevokePersonalAccessTokenCommandTests.cs index be3b059b6f9..4dfde314161 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/PersonalAccessTokens/RevokePersonalAccessTokenCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/PersonalAccessTokens/RevokePersonalAccessTokenCommandTests.cs @@ -57,7 +57,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/DownloadSchemaCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/DownloadSchemaCommandTests.cs index e502c866fe5..e030900757e 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/DownloadSchemaCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/DownloadSchemaCommandTests.cs @@ -65,7 +65,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/PublishSchemaCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/PublishSchemaCommandTests.cs index 2b859ce6003..571bb0c941f 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/PublishSchemaCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/PublishSchemaCommandTests.cs @@ -96,7 +96,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -124,9 +124,7 @@ public async Task PublishSchemaThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Publishing new schema version 'v1' to stage 'dev' of API 'api-1' - ├── Starting publish request - │ └── ✕ Failed to start publish request. + Publishing new schema version 'v1' of API 'api-1' to stage 'dev' └── ✕ Failed to publish a new schema version. """); Assert.Equal(1, result.ExitCode); @@ -156,9 +154,7 @@ public async Task PublishSchemaHasErrors_ReturnsError( result.StdErr.MatchInlineSnapshot(expectedErrorMessage); result.StdOut.MatchInlineSnapshot( """ - Publishing new schema version 'v1' to stage 'dev' of API 'api-1' - ├── Starting publish request - │ └── ✕ Failed to start publish request. + Publishing new schema version 'v1' of API 'api-1' to stage 'dev' └── ✕ Failed to publish a new schema version. """); Assert.Equal(1, result.ExitCode); @@ -195,9 +191,7 @@ public async Task MutationReturnsNullRequestId_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Publishing new schema version 'v1' to stage 'dev' of API 'api-1' - ├── Starting publish request - │ └── ✕ Failed to start publish request. + Publishing new schema version 'v1' of API 'api-1' to stage 'dev' └── ✕ Failed to publish a new schema version. """); result.StdErr.MatchInlineSnapshot( @@ -229,11 +223,8 @@ public async Task ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new schema version 'v1' to stage 'dev' of API 'api-1' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-id). - ├── Processing - │ └── ✓ Published successfully. + Publishing new schema version 'v1' of API 'api-1' to stage 'dev' + ├── Publication request created. (ID: request-id) └── ✓ Published new schema version 'v1' to stage 'dev'. """); } @@ -261,11 +252,8 @@ public async Task WaitForApproval_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new schema version 'v1' to stage 'dev' of API 'api-1' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-id). - ├── Processing - │ └── ✓ Published successfully. + Publishing new schema version 'v1' of API 'api-1' to stage 'dev' + ├── Publication request created. (ID: request-id) └── ✓ Published new schema version 'v1' to stage 'dev'. """); } @@ -292,13 +280,10 @@ public async Task BreakingChanges_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Publishing new schema version 'v1' to stage 'dev' of API 'api-1' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-id). - ├── Processing - │ └── ✕ Processing failed. - │ └── Something went wrong during publish. + Publishing new schema version 'v1' of API 'api-1' to stage 'dev' + ├── Publication request created. (ID: request-id) └── ✕ Failed to publish a new schema version. + └── Something went wrong during publish. """); result.StdErr.MatchInlineSnapshot( """ @@ -330,12 +315,9 @@ public async Task BreakingChanges_Force_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new schema version 'v1' to stage 'dev' of API 'api-1' + Publishing new schema version 'v1' of API 'api-1' to stage 'dev' ├── ! Force push is enabled. - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-id). - ├── Processing - │ └── ✓ Published successfully. + ├── Publication request created. (ID: request-id) └── ✓ Published new schema version 'v1' to stage 'dev'. """); } @@ -365,39 +347,36 @@ public async Task WaitForApproval_BreakingChanges_Approved_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new schema version 'v1' to stage 'dev' of API 'api-1' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-id). - ├── Processing - │ ├── ! Validation failed. - │ │ ├── Invalid GraphQL schema - │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL - │ │ ├── GraphQL schema changes - │ │ │ ├── ✕ Directive foo was modified - │ │ │ │ ├── ✓ Directive location FieldDefinition added - │ │ │ │ └── ✕ Directive location Field removed - │ │ │ ├── ✕ Object type Foo was modified - │ │ │ │ ├── ✓ Field Foo.bar of type String! was added - │ │ │ │ └── ✕ Field Foo.baz of type Int! was removed - │ │ │ ├── ! Enum Status was modified - │ │ │ │ ├── ! Enum value Status.ACTIVE was added - │ │ │ │ └── ✕ Enum value Status.DELETED was removed - │ │ │ ├── ✓ Type system member NewType was added. - │ │ │ └── ✕ Type system member OldType was removed. - │ │ ├── Client 'TestClient' (ID: client-1) - │ │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) - │ │ │ └── foo (10:10) - │ │ ├── OpenAPI collection 'petstore' (ID: collection-1) - │ │ │ └── Endpoint 'GET /fail' - │ │ │ └── The field `person` does not exist on the type `Query`. (1:14) - │ │ ├── MCP Feature Collection 'mcp-collection' (ID: mcp-1) - │ │ │ └── Tool 'Fail' - │ │ │ └── The field `person` does not exist on the type `Query`. (1:14) - │ │ ├── There was a syntax error in your schema document. - │ │ └── Operations are not allowed in a schema document. - │ ├── ⏳ Waiting for approval. Approve in Nitro to continue. - │ ├── Your request has been approved. - │ └── ✓ Published successfully. + Publishing new schema version 'v1' of API 'api-1' to stage 'dev' + ├── Publication request created. (ID: request-id) + ├── ! Validation failed. + │ ├── Invalid GraphQL schema + │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) + │ ├── GraphQL schema changes + │ │ ├── ✕ Directive foo was modified + │ │ │ ├── ✓ Directive location FieldDefinition added + │ │ │ └── ✕ Directive location Field removed + │ │ ├── ✕ Object type Foo was modified + │ │ │ ├── ✓ Field Foo.bar of type String! was added + │ │ │ └── ✕ Field Foo.baz of type Int! was removed + │ │ ├── ! Enum Status was modified + │ │ │ ├── ! Enum value Status.ACTIVE was added + │ │ │ └── ✕ Enum value Status.DELETED was removed + │ │ ├── ✓ Type system member NewType was added. + │ │ └── ✕ Type system member OldType was removed. + │ ├── Client 'TestClient' (ID: client-1) + │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) + │ │ └── foo (10:10) + │ ├── OpenAPI collection 'petstore' (ID: collection-1) + │ │ └── Endpoint 'GET /fail' + │ │ └── The field `person` does not exist on the type `Query`. (1:14) + │ ├── MCP Feature Collection 'mcp-collection' (ID: mcp-1) + │ │ └── Tool 'Fail' + │ │ └── The field `person` does not exist on the type `Query`. (1:14) + │ ├── There was a syntax error in your schema document. + │ └── Operations are not allowed in a schema document. + ├── ⏳ Waiting for approval. Approve in Nitro to continue. + ├── Your request has been approved. └── ✓ Published new schema version 'v1' to stage 'dev'. """); } @@ -430,38 +409,35 @@ Schema publish failed. """); result.StdOut.MatchInlineSnapshot( """ - Publishing new schema version 'v1' to stage 'dev' of API 'api-1' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-id). - ├── Processing - │ ├── ! Validation failed. - │ │ ├── Invalid GraphQL schema - │ │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL - │ │ ├── GraphQL schema changes - │ │ │ ├── ✕ Directive foo was modified - │ │ │ │ ├── ✓ Directive location FieldDefinition added - │ │ │ │ └── ✕ Directive location Field removed - │ │ │ ├── ✕ Object type Foo was modified - │ │ │ │ ├── ✓ Field Foo.bar of type String! was added - │ │ │ │ └── ✕ Field Foo.baz of type Int! was removed - │ │ │ ├── ! Enum Status was modified - │ │ │ │ ├── ! Enum value Status.ACTIVE was added - │ │ │ │ └── ✕ Enum value Status.DELETED was removed - │ │ │ ├── ✓ Type system member NewType was added. - │ │ │ └── ✕ Type system member OldType was removed. - │ │ ├── Client 'TestClient' (ID: client-1) - │ │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) - │ │ │ └── foo (10:10) - │ │ ├── OpenAPI collection 'petstore' (ID: collection-1) - │ │ │ └── Endpoint 'GET /fail' - │ │ │ └── The field `person` does not exist on the type `Query`. (1:14) - │ │ ├── MCP Feature Collection 'mcp-collection' (ID: mcp-1) - │ │ │ └── Tool 'Fail' - │ │ │ └── The field `person` does not exist on the type `Query`. (1:14) - │ │ ├── There was a syntax error in your schema document. - │ │ └── Operations are not allowed in a schema document. - │ ├── ⏳ Waiting for approval. Approve in Nitro to continue. - │ └── ✕ Processing failed. + Publishing new schema version 'v1' of API 'api-1' to stage 'dev' + ├── Publication request created. (ID: request-id) + ├── ! Validation failed. + │ ├── Invalid GraphQL schema + │ │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) + │ ├── GraphQL schema changes + │ │ ├── ✕ Directive foo was modified + │ │ │ ├── ✓ Directive location FieldDefinition added + │ │ │ └── ✕ Directive location Field removed + │ │ ├── ✕ Object type Foo was modified + │ │ │ ├── ✓ Field Foo.bar of type String! was added + │ │ │ └── ✕ Field Foo.baz of type Int! was removed + │ │ ├── ! Enum Status was modified + │ │ │ ├── ! Enum value Status.ACTIVE was added + │ │ │ └── ✕ Enum value Status.DELETED was removed + │ │ ├── ✓ Type system member NewType was added. + │ │ └── ✕ Type system member OldType was removed. + │ ├── Client 'TestClient' (ID: client-1) + │ │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) + │ │ └── foo (10:10) + │ ├── OpenAPI collection 'petstore' (ID: collection-1) + │ │ └── Endpoint 'GET /fail' + │ │ └── The field `person` does not exist on the type `Query`. (1:14) + │ ├── MCP Feature Collection 'mcp-collection' (ID: mcp-1) + │ │ └── Tool 'Fail' + │ │ └── The field `person` does not exist on the type `Query`. (1:14) + │ ├── There was a syntax error in your schema document. + │ └── Operations are not allowed in a schema document. + ├── ⏳ Waiting for approval. Approve in Nitro to continue. └── ✕ Failed to publish a new schema version. """); Assert.Equal(1, result.ExitCode); @@ -487,11 +463,8 @@ public async Task WithEnvVars_ReturnsSuccess() // assert result.AssertSuccess( """ - Publishing new schema version 'v1' to stage 'dev' of API 'api-1' - ├── Starting publish request - │ └── ✓ Publish request created (ID: request-id). - ├── Processing - │ └── ✓ Published successfully. + Publishing new schema version 'v1' of API 'api-1' to stage 'dev' + ├── Publication request created. (ID: request-id) └── ✓ Published new schema version 'v1' to stage 'dev'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/UploadSchemaCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/UploadSchemaCommandTests.cs index 4a126c77fd4..76e0507aeb9 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/UploadSchemaCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/UploadSchemaCommandTests.cs @@ -64,7 +64,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -114,7 +114,7 @@ public async Task UploadSchemaThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Uploading new schema version 'v1' to API 'api-1' + Uploading new schema version 'v1' of API 'api-1' └── ✕ Failed to upload a new schema version. """); Assert.Equal(1, result.ExitCode); @@ -145,7 +145,7 @@ public async Task UploadSchemaHasErrors_ReturnsError( result.StdErr.MatchInlineSnapshot(expectedErrorMessage); result.StdOut.MatchInlineSnapshot( """ - Uploading new schema version 'v1' to API 'api-1' + Uploading new schema version 'v1' of API 'api-1' └── ✕ Failed to upload a new schema version. """); Assert.Equal(1, result.ExitCode); @@ -186,7 +186,7 @@ Could not upload schema. """); result.StdOut.MatchInlineSnapshot( """ - Uploading new schema version 'v1' to API 'api-1' + Uploading new schema version 'v1' of API 'api-1' └── ✕ Failed to upload a new schema version. """); Assert.Equal(1, result.ExitCode); @@ -215,7 +215,7 @@ public async Task UploadsSchema_ReturnsSuccess() System.Text.Encoding.UTF8.GetString(capturedStream.ToArray())); result.AssertSuccess( """ - Uploading new schema version 'v1' to API 'api-1' + Uploading new schema version 'v1' of API 'api-1' └── ✓ Uploaded new schema version 'v1'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/ValidateSchemaCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/ValidateSchemaCommandTests.cs index 5b8aee083c8..4b3158cc57a 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/ValidateSchemaCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Schemas/ValidateSchemaCommandTests.cs @@ -64,7 +64,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -114,7 +114,7 @@ public async Task ValidateSchemaThrows_ReturnsError() """); result.StdOut.MatchInlineSnapshot( """ - Validating schema against stage 'dev' of API 'api-1' + Validating schema of API 'api-1' against stage 'dev' └── ✕ Failed to validate the schema. """); Assert.Equal(1, result.ExitCode); @@ -145,7 +145,7 @@ public async Task ValidateSchemaVersionHasErrors_ReturnsError( result.StdErr.MatchInlineSnapshot(expectedErrorMessage); result.StdOut.MatchInlineSnapshot( """ - Validating schema against stage 'dev' of API 'api-1' + Validating schema of API 'api-1' against stage 'dev' └── ✕ Failed to validate the schema. """); Assert.Equal(1, result.ExitCode); @@ -188,7 +188,7 @@ public async Task MutationReturnsNullRequestId_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - Validating schema against stage 'dev' of API 'api-1' + Validating schema of API 'api-1' against stage 'dev' └── ✕ Failed to validate the schema. """); result.StdErr.MatchInlineSnapshot( @@ -222,9 +222,9 @@ public async Task ReturnsSuccess() System.Text.Encoding.UTF8.GetString(capturedStream.ToArray())); result.AssertSuccess( """ - Validating schema against stage 'dev' of API 'api-1' - ├── Validation request created (ID: request-id). - └── ✓ Validation passed. + Validating schema of API 'api-1' against stage 'dev' + ├── Validation request created. (ID: request-id) + └── ✓ Schema validation successful. """); } @@ -248,9 +248,9 @@ public async Task WithEnvVars_ReturnsSuccess() // assert result.AssertSuccess( """ - Validating schema against stage 'dev' of API 'api-1' - ├── Validation request created (ID: request-id). - └── ✓ Validation passed. + Validating schema of API 'api-1' against stage 'dev' + ├── Validation request created. (ID: request-id) + └── ✓ Schema validation successful. """); } @@ -279,12 +279,12 @@ public async Task BreakingChanges_ReturnsError() // assert result.StdErr.MatchInlineSnapshot( """ - Validation failed. + Schema validation failed. """); result.StdOut.MatchInlineSnapshot( """ - Validating schema against stage 'dev' of API 'api-1' - ├── Validation request created (ID: request-id). + Validating schema of API 'api-1' against stage 'dev' + ├── Validation request created. (ID: request-id) ├── Validating... ├── Validating... └── ✕ Failed to validate the schema. @@ -301,7 +301,7 @@ Validating schema against stage 'dev' of API 'api-1' │ ├── ✓ Type system member NewType was added. │ └── ✕ Type system member OldType was removed. ├── Invalid GraphQL schema - │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. SCHEMA_INTERFACE_NO_IMPL + │ └── There is no object type implementing interface `InterfaceWithoutImplementation`. (SCHEMA_INTERFACE_NO_IMPL) ├── Client 'TestClient' (ID: client-1) │ └── Operation '6D12E4A815C50C504695E548EAF680BC8F337AC87E763E5689C685522A01BC59' (Deployed tags: 1.0.0) │ └── foo (10:10) diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/LoginCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/LoginCommandTests.cs index 087a0175983..e3fdbc1cd71 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/LoginCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/LoginCommandTests.cs @@ -43,7 +43,7 @@ public async Task NonInteractive_ReturnsError() // assert result.AssertError( """ - 'nitro login' requires an interactive console. Use '--api-key' to authenticate command invocations in non-interactive environments. + `nitro login` requires an interactive console. Use '--api-key' to authenticate command invocations in non-interactive environments. """); } @@ -85,7 +85,7 @@ The login was rejected. } [Fact] - public async Task NoWorkspacesAvailable_ReturnsError() + public async Task NoWorkspacesAvailable_ReturnsSuccess_WithWarning() { // arrange SetupInteractionMode(InteractionMode.Interactive); @@ -96,11 +96,15 @@ public async Task NoWorkspacesAvailable_ReturnsError() var result = await ExecuteCommandAsync("login"); // assert - result.StdErr.MatchInlineSnapshot( + result.AssertSuccess( """ - You do not have any workspaces. Run `[bold blue]nitro launch[/]` and create one. + ✓ Logging in via browser + ├── Browser opened at identity.chillicream.com. Continue login there. + ├── ! You do not have any workspaces. Run `nitro launch` and create one. + └── ✓ Logged in as user@test.com """); - Assert.Equal(1, result.ExitCode); + + VerifyNoWorkspaceSelected(); } [Fact] @@ -116,8 +120,14 @@ public async Task SingleWorkspace_AutoSelects_ReturnsSuccess() var result = await ExecuteCommandAsync("login"); // assert - Assert.Empty(result.StdErr); - Assert.Equal(0, result.ExitCode); + result.AssertSuccess( + """ + ✓ Logging in via browser + ├── Browser opened at identity.chillicream.com. Continue login there. + └── ✓ Logged in as user@test.com (Workspace: my-workspace) + """); + + VerifyWorkspaceSelected("ws-1", "my-workspace"); } [Fact] @@ -138,8 +148,9 @@ public async Task MultipleWorkspaces_PromptsForSelection_ReturnsSuccess() var result = await command.RunToCompletionAsync(); // assert - Assert.Empty(result.StdErr); - Assert.Equal(0, result.ExitCode); + result.AssertSuccess(); + + VerifyWorkspaceSelected("ws-1", "first-workspace"); } [Fact] diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/LogoutCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/LogoutCommandTests.cs index 01f3766af9c..728192a54d9 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/LogoutCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/LogoutCommandTests.cs @@ -45,10 +45,10 @@ Logging out } [Fact] - public async Task LogoutThrowsExitException_ReturnsError() + public async Task LogoutThrows_ReturnsError() { // arrange - SetupLogoutThrowsExitException(); + SetupLogoutThrows(); // act var result = await ExecuteCommandAsync("logout"); @@ -59,23 +59,6 @@ public async Task LogoutThrowsExitException_ReturnsError() Logging out └── ✕ Failed to log out. """); - result.StdErr.MatchInlineSnapshot( - """ - Session deletion failed. - """); - Assert.Equal(1, result.ExitCode); - } - - [Fact] - public async Task LogoutThrows_ReturnsError() - { - // arrange - SetupLogoutThrows(); - - // act - var result = await ExecuteCommandAsync("logout"); - - // assert result.StdErr.MatchInlineSnapshot( """ There was an unexpected error: Something unexpected happened. diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/SessionCommandTestBase.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/SessionCommandTestBase.cs index b74dc4feeb3..d83d0dcc932 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/SessionCommandTestBase.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/SessionCommandTestBase.cs @@ -68,13 +68,6 @@ protected void SetupLogoutThrows() .ThrowsAsync(new InvalidOperationException("Something unexpected happened.")); } - protected void SetupLogoutThrowsExitException(string message = "Session deletion failed.") - { - _sessionServiceMock - .Setup(x => x.LogoutAsync(It.IsAny())) - .ThrowsAsync(new ExitException(message)); - } - #endregion #region Workspace Selection diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/StatusCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/StatusCommandTests.cs index 4a583e4b2ab..06d71a610bf 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/StatusCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Session/StatusCommandTests.cs @@ -39,7 +39,7 @@ public async Task NotLoggedIn_ReturnsError() // assert result.AssertError( """ - Not logged in. Run 'nitro login' first. + Not logged in. Run `nitro login` first. """); } @@ -75,7 +75,7 @@ public async Task WithWorkspace_ReturnsSuccess() // assert result.AssertSuccess( """ - Logged in as user@chillicream.com (Workspace from session workspace) + Logged in as user@chillicream.com (Workspace: Workspace from session) """); } @@ -115,7 +115,7 @@ public async Task CustomApiUrl_WithWorkspace_ReturnsSuccess() // assert result.AssertSuccess( """ - Logged in as user@chillicream.com on api.custom.com (my-workspace workspace) + Logged in as user@chillicream.com on api.custom.com (Workspace: my-workspace) """); } } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Stages/DeleteStageCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Stages/DeleteStageCommandTests.cs index 4173ff0ee38..de828b3a8ea 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Stages/DeleteStageCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Stages/DeleteStageCommandTests.cs @@ -61,7 +61,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Stages/EditStagesCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Stages/EditStagesCommandTests.cs index 0e6dcff5f43..97d497a6bc9 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Stages/EditStagesCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Stages/EditStagesCommandTests.cs @@ -59,7 +59,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -79,7 +79,7 @@ public async Task MissingApiId_ReturnsError() // assert result.AssertError( """ - You are not logged in. Run `[bold blue]nitro login[/]` to sign in or manually specify the '--workspace-id' option (if available). + Missing required option '--api-id'. """); } @@ -121,7 +121,6 @@ public async Task WithJsonConfig_ReturnsSuccess_NonInteractive() // assert result.AssertSuccess( """ - ? For which API do you want to edit the stages?: api-1 Updating stages for API 'api-1' └── ✓ Updated stages for API 'api-1'. @@ -202,15 +201,10 @@ public async Task WithInvalidJsonConfig_ReturnsError() "not-valid-json"); // assert - result.StdOut.MatchInlineSnapshot( - """ - ? For which API do you want to edit the stages?: api-1 - """); - result.StdErr.MatchInlineSnapshot( + result.AssertError( """ Could not parse stage configuration """); - Assert.Equal(1, result.ExitCode); } [Fact] @@ -231,7 +225,6 @@ public async Task UpdateStagesThrows_ReturnsError() // assert result.StdOut.MatchInlineSnapshot( """ - ? For which API do you want to edit the stages?: api-1 Updating stages for API 'api-1' └── ✕ Failed to update the stages. """); @@ -294,7 +287,6 @@ public static TheoryData { CreateUpdateStagesApiNotFoundError(), """ - ? For which API do you want to edit the stages?: api-1 Updating stages for API 'api-1' └── ✕ Failed to update the stages. └── API not found @@ -303,7 +295,6 @@ public static TheoryData { CreateUpdateStagesStageNotFoundError(), """ - ? For which API do you want to edit the stages?: api-1 Updating stages for API 'api-1' └── ✕ Failed to update the stages. └── Stage not found @@ -312,7 +303,6 @@ public static TheoryData { CreateUpdateStagesStagesHavePublishedDependenciesError(), """ - ? For which API do you want to edit the stages?: api-1 Updating stages for API 'api-1' └── ✕ Failed to update the stages. └── Stages have published dependencies @@ -321,7 +311,6 @@ public static TheoryData { CreateUpdateStagesStageValidationError(), """ - ? For which API do you want to edit the stages?: api-1 Updating stages for API 'api-1' └── ✕ Failed to update the stages. └── Stage validation failed diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Stages/ListStagesCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Stages/ListStagesCommandTests.cs index ef0c85eb867..ed77555bbf9 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Stages/ListStagesCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Stages/ListStagesCommandTests.cs @@ -50,7 +50,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -70,7 +70,7 @@ public async Task MissingApiId_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - The '--api-id' option is required in non-interactive mode. + Missing required option '--api-id'. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Workspaces/CreateWorkspaceCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Workspaces/CreateWorkspaceCommandTests.cs index f6609b3ece7..8908f625327 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Workspaces/CreateWorkspaceCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Workspaces/CreateWorkspaceCommandTests.cs @@ -57,7 +57,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Workspaces/SetDefaultWorkspaceCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Workspaces/SetDefaultWorkspaceCommandTests.cs index 5236a601301..8427a72533f 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Workspaces/SetDefaultWorkspaceCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Workspaces/SetDefaultWorkspaceCommandTests.cs @@ -56,7 +56,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } @@ -75,7 +75,7 @@ public async Task NoWorkspaces_ReturnsError() // assert result.AssertError( """ - You do not have any workspaces. Run `[bold blue]nitro launch[/]` and create one. + You do not have any workspaces. Run `nitro launch` and create one. """); } @@ -131,7 +131,7 @@ public async Task NoWorkspaceId_ReturnsError() // assert result.AssertError( """ - The '--workspace-id' option is required in non-interactive mode. + Missing required option '--workspace-id'. """); } @@ -179,47 +179,26 @@ The workspace with ID 'ws-999' was not found. } [Fact] - public async Task SetDefault_Should_AutoSelect_When_SingleWorkspaceAndNotForced() + public async Task SingleWorkspace_Should_StillPrompt_When_Interactive() { // arrange - var client = new Mock(MockBehavior.Strict); - client.Setup(x => x.SelectWorkspacesAsync( - null, - 5, - It.IsAny())) - .ReturnsAsync(new ConnectionPage( - [ - new SetDefaultWorkspaceCommand_SelectWorkspace_Query_Me_Workspaces_Edges_Node_Workspace( - "ws-1", "my-workspace", false) - ], null, false)); - - var console = Mock.Of(); - var sessionService = new Mock(MockBehavior.Strict); - sessionService.Setup(x => x.SelectWorkspaceAsync( - It.Is(w => w.Id == "ws-1" && w.Name == "my-workspace"), - It.IsAny())) - .ReturnsAsync(new Services.Sessions.Session( - "session-1", - "subject-1", - "tenant-1", - "https://id.chillicream.com", - "api.chillicream.com", - "user@chillicream.com", - tokens: null, - workspace: new Services.Sessions.Workspace("ws-1", "my-workspace"))); + SetupInteractionMode(InteractionMode.Interactive); + SetupSelectWorkspacesQuery( + new SetDefaultWorkspaceCommand_SelectWorkspace_Query_Me_Workspaces_Edges_Node_Workspace( + WorkspaceId, WorkspaceName, false)); + + var command = StartInteractiveCommand( + "workspace", + "set-default"); // act - var exitCode = await SetDefaultWorkspaceCommand.ExecuteAsync( - forceSelection: false, - console, - client.Object, - sessionService.Object, - CancellationToken.None); + command.SelectOption(0); + + var result = await command.RunToCompletionAsync(); // assert - Assert.Equal(0, exitCode); + result.AssertSuccess(); - client.VerifyAll(); - sessionService.VerifyAll(); + VerifyWorkspaceSelected(WorkspaceId, WorkspaceName); } } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Workspaces/ShowWorkspaceCommandTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Workspaces/ShowWorkspaceCommandTests.cs index 11565b69970..16094fa07ba 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Workspaces/ShowWorkspaceCommandTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Commands/Workspaces/ShowWorkspaceCommandTests.cs @@ -54,7 +54,7 @@ public async Task NoSession_Or_ApiKey_ReturnsError(InteractionMode mode) // assert result.AssertError( """ - This command requires an authenticated user. Either specify '--api-key' or run 'nitro login'. + This command requires an authenticated user. Either specify '--api-key' or run `nitro login`. """); } diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Console/InteractiveNitroConsoleActivityTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Console/InteractiveNitroConsoleActivityTests.cs new file mode 100644 index 00000000000..98dd3d60661 --- /dev/null +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Console/InteractiveNitroConsoleActivityTests.cs @@ -0,0 +1,908 @@ +using ChilliCream.Nitro.CommandLine.Helpers; +using ChilliCream.Nitro.CommandLine.Services; +using Moq; +using Spectre.Console; +using Spectre.Console.Testing; + +namespace ChilliCream.Nitro.CommandLine.Tests.Console; + +public sealed class InteractiveNitroConsoleActivityTests +{ + private static (INitroConsole Console, StringWriter Writer) CreateConsole( + int width = Constants.DefaultPrintWidth) + { + var writer = new StringWriter(); + + var outConsole = new TestConsole(); + outConsole.Profile.Out = new AnsiConsoleOutput(writer); + outConsole.Profile.Width = width; + outConsole.Profile.Capabilities.Interactive = true; + + var errConsole = new TestConsole(); + + var envProvider = new Mock(); + envProvider + .Setup(x => x.GetEnvironmentVariable(It.IsAny())) + .Returns((string?)null); + + var console = new NitroConsole( + outConsole, + errConsole, + envProvider.Object, + new SnapshotActivitySinkFactory()); + return (console, writer); + } + + private static string GetOutput(StringWriter writer) + { + return writer.ToString().TrimEnd(); + } + + [Fact] + public async Task Success_Should_RenderCheckGlyph_When_ActivityCompletes() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Doing work", "Work failed")) + { + activity.Success("Done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Doing work + └── ✓ Done + """); + } + + [Fact] + public async Task Fail_Should_RenderCrossGlyph_When_CalledWithMessage() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Doing work", "Work failed")) + { + activity.Fail("Something went wrong"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Doing work + └── ✕ Something went wrong + """); + } + + [Fact] + public async Task FailAllAsync_Should_RenderCrossGlyphWithDetails_When_CalledWithRenderable() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Doing work", "Work failed")) + { + await activity.FailAllAsync(new Text("Error detail line 1\nError detail line 2")); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Doing work + └── ✕ Work failed + Error detail line 1 + Error detail line 2 + """); + } + + [Fact] + public async Task StartChildActivity_Should_RenderTreeStructure_When_ChildSucceeds() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child step", "Child failed")) + { + child.Success("Child done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + ├── ✓ Child done + └── ✓ Root done + """); + } + + [Fact] + public async Task ChildSuccess_Should_CollapseToSingleLine_When_ChildHasNoUpdates() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child step", "Child failed")) + { + child.Success("Child done"); + } + + activity.Success("Root done"); + } + + // assert — child collapses: entry text replaced with success message, no nested terminator + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + ├── ✓ Child done + └── ✓ Root done + """); + } + + [Fact] + public async Task ChildSuccess_Should_NotCollapse_When_ChildHasUpdates() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child step", "Child failed")) + { + child.Update("working..."); + child.Success("Child done"); + } + + activity.Success("Root done"); + } + + // assert — child entry stays, success appended as a nested terminator + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + ├── ✓ Child step + │ ├── working... + │ └── ✓ Child done + └── ✓ Root done + """); + } + + [Fact] + public async Task ChildFail_Should_FoldIntoTitle_When_ChildHasNoUpdates() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child step", "Child failed")) + { + child.Fail("Child error"); + } + + activity.Fail("Root error"); + } + + // assert — child title collapses; root's own terminator is suppressed + // because the last child already conveys the failure + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Root + └── ✕ Child error + """); + } + + [Fact] + public async Task ChildFail_Should_NotFold_When_ChildHasUpdates() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child step", "Child failed")) + { + child.Update("working..."); + child.Fail("Child error"); + } + + activity.Fail("Root error"); + } + + // assert — child retains title; root's own terminator is suppressed because + // the last child already conveys the failure + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Root + └── ✕ Child step + ├── working... + └── ✕ Child error + """); + } + + [Fact] + public async Task StartChildActivity_Should_RenderNestedTreeStructure_When_GrandchildSucceeds() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + await using (var grandchild = child.StartChildActivity("Grandchild", "Grandchild failed")) + { + grandchild.Success("Grandchild done"); + } + + child.Success("Child done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + ├── ✓ Child + │ ├── ✓ Grandchild done + │ └── ✓ Child done + └── ✓ Root done + """); + } + + [Fact] + public async Task GrandchildFail_Should_CollapseTerminators_AtEveryLevel() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + await using (var grandchild = child.StartChildActivity("Grandchild", "Grandchild failed")) + { + grandchild.Fail("Grandchild error"); + } + + child.Fail("Child aborted"); + } + + activity.Fail("Root error"); + } + + // assert — explicit terminators at root and child are suppressed; the grandchild + // failure stays as the final error line + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Root + └── ✕ Child + └── ✕ Grandchild error + """); + } + + [Fact] + public async Task GrandchildFail_Should_PreserveUpdatesAtEachLevel() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + child.Update("Child step 1"); + + await using (var grandchild = child.StartChildActivity("Grandchild", "Grandchild failed")) + { + grandchild.Update("Grandchild step 1"); + grandchild.Fail("Grandchild error"); + } + } + } + + // assert — updates at child and grandchild stay visible; dispose-driven failure + // cascade doesn't add extra terminators at child or root + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Root + └── ✕ Child + ├── Child step 1 + └── ✕ Grandchild + ├── Grandchild step 1 + └── ✕ Grandchild error + """); + } + + [Fact] + public async Task FailAllAsync_Should_PropagateFailure_When_ChildDisposesWithoutCompletion() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + // child is not completed — DisposeAsync will call FailAllAsync + } + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Root + └── ✕ Child failed + """); + } + + [Fact] + public async Task Update_Should_BeIgnored_When_CalledAfterSuccess() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Doing work", "Work failed")) + { + activity.Success("Done"); + activity.Update("This should be ignored"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Doing work + └── ✓ Done + """); + } + + [Fact] + public async Task Update_Should_RenderMultipleUpdates_When_CalledWithDifferentKinds() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Doing work", "Work failed")) + { + activity.Update("Regular update"); + activity.Update("Warning update", ActivityUpdateKind.Warning); + activity.Update("Waiting update", ActivityUpdateKind.Waiting); + activity.Update("Success update", ActivityUpdateKind.Success); + activity.Success("Done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Doing work + ├── Regular update + ├── ! Warning update + ├── ⏳ Waiting update + ├── ✓ Success update + └── ✓ Done + """); + } + + [Fact] + public async Task Warning_Should_RenderExclamationGlyph_When_ActivityCompletes() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Doing work", "Work failed")) + { + activity.Warning("Something is off"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ! Doing work + └── ! Something is off + """); + } + + [Fact] + public async Task Start_Should_WrapTitle_When_TitleExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity( + "This is a very long root title that should wrap", + "Failed")) + { + activity.Success("Done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ This is a very long root + │ title that should wrap + └── ✓ Done + """); + } + + [Fact] + public async Task Update_Should_WrapMessage_When_MessageExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity("Root", "Failed")) + { + activity.Update("This update message is long enough to wrap at narrow width"); + activity.Success("Done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + ├── This update message is + │ long enough to wrap at + │ narrow width + └── ✓ Done + """); + } + + [Fact] + public async Task Success_Should_WrapMessage_When_MessageExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity("Root", "Failed")) + { + activity.Success("This success message is long enough to wrap at narrow width"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + └── ✓ This success message is + long enough to wrap at + narrow width + """); + } + + [Fact] + public async Task Fail_Should_WrapMessage_When_MessageExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity("Root", "Failed")) + { + activity.Fail("This failure message is long enough to wrap at narrow width"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Root + └── ✕ This failure message is + long enough to wrap at + narrow width + """); + } + + [Fact] + public async Task StartChildActivity_Should_WrapTitle_When_TitleExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity("Root", "Failed")) + { + await using (var child = activity.StartChildActivity( + "This child title is long enough to wrap", + "Child failed")) + { + // give child a sub-update so its title is preserved on success + child.Update("Working"); + child.Success("Done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + ├── ✓ This child title is long + │ │ enough to wrap + │ ├── Working + │ └── ✓ Done + └── ✓ Root done + """); + } + + [Fact] + public async Task ChildUpdate_Should_WrapMessage_When_MessageExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity("Root", "Failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + child.Update("This child update is long enough to wrap"); + child.Success("Done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + ├── ✓ Child + │ ├── This child update is + │ │ long enough to wrap + │ └── ✓ Done + └── ✓ Root done + """); + } + + [Fact] + public async Task ChildSuccess_Should_WrapMessage_When_MessageExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity("Root", "Failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + child.Success("This child success message wraps"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + ├── ✓ This child success + │ message wraps + └── ✓ Root done + """); + } + + [Fact] + public async Task ChildFail_Should_WrapMessage_When_MessageExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity("Root", "Failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + child.Fail("This child failure message wraps"); + } + + activity.Fail("Root error"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Root + └── ✕ This child failure + message wraps + """); + } + + [Fact] + public async Task Update_Should_RenderDetails_When_CalledWithRenderable() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Doing work", "Work failed")) + { + activity.Update("Status", details: new Text("Detail line 1\nDetail line 2")); + activity.Success("Done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Doing work + ├── Status + │ Detail line 1 + │ Detail line 2 + └── ✓ Done + """); + } + + [Fact] + public async Task ChildUpdate_Should_RenderDetails_When_CalledWithRenderable() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + child.Update("Status", details: new Text("Detail line 1\nDetail line 2")); + child.Success("Child done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + ├── ✓ Child + │ ├── Status + │ │ Detail line 1 + │ │ Detail line 2 + │ └── ✓ Child done + └── ✓ Root done + """); + } + + [Fact] + public async Task ChildFailAllAsync_Should_RenderCrossGlyphWithDetails_When_CalledWithRenderable() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + await child.FailAllAsync(new Text("Error detail line 1\nError detail line 2")); + } + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Root + └── ✕ Child failed + Error detail line 1 + Error detail line 2 + """); + } + + [Fact] + public async Task ChildWarning_Should_RenderExclamationGlyph_When_ActivityCompletes() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + child.Warning("Something is off"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + ├── ! Something is off + └── ✓ Root done + """); + } + + [Fact] + public async Task StartChildActivity_Should_RenderTreeStructure_When_MultipleSiblings() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var first = activity.StartChildActivity("First child", "First failed")) + { + first.Success("First done"); + } + + await using (var second = activity.StartChildActivity("Second child", "Second failed")) + { + second.Success("Second done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + ├── ✓ First done + ├── ✓ Second done + └── ✓ Root done + """); + } + + [Fact] + public async Task FailAllAsync_Should_PropagateFailure_When_GrandchildDisposesWithoutCompletion() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + await using (var grandchild = child.StartChildActivity("Grandchild", "Grandchild failed")) + { + // grandchild not completed — DisposeAsync cascades up + } + } + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Root + └── ✕ Child + └── ✕ Grandchild failed + """); + } + + [Fact] + public async Task Update_Should_RenderClockGlyph_When_CalledWithWaitingKind() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Doing work", "Work failed")) + { + activity.Update("Please wait", ActivityUpdateKind.Waiting); + activity.Success("Done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Doing work + ├── ⏳ Please wait + └── ✓ Done + """); + } + + [Fact] + public async Task DisposeAsync_Should_TriggerFailure_When_NoCompletionCalled() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Doing work", "Work failed")) + { + // no explicit completion — DisposeAsync should trigger failure + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Doing work + └── ✕ Work failed + """); + } + + [Fact] + public async Task FailAllAsync_Should_KeepAllSiblings_When_MultipleSiblingsFailed() + { + // arrange + var (console, writer) = CreateConsole(); + + // act — one sibling explicitly fails, the next propagates failure via + // FailAllAsync (no details). Parent ends up with two failed children and + // no terminator of its own. Guards against the earlier heuristic that + // hid the last sibling whenever two consecutive children were failed. + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var first = activity.StartChildActivity("First", "First failed")) + { + first.Fail("First error"); + } + + await using (var second = activity.StartChildActivity("Second", "Second failed")) + { + await second.FailAllAsync(); + } + } + + // assert — both siblings stay visible; suppression only fires for explicit + // terminator children added via CompleteChild + GetOutput(writer).MatchInlineSnapshot( + """ + ✕ Root + ├── ✕ First error + └── ✕ Second failed + """); + } + + [Fact] + public async Task ChildSuccess_Should_FailActiveDescendants_When_LeakedGrandchild() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + // leak grandchild — its active state is flipped to Failed by child.Success + var grandchild = child.StartChildActivity("Grandchild", "Grandchild failed"); + _ = grandchild; + + child.Success("Child done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + ✓ Root + ├── ✓ Child + │ ├── ✕ Grandchild + │ └── ✓ Child done + └── ✓ Root done + """); + } +} diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Console/NitroConsoleActivityTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Console/NitroConsoleActivityTests.cs new file mode 100644 index 00000000000..0d3b5dfe453 --- /dev/null +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Console/NitroConsoleActivityTests.cs @@ -0,0 +1,746 @@ +using ChilliCream.Nitro.CommandLine.Helpers; +using ChilliCream.Nitro.CommandLine.Services; +using Moq; +using Spectre.Console; +using Spectre.Console.Testing; + +namespace ChilliCream.Nitro.CommandLine.Tests.Console; + +public sealed class NitroConsoleActivityTests +{ + private static (INitroConsole Console, StringWriter Writer) CreateConsole( + int width = Constants.DefaultPrintWidth) + { + var writer = new StringWriter(); + + var outConsole = new TestConsole(); + outConsole.Profile.Out = new AnsiConsoleOutput(writer); + outConsole.Profile.Width = width; + outConsole.Profile.Capabilities.Interactive = false; + + var errConsole = new TestConsole(); + + var envProvider = new Mock(); + envProvider + .Setup(x => x.GetEnvironmentVariable(It.IsAny())) + .Returns((string?)null); + + var console = new NitroConsole( + outConsole, + errConsole, + envProvider.Object, + new SnapshotActivitySinkFactory()); + return (console, writer); + } + + private static string GetOutput(StringWriter writer) + { + return writer.ToString().TrimEnd(); + } + + [Fact] + public async Task Success_Should_WriteCheckGlyph_When_ActivityCompletes() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Doing work", "Work failed")) + { + activity.Success("Done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Doing work + └── ✓ Done + """); + } + + [Fact] + public async Task Fail_Should_WriteCrossGlyph_When_CalledWithMessage() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Doing work", "Work failed")) + { + activity.Fail("Something went wrong"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Doing work + └── ✕ Something went wrong + """); + } + + [Fact] + public async Task FailAllAsync_Should_WriteCrossGlyphWithDetails_When_CalledWithRenderable() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Doing work", "Work failed")) + { + await activity.FailAllAsync(new Text("Error detail line 1\nError detail line 2")); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Doing work + └── ✕ Work failed + Error detail line 1 + Error detail line 2 + """); + } + + [Fact] + public async Task StartChildActivity_Should_WriteTreeStructure_When_ChildSucceeds() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child step", "Child failed")) + { + child.Success("Child done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── Child step + │ └── ✓ Child done + └── ✓ Root done + """); + } + + [Fact] + public async Task StartChildActivity_Should_WriteTreeStructure_When_ChildFails() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child step", "Child failed")) + { + child.Fail("Child error"); + } + + activity.Fail("Root error"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── Child step + │ └── ✕ Child error + └── ✕ Root error + """); + } + + [Fact] + public async Task StartChildActivity_Should_WriteNestedTreeStructure_When_GrandchildSucceeds() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + await using (var grandchild = child.StartChildActivity("Grandchild", "Grandchild failed")) + { + grandchild.Success("Grandchild done"); + } + + child.Success("Child done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── Child + │ ├── Grandchild + │ │ └── ✓ Grandchild done + │ └── ✓ Child done + └── ✓ Root done + """); + } + + [Fact] + public async Task FailAllAsync_Should_PropagateFailure_When_ChildDisposesWithoutCompletion() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + // child is not completed — DisposeAsync will call FailAllAsync + } + + // root is already failed via FailAllAsync, so this is a no-op + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── Child + │ └── ✕ Child failed + └── ✕ Root failed + """); + } + + [Fact] + public async Task Update_Should_BeIgnored_When_CalledAfterSuccess() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Doing work", "Work failed")) + { + activity.Success("Done"); + activity.Update("This should be ignored"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Doing work + └── ✓ Done + """); + } + + [Fact] + public async Task Update_Should_WriteMultipleUpdates_When_CalledWithDifferentKinds() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Doing work", "Work failed")) + { + activity.Update("Regular update"); + activity.Update("Warning update", ActivityUpdateKind.Warning); + activity.Update("Waiting update", ActivityUpdateKind.Waiting); + activity.Update("Success update", ActivityUpdateKind.Success); + activity.Success("Done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Doing work + ├── Regular update + ├── ! Warning update + ├── ⏳ Waiting update + ├── ✓ Success update + └── ✓ Done + """); + } + + [Fact] + public async Task Warning_Should_WriteExclamationGlyph_When_ActivityCompletes() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Doing work", "Work failed")) + { + activity.Warning("Something is off"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Doing work + └── ! Something is off + """); + } + + [Fact] + public async Task Start_Should_WrapTitle_When_TitleExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity( + "This is a very long root title that should wrap", + "Failed")) + { + activity.Success("Done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + This is a very long root + │ title that should wrap + └── ✓ Done + """); + } + + [Fact] + public async Task Update_Should_WrapMessage_When_MessageExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity( "Root", "Failed")) + { + activity.Update("This update message is long enough to wrap at narrow width"); + activity.Success("Done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── This update message is + │ long enough to wrap at + │ narrow width + └── ✓ Done + """); + } + + [Fact] + public async Task Success_Should_WrapMessage_When_MessageExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity( "Root", "Failed")) + { + activity.Success("This success message is long enough to wrap at narrow width"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + └── ✓ This success message + is long enough to wrap + at narrow width + """); + } + + [Fact] + public async Task Fail_Should_WrapMessage_When_MessageExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity( "Root", "Failed")) + { + activity.Fail("This failure message is long enough to wrap at narrow width"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + └── ✕ This failure message + is long enough to wrap + at narrow width + """); + } + + [Fact] + public async Task StartChildActivity_Should_WrapTitle_When_TitleExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity( "Root", "Failed")) + { + await using (var child = activity.StartChildActivity( + "This child title is long enough to wrap", + "Child failed")) + { + child.Success("Done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── This child title is long + │ enough to wrap + │ └── ✓ Done + └── ✓ Root done + """); + } + + [Fact] + public async Task ChildUpdate_Should_WrapMessage_When_MessageExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity( "Root", "Failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + child.Update("This child update is long enough to wrap"); + child.Success("Done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── Child + │ ├── This child update is + │ │ long enough to wrap + │ └── ✓ Done + └── ✓ Root done + """); + } + + [Fact] + public async Task ChildSuccess_Should_WrapMessage_When_MessageExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity( "Root", "Failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + child.Success("This child success message wraps"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── Child + │ └── ✓ This child success + │ message wraps + └── ✓ Root done + """); + } + + [Fact] + public async Task ChildFail_Should_WrapMessage_When_MessageExceedsWidth() + { + // arrange + var (console, writer) = CreateConsole(width: 30); + + // act + await using (var activity = console.StartActivity( "Root", "Failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + child.Fail("This child failure message wraps"); + } + + activity.Fail("Root error"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── Child + │ └── ✕ This child failure + │ message wraps + └── ✕ Root error + """); + } + + [Fact] + public async Task Update_Should_WriteDetails_When_CalledWithRenderable() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Doing work", "Work failed")) + { + activity.Update("Status", details: new Text("Detail line 1\nDetail line 2")); + activity.Success("Done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Doing work + ├── Status + │ Detail line 1 + │ Detail line 2 + └── ✓ Done + """); + } + + [Fact] + public async Task ChildUpdate_Should_WriteDetails_When_CalledWithRenderable() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + child.Update("Status", details: new Text("Detail line 1\nDetail line 2")); + child.Success("Child done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── Child + │ ├── Status + │ │ Detail line 1 + │ │ Detail line 2 + │ └── ✓ Child done + └── ✓ Root done + """); + } + + [Fact] + public async Task ChildFailAllAsync_Should_WriteCrossGlyphWithDetails_When_CalledWithRenderable() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + await child.FailAllAsync(new Text("Error detail line 1\nError detail line 2")); + } + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── Child + │ └── ✕ Child failed + │ Error detail line 1 + │ Error detail line 2 + └── ✕ Root failed + """); + } + + [Fact] + public async Task ChildWarning_Should_WriteExclamationGlyph_When_ActivityCompletes() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + child.Warning("Something is off"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── Child + │ └── ! Something is off + └── ✓ Root done + """); + } + + [Fact] + public async Task StartChildActivity_Should_WriteTreeStructure_When_MultipleSiblings() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Root", "Root failed")) + { + await using (var first = activity.StartChildActivity("First child", "First failed")) + { + first.Success("First done"); + } + + await using (var second = activity.StartChildActivity("Second child", "Second failed")) + { + second.Success("Second done"); + } + + activity.Success("Root done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── First child + │ └── ✓ First done + ├── Second child + │ └── ✓ Second done + └── ✓ Root done + """); + } + + [Fact] + public async Task FailAllAsync_Should_PropagateFailure_When_GrandchildDisposesWithoutCompletion() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Root", "Root failed")) + { + await using (var child = activity.StartChildActivity("Child", "Child failed")) + { + await using (var grandchild = child.StartChildActivity("Grandchild", "Grandchild failed")) + { + // grandchild not completed — DisposeAsync cascades up + } + + // child already failed via FailAllAsync + } + + // root already failed via FailAllAsync + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── Child + │ ├── Grandchild + │ │ └── ✕ Grandchild failed + │ └── ✕ Child failed + └── ✕ Root failed + """); + } + + [Fact] + public async Task Update_Should_WriteClockGlyph_When_CalledWithWaitingKind() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Doing work", "Work failed")) + { + activity.Update("Please wait", ActivityUpdateKind.Waiting); + activity.Success("Done"); + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Doing work + ├── ⏳ Please wait + └── ✓ Done + """); + } + + [Fact] + public async Task FailAllAsync_Should_KeepAllSiblings_When_MultipleSiblingsFailed() + { + // arrange + var (console, writer) = CreateConsole(); + + // act — one sibling explicitly fails, the next propagates failure via + // FailAllAsync (no details). Parent ends up with two failed children and + // no terminator of its own. + await using (var activity = console.StartActivity("Root", "Root failed")) + { + await using (var first = activity.StartChildActivity("First", "First failed")) + { + first.Fail("First error"); + } + + await using (var second = activity.StartChildActivity("Second", "Second failed")) + { + await second.FailAllAsync(); + } + } + + // assert — every sibling stays visible; none is dropped by the renderer + GetOutput(writer).MatchInlineSnapshot( + """ + Root + ├── First + │ └── ✕ First error + ├── Second + │ └── ✕ Second failed + └── ✕ Root failed + """); + } + + [Fact] + public async Task DisposeAsync_Should_TriggerFailure_When_NoCompletionCalled() + { + // arrange + var (console, writer) = CreateConsole(); + + // act + await using (var activity = console.StartActivity( "Doing work", "Work failed")) + { + // no explicit completion — DisposeAsync should trigger failure + } + + // assert + GetOutput(writer).MatchInlineSnapshot( + """ + Doing work + └── ✕ Work failed + """); + } +} diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Console/SnapshotActivitySink.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Console/SnapshotActivitySink.cs new file mode 100644 index 00000000000..18a643c9b77 --- /dev/null +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Console/SnapshotActivitySink.cs @@ -0,0 +1,56 @@ +using Spectre.Console.Rendering; + +namespace ChilliCream.Nitro.CommandLine.Tests.Console; + +internal sealed class SnapshotActivitySink : IActivitySink +{ + private readonly INitroConsole _console; + private readonly ActivityTree _tree = new(); + + public SnapshotActivitySink(INitroConsole console) + { + _console = console; + } + + public Task Completion => Task.CompletedTask; + + public ActivityEntry AddRoot(string text) + { + return _tree.AddRoot(text); + } + + public ActivityEntry AddChild(ActivityEntry parent, string text, ActivityState state) + { + return _tree.AddChild(parent, text, state); + } + + public ActivityEntry CompleteChild(ActivityEntry parent, string text, ActivityState state) + { + return _tree.AddChild(parent, text, state, isTerminator: true); + } + + public void SetState(ActivityEntry entry, ActivityState state) + { + _tree.SetEntryState(entry, state); + } + + public void SetTextAndState(ActivityEntry entry, string text, ActivityState state) + { + _tree.SetEntryTextAndState(entry, text, state); + } + + public void SetDetails(ActivityEntry entry, IRenderable details) + { + _tree.SetEntryDetails(entry, details); + } + + public void FailActiveDescendants(ActivityEntry entry) + { + _tree.FailActiveDescendants(entry); + } + + public void Stop() + { + _console.Write(_tree); + } +} diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Console/SnapshotActivitySinkFactory.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Console/SnapshotActivitySinkFactory.cs new file mode 100644 index 00000000000..6148494fd4b --- /dev/null +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Console/SnapshotActivitySinkFactory.cs @@ -0,0 +1,14 @@ +namespace ChilliCream.Nitro.CommandLine.Tests.Console; + +internal sealed class SnapshotActivitySinkFactory : IActivitySinkFactory +{ + public IActivitySink Create(INitroConsole console, bool isInteractive) + { + if (isInteractive) + { + return new SnapshotActivitySink(console); + } + + return new StreamingActivitySink(console); + } +} diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/HttpClient/NitroClientRegistrationTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/HttpClient/NitroClientRegistrationTests.cs index 01a9f0a7d86..e6e30a3c6b1 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/HttpClient/NitroClientRegistrationTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/HttpClient/NitroClientRegistrationTests.cs @@ -14,6 +14,7 @@ using ChilliCream.Nitro.Client.Workspaces; using ChilliCream.Nitro.CommandLine.Services; using ChilliCream.Nitro.CommandLine.Services.Sessions; +using ChilliCream.Nitro.CommandLine.Tests.Console; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Moq; @@ -66,18 +67,24 @@ public async Task ExecuteAsync_Should_PreferApiKey_Over_SessionToken() Assert.False(client.DefaultRequestHeaders.Contains("Authorization")); } - [Fact] - public async Task ExecuteAsync_Should_UseExplicitCloudUrl() + [Theory] + [InlineData("custom.host.com", "https://custom.host.com/graphql")] + [InlineData("https://custom.host.com", "https://custom.host.com/graphql")] + [InlineData("http://custom.host.com", "http://custom.host.com/graphql")] + [InlineData("http://custom.host.com/graphql", "http://custom.host.com/graphql")] + [InlineData("https://custom.host.com/graphql", "https://custom.host.com/graphql")] + [InlineData("custom.host.com/graphql", "https://custom.host.com/graphql")] + [InlineData("https://custom.host.com/some/path", "https://custom.host.com/graphql")] + [InlineData("https://custom.host.com/graphql?foo=bar", "https://custom.host.com/graphql")] + public async Task ExecuteAsync_Should_NormalizeCloudUrl(string input, string expected) { // Act await using var provider = await BuildAndExecuteAsync( - ["--api-key", "x", "--cloud-url", "custom.host.com"]); + ["--api-key", "x", "--cloud-url", input]); using var client = CreateApiClient(provider); // Assert - Assert.Equal( - new Uri("https://custom.host.com/graphql"), - client.BaseAddress); + Assert.Equal(new Uri(expected), client.BaseAddress); } [Fact] @@ -110,13 +117,15 @@ public async Task ExecuteAsync_Should_UseDefaultUrl_When_NoSessionAndNoExplicitU } [Fact] - public async Task ExecuteAsync_Should_Throw_When_NoAuthAvailable() + public async Task ExecuteAsync_Should_NotSetAuth_When_NoAuthAvailable() { - // Arrange + // Act await using var provider = await BuildAndExecuteAsync([]); + using var client = CreateApiClient(provider); - // Act & Assert - Assert.Throws(() => CreateApiClient(provider)); + // Assert + Assert.False(client.DefaultRequestHeaders.Contains("CCC-api-key")); + Assert.False(client.DefaultRequestHeaders.Contains("Authorization")); } private static async Task BuildAndExecuteAsync( @@ -155,7 +164,11 @@ private static async Task BuildAndExecuteAsync( var testConsole = new TestConsole(); var errorConsole = new TestConsole(); services.AddSingleton( - new NitroConsole(testConsole, errorConsole, new EnvironmentVariableProvider())); + new NitroConsole( + testConsole, + errorConsole, + new EnvironmentVariableProvider(), + new SnapshotActivitySinkFactory())); var provider = services.BuildServiceProvider(); var rootCommand = new NitroRootCommand(); diff --git a/src/Nitro/CommandLine/test/CommandLine.Tests/Options/EnvironmentVariableDefaultTests.cs b/src/Nitro/CommandLine/test/CommandLine.Tests/Options/EnvironmentVariableDefaultTests.cs index b33aa74fe0a..2b03b9078b6 100644 --- a/src/Nitro/CommandLine/test/CommandLine.Tests/Options/EnvironmentVariableDefaultTests.cs +++ b/src/Nitro/CommandLine/test/CommandLine.Tests/Options/EnvironmentVariableDefaultTests.cs @@ -2,6 +2,7 @@ using ChilliCream.Nitro.Client; using ChilliCream.Nitro.CommandLine.Services; using ChilliCream.Nitro.CommandLine.Services.Sessions; +using ChilliCream.Nitro.CommandLine.Tests.Console; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Moq; @@ -41,7 +42,11 @@ public async Task DefaultFromEnvironmentValue_Should_ReadFromProvider_When_Envir var testConsole = new TestConsole(); var errorConsole = new TestConsole(); services.AddSingleton( - new NitroConsole(testConsole, errorConsole, envProviderMock.Object)); + new NitroConsole( + testConsole, + errorConsole, + envProviderMock.Object, + new SnapshotActivitySinkFactory())); await using var provider = services.BuildServiceProvider(); diff --git a/src/StrawberryShake/Client/src/Core/CachePolicy.Defaults.cs b/src/StrawberryShake/Client/src/Core/CachePolicy.Defaults.cs index 87ff4d24c5b..dad25bfee00 100644 --- a/src/StrawberryShake/Client/src/Core/CachePolicy.Defaults.cs +++ b/src/StrawberryShake/Client/src/Core/CachePolicy.Defaults.cs @@ -13,7 +13,7 @@ public static CachePolicy Default(IStoreAccessor storeAccessor, TimeSpan timeToL var lastClean = DateTime.UtcNow; var cleaning = false; - return new(storeAccessor.OperationStore.Watch().Subscribe(result => + return new(storeAccessor.OperationStore.Watch().Subscribe(_ => { var time = DateTime.UtcNow; diff --git a/src/StrawberryShake/Client/src/Core/IConnection.cs b/src/StrawberryShake/Client/src/Core/IConnection.cs index 462e088e30b..68ebabe3662 100644 --- a/src/StrawberryShake/Client/src/Core/IConnection.cs +++ b/src/StrawberryShake/Client/src/Core/IConnection.cs @@ -4,7 +4,9 @@ namespace StrawberryShake; /// A connection represents a transport connection to a GraphQL server and allows to execute /// requests against it. /// -/// +/// +/// The type of the response body. +/// public interface IConnection where TResponseBody : class { /// diff --git a/src/StrawberryShake/Client/src/Core/IEntityStoreSnapshot.cs b/src/StrawberryShake/Client/src/Core/IEntityStoreSnapshot.cs index 10c0ad50e4c..1014c0ac0c2 100644 --- a/src/StrawberryShake/Client/src/Core/IEntityStoreSnapshot.cs +++ b/src/StrawberryShake/Client/src/Core/IEntityStoreSnapshot.cs @@ -54,7 +54,9 @@ bool TryGetEntity(EntityId id, [NotNullWhen(true)] out TEntity? entity) /// /// The entity type. /// - /// + /// + /// The list of tracked entities matching the provided IDs. + /// IReadOnlyList GetEntities(IEnumerable ids) where TEntity : class; diff --git a/src/StrawberryShake/Client/src/Core/Internal/ArrayWriter.cs b/src/StrawberryShake/Client/src/Core/Internal/ArrayWriter.cs index 39ba3a84c30..cd6a943019c 100644 --- a/src/StrawberryShake/Client/src/Core/Internal/ArrayWriter.cs +++ b/src/StrawberryShake/Client/src/Core/Internal/ArrayWriter.cs @@ -137,7 +137,9 @@ public Span GetSpan(int sizeHint = 0) /// /// Gets the buffer as an /// - /// + /// + /// The written data as an . + /// public ArraySegment ToArraySegment() => new(_buffer, 0, _start); /// diff --git a/src/StrawberryShake/Client/src/Core/OperationResult.cs b/src/StrawberryShake/Client/src/Core/OperationResult.cs index 06f8f4e1761..ca86ce58dc0 100644 --- a/src/StrawberryShake/Client/src/Core/OperationResult.cs +++ b/src/StrawberryShake/Client/src/Core/OperationResult.cs @@ -78,7 +78,7 @@ public OperationResult( /// /// The data info. /// - /// + /// A new result with the updated data. public IOperationResult WithData(T data, IOperationResultDataInfo dataInfo) => new OperationResult(data, dataInfo, DataFactory, Errors, Extensions, ContextData); } diff --git a/src/StrawberryShake/Client/src/Core/Serialization/UploadSerializer.cs b/src/StrawberryShake/Client/src/Core/Serialization/UploadSerializer.cs index c7569459f96..1d538853fa9 100644 --- a/src/StrawberryShake/Client/src/Core/Serialization/UploadSerializer.cs +++ b/src/StrawberryShake/Client/src/Core/Serialization/UploadSerializer.cs @@ -8,7 +8,7 @@ public class UploadSerializer : ScalarSerializer /// /// Creates a new instance of /// - /// + /// The name of the scalar type. public UploadSerializer(string typeName = BuiltInScalarNames.Upload) : base(typeName) { diff --git a/src/StrawberryShake/Client/src/Transport.Http/HttpConnection.cs b/src/StrawberryShake/Client/src/Transport.Http/HttpConnection.cs index f06725c2ff3..1fff3518d5c 100644 --- a/src/StrawberryShake/Client/src/Transport.Http/HttpConnection.cs +++ b/src/StrawberryShake/Client/src/Transport.Http/HttpConnection.cs @@ -152,10 +152,7 @@ protected virtual Response CreateResponse( MapVariables(list); } - if (copy is not null) - { - copy[variable.Key] = value; - } + copy?[variable.Key] = value; } return copy ?? variables; diff --git a/src/StrawberryShake/Client/src/Transport.WebSockets/ISocketClient.cs b/src/StrawberryShake/Client/src/Transport.WebSockets/ISocketClient.cs index a6ce36177fc..06d92f6a4d0 100644 --- a/src/StrawberryShake/Client/src/Transport.WebSockets/ISocketClient.cs +++ b/src/StrawberryShake/Client/src/Transport.WebSockets/ISocketClient.cs @@ -67,7 +67,7 @@ Task OpenAsync( /// A message why the connection was closes /// The close status on how the socket was closes /// The cancellation token to cancel the operation - /// + /// A task representing the asynchronous close operation. Task CloseAsync( string message, SocketCloseStatus closeStatus, diff --git a/src/StrawberryShake/Client/src/Transport.WebSockets/ISocketProtocol.cs b/src/StrawberryShake/Client/src/Transport.WebSockets/ISocketProtocol.cs index a0df8ec21f1..f4ac2f75089 100644 --- a/src/StrawberryShake/Client/src/Transport.WebSockets/ISocketProtocol.cs +++ b/src/StrawberryShake/Client/src/Transport.WebSockets/ISocketProtocol.cs @@ -83,7 +83,7 @@ Task StopOperationAsync( /// /// Unsubscribes a listener from the protocol.. /// - /// + /// The listener to remove from the protocol. void Unsubscribe(OnReceiveAsync listener); /// diff --git a/src/StrawberryShake/Client/src/Transport.WebSockets/Protocols/GraphQLWebSocket/GraphQLWebSocketMessageParser.cs b/src/StrawberryShake/Client/src/Transport.WebSockets/Protocols/GraphQLWebSocket/GraphQLWebSocketMessageParser.cs index 3729fec52af..69ed5f449e5 100644 --- a/src/StrawberryShake/Client/src/Transport.WebSockets/Protocols/GraphQLWebSocket/GraphQLWebSocketMessageParser.cs +++ b/src/StrawberryShake/Client/src/Transport.WebSockets/Protocols/GraphQLWebSocket/GraphQLWebSocketMessageParser.cs @@ -43,7 +43,7 @@ private GraphQLWebSocketMessageParser(ReadOnlySequence messageData) /// /// Parses the message out of the sequence /// - /// + /// The parsed message. /// /// Thrown when an invalid token, an unknown field or the type is not specified /// diff --git a/src/StrawberryShake/Client/src/Transport.WebSockets/WebSocketConnection.cs b/src/StrawberryShake/Client/src/Transport.WebSockets/WebSocketConnection.cs index b39782523e9..316fb21ea6b 100644 --- a/src/StrawberryShake/Client/src/Transport.WebSockets/WebSocketConnection.cs +++ b/src/StrawberryShake/Client/src/Transport.WebSockets/WebSocketConnection.cs @@ -15,7 +15,9 @@ public class WebSocketConnection : IWebSocketConnection /// /// Creates a new instance of a /// - /// + /// + /// A factory delegate that creates a new for the WebSocket connection. + /// public WebSocketConnection(Func> sessionFactory) { _sessionFactory = sessionFactory ?? diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/Builders/TypeReferenceBuilder.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/Builders/TypeReferenceBuilder.cs index fed4e971b77..5be492de3e4 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/Builders/TypeReferenceBuilder.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration.CSharp/Builders/TypeReferenceBuilder.cs @@ -27,11 +27,6 @@ public TypeReferenceBuilder SkipTrailingSpace() return this; } - public TypeReferenceBuilder SetNameSpace(string @namespace) - { - return this; - } - public TypeReferenceBuilder SetListType() { _buildOrder.Push(TypeKindToken.List); diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FieldCollector.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FieldCollector.cs index 6fdf84008ea..4b012847c20 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FieldCollector.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FieldCollector.cs @@ -188,7 +188,7 @@ internal static void ResolveFieldSelection( } } - private static bool IsConditional(IHasDirectives hasDirectives) => false; + private static bool IsConditional(IHasDirectives _) => false; private void ResolveFragmentSpread( FragmentSpreadNode fragmentSpreadSyntax, diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/InputFieldModel.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/InputFieldModel.cs index f24d7a23efa..d7ab35feaad 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/InputFieldModel.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/Models/InputFieldModel.cs @@ -13,8 +13,8 @@ public class InputFieldModel : IFieldModel /// /// The property name. /// The property xml documentation summary. - /// - /// + /// The GraphQL input value definition. + /// The GraphQL input type of the field. public InputFieldModel( string name, string? description, diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/ClientDescriptor.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/ClientDescriptor.cs index 0d985069623..366bee2f991 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/ClientDescriptor.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/ClientDescriptor.cs @@ -22,7 +22,6 @@ public ClientDescriptor( /// /// Gets the client name /// - /// public string Name => RuntimeType.Name; /// diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/DataTypeDescriptor.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/DataTypeDescriptor.cs index 5ba4651a5b9..2760482cc80 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/DataTypeDescriptor.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/DataTypeDescriptor.cs @@ -8,17 +8,23 @@ public sealed class DataTypeDescriptor : ICodeDescriptor /// Describes the DataType /// /// - /// + /// The name of the data type. /// /// - /// + /// The namespace of the data type. /// /// /// The types that are subsets of the DataType represented by this descriptor. /// - /// - /// - /// + /// + /// The interfaces that the data type implements. + /// + /// + /// The XML documentation of the data type. + /// + /// + /// Indicates whether the data type is an interface. + /// public DataTypeDescriptor( string name, string @namespace, diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/EntityIdDescriptor.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/EntityIdDescriptor.cs index a0d722e37ec..0c9ad991a5f 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/EntityIdDescriptor.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/EntityIdDescriptor.cs @@ -30,7 +30,6 @@ public EntityIdDescriptor( /// /// Gets the name of the field or entity. /// - /// public string Name { get; } /// diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/EntityTypeDescriptor.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/EntityTypeDescriptor.cs index 9323b03ad9d..54e6ad3c0d3 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/EntityTypeDescriptor.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/EntityTypeDescriptor.cs @@ -10,7 +10,7 @@ public sealed class EntityTypeDescriptor : ICodeDescriptor /// /// The name of the GraphQL type /// - /// + /// The runtime type information of the entity. /// /// The properties of this entity. /// diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/ScalarEntityIdDescriptor.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/ScalarEntityIdDescriptor.cs index e316a781dda..a51728821ac 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/ScalarEntityIdDescriptor.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Descriptors/ScalarEntityIdDescriptor.cs @@ -30,7 +30,6 @@ public ScalarEntityIdDescriptor( /// /// Gets the name of the field or entity. /// - /// public string Name { get; } /// diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ErrorHelper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ErrorHelper.cs index 0545d5c2300..e1e47b84c09 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ErrorHelper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ErrorHelper.cs @@ -13,7 +13,9 @@ public static class ErrorHelper public static IError WithFileReference( this IError error, +#pragma warning disable RCS1163 IDictionary fileLookup) +#pragma warning restore RCS1163 { var extensions = ImmutableOrderedDictionary.Empty .Add(TitleExtensionKey, "Schema validation error") diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ICSharpSyntaxGenerator.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ICSharpSyntaxGenerator.cs index fb472e18797..340e8c668d5 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ICSharpSyntaxGenerator.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/ICSharpSyntaxGenerator.cs @@ -13,7 +13,10 @@ public interface ICSharpSyntaxGenerator /// /// Settings for the code generation /// - /// + /// + /// true if this generator can handle the descriptor; + /// otherwise, false. + /// bool CanHandle( ICodeDescriptor descriptor, CSharpSyntaxGeneratorSettings settings); diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/StoreAccessorMapper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/StoreAccessorMapper.cs index 6d0a9badefb..f3d30acc1ee 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/StoreAccessorMapper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Mappers/StoreAccessorMapper.cs @@ -6,7 +6,7 @@ namespace StrawberryShake.CodeGeneration.Mappers; public static class StoreAccessorMapper { public static void Map( - ClientModel model, + ClientModel _, IMapperContext context) { context.Register( diff --git a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/OperationDocumentHelper.cs b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/OperationDocumentHelper.cs index e07d8bd063d..6b61f955392 100644 --- a/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/OperationDocumentHelper.cs +++ b/src/StrawberryShake/CodeGeneration/src/CodeGeneration/Utilities/OperationDocumentHelper.cs @@ -21,7 +21,7 @@ internal static class OperationDocumentHelper /// /// The schema to validate queries against. /// - /// + /// The merged operation documents. /// public static OperationDocuments CreateOperationDocuments( IEnumerable documents, From e4a87d8e251de3142c798eb56ac45644596faac0 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Fri, 17 Apr 2026 20:59:05 +0800 Subject: [PATCH 5/6] Fix tests broken by default-security environment check change Tests using introspection queries in CreateServices helpers needed explicit DisableIntrospection(disable: false) override after the environment null-check was flipped from == false to != true. Also replace non-deterministic ID snapshots in DefaultSecurity cycle-depth tests with a direct Errors assertion. --- .../AnnotationBasedAuthorizationTests.cs | 1 + .../CodeFirstAuthorizationTests.cs | 1 + ...orBuilderExtensions_OptInFeatures.Tests.cs | 1 + .../DefaultSecurityTests.cs | 7 +- ...Production_FieldCycleDepthIsNotEnforced.md | 411 ------------------ ...evelopment_FieldCycleDepthIsNotEnforced.md | 411 ------------------ 6 files changed, 8 insertions(+), 824 deletions(-) delete mode 100644 src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_Disabled_InProduction_FieldCycleDepthIsNotEnforced.md delete mode 100644 src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_InDevelopment_FieldCycleDepthIsNotEnforced.md diff --git a/src/HotChocolate/Core/test/Authorization.Tests/AnnotationBasedAuthorizationTests.cs b/src/HotChocolate/Core/test/Authorization.Tests/AnnotationBasedAuthorizationTests.cs index dde463590e6..88206aaeb28 100644 --- a/src/HotChocolate/Core/test/Authorization.Tests/AnnotationBasedAuthorizationTests.cs +++ b/src/HotChocolate/Core/test/Authorization.Tests/AnnotationBasedAuthorizationTests.cs @@ -977,6 +977,7 @@ private static IServiceProvider CreateServices( Action? configure = null) => new ServiceCollection() .AddGraphQLServer() + .DisableIntrospection(disable: false) .AddQueryType() .AddUnionType() .AddType() diff --git a/src/HotChocolate/Core/test/Authorization.Tests/CodeFirstAuthorizationTests.cs b/src/HotChocolate/Core/test/Authorization.Tests/CodeFirstAuthorizationTests.cs index bc330f47a1c..499389a6e2e 100644 --- a/src/HotChocolate/Core/test/Authorization.Tests/CodeFirstAuthorizationTests.cs +++ b/src/HotChocolate/Core/test/Authorization.Tests/CodeFirstAuthorizationTests.cs @@ -462,6 +462,7 @@ private static IServiceProvider CreateServices( Action? configure = null) => new ServiceCollection() .AddGraphQLServer() + .DisableIntrospection(disable: false) .AddQueryType() .AddGlobalObjectIdentification(o => o.EnsureAllNodesCanBeResolved = false) .AddAuthorizationHandler(_ => handler) diff --git a/src/HotChocolate/Core/test/Execution.Tests/DependencyInjection/RequestExecutorBuilderExtensions_OptInFeatures.Tests.cs b/src/HotChocolate/Core/test/Execution.Tests/DependencyInjection/RequestExecutorBuilderExtensions_OptInFeatures.Tests.cs index 430e77f3290..bb6ac06f90c 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/DependencyInjection/RequestExecutorBuilderExtensions_OptInFeatures.Tests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/DependencyInjection/RequestExecutorBuilderExtensions_OptInFeatures.Tests.cs @@ -39,6 +39,7 @@ public async Task ExecuteRequestAsync_OptInFeatureStability_MatchesSnapshot() { (await new ServiceCollection() .AddGraphQLServer() + .DisableIntrospection(disable: false) .ModifyOptions(o => o.EnableOptInFeatures = true) .AddQueryType(d => d.Name("Query").Field("foo").Resolve("bar")) .OptInFeatureStability("feature1", "stability1") diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/DefaultSecurityTests.cs b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/DefaultSecurityTests.cs index 692dc1dc11c..673acfcfe4e 100644 --- a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/DefaultSecurityTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/DefaultSecurityTests.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using HotChocolate.AspNetCore; using HotChocolate.Transport.Http; using Microsoft.Extensions.DependencyInjection; @@ -243,7 +244,8 @@ public async Task DefaultSecurity_InDevelopment_FieldCycleDepthIsNotEnforced() // assert - query passes validation and executes (no HC0087 error) using var response = await result.ReadAsResultAsync(); - response.MatchMarkdownSnapshot(); + Assert.Equal(JsonValueKind.Undefined, response.Errors.ValueKind); + Assert.Equal(JsonValueKind.Object, response.Data.ValueKind); } [Fact] @@ -288,6 +290,7 @@ public async Task DefaultSecurity_Disabled_InProduction_FieldCycleDepthIsNotEnfo // assert - query passes validation and executes (no HC0087 error) using var response = await result.ReadAsResultAsync(); - response.MatchMarkdownSnapshot(); + Assert.Equal(JsonValueKind.Undefined, response.Errors.ValueKind); + Assert.Equal(JsonValueKind.Object, response.Data.ValueKind); } } diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_Disabled_InProduction_FieldCycleDepthIsNotEnforced.md b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_Disabled_InProduction_FieldCycleDepthIsNotEnforced.md deleted file mode 100644 index 1cc6c4fece0..00000000000 --- a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_Disabled_InProduction_FieldCycleDepthIsNotEnforced.md +++ /dev/null @@ -1,411 +0,0 @@ -# DefaultSecurity_Disabled_InProduction_FieldCycleDepthIsNotEnforced - -```text -{ - "data": { - "human": { - "relatives": [ - { - "relatives": [ - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46NTk=" - }, - { - "name": "Human: SHVtYW46NjA=" - }, - { - "name": "Human: SHVtYW46NjE=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NjI=" - }, - { - "name": "Human: SHVtYW46NjM=" - }, - { - "name": "Human: SHVtYW46NjQ=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NjU=" - }, - { - "name": "Human: SHVtYW46NjY=" - }, - { - "name": "Human: SHVtYW46Njc=" - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46NTA=" - }, - { - "name": "Human: SHVtYW46NTE=" - }, - { - "name": "Human: SHVtYW46NTI=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NTM=" - }, - { - "name": "Human: SHVtYW46NTQ=" - }, - { - "name": "Human: SHVtYW46NTU=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NTY=" - }, - { - "name": "Human: SHVtYW46NTc=" - }, - { - "name": "Human: SHVtYW46NTg=" - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46NDE=" - }, - { - "name": "Human: SHVtYW46NDI=" - }, - { - "name": "Human: SHVtYW46NDM=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NDQ=" - }, - { - "name": "Human: SHVtYW46NDU=" - }, - { - "name": "Human: SHVtYW46NDY=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NDc=" - }, - { - "name": "Human: SHVtYW46NDg=" - }, - { - "name": "Human: SHVtYW46NDk=" - } - ] - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46ODY=" - }, - { - "name": "Human: SHVtYW46ODc=" - }, - { - "name": "Human: SHVtYW46ODg=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46ODk=" - }, - { - "name": "Human: SHVtYW46OTA=" - }, - { - "name": "Human: SHVtYW46OTE=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46OTI=" - }, - { - "name": "Human: SHVtYW46OTM=" - }, - { - "name": "Human: SHVtYW46OTQ=" - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46Nzc=" - }, - { - "name": "Human: SHVtYW46Nzg=" - }, - { - "name": "Human: SHVtYW46Nzk=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46ODA=" - }, - { - "name": "Human: SHVtYW46ODE=" - }, - { - "name": "Human: SHVtYW46ODI=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46ODM=" - }, - { - "name": "Human: SHVtYW46ODQ=" - }, - { - "name": "Human: SHVtYW46ODU=" - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46Njg=" - }, - { - "name": "Human: SHVtYW46Njk=" - }, - { - "name": "Human: SHVtYW46NzA=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NzE=" - }, - { - "name": "Human: SHVtYW46NzI=" - }, - { - "name": "Human: SHVtYW46NzM=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NzQ=" - }, - { - "name": "Human: SHVtYW46NzU=" - }, - { - "name": "Human: SHVtYW46NzY=" - } - ] - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46MTEz" - }, - { - "name": "Human: SHVtYW46MTE0" - }, - { - "name": "Human: SHVtYW46MTE1" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46MTE2" - }, - { - "name": "Human: SHVtYW46MTE3" - }, - { - "name": "Human: SHVtYW46MTE4" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46MTE5" - }, - { - "name": "Human: SHVtYW46MTIw" - }, - { - "name": "Human: SHVtYW46MTIx" - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46MTA0" - }, - { - "name": "Human: SHVtYW46MTA1" - }, - { - "name": "Human: SHVtYW46MTA2" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46MTA3" - }, - { - "name": "Human: SHVtYW46MTA4" - }, - { - "name": "Human: SHVtYW46MTA5" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46MTEw" - }, - { - "name": "Human: SHVtYW46MTEx" - }, - { - "name": "Human: SHVtYW46MTEy" - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46OTU=" - }, - { - "name": "Human: SHVtYW46OTY=" - }, - { - "name": "Human: SHVtYW46OTc=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46OTg=" - }, - { - "name": "Human: SHVtYW46OTk=" - }, - { - "name": "Human: SHVtYW46MTAw" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46MTAx" - }, - { - "name": "Human: SHVtYW46MTAy" - }, - { - "name": "Human: SHVtYW46MTAz" - } - ] - } - ] - } - ] - } - ] - } - } -} -``` diff --git a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_InDevelopment_FieldCycleDepthIsNotEnforced.md b/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_InDevelopment_FieldCycleDepthIsNotEnforced.md deleted file mode 100644 index 716146a1d51..00000000000 --- a/src/HotChocolate/Fusion/test/Fusion.AspNetCore.Tests/__snapshots__/DefaultSecurityTests.DefaultSecurity_InDevelopment_FieldCycleDepthIsNotEnforced.md +++ /dev/null @@ -1,411 +0,0 @@ -# DefaultSecurity_InDevelopment_FieldCycleDepthIsNotEnforced - -```text -{ - "data": { - "human": { - "relatives": [ - { - "relatives": [ - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46NTk=" - }, - { - "name": "Human: SHVtYW46NjA=" - }, - { - "name": "Human: SHVtYW46NjE=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NjI=" - }, - { - "name": "Human: SHVtYW46NjM=" - }, - { - "name": "Human: SHVtYW46NjQ=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NjU=" - }, - { - "name": "Human: SHVtYW46NjY=" - }, - { - "name": "Human: SHVtYW46Njc=" - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46NTA=" - }, - { - "name": "Human: SHVtYW46NTE=" - }, - { - "name": "Human: SHVtYW46NTI=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NTM=" - }, - { - "name": "Human: SHVtYW46NTQ=" - }, - { - "name": "Human: SHVtYW46NTU=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NTY=" - }, - { - "name": "Human: SHVtYW46NTc=" - }, - { - "name": "Human: SHVtYW46NTg=" - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46NDE=" - }, - { - "name": "Human: SHVtYW46NDI=" - }, - { - "name": "Human: SHVtYW46NDM=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NDQ=" - }, - { - "name": "Human: SHVtYW46NDU=" - }, - { - "name": "Human: SHVtYW46NDY=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NDc=" - }, - { - "name": "Human: SHVtYW46NDg=" - }, - { - "name": "Human: SHVtYW46NDk=" - } - ] - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46ODY=" - }, - { - "name": "Human: SHVtYW46ODc=" - }, - { - "name": "Human: SHVtYW46ODg=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46ODk=" - }, - { - "name": "Human: SHVtYW46OTA=" - }, - { - "name": "Human: SHVtYW46OTE=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46OTI=" - }, - { - "name": "Human: SHVtYW46OTM=" - }, - { - "name": "Human: SHVtYW46OTQ=" - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46Nzc=" - }, - { - "name": "Human: SHVtYW46Nzg=" - }, - { - "name": "Human: SHVtYW46Nzk=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46ODA=" - }, - { - "name": "Human: SHVtYW46ODE=" - }, - { - "name": "Human: SHVtYW46ODI=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46ODM=" - }, - { - "name": "Human: SHVtYW46ODQ=" - }, - { - "name": "Human: SHVtYW46ODU=" - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46Njg=" - }, - { - "name": "Human: SHVtYW46Njk=" - }, - { - "name": "Human: SHVtYW46NzA=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NzE=" - }, - { - "name": "Human: SHVtYW46NzI=" - }, - { - "name": "Human: SHVtYW46NzM=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46NzQ=" - }, - { - "name": "Human: SHVtYW46NzU=" - }, - { - "name": "Human: SHVtYW46NzY=" - } - ] - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46MTEz" - }, - { - "name": "Human: SHVtYW46MTE0" - }, - { - "name": "Human: SHVtYW46MTE1" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46MTE2" - }, - { - "name": "Human: SHVtYW46MTE3" - }, - { - "name": "Human: SHVtYW46MTE4" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46MTE5" - }, - { - "name": "Human: SHVtYW46MTIw" - }, - { - "name": "Human: SHVtYW46MTIx" - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46MTA0" - }, - { - "name": "Human: SHVtYW46MTA1" - }, - { - "name": "Human: SHVtYW46MTA2" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46MTA3" - }, - { - "name": "Human: SHVtYW46MTA4" - }, - { - "name": "Human: SHVtYW46MTA5" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46MTEw" - }, - { - "name": "Human: SHVtYW46MTEx" - }, - { - "name": "Human: SHVtYW46MTEy" - } - ] - } - ] - }, - { - "relatives": [ - { - "relatives": [ - { - "name": "Human: SHVtYW46OTU=" - }, - { - "name": "Human: SHVtYW46OTY=" - }, - { - "name": "Human: SHVtYW46OTc=" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46OTg=" - }, - { - "name": "Human: SHVtYW46OTk=" - }, - { - "name": "Human: SHVtYW46MTAw" - } - ] - }, - { - "relatives": [ - { - "name": "Human: SHVtYW46MTAx" - }, - { - "name": "Human: SHVtYW46MTAy" - }, - { - "name": "Human: SHVtYW46MTAz" - } - ] - } - ] - } - ] - } - ] - } - } -} -``` From d5e7e5eb36e382aa1809ad1dc401bcca532fd4f7 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Sat, 18 Apr 2026 00:59:57 +0800 Subject: [PATCH 6/6] Make tests more stable --- .../StarWarsCodeFirstTests.cs | 17 ++---- .../OperationCompilerSingleFlightTests.cs | 55 +++++++++---------- 2 files changed, 31 insertions(+), 41 deletions(-) diff --git a/src/HotChocolate/Core/test/Execution.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs index 0bd5f511671..2d827999f71 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Integration/StarWarsCodeFirst/StarWarsCodeFirstTests.cs @@ -822,7 +822,9 @@ public async Task SubscribeToReview() } """); - var results = subscriptionResult.ReadResultsAsync(); + // Get the enumerator before publishing so the consumer is registered + // and won't race with event dispatch. + await using var enumerator = subscriptionResult.ReadResultsAsync().GetAsyncEnumerator(); await executor.ExecuteAsync( """ @@ -834,17 +836,8 @@ await executor.ExecuteAsync( } """); - OperationResult? eventResult = null; - - using (var cts = new CancellationTokenSource(2000)) - { - await foreach (var queryResult in results.WithCancellation(cts.Token) - .ConfigureAwait(false)) - { - eventResult = queryResult; - break; - } - } + Assert.True(await enumerator.MoveNextAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(30))); + var eventResult = enumerator.Current; snapshot.Add(eventResult); diff --git a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/OperationCompilerSingleFlightTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/OperationCompilerSingleFlightTests.cs index 8c04aeb9a51..c194ce23032 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Pipeline/OperationCompilerSingleFlightTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Pipeline/OperationCompilerSingleFlightTests.cs @@ -152,10 +152,11 @@ query FailureCoalesce { public async Task Follower_Cancellation_Should_Not_Cancel_Leader_Compilation() { // arrange - using var leaderCts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); - using var followerCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(200)); + using var testCts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + using var followerCts = new CancellationTokenSource(); var compileCount = 0; var compileGate = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var leaderEntered = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var executor = await new ServiceCollection() .AddGraphQL() @@ -163,13 +164,23 @@ public async Task Follower_Cancellation_Should_Not_Cancel_Leader_Compilation() .UseDefaultPipeline() .AddDiagnosticEventListener(_ => new CompileCountListener(() => Interlocked.Increment(ref compileCount))) .UseRequest( - (_, next) => CreateBlockingMiddleware(next, compileGate), + (_, next) => async context => + { + // Only block the leader. + if (context.Features.Get>() is not null) + { + leaderEntered.TrySetResult(); + await compileGate.Task; + } + + await next(context); + }, key: "Blocking", before: WellKnownRequestMiddleware.OperationResolverMiddleware, allowMultiple: true) .Services .BuildServiceProvider() - .GetRequestExecutorAsync(cancellationToken: leaderCts.Token); + .GetRequestExecutorAsync(cancellationToken: testCts.Token); const string operationText = """ @@ -180,27 +191,27 @@ query CancelFollowerOnly { // act var leaderTask = Task.Run( - () => executor.ExecuteAsync(operationText, leaderCts.Token), + () => executor.ExecuteAsync(operationText, testCts.Token), CancellationToken.None); - // Give the leader time to enter the pipeline and register in-flight. - await Task.Delay(50, leaderCts.Token); + // Wait for the leader to enter the pipeline and register in-flight. + await leaderEntered.Task.WaitAsync(testCts.Token); var followerTask = Task.Run( () => executor.ExecuteAsync(operationText, followerCts.Token), CancellationToken.None); - // Wait for the follower to cancel. - var followerCompletion = await Task.WhenAny( - followerTask, - Task.Delay(TimeSpan.FromSeconds(3), leaderCts.Token)); + // Give the follower a brief moment to register as a waiter behind the leader, + // then explicitly cancel it. + await Task.Delay(50, testCts.Token); + followerCts.Cancel(); + + // Wait for the follower to observe cancellation (hang-guard only; normal completion is fast). + var followerResult = await followerTask.WaitAsync(testCts.Token); // Release the leader. compileGate.TrySetResult(); - Assert.Same(followerTask, followerCompletion); - - var followerResult = await followerTask; - var leaderResult = await leaderTask; + var leaderResult = await leaderTask.WaitAsync(testCts.Token); // assert var followerErrors = Assert.IsType(followerResult).Errors; @@ -245,20 +256,6 @@ private static RequestDelegate CreateThrowingMiddleware( await next(context); }; - private static RequestDelegate CreateBlockingMiddleware( - RequestDelegate next, - TaskCompletionSource gate) - => async context => - { - // Only block the leader. - if (context.Features.Get>() is not null) - { - await gate.Task; - } - - await next(context); - }; - private sealed class RequestGate(int expectedRequests) { private readonly TaskCompletionSource _allArrived =