public async void TestOrderedRetryPlanRunnerCancellation()
        {
            // Arrange
            var cts = new CancellationTokenSource();

            Mock <ICommand>[] commands = new[]
            {
                this.MakeMockCommandThatWorks("c1"),
                this.MakeMockCommandThatWorks("c2", () => cts.Cancel()),
                this.MakeMockCommandThatWorks("c3"),
            };
            var       plan                 = new Plan(commands.Select(c => c.Object).ToList());
            var       systemTime           = new Mock <ISystemTime>();
            const int CoolOffTimeInSeconds = 10;
            const int MaxRunCount          = 2;
            var       runner               = new OrderedRetryPlanRunner(MaxRunCount, CoolOffTimeInSeconds, systemTime.Object);

            // Act
            await runner.ExecuteAsync(1, plan, cts.Token);

            // Assert
            commands[0].Verify(m => m.ExecuteAsync(cts.Token), Times.Once());
            commands[1].Verify(m => m.ExecuteAsync(cts.Token), Times.Once());
            commands[2].Verify(m => m.ExecuteAsync(cts.Token), Times.Never());
        }
        public async void ExecuteAsyncRunsSkippedCommandAfterInitialTimeout()
        {
            // Arrange
            var       systemTime           = new Mock <ISystemTime>();
            const int CoolOffTimeInSeconds = 10;
            var       runner   = new OrderedRetryPlanRunner(5, CoolOffTimeInSeconds, systemTime.Object);
            var       commands = new List <Mock <ICommand> >
            {
                this.MakeMockCommandThatWorks("cmd1"),
                this.MakeMockCommandThatThrows("badcmd1"),
                this.MakeMockCommandThatWorks("cmd3")
            };
            var plan = new Plan(commands.Select(c => c.Object).ToList());
            CancellationToken token = CancellationToken.None;

            DateTime callTime = DateTime.UtcNow;

            systemTime.SetupGet(s => s.UtcNow)
            .Returns(() => callTime)
            .Callback(() => callTime = callTime.AddSeconds(25));

            // Act
            await Assert.ThrowsAsync <AggregateException>(() => runner.ExecuteAsync(1, plan, token));

            await Assert.ThrowsAsync <AggregateException>(() => runner.ExecuteAsync(1, plan, token));

            // Assert
            commands.ForEach(mc => mc.Verify(c => c.ExecuteAsync(token), Times.Exactly(2)));
        }
        public async void ExecuteAsyncSkipsCommandThatThrowsDuringSecondRun()
        {
            // Arrange
            var runner   = new OrderedRetryPlanRunner(5, 10, SystemTime.Instance);
            var commands = new List <Mock <ICommand> >
            {
                this.MakeMockCommandThatWorks("cmd1"),
                this.MakeMockCommandThatThrows("badcmd1"),
                this.MakeMockCommandThatWorks("cmd3")
            };
            var plan = new Plan(commands.Select(c => c.Object).ToList());
            CancellationToken token = CancellationToken.None;

            // Act
            await Assert.ThrowsAsync <AggregateException>(() => runner.ExecuteAsync(1, plan, token));

            await runner.ExecuteAsync(1, plan, token);

            // Assert
            List <Mock <ICommand> > goodCommands = commands.Where(c => c.Object.Id != "badcmd1").ToList();

            goodCommands.ForEach(mc => mc.Verify(c => c.ExecuteAsync(token), Times.Exactly(2)));
            commands
            .Except(goodCommands)
            .ToList()
            .ForEach(mc => mc.Verify(c => c.ExecuteAsync(token), Times.Once()));
        }
        public async void TestExecuteAsyncInputs()
        {
            // Arrange
            var runner = new OrderedRetryPlanRunner(5, 10, SystemTime.Instance);
            var plan   = new Plan(new List <ICommand>());
            CancellationToken token = CancellationToken.None;

            // Act
            // Assert
            await Assert.ThrowsAsync <ArgumentOutOfRangeException>(
                () => runner.ExecuteAsync(-2, plan, token));

            await Assert.ThrowsAsync <ArgumentNullException>(
                () => runner.ExecuteAsync(10, null, token));
        }
        public async void ExecuteAsyncRunsPlanCommandsEvenIfOneThrows()
        {
            // Arrange
            var runner   = new OrderedRetryPlanRunner(5, 10, SystemTime.Instance);
            var commands = new List <Mock <ICommand> >
            {
                this.MakeMockCommandThatWorks("cmd1"),
                this.MakeMockCommandThatThrows("badcmd1"),
                this.MakeMockCommandThatWorks("cmd3")
            };
            var plan = new Plan(commands.Select(c => c.Object).ToList());
            CancellationToken token = CancellationToken.None;

            // Act
            await Assert.ThrowsAsync <AggregateException>(() => runner.ExecuteAsync(1, plan, token));

            // Assert
            commands.ForEach(mc => mc.Verify(c => c.ExecuteAsync(token), Times.Once()));
        }
        public async void ExecuteAsyncResetsStatsOnFailingCommandOnceItSucceeds()
        {
            // Arrange
            var             systemTime           = new Mock <ISystemTime>();
            const int       CoolOffTimeInSeconds = 10;
            const int       MaxRunCount          = 2;
            var             runner      = new OrderedRetryPlanRunner(MaxRunCount, CoolOffTimeInSeconds, systemTime.Object);
            Mock <ICommand> goodCommand = this.MakeMockCommandThatWorks("cmd1");
            Mock <ICommand> badCommand  = this.MakeMockCommandThatThrows("badcmd1");
            var             commands    = new List <Mock <ICommand> >
            {
                goodCommand, badCommand
            };
            var plan = new Plan(commands.Select(c => c.Object).ToList());
            CancellationToken token = CancellationToken.None;

            DateTime callTime = DateTime.UtcNow;

            systemTime.SetupGet(s => s.UtcNow)
            .Returns(() => callTime)
            .Callback(() => callTime = callTime.AddSeconds(25));

            await Assert.ThrowsAsync <AggregateException>(() => runner.ExecuteAsync(1, plan, token));

            // make it so that the bad command runs fine now
            badCommand.Setup(c => c.ExecuteAsync(token)).Returns(Task.CompletedTask);

            // Act
            await runner.ExecuteAsync(1, plan, token);

            // now if we have the command fail, the retry count should be 1 which
            // means that during yet another run it should get executed after another 20 seconds
            badCommand.Setup(c => c.ExecuteAsync(token)).ThrowsAsync(new InvalidOperationException("No donuts for you"));
            await Assert.ThrowsAsync <AggregateException>(() => runner.ExecuteAsync(1, plan, token));

            await Assert.ThrowsAsync <AggregateException>(() => runner.ExecuteAsync(1, plan, token));

            // Assert
            goodCommand.Verify(c => c.ExecuteAsync(token), Times.Exactly(4));
            badCommand.Verify(c => c.ExecuteAsync(token), Times.Exactly(4));
        }
        public async void ExecuteAsyncRunsPlanCommandsTwiceForSameDeployment()
        {
            // Arrange
            var runner   = new OrderedRetryPlanRunner(5, 10, SystemTime.Instance);
            var commands = new List <Mock <ICommand> >
            {
                this.MakeMockCommandThatWorks("cmd1"),
                this.MakeMockCommandThatWorks("cmd2"),
                this.MakeMockCommandThatWorks("cmd3")
            };
            var plan = new Plan(commands.Select(c => c.Object).ToList());
            CancellationToken token = CancellationToken.None;

            // Act
            await runner.ExecuteAsync(1, plan, token);

            await runner.ExecuteAsync(1, plan, token);

            // Assert
            commands.ForEach(mc => mc.Verify(c => c.ExecuteAsync(token), Times.Exactly(2)));
        }