diff --git a/.gitignore b/.gitignore index a52a7183..5dd61f94 100644 --- a/.gitignore +++ b/.gitignore @@ -285,3 +285,5 @@ __pycache__/ *.btm.cs *.odx.cs *.xsd.cs + +.DS_Store diff --git a/src/SeqCli/Cli/Commands/ApiKey/UpdateCommand.cs b/src/SeqCli/Cli/Commands/ApiKey/UpdateCommand.cs new file mode 100644 index 00000000..b8ecff35 --- /dev/null +++ b/src/SeqCli/Cli/Commands/ApiKey/UpdateCommand.cs @@ -0,0 +1,25 @@ +// Copyright © Datalust Pty Ltd and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Seq.Api; +using SeqCli.Connection; + +namespace SeqCli.Cli.Commands.ApiKey; + +[Command("apikey", "update", + "Update an existing API key", + Example="seqcli apikey update --json '{...}'")] +class UpdateCommand(SeqConnectionFactory connectionFactory): + Shared.UpdateCommand(connectionFactory, "apikey", nameof(SeqConnection.ApiKeys), "API key"); + \ No newline at end of file diff --git a/src/SeqCli/Cli/Commands/AppInstance/UpdateCommand.cs b/src/SeqCli/Cli/Commands/AppInstance/UpdateCommand.cs new file mode 100644 index 00000000..f46d7760 --- /dev/null +++ b/src/SeqCli/Cli/Commands/AppInstance/UpdateCommand.cs @@ -0,0 +1,25 @@ +// Copyright © Datalust Pty Ltd and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Seq.Api; +using SeqCli.Connection; + +namespace SeqCli.Cli.Commands.AppInstance; + +[Command("appinstance", "update", + "Update an existing app instance", + Example="seqcli appinstance update --json '{...}'")] +class UpdateCommand(SeqConnectionFactory connectionFactory): + Shared.UpdateCommand(connectionFactory, "appinstance", nameof(SeqConnection.AppInstances), "app instance"); + \ No newline at end of file diff --git a/src/SeqCli/Cli/Commands/Feed/UpdateCommand.cs b/src/SeqCli/Cli/Commands/Feed/UpdateCommand.cs new file mode 100644 index 00000000..1dd5d265 --- /dev/null +++ b/src/SeqCli/Cli/Commands/Feed/UpdateCommand.cs @@ -0,0 +1,25 @@ +// Copyright © Datalust Pty Ltd and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Seq.Api; +using SeqCli.Connection; + +namespace SeqCli.Cli.Commands.Feed; + +[Command("feed", "update", + "Update an existing NuGet feed", + Example="seqcli feed update --json '{...}'")] +class UpdateCommand(SeqConnectionFactory connectionFactory): + Shared.UpdateCommand(connectionFactory, "feed", nameof(SeqConnection.Feeds), "NuGet feed"); + \ No newline at end of file diff --git a/src/SeqCli/Cli/Commands/RetentionPolicy/UpdateCommand.cs b/src/SeqCli/Cli/Commands/RetentionPolicy/UpdateCommand.cs new file mode 100644 index 00000000..12dded9e --- /dev/null +++ b/src/SeqCli/Cli/Commands/RetentionPolicy/UpdateCommand.cs @@ -0,0 +1,25 @@ +// Copyright © Datalust Pty Ltd and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Seq.Api; +using SeqCli.Connection; + +namespace SeqCli.Cli.Commands.RetentionPolicy; + +[Command("retention", "update", + "Update an existing retention policy", + Example="seqcli retention update --json '{...}'")] +class UpdateCommand(SeqConnectionFactory connectionFactory): + Shared.UpdateCommand(connectionFactory, "retention", nameof(SeqConnection.RetentionPolicies), "retention policy"); + \ No newline at end of file diff --git a/src/SeqCli/Cli/Commands/Shared/UpdateCommand.cs b/src/SeqCli/Cli/Commands/Shared/UpdateCommand.cs new file mode 100644 index 00000000..df555ee8 --- /dev/null +++ b/src/SeqCli/Cli/Commands/Shared/UpdateCommand.cs @@ -0,0 +1,92 @@ +// Copyright © Datalust Pty Ltd and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using SeqCli.Cli.Features; +using SeqCli.Connection; +using SeqCli.Templates.Ast; +using SeqCli.Templates.Import; +using SeqCli.Templates.Parser; +using SeqCli.Util; +using Serilog; + +namespace SeqCli.Cli.Commands.Shared; + +abstract class UpdateCommand: Command +{ + readonly SeqConnectionFactory _connectionFactory; + + readonly ConnectionFeature _connection; + readonly string _resourceGroupName; + readonly string _entityName; + + string? _json; + bool _jsonStdin; + + protected UpdateCommand(SeqConnectionFactory connectionFactory, string commandGroupName, string resourceGroupName, string? entityName = null) + { + _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); + _resourceGroupName = resourceGroupName; + _entityName = entityName ?? commandGroupName; + + Options.Add( + "json=", + $"The updated {_entityName} in JSON format; this can be produced using `seqcli {commandGroupName} list --json`", + p => _json = ArgumentString.Normalize(p)); + + Options.Add( + "json-stdin", + $"Read the updated {_entityName} as JSON from `STDIN`", + _ => _jsonStdin = true); + + _connection = Enable(); + } + + protected override async Task Run() + { + var connection = _connectionFactory.Connect(_connection); + + if (_json == null && !_jsonStdin) + { + Log.Error("One of either `--json` or `--json-stdin` must be specified"); + return 1; + } + + var json = _json ?? await Console.In.ReadToEndAsync(); + + if (!JsonTemplateParser.TryParse(json, out var template, out var error, out _)) + { + Log.Error("The {EntityName} JSON could not be parsed: {Error}", _entityName, error); + return 1; + } + + if (template is not JsonTemplateObject obj || + !obj.Members.TryGetValue("Id", out var idValue) || + idValue is not JsonTemplateString id) + { + Log.Error("The {EntityName} JSON must be an object literal with a valid string `Id` property", _entityName); + return 1; + } + + var templateName = "JSON"; + var entityTemplate = new EntityTemplate(_resourceGroupName, templateName, template); + var state = new TemplateImportState(); + state.AddOrUpdateCreatedEntityId(templateName, id.Value); + await TemplateSetImporter.ImportAsync([entityTemplate], connection, new Dictionary(), state, merge: false); + + return 0; + } +} diff --git a/src/SeqCli/Cli/Commands/Signal/UpdateCommand.cs b/src/SeqCli/Cli/Commands/Signal/UpdateCommand.cs new file mode 100644 index 00000000..de734755 --- /dev/null +++ b/src/SeqCli/Cli/Commands/Signal/UpdateCommand.cs @@ -0,0 +1,25 @@ +// Copyright © Datalust Pty Ltd and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Seq.Api; +using SeqCli.Connection; + +namespace SeqCli.Cli.Commands.Signal; + +[Command("signal", "update", + "Update an existing signal", + Example="seqcli signal update --json '{...}'")] +class UpdateCommand(SeqConnectionFactory connectionFactory): + Shared.UpdateCommand(connectionFactory, "signal", nameof(SeqConnection.Signals)); + \ No newline at end of file diff --git a/src/SeqCli/Cli/Commands/User/UpdateCommand.cs b/src/SeqCli/Cli/Commands/User/UpdateCommand.cs new file mode 100644 index 00000000..d440037c --- /dev/null +++ b/src/SeqCli/Cli/Commands/User/UpdateCommand.cs @@ -0,0 +1,25 @@ +// Copyright © Datalust Pty Ltd and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Seq.Api; +using SeqCli.Connection; + +namespace SeqCli.Cli.Commands.User; + +[Command("user", "update", + "Update an existing user", + Example="seqcli user update --json '{...}'")] +class UpdateCommand(SeqConnectionFactory connectionFactory): + Shared.UpdateCommand(connectionFactory, "user", nameof(SeqConnection.Users)); + \ No newline at end of file diff --git a/src/SeqCli/Cli/Commands/Workspace/UpdateCommand.cs b/src/SeqCli/Cli/Commands/Workspace/UpdateCommand.cs new file mode 100644 index 00000000..c457506d --- /dev/null +++ b/src/SeqCli/Cli/Commands/Workspace/UpdateCommand.cs @@ -0,0 +1,25 @@ +// Copyright © Datalust Pty Ltd and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Seq.Api; +using SeqCli.Connection; + +namespace SeqCli.Cli.Commands.Workspace; + +[Command("workspace", "update", + "Update an existing workspace", + Example="seqcli workspace update --json '{...}'")] +class UpdateCommand(SeqConnectionFactory connectionFactory): + Shared.UpdateCommand(connectionFactory, "workspace", nameof(SeqConnection.Workspaces)); + \ No newline at end of file diff --git a/src/SeqCli/Program.cs b/src/SeqCli/Program.cs index e7d55cdc..b12c9d6c 100644 --- a/src/SeqCli/Program.cs +++ b/src/SeqCli/Program.cs @@ -60,7 +60,7 @@ static async Task Main(string[] args) } finally { - Log.CloseAndFlush(); + await Log.CloseAndFlushAsync(); } } } \ No newline at end of file diff --git a/src/SeqCli/Properties/launchSettings.json b/src/SeqCli/Properties/launchSettings.json new file mode 100644 index 00000000..6ff080fb --- /dev/null +++ b/src/SeqCli/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "SeqCli": { + "commandName": "Project", + "commandLineArgs": "signal update --json-stdin" + } + } +} diff --git a/src/SeqCli/Templates/Import/EntityTemplateFile.cs b/src/SeqCli/Templates/Import/EntityTemplate.cs similarity index 100% rename from src/SeqCli/Templates/Import/EntityTemplateFile.cs rename to src/SeqCli/Templates/Import/EntityTemplate.cs diff --git a/src/SeqCli/Templates/Import/EntityTemplateFileLoader.cs b/src/SeqCli/Templates/Import/EntityTemplateLoader.cs similarity index 95% rename from src/SeqCli/Templates/Import/EntityTemplateFileLoader.cs rename to src/SeqCli/Templates/Import/EntityTemplateLoader.cs index 949daf07..39a0f857 100644 --- a/src/SeqCli/Templates/Import/EntityTemplateFileLoader.cs +++ b/src/SeqCli/Templates/Import/EntityTemplateLoader.cs @@ -42,6 +42,7 @@ public static bool Load(string path, [MaybeNullWhen(false)] out EntityTemplate t if (root is not JsonTemplateObject rootDictionary || !rootDictionary.Members.TryGetValue("$entity", out var resourceToken) || resourceToken is not JsonTemplateString resource || + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract resource.Value is null) { template = null; diff --git a/src/SeqCli/Templates/Import/TemplateSetImporter.cs b/src/SeqCli/Templates/Import/TemplateSetImporter.cs index fe2c3ed4..399e4bcf 100644 --- a/src/SeqCli/Templates/Import/TemplateSetImporter.cs +++ b/src/SeqCli/Templates/Import/TemplateSetImporter.cs @@ -38,9 +38,9 @@ static class TemplateSetImporter TemplateImportState state, bool merge) { - var ordering = new[] {"users", "signals", "apps", "appinstances", + var ordering = new List {"users", "signals", "apps", "appinstances", "dashboards", "sqlqueries", "workspaces", "retentionpolicies", - "alerts", "expressionindexes"}.ToList(); + "alerts", "expressionindexes"}; var sorted = templates.OrderBy(t => ordering.IndexOf(t.ResourceGroup)); @@ -77,7 +77,7 @@ static class TemplateSetImporter var resourceGroup = await connection.Client.GetAsync(apiRoot, link.Key); // ExpressionIndexes with mapped ids or identical expressions are assumed to be equivalent. - var immutableTarget = template.ResourceGroup != "ExpressionIndexes"; + var immutableTarget = template.ResourceGroup.Equals("ExpressionIndexes", StringComparison.OrdinalIgnoreCase); if (state.TryGetCreatedEntityId(template.Name, out var existingId) && await CheckEntityExistenceAsync(connection, resourceGroup, existingId)) diff --git a/test/SeqCli.EndToEnd/Shared/UpdateCommandTests.cs b/test/SeqCli.EndToEnd/Shared/UpdateCommandTests.cs new file mode 100644 index 00000000..6f72d949 --- /dev/null +++ b/test/SeqCli.EndToEnd/Shared/UpdateCommandTests.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Seq.Api; +using SeqCli.EndToEnd.Support; +using Serilog; +using Xunit; + +namespace SeqCli.EndToEnd.Shared; + +class UpdateCommandTests(TestConfiguration configuration): ICliTestCase +{ + public async Task ExecuteAsync(SeqConnection connection, ILogger logger, CliCommandRunner runner) + { + // Ensure there's at least one API key... + var apiKey = await connection.ApiKeys.TemplateAsync(); + apiKey.Title = "Test"; + await connection.ApiKeys.AddAsync(apiKey); + + var exit = runner.Exec("app install", "--package-id Seq.App.EmailPlus"); + Assert.Equal(0, exit); + + // One app instance... + var app = (await connection.Apps.ListAsync()).Single(); + + var title = Guid.NewGuid().ToString("N"); + exit = runner.Exec("appinstance create", $"-t {title} --app {app.Id} --stream -p To=example@example.com -p From=example@example.com -p Host=localhost"); + Assert.Equal(0, exit); + + // One retention policy... + var retentionPolicy = await connection.RetentionPolicies.TemplateAsync(); + retentionPolicy.RetentionTime = TimeSpan.FromDays(100); + await connection.RetentionPolicies.AddAsync(retentionPolicy); + + // One workspace... + var workspace = await connection.Workspaces.TemplateAsync(); + workspace.OwnerId = null; + await connection.Workspaces.AddAsync(workspace); + + foreach (var commandGroup in new[] + { + "apikey", + "appinstance", + "feed", + "retention", + "signal", + "user", + "workspace" + }) + { + try + { + ListFirstThenUpdate(runner, commandGroup); + } + catch (Exception ex) + { + throw new Exception($"Failed in `{commandGroup}` command group.", ex); + } + } + } + + void ListFirstThenUpdate(CliCommandRunner runner, string commandGroup) + { + var exit = runner.Exec($"{commandGroup} list", "--json"); + Assert.Equal(0, exit); + + var json = new StringReader(runner.LastRunProcess!.Output).ReadLine()?.Trim(); + Assert.StartsWith("{", json); + Assert.EndsWith("}", json); + + using var process = configuration.SpawnCliProcess($"{commandGroup} update", "--json-stdin", supplyInput: true); + process.WriteLineStdin(json); + process.CompleteStdin(); + + exit = process.WaitForExit(CliCommandRunner.DefaultExecTimeout); + Assert.Equal(0, exit); + } +} diff --git a/test/SeqCli.EndToEnd/Support/CaptiveProcess.cs b/test/SeqCli.EndToEnd/Support/CaptiveProcess.cs index a08bd598..5d9bd00f 100644 --- a/test/SeqCli.EndToEnd/Support/CaptiveProcess.cs +++ b/test/SeqCli.EndToEnd/Support/CaptiveProcess.cs @@ -12,17 +12,18 @@ public sealed class CaptiveProcess : ITestProcess, IDisposable readonly string _stopCommandFullExePath; readonly string _stopCommandArgs; readonly Process _process; - readonly ManualResetEvent _outputComplete = new ManualResetEvent(false); - readonly ManualResetEvent _errorComplete = new ManualResetEvent(false); + readonly ManualResetEvent _outputComplete = new(false); + readonly ManualResetEvent _errorComplete = new(false); - readonly object _sync = new object(); - readonly StringWriter _output = new StringWriter(); + readonly object _sync = new(); + readonly StringWriter _output = new(); public CaptiveProcess( string fullExePath, string args = null, IDictionary environment = null, bool captureOutput = true, + bool supplyInput = false, string stopCommandFullExePath = null, string stopCommandArgs = null) { @@ -36,6 +37,7 @@ public CaptiveProcess( UseShellExecute = false, RedirectStandardError = captureOutput, RedirectStandardOutput = captureOutput, + RedirectStandardInput = supplyInput, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, ErrorDialog = false, @@ -45,9 +47,9 @@ public CaptiveProcess( if (environment != null) { - foreach (var kvp in environment) + foreach (var (name, value) in environment) { - startInfo.Environment.Add(kvp.Key, kvp.Value); + startInfo.Environment.Add(name, value); } } @@ -57,7 +59,7 @@ public CaptiveProcess( if (captureOutput) { - _process.OutputDataReceived += (o, e) => + _process.OutputDataReceived += (_, e) => { if (e.Data == null) _outputComplete.Set(); @@ -66,7 +68,7 @@ public CaptiveProcess( }; _process.BeginOutputReadLine(); - _process.ErrorDataReceived += (o, e) => + _process.ErrorDataReceived += (_, e) => { if (e.Data == null) _errorComplete.Set(); @@ -77,6 +79,16 @@ public CaptiveProcess( } } + public void WriteLineStdin(string s) + { + _process.StandardInput.WriteLine(s); + } + + public void CompleteStdin() + { + _process.StandardInput.Close(); + } + void WriteOutput(string o) { lock (_sync) diff --git a/test/SeqCli.EndToEnd/Support/CliCommandRunner.cs b/test/SeqCli.EndToEnd/Support/CliCommandRunner.cs index 1955d913..0ee0c4b6 100644 --- a/test/SeqCli.EndToEnd/Support/CliCommandRunner.cs +++ b/test/SeqCli.EndToEnd/Support/CliCommandRunner.cs @@ -6,7 +6,7 @@ namespace SeqCli.EndToEnd.Support; public class CliCommandRunner(TestConfiguration configuration) { - static readonly TimeSpan DefaultExecTimeout = TimeSpan.FromSeconds(10); + public static readonly TimeSpan DefaultExecTimeout = TimeSpan.FromSeconds(10); public ITestProcess? LastRunProcess { get; private set; } diff --git a/test/SeqCli.EndToEnd/Support/IsolatedTestCase.cs b/test/SeqCli.EndToEnd/Support/IsolatedTestCase.cs index d9b20ba1..0447d6fa 100644 --- a/test/SeqCli.EndToEnd/Support/IsolatedTestCase.cs +++ b/test/SeqCli.EndToEnd/Support/IsolatedTestCase.cs @@ -22,7 +22,8 @@ public IsolatedTestCase( Lazy logger, CliCommandRunner commandRunner, Lazy licenseSetup, - ICliTestCase testCase) + ICliTestCase testCase, + TestConfiguration configuration) { _serverProcess = serverProcess; _connection = connection; @@ -30,10 +31,12 @@ public IsolatedTestCase( _commandRunner = commandRunner; _licenseSetup = licenseSetup; _testCase = testCase ?? throw new ArgumentNullException(nameof(testCase)); + Configuration = configuration; } public string Description => _testCase.GetType().Name; public string Output => _commandRunner.LastRunProcess?.Output ??_lastRunProcess?.Output ?? ""; + public TestConfiguration Configuration { get; } void ForceStartup() { diff --git a/test/SeqCli.EndToEnd/Support/IsolatedTestCaseRegistrationSource.cs b/test/SeqCli.EndToEnd/Support/IsolatedTestCaseRegistrationSource.cs index 8733415a..b4d6f01a 100644 --- a/test/SeqCli.EndToEnd/Support/IsolatedTestCaseRegistrationSource.cs +++ b/test/SeqCli.EndToEnd/Support/IsolatedTestCaseRegistrationSource.cs @@ -32,7 +32,8 @@ public IEnumerable RegistrationsFor(Service service, Fun ctx.Resolve>(), ctx.Resolve(), ctx.Resolve>(), - tc); + tc, + ctx.Resolve()); }), new CurrentScopeLifetime(), InstanceSharing.None, diff --git a/test/SeqCli.EndToEnd/Support/TestConfiguration.cs b/test/SeqCli.EndToEnd/Support/TestConfiguration.cs index ee80491a..dd715813 100644 --- a/test/SeqCli.EndToEnd/Support/TestConfiguration.cs +++ b/test/SeqCli.EndToEnd/Support/TestConfiguration.cs @@ -1,25 +1,27 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; namespace SeqCli.EndToEnd.Support; public class TestConfiguration(Args args) { - static int ServerListenPort => 9989; + static int _nextServerPort = 9989; + readonly int _serverListenPort = Interlocked.Increment(ref _nextServerPort); #pragma warning disable CA1822 - public string ServerListenUrl => $"http://localhost:{ServerListenPort}"; + public string ServerListenUrl => $"http://localhost:{_serverListenPort}"; #pragma warning restore CA1822 - string EquivalentBaseDirectory { get; } = AppDomain.CurrentDomain.BaseDirectory + static string EquivalentBaseDirectory { get; } = AppDomain.CurrentDomain.BaseDirectory .Replace(Path.Combine("test", "SeqCli.EndToEnd"), Path.Combine("src", "SeqCli")); - public string TestedBinary => Path.Combine(EquivalentBaseDirectory, "seqcli.dll"); + public static string TestedBinary => Path.Combine(EquivalentBaseDirectory, "seqcli.dll"); public bool IsMultiuser => args.Multiuser(); - public CaptiveProcess SpawnCliProcess(string command, string additionalArgs = null, Dictionary environment = null, bool skipServerArg = false) + public CaptiveProcess SpawnCliProcess(string command, string additionalArgs = null, Dictionary environment = null, bool skipServerArg = false, bool supplyInput = false) { if (command == null) throw new ArgumentNullException(nameof(command)); @@ -27,7 +29,7 @@ public CaptiveProcess SpawnCliProcess(string command, string additionalArgs = nu if (!skipServerArg) commandWithArgs += $" --server=\"{ServerListenUrl}\""; - return new CaptiveProcess("dotnet", $"{TestedBinary} {commandWithArgs}", environment); + return new CaptiveProcess("dotnet", $"{TestedBinary} {commandWithArgs}", environment, supplyInput: supplyInput); } public CaptiveProcess SpawnServerProcess(string storagePath) @@ -38,7 +40,8 @@ public CaptiveProcess SpawnServerProcess(string storagePath) if (args.UseDockerSeq(out var imageTag)) { var containerName = Guid.NewGuid().ToString("n"); - return new CaptiveProcess("docker", $"run --name {containerName} -it --rm -e ACCEPT_EULA=Y -p {ServerListenPort}:80 datalust/seq:{imageTag}", stopCommandFullExePath: "docker", stopCommandArgs: $"stop {containerName}"); + const string containerRuntime = "docker"; + return new CaptiveProcess(containerRuntime, $"run --name {containerName} -it --rm -e ACCEPT_EULA=Y -p {_serverListenPort}:80 datalust/seq:{imageTag}", stopCommandFullExePath: containerRuntime, stopCommandArgs: $"stop {containerName}"); } return new CaptiveProcess("seq", commandWithArgs); diff --git a/test/SeqCli.EndToEnd/Support/TestDriver.cs b/test/SeqCli.EndToEnd/Support/TestDriver.cs index aba533a5..2d53b8b9 100644 --- a/test/SeqCli.EndToEnd/Support/TestDriver.cs +++ b/test/SeqCli.EndToEnd/Support/TestDriver.cs @@ -9,39 +9,36 @@ namespace SeqCli.EndToEnd.Support; class TestDriver { - readonly TestConfiguration _configuration; readonly IEnumerable>>> _cases; public TestDriver( - TestConfiguration configuration, IEnumerable>>> cases) { - _configuration = configuration; _cases = cases; } public async Task Run() { Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine($"TESTING {_configuration.TestedBinary}"); + Console.WriteLine($"TESTING {TestConfiguration.TestedBinary}"); Console.ResetColor(); int count = 0, passedCount = 0, skippedCount = 0; var failed = new List(); - foreach (var testCaseFactory in _cases.OrderBy(c => Guid.NewGuid())) + foreach (var testCaseFactory in _cases.OrderBy(_ => Guid.NewGuid())) { count++; await using var testCase = testCaseFactory.Value(); Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine($"RUNNING {testCase.Value.Description.PadRight(50)}"); + Console.WriteLine($"RUNNING {testCase.Value.Description,-50}"); Console.ResetColor(); var isMultiuser = testCaseFactory.Metadata.TryGetValue("Multiuser", out var multiuser) && true.Equals(multiuser); testCaseFactory.Metadata.TryGetValue("MinimumApiVersion", out var minSeqVersion); - if (isMultiuser != _configuration.IsMultiuser || minSeqVersion != null && + if (isMultiuser != testCase.Value.Configuration.IsMultiuser || minSeqVersion != null && !await testCase.Value.IsSupportedApiVersion((string)minSeqVersion)) { skippedCount++; diff --git a/test/SeqCli.EndToEnd/TestDriverModule.cs b/test/SeqCli.EndToEnd/TestDriverModule.cs index adc23757..e0bbaf72 100644 --- a/test/SeqCli.EndToEnd/TestDriverModule.cs +++ b/test/SeqCli.EndToEnd/TestDriverModule.cs @@ -43,9 +43,9 @@ protected override void Load(ContainerBuilder builder) return m; }); - builder.RegisterType().SingleInstance(); + builder.RegisterType().InstancePerOwned(); - builder.RegisterType().SingleInstance(); + builder.RegisterType().InstancePerOwned(); builder.RegisterType().InstancePerOwned(); builder.RegisterType(); builder.RegisterType();