Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CosmosDBShell.Tests/Integration/ShellProcessTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,31 @@ public async Task HelpOption_PrintsHelpAndExitsZero()
Assert.Contains("--version", result.StdOut, StringComparison.OrdinalIgnoreCase);
}

[Fact]
public async Task UnknownRootArgument_ReturnsUnknownArgumentError()
{
var result = await RunShellAsync(
stdinScript: null,
extraArgs: ["not-a-root-option"],
cancellationToken: TestContext.Current.CancellationToken);

Assert.NotEqual(0, result.ExitCode);
Assert.Contains("Unrecognized argument 'not-a-root-option'", result.StdOut);
Assert.DoesNotContain("Option '--connect-mode' is defined with a bad format", result.StdOut);
}

[Fact]
public async Task MissingConnectValue_ReportsUserFacingOptionAlias()
{
var result = await RunShellAsync(
stdinScript: null,
extraArgs: ["--connect"],
cancellationToken: TestContext.Current.CancellationToken);

Assert.NotEqual(0, result.ExitCode);
Assert.Contains("Required option '--connect' is missing", result.StdOut);
}

[Fact]
[Trait("Category", "Emulator")]
public async Task ConnectOption_WithExecuteAndQuit_RunsCommandAgainstEmulator()
Expand Down
113 changes: 113 additions & 0 deletions CosmosDBShell.Tests/UtilTest/NormalizeArgumentsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------

namespace CosmosShell.Tests.UtilTest;

public class NormalizeArgumentsTests
{
[Fact]
public void EmptyArgs_ReturnsEmpty()
{
Assert.Empty(Program.NormalizeArguments([]));
}

[Fact]
public void NonCommandArgs_PassThroughUnchanged()
{
var input = new[] { "--connect", "endpoint", "--verbose" };
Assert.Equal(input, Program.NormalizeArguments(input));
}

[Fact]
public void DashC_ConsumesRemainingTokensAsSingleString()
{
var result = Program.NormalizeArguments(["-c", "help", "mkitem"]);
Assert.Equal(["-c", "help mkitem"], result);
}

[Fact]
public void DashK_ConsumesRemainingTokensAsSingleString()
{
var result = Program.NormalizeArguments(["-k", "help", "mkitem"]);
Assert.Equal(["-k", "help mkitem"], result);
}

[Theory]
[InlineData("/c")]
[InlineData("/C")]
public void SlashC_IsTranslatedToDashC(string token)
{
var result = Program.NormalizeArguments([token, "help", "mkitem"]);
Assert.Equal(["-c", "help mkitem"], result);
}

[Theory]
[InlineData("/k")]
[InlineData("/K")]
public void SlashK_IsTranslatedToDashK(string token)
{
var result = Program.NormalizeArguments([token, "help", "mkitem"]);
Assert.Equal(["-k", "help mkitem"], result);
}

[Fact]
public void AppOptionsBeforeDashC_ArePreserved()
{
var result = Program.NormalizeArguments(
["--connect", "endpoint", "-c", "help", "mkitem"]);
Assert.Equal(["--connect", "endpoint", "-c", "help mkitem"], result);
}

[Fact]
public void DashCWithoutTail_LeavesDashCAlone()
{
var result = Program.NormalizeArguments(["-c"]);
Assert.Equal(["-c"], result);
}

[Fact]
public void DashCWithQuotedSingleToken_StaysSingleToken()
{
var result = Program.NormalizeArguments(["-c", "help mkitem"]);
Assert.Equal(["-c", "help mkitem"], result);
}

[Fact]
public void TokensThatLookLikeOptions_AfterDashC_AreAbsorbed()
{
var result = Program.NormalizeArguments(
["-c", "seed.csh", "--connect", "xyz"]);
Assert.Equal(["-c", "seed.csh --connect xyz"], result);
}

[Fact]
public void TakePreCommandArgs_ReturnsEverythingBeforeDashC()
{
var result = Program.TakePreCommandArgs(
["--verbose", "-c", "help"]);
Assert.Equal(["--verbose"], result);
}

[Fact]
public void TakePreCommandArgs_ReturnsEverythingBeforeDashK()
{
var result = Program.TakePreCommandArgs(
["--connect", "ep", "-k", "help"]);
Assert.Equal(["--connect", "ep"], result);
}

[Fact]
public void TakePreCommandArgs_NoCommandMarker_ReturnsAll()
{
var input = new[] { "--connect", "endpoint", "--verbose" };
Assert.Equal(input, Program.TakePreCommandArgs(input));
}

[Fact]
public void TakePreCommandArgs_DashCFirst_ReturnsEmpty()
{
var result = Program.TakePreCommandArgs(["-c", "--help"]);
Assert.Empty(result);
}
}
6 changes: 0 additions & 6 deletions CosmosDBShell.Tests/UtilTest/ParseDocDBConnectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------

