From 5c15d134a39c0a183461a3738e48626b591834a5 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Thu, 28 Nov 2024 22:14:34 +1030 Subject: [PATCH] Refactors to add tests for branch commands (#78) --- .../Branch/AddBranchCommandHandlerTests.cs | 266 +++++++++++++++ .../Branch/NewBranchCommandHandlerTests.cs | 310 ++++++++++++++++++ src/Stack/Commands/Branch/AddBranchCommand.cs | 67 +++- src/Stack/Commands/Branch/NewBranchCommand.cs | 76 ++++- 4 files changed, 694 insertions(+), 25 deletions(-) create mode 100644 src/Stack.Tests/Commands/Branch/AddBranchCommandHandlerTests.cs create mode 100644 src/Stack.Tests/Commands/Branch/NewBranchCommandHandlerTests.cs diff --git a/src/Stack.Tests/Commands/Branch/AddBranchCommandHandlerTests.cs b/src/Stack.Tests/Commands/Branch/AddBranchCommandHandlerTests.cs new file mode 100644 index 0000000..68d0445 --- /dev/null +++ b/src/Stack.Tests/Commands/Branch/AddBranchCommandHandlerTests.cs @@ -0,0 +1,266 @@ +using FluentAssertions; +using NSubstitute; +using Stack.Commands; +using Stack.Commands.Helpers; +using Stack.Config; +using Stack.Git; +using Stack.Infrastructure; +using Stack.Tests.Helpers; + +namespace Stack.Tests.Commands.Stack; + +public class AddBranchCommandHandlerTests +{ + [Fact] + public async Task WhenNoInputsProvided_AsksForStackAndBranchAndConfirms_AddsBranchToStack() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new AddBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(true); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + stackConfig + .WhenForAnyArgs(s => s.Save(Arg.Any>())) + .Do(ci => stacks = ci.ArgAt>(0)); + + inputProvider.Select(Questions.SelectStack, Arg.Any()).Returns("Stack1"); + inputProvider.Select(Questions.SelectBranch, Arg.Any()).Returns("branch-5"); + + // Act + await handler.Handle(AddBranchCommandInputs.Empty); + + // Assert + stacks.Should().BeEquivalentTo(new List + { + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + }); + } + + [Fact] + public async Task WhenStackNameProvided_DoesNotAskForStackName_AddsBranchFromStack() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new AddBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(true); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + stackConfig + .WhenForAnyArgs(s => s.Save(Arg.Any>())) + .Do(ci => stacks = ci.ArgAt>(0)); + + inputProvider.Select(Questions.SelectBranch, Arg.Any()).Returns("branch-5"); + + // Act + await handler.Handle(new AddBranchCommandInputs("Stack1", null)); + + // Assert + inputProvider.DidNotReceive().Select(Questions.SelectStack, Arg.Any()); + stacks.Should().BeEquivalentTo(new List + { + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + }); + } + + [Fact] + public async Task WhenStackNameProvided_ButStackDoesNotExist_Throws() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new AddBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + + // Act and assert + var invalidStackName = Some.Name(); + await handler.Invoking(async h => await h.Handle(new AddBranchCommandInputs(invalidStackName, null))) + .Should() + .ThrowAsync() + .WithMessage($"Stack '{invalidStackName}' not found."); + } + + [Fact] + public async Task WhenBranchNameProvided_DoesNotAskForBranchName_AddsBranchFromStack() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new AddBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(true); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + stackConfig + .WhenForAnyArgs(s => s.Save(Arg.Any>())) + .Do(ci => stacks = ci.ArgAt>(0)); + + inputProvider.Select(Questions.SelectStack, Arg.Any()).Returns("Stack1"); + + // Act + await handler.Handle(new AddBranchCommandInputs(null, "branch-5")); + + // Assert + stacks.Should().BeEquivalentTo(new List + { + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + }); + inputProvider.DidNotReceive().Select(Questions.SelectBranch, Arg.Any()); + } + + [Fact] + public async Task WhenBranchNameProvided_ButBranchDoesNotExistLocally_Throws() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new AddBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(false); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + + inputProvider.Select(Questions.SelectStack, Arg.Any()).Returns("Stack1"); + + // Act and assert + var invalidBranchName = Some.Name(); + await handler.Invoking(async h => await h.Handle(new AddBranchCommandInputs(null, invalidBranchName))) + .Should() + .ThrowAsync() + .WithMessage($"Branch '{invalidBranchName}' does not exist locally."); + } + + [Fact] + public async Task WhenBranchNameProvided_ButBranchAlreadyExistsInStack_Throws() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new AddBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(true); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + + inputProvider.Select(Questions.SelectStack, Arg.Any()).Returns("Stack1"); + + // Act and assert + await handler.Invoking(async h => await h.Handle(new AddBranchCommandInputs(null, "branch-5"))) + .Should() + .ThrowAsync() + .WithMessage($"Branch 'branch-5' already exists in stack 'Stack1'."); + } + + [Fact] + public async Task WhenAllInputsProvided_DoesNotAskForAnything_AddsBranchFromStack() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new AddBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(true); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + stackConfig + .WhenForAnyArgs(s => s.Save(Arg.Any>())) + .Do(ci => stacks = ci.ArgAt>(0)); + + // Act + await handler.Handle(new AddBranchCommandInputs("Stack1", "branch-5")); + + // Assert + stacks.Should().BeEquivalentTo(new List + { + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + }); + inputProvider.ReceivedCalls().Should().BeEmpty(); + } +} diff --git a/src/Stack.Tests/Commands/Branch/NewBranchCommandHandlerTests.cs b/src/Stack.Tests/Commands/Branch/NewBranchCommandHandlerTests.cs new file mode 100644 index 0000000..6584639 --- /dev/null +++ b/src/Stack.Tests/Commands/Branch/NewBranchCommandHandlerTests.cs @@ -0,0 +1,310 @@ +using FluentAssertions; +using NSubstitute; +using Stack.Commands; +using Stack.Commands.Helpers; +using Stack.Config; +using Stack.Git; +using Stack.Infrastructure; +using Stack.Tests.Helpers; + +namespace Stack.Tests.Commands.Stack; + +public class NewBranchCommandHandlerTests +{ + [Fact] + public async Task WhenNoInputsProvided_AsksForStackAndBranchAndConfirms_CreatesAndAddsBranchToStackAndSwitchesToBranch() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new NewBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(false); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + stackConfig + .WhenForAnyArgs(s => s.Save(Arg.Any>())) + .Do(ci => stacks = ci.ArgAt>(0)); + + inputProvider.Select(Questions.SelectStack, Arg.Any()).Returns("Stack1"); + inputProvider.Text(Questions.BranchName).Returns("branch-5"); + inputProvider.Confirm(Questions.ConfirmSwitchToBranch).Returns(true); + + // Act + await handler.Handle(NewBranchCommandInputs.Empty); + + // Assert + stacks.Should().BeEquivalentTo(new List + { + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + }); + gitOperations.Received().ChangeBranch("branch-5"); + } + + [Fact] + public async Task WhenSwitchBranchIsFalse_CreatsAndAddsBranchToStackButDoesNotSwitchToBranch() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new NewBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(false); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + stackConfig + .WhenForAnyArgs(s => s.Save(Arg.Any>())) + .Do(ci => stacks = ci.ArgAt>(0)); + + inputProvider.Select(Questions.SelectStack, Arg.Any()).Returns("Stack1"); + inputProvider.Text(Questions.BranchName).Returns("branch-5"); + inputProvider.Confirm(Questions.ConfirmSwitchToBranch).Returns(false); + + // Act + await handler.Handle(NewBranchCommandInputs.Empty); + + // Assert + stacks.Should().BeEquivalentTo(new List + { + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + }); + gitOperations.DidNotReceive().ChangeBranch("branch-5"); + } + + [Fact] + public async Task WhenStackNameProvided_DoesNotAskForStackName_CreatesAndAddsBranchFromStack() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new NewBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(false); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + stackConfig + .WhenForAnyArgs(s => s.Save(Arg.Any>())) + .Do(ci => stacks = ci.ArgAt>(0)); + + inputProvider.Text(Questions.BranchName).Returns("branch-5"); + + // Act + await handler.Handle(new NewBranchCommandInputs("Stack1", null, false)); + + // Assert + inputProvider.DidNotReceive().Select(Questions.SelectStack, Arg.Any()); + stacks.Should().BeEquivalentTo(new List + { + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + }); + } + + [Fact] + public async Task WhenStackNameProvided_ButStackDoesNotExist_Throws() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new NewBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + + // Act and assert + var invalidStackName = Some.Name(); + await handler.Invoking(async h => await h.Handle(new NewBranchCommandInputs(invalidStackName, null, false))) + .Should() + .ThrowAsync() + .WithMessage($"Stack '{invalidStackName}' not found."); + } + + [Fact] + public async Task WhenBranchNameProvided_DoesNotAskForBranchName_CreatesAndAddsBranchFromStack() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new NewBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(false); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + stackConfig + .WhenForAnyArgs(s => s.Save(Arg.Any>())) + .Do(ci => stacks = ci.ArgAt>(0)); + + inputProvider.Select(Questions.SelectStack, Arg.Any()).Returns("Stack1"); + + // Act + await handler.Handle(new NewBranchCommandInputs(null, "branch-5", false)); + + // Assert + stacks.Should().BeEquivalentTo(new List + { + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + }); + inputProvider.DidNotReceive().Text(Questions.BranchName); + } + + [Fact] + public async Task WhenBranchNameProvided_ButBranchAlreadyExistLocally_Throws() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new NewBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(true); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + + inputProvider.Select(Questions.SelectStack, Arg.Any()).Returns("Stack1"); + + // Act and assert + var invalidBranchName = Some.Name(); + await handler.Invoking(async h => await h.Handle(new NewBranchCommandInputs(null, "branch-5", false))) + .Should() + .ThrowAsync() + .WithMessage($"Branch 'branch-5' already exists locally."); + } + + [Fact] + public async Task WhenBranchNameProvided_ButBranchAlreadyExistsInStack_Throws() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new NewBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(false); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + + inputProvider.Select(Questions.SelectStack, Arg.Any()).Returns("Stack1"); + + // Act and assert + await handler.Invoking(async h => await h.Handle(new NewBranchCommandInputs(null, "branch-5", false))) + .Should() + .ThrowAsync() + .WithMessage($"Branch 'branch-5' already exists in stack 'Stack1'."); + } + + [Fact] + public async Task WhenAllInputsProvided_DoesNotAskForAnything_CreatesAndAddsBranchFromStackAndChangesToBranch() + { + // Arrange + var gitOperations = Substitute.For(); + var stackConfig = Substitute.For(); + var inputProvider = Substitute.For(); + var outputProvider = Substitute.For(); + var handler = new NewBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); + + var remoteUri = Some.HttpsUri().ToString(); + + gitOperations.GetRemoteUri().Returns(remoteUri); + gitOperations.GetCurrentBranch().Returns("branch-1"); + gitOperations.DoesLocalBranchExist("branch-5").Returns(false); + + var stacks = new List( + [ + new("Stack1", remoteUri, "branch-1", ["branch-3"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + ]); + stackConfig.Load().Returns(stacks); + stackConfig + .WhenForAnyArgs(s => s.Save(Arg.Any>())) + .Do(ci => stacks = ci.ArgAt>(0)); + + // Act + await handler.Handle(new NewBranchCommandInputs("Stack1", "branch-5", true)); + + // Assert + stacks.Should().BeEquivalentTo(new List + { + new("Stack1", remoteUri, "branch-1", ["branch-3", "branch-5"]), + new("Stack2", remoteUri, "branch-2", ["branch-4"]) + }); + inputProvider.ReceivedCalls().Should().BeEmpty(); + } +} diff --git a/src/Stack/Commands/Branch/AddBranchCommand.cs b/src/Stack/Commands/Branch/AddBranchCommand.cs index f010c73..5071593 100644 --- a/src/Stack/Commands/Branch/AddBranchCommand.cs +++ b/src/Stack/Commands/Branch/AddBranchCommand.cs @@ -1,8 +1,10 @@ using System.ComponentModel; using Spectre.Console; using Spectre.Console.Cli; +using Stack.Commands.Helpers; using Stack.Config; using Stack.Git; +using Stack.Infrastructure; namespace Stack.Commands; @@ -24,8 +26,35 @@ public override async Task ExecuteAsync(CommandContext context, AddBranchCo await Task.CompletedTask; var console = AnsiConsole.Console; - var gitOperations = new GitOperations(console, settings.GetGitOperationSettings()); - var stackConfig = new StackConfig(); + + var handler = new AddBranchCommandHandler( + new ConsoleInputProvider(console), + new ConsoleOutputProvider(console), + new GitOperations(console, settings.GetGitOperationSettings()), + new StackConfig()); + + await handler.Handle(new AddBranchCommandInputs(settings.Stack, settings.Name)); + + return 0; + } +} + +public record AddBranchCommandInputs(string? StackName, string? BranchName) +{ + public static AddBranchCommandInputs Empty => new(null, null); +} + +public record AddBranchCommandResponse(); + +public class AddBranchCommandHandler( + IInputProvider inputProvider, + IOutputProvider outputProvider, + IGitOperations gitOperations, + IStackConfig stackConfig) +{ + public async Task Handle(AddBranchCommandInputs inputs) + { + await Task.CompletedTask; var defaultBranch = gitOperations.GetDefaultBranch(); var remoteUri = gitOperations.GetRemoteUri(); @@ -38,24 +67,40 @@ public override async Task ExecuteAsync(CommandContext context, AddBranchCo if (stacksForRemote.Count == 0) { - console.WriteLine("No stacks found for current repository."); - return 0; + outputProvider.Information("No stacks found for current repository."); + return new AddBranchCommandResponse(); } - var stackSelection = settings.Stack ?? console.Prompt(Prompts.Stack(stacksForRemote, currentBranch)); - var stack = stacksForRemote.First(s => s.Name.Equals(stackSelection, StringComparison.OrdinalIgnoreCase)); + var stackNames = stacksForRemote.OrderByCurrentStackThenByName(currentBranch).Select(s => s.Name).ToArray(); + var stackSelection = inputs.StackName ?? inputProvider.Select(Questions.SelectStack, stackNames); + var stack = stacksForRemote.FirstOrDefault(s => s.Name.Equals(stackSelection, StringComparison.OrdinalIgnoreCase)); + + if (stack is null) + { + throw new InvalidOperationException($"Stack '{inputs.StackName}' not found."); + } var sourceBranch = stack.Branches.LastOrDefault() ?? stack.SourceBranch; + var branchName = inputs.BranchName ?? inputProvider.Select(Questions.SelectBranch, branches); + + if (stack.Branches.Contains(branchName)) + { + throw new InvalidOperationException($"Branch '{branchName}' already exists in stack '{stack.Name}'."); + } - var branchName = settings.Name ?? console.Prompt(Prompts.Branch(branches)); + if (!gitOperations.DoesLocalBranchExist(branchName)) + { + throw new InvalidOperationException($"Branch '{branchName}' does not exist locally."); + } - console.WriteLine($"Adding branch '{branchName}' to stack '{stack.Name}'"); + outputProvider.Information($"Adding branch {branchName.Branch()} to stack {stack.Name.Stack()}"); stack.Branches.Add(branchName); stackConfig.Save(stacks); - console.WriteLine($"Branch added"); - return 0; + outputProvider.Information($"Branch added"); + + return new AddBranchCommandResponse(); } -} +} \ No newline at end of file diff --git a/src/Stack/Commands/Branch/NewBranchCommand.cs b/src/Stack/Commands/Branch/NewBranchCommand.cs index f3098dc..5e41768 100644 --- a/src/Stack/Commands/Branch/NewBranchCommand.cs +++ b/src/Stack/Commands/Branch/NewBranchCommand.cs @@ -1,8 +1,10 @@ using System.ComponentModel; using Spectre.Console; using Spectre.Console.Cli; +using Stack.Commands.Helpers; using Stack.Config; using Stack.Git; +using Stack.Infrastructure; namespace Stack.Commands; @@ -15,6 +17,10 @@ public class NewBranchCommandSettings : DryRunCommandSettingsBase [Description("The name of the branch to create.")] [CommandOption("-n|--name")] public string? Name { get; init; } + + [Description("Force creating the branch without prompting.")] + [CommandOption("-f|--force")] + public bool Force { get; init; } } public class NewBranchCommand : AsyncCommand @@ -24,12 +30,40 @@ public override async Task ExecuteAsync(CommandContext context, NewBranchCo await Task.CompletedTask; var console = AnsiConsole.Console; - var gitOperations = new GitOperations(console, settings.GetGitOperationSettings()); - var stackConfig = new StackConfig(); + + var handler = new NewBranchCommandHandler( + new ConsoleInputProvider(console), + new ConsoleOutputProvider(console), + new GitOperations(console, settings.GetGitOperationSettings()), + new StackConfig()); + + await handler.Handle(new NewBranchCommandInputs(settings.Stack, settings.Name, settings.Force)); + + return 0; + } +} + +public record NewBranchCommandInputs(string? StackName, string? BranchName, bool Force) +{ + public static NewBranchCommandInputs Empty => new(null, null, false); +} + +public record NewBranchCommandResponse(); + +public class NewBranchCommandHandler( + IInputProvider inputProvider, + IOutputProvider outputProvider, + IGitOperations gitOperations, + IStackConfig stackConfig) +{ + public async Task Handle(NewBranchCommandInputs inputs) + { + await Task.CompletedTask; var defaultBranch = gitOperations.GetDefaultBranch(); var remoteUri = gitOperations.GetRemoteUri(); var currentBranch = gitOperations.GetCurrentBranch(); + var branches = gitOperations.GetLocalBranchesOrderedByMostRecentCommitterDate(); var stacks = stackConfig.Load(); @@ -37,18 +71,34 @@ public override async Task ExecuteAsync(CommandContext context, NewBranchCo if (stacksForRemote.Count == 0) { - console.WriteLine("No stacks found for current repository."); - return 0; + outputProvider.Information("No stacks found for current repository."); + return new NewBranchCommandResponse(); } - var stackSelection = settings.Stack ?? console.Prompt(Prompts.Stack(stacksForRemote, currentBranch)); - var stack = stacksForRemote.First(s => s.Name.Equals(stackSelection, StringComparison.OrdinalIgnoreCase)); + var stackNames = stacksForRemote.OrderByCurrentStackThenByName(currentBranch).Select(s => s.Name).ToArray(); + var stackSelection = inputs.StackName ?? inputProvider.Select(Questions.SelectStack, stackNames); + var stack = stacksForRemote.FirstOrDefault(s => s.Name.Equals(stackSelection, StringComparison.OrdinalIgnoreCase)); + + if (stack is null) + { + throw new InvalidOperationException($"Stack '{inputs.StackName}' not found."); + } var sourceBranch = stack.Branches.LastOrDefault() ?? stack.SourceBranch; - var branchName = settings.Name ?? console.Prompt(new TextPrompt("Branch name:")); + var branchName = inputs.BranchName ?? inputProvider.Text(Questions.BranchName); - console.WriteLine($"Creating branch '{branchName}' from '{sourceBranch}' in stack '{stack.Name}'"); + if (stack.Branches.Contains(branchName)) + { + throw new InvalidOperationException($"Branch '{branchName}' already exists in stack '{stack.Name}'."); + } + + if (gitOperations.DoesLocalBranchExist(branchName)) + { + throw new InvalidOperationException($"Branch '{branchName}' already exists locally."); + } + + outputProvider.Information($"Creating branch {branchName.Branch()} from {sourceBranch.Branch()} in stack {stack.Name.Stack()}"); gitOperations.CreateNewBranch(branchName, sourceBranch); gitOperations.PushNewBranch(branchName); @@ -57,15 +107,13 @@ public override async Task ExecuteAsync(CommandContext context, NewBranchCo stackConfig.Save(stacks); - console.WriteLine($"Branch created"); + outputProvider.Information($"Branch created"); - var switchToNewBranch = console.Prompt(new ConfirmationPrompt("Do you want to switch to the new branch?")); - - if (switchToNewBranch) + if (inputs.Force || inputProvider.Confirm(Questions.ConfirmSwitchToBranch)) { gitOperations.ChangeBranch(branchName); } - return 0; + return new NewBranchCommandResponse(); } -} +} \ No newline at end of file