diff --git a/build/dependencies.private.props b/build/dependencies.private.props index 78c413be1..e62f55d68 100644 --- a/build/dependencies.private.props +++ b/build/dependencies.private.props @@ -8,8 +8,8 @@ 8.0.0 17.11.0 4.20.70 - 2.4.2 - 2.4.2 + 2.8.1 + 2.8.2 4.2.2 8.0.8 4.5.4 diff --git a/build/dependencies.props b/build/dependencies.props index 451dfddd2..6a30ff89c 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -54,7 +54,7 @@ 1.1.1 - 1.1.1 + 2.0.0-beta4.22272.1 8.0.11 diff --git a/src/Microsoft.Azure.SignalR.Emulator/Microsoft.Azure.SignalR.Emulator.csproj b/src/Microsoft.Azure.SignalR.Emulator/Microsoft.Azure.SignalR.Emulator.csproj index 6d18f412a..9f52ad2ef 100644 --- a/src/Microsoft.Azure.SignalR.Emulator/Microsoft.Azure.SignalR.Emulator.csproj +++ b/src/Microsoft.Azure.SignalR.Emulator/Microsoft.Azure.SignalR.Emulator.csproj @@ -14,9 +14,7 @@ - + - - diff --git a/src/Microsoft.Azure.SignalR.Emulator/Program.cs b/src/Microsoft.Azure.SignalR.Emulator/Program.cs index 97a0d9b13..d83400a87 100644 --- a/src/Microsoft.Azure.SignalR.Emulator/Program.cs +++ b/src/Microsoft.Azure.SignalR.Emulator/Program.cs @@ -2,15 +2,18 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.CommandLine; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net; +using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; +#nullable enable namespace Microsoft.Azure.SignalR.Emulator { public class Program @@ -19,189 +22,199 @@ public class Program private static readonly string SettingsFileName = "settings.json"; private static readonly string SettingsFile = Path.GetFullPath(SettingsFileName); private static readonly string AppSettingsFile = Path.Combine(AppContext.BaseDirectory, "appsettings.json"); - internal static readonly string ProgramDefaultSettingsFile = Path.Combine(AppContext.BaseDirectory, SettingsFileName); - public static void Main(string[] args) + public static async Task Main(string[] args) { - var app = new CommandLineApplication(); - app.Name = "asrs-emulator"; - app.Description = "The local emulator for Azure SignalR Serverless features."; - app.HelpOption("-h|--help"); - - app.Command("upstream", command => + var rootCommand = new RootCommand("The local emulator for Azure SignalR Serverless features.") { - command.Description = "To init/list the upstream options"; - command.HelpOption("-h|--help"); - command.Command("init", c => - { - c.Description = "Init the default upstream options into a settings.json config. Use -o to specify the folder to export the default settings."; - var configOptions = c.Option("-o|--output", "Specify the folder to init the upstream settings file.", CommandOptionType.SingleValue); - c.HelpOption("-h|--help"); - c.OnExecute(() => - { - string outputFile = configOptions.HasValue() ? Path.GetFullPath(Path.Combine(configOptions.Value(), SettingsFileName)) : SettingsFile; - if (File.Exists(outputFile)) - { - Console.WriteLine($"Already contains '{outputFile}', still want to override it with the default one? (N/y)"); - if (Console.ReadKey().Key != ConsoleKey.Y) - { - return 0; - } + Name = "asrs-emulator" + }; - Console.WriteLine(); - } + rootCommand.AddCommand(CreateUpstreamCommand()); + rootCommand.AddCommand(CreateStartCommand()); - Directory.CreateDirectory(Path.GetDirectoryName(outputFile)); - File.Copy(ProgramDefaultSettingsFile, outputFile, true); + return await rootCommand.InvokeAsync(args); + } - Console.WriteLine($"Exported default settings to '{outputFile}'."); - return 0; - }); - }); - command.Command("list", c => - { - c.Description = "List current upstream options. Use -c to specify the folder or file to read the settings."; - var configOptions = c.Option("-c|--config", "Specify the upstream settings file to load from.", CommandOptionType.SingleValue); - c.HelpOption("-h|--help"); - c.OnExecute(() => - { - if (!TryGetConfigFilePath(configOptions, out var config)) - { - return 1; - } + private static Command CreateUpstreamCommand() + { + var upstreamCommand = new Command("upstream", "To init/list the upstream options") + { + CreateInitCommand(), + CreateListCommand() + }; - var host = CreateHostBuilder(args, null, DefaultPort, config).Build(); - - Console.WriteLine($"Loaded upstream settings from '{config}'"); - - var option = host.Services.GetRequiredService>(); - option.Value.Print(); - return 0; - }); - }); - }); + return upstreamCommand; + } - app.Command("start", command => + private static Command CreateInitCommand() + { + var outputOption = new Option( + new[] { "-o", "--output" }, + "Specify the folder to init the upstream settings file." + ); + + var initCommand = new Command("init", "Init the default upstream options into a settings.json config") { - command.Description = "To start the emulator."; - var portOptions = command.Option("-p|--port", "Specify the port to use.", CommandOptionType.SingleValue); - var ipOptions = command.Option("-i|--ip", "Specify the IP address to use.", CommandOptionType.SingleValue); - var configOptions = command.Option("-c|--config", "Specify the upstream settings file to load from.", CommandOptionType.SingleValue); - command.HelpOption("-h|--help"); - command.OnExecute(() => + outputOption + }; + + initCommand.SetHandler((string? output) => + { + string outputFile = !string.IsNullOrEmpty(output) + ? Path.GetFullPath(Path.Combine(output, SettingsFileName)) + : SettingsFile; + + if (File.Exists(outputFile)) { - if (!TryGetPort(portOptions, out var port) || !TryGetConfigFilePath(configOptions, out var config) || !TryGetIpAddress(ipOptions, out var ip)) + Console.WriteLine($"Already contains '{outputFile}', still want to override it with the default one? (N/y)"); + if (Console.ReadKey().Key != ConsoleKey.Y) { - return 1; + return; } - Console.WriteLine($"Loaded settings from '{config}'. Changes to the settings file will be hot-loaded into the emulator."); + Console.WriteLine(); + } - CreateHostBuilder(args, ip, port, config).Build().Run(); - return 0; - }); - }); + Directory.CreateDirectory(Path.GetDirectoryName(outputFile)!); + File.Copy(ProgramDefaultSettingsFile, outputFile, true); - app.OnExecute(() => + Console.WriteLine($"Exported default settings to '{outputFile}'."); + }, outputOption); + + return initCommand; + } + + private static Command CreateListCommand() + { + var configOption = new Option( + new[] { "-c", "--config" }, + "Specify the upstream settings file to load from." + ); + + var listCommand = new Command("list", "List current upstream options") { - app.ShowHelp(); - return 0; - }); + configOption + }; - try + listCommand.SetHandler((string? config) => { - app.Execute(args); - } - catch (Exception e) + if (!TryGetConfigFilePath(config, out var configFile)) + { + return; + } + + var host = CreateHostBuilder(null, null, DefaultPort, configFile).Build(); + + Console.WriteLine($"Loaded upstream settings from '{configFile}'"); + + var options = host.Services.GetRequiredService>(); + options.Value.Print(); + }, configOption); + + return listCommand; + } + + private static Command CreateStartCommand() + { + var portOption = new Option( + new[] { "-p", "--port" }, + () => DefaultPort, + "Specify the port to use." + ); + var ipOption = new Option( + new[] { "-i", "--ip" }, + "Specify the IP address to use." + ); + var configOption = new Option( + new[] { "-c", "--config" }, + "Specify the upstream settings file to load from." + ); + + var startCommand = new Command("start", "To start the emulator.") { - Console.WriteLine($"Error starting emulator: {e.Message}."); - } + portOption, + ipOption, + configOption + }; + + startCommand.SetHandler((int? port, string? ip, string? config) => + { + if (!TryGetPort(port, out var actualPort) || + !TryGetConfigFilePath(config, out var configFile) || + !TryGetIpAddress(ip, out var actualIp)) + { + return; + } + + Console.WriteLine($"Loaded settings from '{configFile}'. Changes to the settings file will be hot-loaded into the emulator."); + + CreateHostBuilder(null, actualIp, actualPort, configFile).Build().Run(); + }, portOption, ipOption, configOption); + + return startCommand; } - public static IHostBuilder CreateHostBuilder(string[] args, IPAddress ip, int port, string configFile) + private static IHostBuilder CreateHostBuilder(string[]? args, IPAddress? ip, int port, string configFile) { return Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); - webBuilder.UseKestrel(o => + webBuilder.UseKestrel(options => { if (ip == null) { - o.ListenLocalhost(port); + options.ListenLocalhost(port); } else { - o.Listen(ip, port); + options.Listen(ip, port); } }); }) - .ConfigureAppConfiguration(s => + .ConfigureAppConfiguration(config => { - s.AddJsonFile(AppSettingsFile, optional: true, reloadOnChange: true); - s.AddJsonFile(configFile, optional: true, reloadOnChange: true); + config.AddJsonFile(AppSettingsFile, optional: true, reloadOnChange: true); + config.AddJsonFile(configFile, optional: true, reloadOnChange: true); }); } - private static bool TryGetIpAddress(CommandOption ipOptions, out IPAddress ip) + private static bool TryGetIpAddress(string? ipOption, out IPAddress? ip) { - if (ipOptions.HasValue()) + if (!string.IsNullOrEmpty(ipOption)) { - var val = ipOptions.Value(); - - if (IPAddress.TryParse(val, out ip)) + if (IPAddress.TryParse(ipOption, out ip)) { return true; } - else - { - Console.WriteLine($"Invalid IP address value: {val}"); - return false; - } - } - else - { - ip = null; - return true; + + Console.WriteLine($"Invalid IP address value: {ipOption}"); + return false; } + + ip = null; + return true; } - private static bool TryGetPort(CommandOption portOption, out int port) + private static bool TryGetPort(int? portOption, out int port) { - if (portOption.HasValue()) - { - var val = portOption.Value(); - - if( int.TryParse(val, out port)) - { - return true; - } - else - { - Console.WriteLine($"Invalid port value: {val}"); - return false; - } - } - else - { - port = DefaultPort; - return true; - } + port = portOption ?? DefaultPort; + return true; } - private static bool TryGetConfigFilePath(CommandOption configOption, out string path) + private static bool TryGetConfigFilePath(string? configOption, [NotNullWhen(true)]out string? path) { - if (configOption.HasValue()) + if (!string.IsNullOrEmpty(configOption)) { - var fileAttempt = Path.GetFullPath(configOption.Value()); + var fileAttempt = Path.GetFullPath(configOption); if (File.Exists(fileAttempt)) { path = fileAttempt; return true; } - // Try this as a folder var folderAttempt = Path.GetFullPath(Path.Combine(fileAttempt, SettingsFileName)); if (File.Exists(folderAttempt)) { @@ -213,11 +226,9 @@ private static bool TryGetConfigFilePath(CommandOption configOption, out string path = null; return false; } - else - { - path = SettingsFile; - return true; - } + + path = SettingsFile; + return true; } } } diff --git a/test/Directory.Build.props b/test/Directory.Build.props index b0aaa81b2..189d3799b 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -6,7 +6,5 @@ - - diff --git a/test/Microsoft.Azure.SignalR.Emulator.Tests/CommandInputFacts.cs b/test/Microsoft.Azure.SignalR.Emulator.Tests/CommandInputFacts.cs index 86c8b9dfc..5ee639058 100644 --- a/test/Microsoft.Azure.SignalR.Emulator.Tests/CommandInputFacts.cs +++ b/test/Microsoft.Azure.SignalR.Emulator.Tests/CommandInputFacts.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -23,61 +24,69 @@ public class CommandInputFacts : IDisposable private readonly ITestOutputHelper _output; private const string HelpInfo = @" +Description: + The local emulator for Azure SignalR Serverless features. -Usage: asrs-emulator [options] [command] +Usage: + asrs-emulator [command] [options] Options: - -h|--help Show help information + --version Show version information + -?, -h, --help Show help and usage information Commands: - start To start the emulator. upstream To init/list the upstream options - -Use ""asrs-emulator [command] --help"" for more information about a command. - + start To start the emulator. "; private const string StartHelpInfo = @" +Description: + To start the emulator. -Usage: asrs-emulator start [options] +Usage: + asrs-emulator start [options] Options: - -p|--port Specify the port to use. - -i|--ip Specify the IP address to use. - -c|--config Specify the upstream settings file to load from. - -h|--help Show help information - + -p, --port Specify the port to use. [default: 8888] + -i, --ip Specify the IP address to use. + -c, --config Specify the upstream settings file to load from. + -?, -h, --help Show help and usage information "; private const string UpstreamHelpInfo = @" +Description: + To init/list the upstream options -Usage: asrs-emulator upstream [options] [command] +Usage: + asrs-emulator upstream [command] [options] Options: - -h|--help Show help information + -?, -h, --help Show help and usage information Commands: - init Init the default upstream options into a settings.json config. Use -o to specify the folder to export the default settings. - list List current upstream options. Use -c to specify the folder or file to read the settings. - -Use ""upstream [command] --help"" for more information about a command. - + init Init the default upstream options into a settings.json config + list List current upstream options "; private const string UpstreamInitHelpInfo = @" +Description: + Init the default upstream options into a settings.json config -Usage: asrs-emulator upstream init [options] +Usage: + asrs-emulator upstream init [options] Options: - -o|--output Specify the folder to init the upstream settings file. - -h|--help Show help information + -o, --output Specify the folder to init the upstream settings file. + -?, -h, --help Show help and usage information "; private const string UpstreamListHelpInfo = @" +Description: + List current upstream options -Usage: asrs-emulator upstream list [options] +Usage: + asrs-emulator upstream list [options] Options: - -c|--config Specify the upstream settings file to load from. - -h|--help Show help information - + -c, --config Specify the upstream settings file to load from. + -?, -h, --help Show help and usage information "; public static IEnumerable TestData = new List<(string command, string output)> @@ -93,11 +102,11 @@ list List current upstream options. Use -c to specify the folder or file to rea ("upstream init --help", UpstreamInitHelpInfo), ("upstream list -h", UpstreamListHelpInfo), ("upstream list --help", UpstreamListHelpInfo), - ("invalid", @"Specify --help for a list of available options and commands. -Error starting emulator: Unrecognized command or argument 'invalid'. -"), - ("-a", @"Specify --help for a list of available options and commands. -Error starting emulator: Unrecognized option '-a'. + ("invalid", HelpInfo), + ("-a", $@" +'-a' was not matched. Did you mean one of the following? +-h +{HelpInfo} "), ("upstream list", $@"Loaded upstream settings from '{Program.ProgramDefaultSettingsFile}' Current Upstream Settings: @@ -112,10 +121,10 @@ public CommandInputFacts(ITestOutputHelper output) [Theory] [MemberData(nameof(TestData))] - public void CommandTests(string input, string expectedOutput) + public async Task CommandTests(string input, string expectedOutput) { Console.WriteLine(input); - Program.Main(GetArgs(input)); + await Program.Main(GetArgs(input)); var output = _writer.ToString(); _output.WriteLine(output); Assert.Equal(Normalize(input + expectedOutput), Normalize(output)); diff --git a/test/Microsoft.Azure.SignalR.Management.Tests/NegotiateProcessorFacts.cs b/test/Microsoft.Azure.SignalR.Management.Tests/NegotiateProcessorFacts.cs index 1b115a157..22ef9a3b5 100644 --- a/test/Microsoft.Azure.SignalR.Management.Tests/NegotiateProcessorFacts.cs +++ b/test/Microsoft.Azure.SignalR.Management.Tests/NegotiateProcessorFacts.cs @@ -48,9 +48,9 @@ public async Task GenerateTokenWithCloseOnAuthExpiration() var now = DateTimeOffset.UtcNow; var negotiateResponse = await hubContext.NegotiateAsync(new NegotiationOptions { CloseOnAuthenticationExpiration = true, TokenLifetime = TimeSpan.FromSeconds(30) }); var token = JwtTokenHelper.JwtHandler.ReadJwtToken(negotiateResponse.AccessToken); - var closeOnAuthExpiration = Assert.Single(token.Claims.Where(c => c.Type == Constants.ClaimType.CloseOnAuthExpiration)); + var closeOnAuthExpiration = Assert.Single(token.Claims, c => c.Type == Constants.ClaimType.CloseOnAuthExpiration); Assert.Equal("true", closeOnAuthExpiration.Value); - var ttl = Assert.Single(token.Claims.Where(c => c.Type == Constants.ClaimType.AuthExpiresOn)); + var ttl = Assert.Single(token.Claims, c => c.Type == Constants.ClaimType.AuthExpiresOn); Assert.True(long.TryParse(ttl.Value, out var expiresOn)); Assert.InRange(DateTimeOffset.FromUnixTimeSeconds(expiresOn), now.AddSeconds(29), now.AddSeconds(32)); } diff --git a/test/Microsoft.Azure.SignalR.Tests.Common/Microsoft.Azure.SignalR.Tests.Common.csproj b/test/Microsoft.Azure.SignalR.Tests.Common/Microsoft.Azure.SignalR.Tests.Common.csproj index b71e700ab..5995b9284 100644 --- a/test/Microsoft.Azure.SignalR.Tests.Common/Microsoft.Azure.SignalR.Tests.Common.csproj +++ b/test/Microsoft.Azure.SignalR.Tests.Common/Microsoft.Azure.SignalR.Tests.Common.csproj @@ -21,6 +21,9 @@ + + + diff --git a/test/Microsoft.Azure.SignalR.Tests.Common/VerifiableLoggedTest.cs b/test/Microsoft.Azure.SignalR.Tests.Common/VerifiableLoggedTest.cs index 50487d9a2..233ef5388 100644 --- a/test/Microsoft.Azure.SignalR.Tests.Common/VerifiableLoggedTest.cs +++ b/test/Microsoft.Azure.SignalR.Tests.Common/VerifiableLoggedTest.cs @@ -13,7 +13,7 @@ public class VerifiableLoggedTest(ITestOutputHelper output) : LoggedTest(output) { public static async Task RetryWhenExceptionThrows(Func asyncFunc, int maxCount = 3) { - AssertActualExpectedException last = null; + NotEqualException last = null; int i; for (i = 0; i < maxCount; i++) { @@ -22,7 +22,7 @@ public static async Task RetryWhenExceptionThrows(Func asyncFunc, int maxC await asyncFunc(); break; } - catch (AssertActualExpectedException e) + catch (NotEqualException e) { last = e; continue;