using CommandLine;
using Azure.Data.Cosmos.Shell.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CosmosShell.Tests.UtilTest;

Expand Down
20 changes: 0 additions & 20 deletions CosmosDBShell.Tests/UtilTest/SentenceBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------

using CommandLine;
using Azure.Data.Cosmos.Shell.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CosmosShell.Tests.UtilTest;

Expand All @@ -24,18 +18,4 @@ public void TestCommandDescriptions()
Assert.NotNull(LocalizableSentenceBuilder.ConnectionString);
Assert.NotNull(LocalizableSentenceBuilder.Command);
}

[Fact]
public void TestLocalizableSentenceBuilder()
{
var builder = new LocalizableSentenceBuilder();
Assert.NotNull(builder.RequiredWord());
Assert.NotNull(builder.ErrorsHeadingText());
Assert.NotNull(builder.UsageHeadingText());
Assert.NotNull(builder.OptionGroupWord());
Assert.NotNull(builder.HelpCommandText(true));
Assert.NotNull(builder.HelpCommandText(false));
Assert.NotNull(builder.VersionCommandText(true));
Assert.NotNull(builder.VersionCommandText(false));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ namespace Azure.Data.Cosmos.Shell.Commands;
using Azure.Data.Cosmos.Shell.Core;
using Azure.Data.Cosmos.Shell.Parser;
using Azure.Data.Cosmos.Shell.Util;
using CommandLine.Text;
using RadLine;
using Spectre.Console;
using static System.Net.Mime.MediaTypeNames;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

namespace Azure.Data.Cosmos.Shell.Util;

using global::CommandLine;
using global::CommandLine.Text;

using Spectre.Console;

public class LocalizableSentenceBuilder : SentenceBuilder
/// <summary>
/// Static accessors for localized CLI option descriptions and usage strings.
/// Previously implemented <c>CommandLine.Text.SentenceBuilder</c>; after the
/// migration to <c>System.CommandLine</c> these strings are pulled directly
/// by the option builder in <c>Program.cs</c>.
/// </summary>
public static class LocalizableSentenceBuilder
{
public static string ExecuteAndContinue => MessageService.GetString("help-ExecuteAndContinue");

Expand Down Expand Up @@ -43,111 +44,5 @@ public class LocalizableSentenceBuilder : SentenceBuilder

public static string Verbose => MessageService.GetString("help-Verbose");

public override Func<string> RequiredWord => () => MessageService.GetString("help-RequiredWord");

public override Func<string> ErrorsHeadingText => () => MessageService.GetString("help-ErrorsHeadingText");

public override Func<string> UsageHeadingText => () => MessageService.GetString("help-UsageHeadingText");

public override Func<string> OptionGroupWord => () => MessageService.GetString("help-OptionGroupWord");

public override Func<bool, string> HelpCommandText
{
get
{
return isOption => isOption
? MessageService.GetString("help-HelpCommandScreenText")
: MessageService.GetString("help-HelpCommandMoreText");
}
}

public override Func<bool, string> VersionCommandText => (b) => MessageService.GetString("help-VersionCommandText");

public override Func<Error, string> FormatError
{
get
{
return error =>
{
switch (error.Tag)
{
case ErrorType.BadFormatTokenError:
return MessageService.GetString("help-error-BadFormatTokenError", new Dictionary<string, object> { { "token", ((BadFormatTokenError)error).Token } });
case ErrorType.MissingValueOptionError:
return MessageService.GetString("help-error-MissingValueOptionError", new Dictionary<string, object> { { "option", ((MissingValueOptionError)error).NameInfo.NameText } });
case ErrorType.UnknownOptionError:
return MessageService.GetString("help-error-UnknownOptionError", new Dictionary<string, object> { { "option", ((UnknownOptionError)error).Token } });
case ErrorType.MissingRequiredOptionError:
var errMisssing = (MissingRequiredOptionError)error;
return errMisssing.NameInfo.Equals(NameInfo.EmptyName)
? MessageService.GetString("help-error-MissingRequiredOptionError1")
: MessageService.GetString("help-error-MissingRequiredOptionError2", new Dictionary<string, object> { { "option", errMisssing.NameInfo.NameText } });
case ErrorType.BadFormatConversionError:
var badFormat = (BadFormatConversionError)error;
return badFormat.NameInfo.Equals(NameInfo.EmptyName)
? MessageService.GetString("help-error-BadFormatConversionError1")
: MessageService.GetString("help-error-BadFormatConversionError2", new Dictionary<string, object> { { "option", badFormat.NameInfo.NameText } });
case ErrorType.SequenceOutOfRangeError:
var seqOutRange = (SequenceOutOfRangeError)error;
return seqOutRange.NameInfo.Equals(NameInfo.EmptyName)
? MessageService.GetString("help-error-SequenceOutOfRangeError1")
: MessageService.GetString("help-error-SequenceOutOfRangeError2", new Dictionary<string, object> { { "option", seqOutRange.NameInfo.NameText } });
case ErrorType.BadVerbSelectedError:
return MessageService.GetString("help-error-BadVerbSelectedError", new Dictionary<string, object> { { "token", ((BadVerbSelectedError)error).Token } });
case ErrorType.NoVerbSelectedError:
return MessageService.GetString("help-error-NoVerbSelectedError");
case ErrorType.RepeatedOptionError:
return MessageService.GetString("help-error-RepeatedOptionError", new Dictionary<string, object> { { "option", ((RepeatedOptionError)error).NameInfo.NameText } });
case ErrorType.SetValueExceptionError:
var setValueError = (SetValueExceptionError)error;
return MessageService.GetString("help-error-SetValueExceptionError", new Dictionary<string, object>
{
{ "option", setValueError.NameInfo.NameText },
{ "message", setValueError.Exception.Message },
});
case ErrorType.MissingGroupOptionError:
var missingGroupOptionError = (MissingGroupOptionError)error;
return MessageService.GetString("help-error-MissingGroupOptionError", new Dictionary<string, object>
{
{ "option", missingGroupOptionError.Group },
{ "req_options", string.Join(", ", missingGroupOptionError.Names.Select(n => n.NameText)) },
});
case ErrorType.GroupOptionAmbiguityError:
var groupOptionAmbiguityError = (GroupOptionAmbiguityError)error;
return MessageService.GetString("help-error-GroupOptionAmbiguityError", new Dictionary<string, object> { { "option", groupOptionAmbiguityError.Option.NameText } });
case ErrorType.MultipleDefaultVerbsError:
return MultipleDefaultVerbsError.ErrorMessage;
}

throw new InvalidOperationException();
};
}
}

public override Func<IEnumerable<MutuallyExclusiveSetError>, string> FormatMutuallyExclusiveSetErrors => errors =>
{
var bySet = from e in errors
group e by e.SetName into g
select new { SetName = g.Key, Errors = g.ToList() };

var msgs = bySet.Select(
set =>
{
var names = string.Join(string.Empty, from e in set.Errors select "'" + e.NameInfo.NameText + "', ");
var namesCount = set.Errors.Count;

var incompat = string.Join(
string.Empty,
(from x in (from s in bySet where !s.SetName.Equals(set.SetName) from e in s.Errors select e).Distinct()
select "'" + x.NameInfo.NameText + "', ").ToArray());

return MessageService.GetString("help-error-FormatMutuallyExclusiveSetErrors", new Dictionary<string, object>
{
{ "count", namesCount },
{ "option", names[..^2] },
{ "incompat", incompat[..^2] },
});
}).ToArray();
return string.Join(Environment.NewLine, msgs);
};
}
public static string UsageHeadingText => MessageService.GetString("help-UsageHeadingText");
}
2 changes: 1 addition & 1 deletion CosmosDBShell/CosmosDBShell.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Azure.Identity.Broker" />
<PackageReference Include="CommandLineParser" />
<PackageReference Include="System.CommandLine" />
<PackageReference Include="Fluent.Net" />
<PackageReference Include="Microsoft.Azure.Cosmos" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
Expand Down
Loading
Loading