public async Task PostsResults(List <Project> projects, List <Repository> repos)
        {
            Harbor.Setup(h => h.GetAllProjects()).ReturnsAsync(projects);
            Harbor.Setup(h => h.GetRepositories(It.IsAny <int>())).ReturnsAsync(repos);

            var tags = _fixture.CreateMany <Tag>(10);

            Harbor.Setup(h => h.GetTags(It.IsAny <string>())).ReturnsAsync(tags);

            ProcessResult result = null;

            NotificationHandler.Setup(n => n.Notify(It.IsAny <ProcessResult>())).Callback <ProcessResult>(c => result = c);

            Rules.Setup(r => r.Load()).Returns(new RuleSet
            {
                IgnoreGlobally = new RuleSet.GlobalIgnoreSettings
                {
                    Projects = new[] { projects.First().Name },
                    Repos    = new[] { repos.First().Name },
                },
                DefaultRule = new Rule
                {
                    Project = new Regex(".*"),
                    Repo    = new Regex(".*"),
                    Tag     = new Regex(".*"),
                    Keep    = 5
                }
            }.EnsureDefaults());

            await _sut.Process();

            Assert.NotNull(result);
            Assert.Equal(new ProcessResult(removedTags: 5, ignoredTags: 5, ignoredRepos: projects.Count - 1, ignoredProjects: 1), result);
        }
        public async Task DoesNotDeleteOnDryRun()
        {
            Settings.Nondestructive = true;

            var tags    = _fixture.CreateMany <Tag>(10);
            var project = _fixture.Create <Project>();
            var repo    = _fixture.Create <Repository>();

            Rules.Setup(r => r.Load()).Returns(new RuleSet
            {
                DefaultRule = new Rule
                {
                    Project = new Regex(".*"),
                    Repo    = new Regex(".*"),
                    Tag     = new Regex(".*"),
                    Keep    = 3
                }
            }.EnsureDefaults());

            Harbor.Setup(h => h.GetAllProjects()).ReturnsAsync(new[] { project });
            Harbor.Setup(h => h.GetRepositories(It.IsAny <int>())).ReturnsAsync(new[] { repo });
            Harbor.Setup(h => h.GetTags(It.IsAny <string>())).ReturnsAsync(tags);

            await _sut.Process();

            Harbor.Verify(h => h.DeleteTag(It.IsAny <string>(), It.IsAny <string>()), Times.Never);
        }
        public async Task KeepsSpecifiedNumberOfTagsViaDefault()
        {
            var tags = _fixture.CreateMany <Tag>(_ruleSet.DefaultRule.Keep + 5);

            Harbor.Setup(h => h.GetTags(It.IsAny <string>())).ReturnsAsync(tags);

            await _sut.Process();

            Harbor.Verify(h => h.DeleteTag(It.IsAny <string>(), It.IsAny <string>()), Times.Exactly(5));
        }
        public RuleTests()
        {
            Settings.Nondestructive = false;

            _project    = _fixture.Create <Project>();
            _repository = _fixture.Create <Repository>();

            Harbor.Setup(h => h.GetAllProjects()).ReturnsAsync(() => new[] { _project });
            Harbor.Setup(h => h.GetRepositories(It.IsAny <int>())).ReturnsAsync(() => new[] { _repository });
        }
        public async Task ExcludesGloballyIgnoredProject(Project p)
        {
            _ruleSet.IgnoreGlobally.Projects = new[] { p.Name };

            Harbor.Setup(h => h.GetAllProjects()).ReturnsAsync(new[] { p });

            await _sut.Process();

            Serilog.Verify(l => l.Verbose("Skipping project {@project}", p.Name), Times.Once);
            Harbor.Verify(h => h.GetRepositories(It.IsAny <int>()), Times.Never);
        }
        public async Task ExcludesGloballyIgnoredRepos(Project p, Repository r)
        {
            _ruleSet.IgnoreGlobally.Repos = new[] { r.Name };

            Harbor.Setup(h => h.GetAllProjects()).ReturnsAsync(new[] { p });
            Harbor.Setup(h => h.GetRepositories(It.IsAny <int>())).ReturnsAsync(new[] { r });

            await _sut.Process();

            Serilog.Verify(l => l.Verbose("Skipping repository {@repository}", r), Times.Once);
            Harbor.Verify(h => h.GetTags(It.IsAny <string>()), Times.Never);
        }
        public async Task RuleExcludesTag(Tag t)
        {
            _fixture.Inject(new Regex(".*"));
            _ruleSet.Rules.Add(_fixture.Build <Rule>().WithAutoProperties().With(r => r.Ignore, new[] { t.Name }).Create());

            Harbor.Setup(h => h.GetTags(It.IsAny <string>())).ReturnsAsync(new[] { t });

            await _sut.Process();

            Serilog.Verify(l => l.Information("Tag {repo}:{name} skipped because it was found in an ignore list that applies to {repo}", t.Repository, t.Name, _repository.Name), Times.Once);
            Harbor.Verify(h => h.DeleteTag(It.IsAny <string>(), It.IsAny <string>()), Times.Never);
        }
        public async Task ExcludesGloballyIgnoredTags(Project p, Repository r, Tag t)
        {
            _ruleSet.IgnoreGlobally.Tags = new[] { t.Name };

            Harbor.Setup(h => h.GetAllProjects()).ReturnsAsync(new[] { p });
            Harbor.Setup(h => h.GetRepositories(It.IsAny <int>())).ReturnsAsync(new[] { r });
            Harbor.Setup(h => h.GetTags(It.IsAny <string>())).ReturnsAsync(new[] { t });

            await _sut.Process();

            Serilog.Verify(l => l.Information("Tag {repo}:{name} skipped due to global ignore rules", t.Repository, t.Name), Times.Once);
            Harbor.Verify(h => h.DeleteTag(It.IsAny <string>(), It.IsAny <string>()), Times.Never);
        }
        public async Task UnmatchedTagsAreKeptImplicitly()
        {
            _ruleSet.DefaultRule.Tag = new Regex("^$");

            var tags = _fixture.CreateMany <Tag>(10);

            Harbor.Setup(h => h.GetTags(It.IsAny <string>())).ReturnsAsync(tags);

            await _sut.Process();

            Harbor.Verify(h => h.DeleteTag(It.IsAny <string>(), It.IsAny <string>()), Times.Never);
            Serilog.Verify(l => l.Warning("The default rule did not match all remaining tags for {@repo}. {count} remaining tags will be kept", _repository, 10), Times.Once);
        }
        public async Task KeepsTagIgnoredByDefaultRule()
        {
            _ruleSet.DefaultRule.Ignore = new[] { "latest" };

            var tags = _fixture.CreateMany <Tag>(10).Append(_fixture.Build <Tag>().WithAutoProperties().With(t => t.Name, "latest").Create());

            Harbor.Setup(h => h.GetTags(It.IsAny <string>())).ReturnsAsync(tags);

            await _sut.Process();

            Harbor.Verify(h => h.DeleteTag(It.IsAny <string>(), It.IsAny <string>()), Times.Exactly(5));
            Harbor.Verify(h => h.DeleteTag(It.IsAny <string>(), "latest"), Times.Never);
        }
        public async Task KeptByRule()
        {
            _fixture.Inject(new Regex(".*"));
            _ruleSet.Rules.Add(_fixture.Build <Rule>().WithAutoProperties().With(r => r.Keep, 5).Without(r => r.Ignore).Create());
            _ruleSet.DefaultRule.Tag = new Regex("^$");

            var tags = _fixture.CreateMany <Tag>(10);

            Harbor.Setup(h => h.GetTags(It.IsAny <string>())).ReturnsAsync(tags);

            await _sut.Process();

            Harbor.Verify(h => h.DeleteTag(It.IsAny <string>(), It.IsAny <string>()), Times.Exactly(5));
        }
        public async Task SkipsHarbor14CorruptTags()
        {
            var tag = _fixture.Build <Tag>()
                      .WithAutoProperties()
                      .Without(t => t.Digest)
                      .Create();

            Harbor.Setup(h => h.GetTags(It.IsAny <string>())).ReturnsAsync(new[] { tag });

            await _sut.Process();

            Serilog.Verify(l => l.Warning("Tag {repo}:{name} does not have a digest and was likely corrupted during a delete operation. This tag will be skipped. See https://github.com/vmware/harbor/issues/4214 for details", tag.Repository, tag.Name), Times.Once);
            Harbor.Verify(h => h.DeleteTag(It.IsAny <string>(), It.IsAny <string>()), Times.Never);
        }
        public async Task PostsUnhandledExceptions(RuleSet set)
        {
            Harbor.Setup(h => h.GetAllProjects()).ThrowsAsync(new Exception("Dummy"));

            Exception unhandledException = null;

            NotificationHandler.Setup(n => n.NotifyUnhandledException(It.IsAny <Exception>())).Callback <Exception>(ex => unhandledException = ex);

            Rules.Setup(r => r.Load()).Returns(set.EnsureDefaults());

            await Assert.ThrowsAsync <Exception>(async() => await _sut.Process());

            Assert.NotNull(unhandledException);
            Assert.Equal("Dummy", unhandledException.Message);
        }
        public async Task TagsThatHaveTheSameDigestAreKept()
        {
            _ruleSet.IgnoreGlobally.Tags = new[] { "latest" };

            var tags = _fixture.CreateMany <Tag>(10).ToList();

            var digest = _fixture.Create <string>();
            var latest = _fixture.Build <Tag>().WithAutoProperties().With(t => t.Digest, digest).With(t => t.Name, "latest").Create();
            var dup    = _fixture.Build <Tag>().WithAutoProperties().With(t => t.Digest, digest).With(t => t.Name, "dup").With(t => t.CreatedAt, DateTime.MinValue).Create();

            Harbor.Setup(h => h.GetTags(It.IsAny <string>())).ReturnsAsync(tags.Union(new[] { latest, dup }));

            await _sut.Process();

            Harbor.Verify(h => h.DeleteTag(It.IsAny <string>(), It.IsAny <string>()), Times.Exactly(5));
            Harbor.Verify(h => h.DeleteTag(latest.Repository, latest.Name), Times.Never);
            Harbor.Verify(h => h.DeleteTag(dup.Repository, dup.Name), Times.Never);
        }
        public async Task DefaultRuleProcessesRemainingTags()
        {
            _fixture.Inject(new Regex("^((?!foo).)*$"));
            _ruleSet.Rules.Add(_fixture.Build <Rule>().WithAutoProperties().With(r => r.Keep, 5).Without(r => r.Ignore).Create());
            _ruleSet.DefaultRule.Tag  = new Regex("^foo.*$");
            _ruleSet.DefaultRule.Keep = 3;

            var tags = _fixture.CreateMany <Tag>(10).ToList();

            foreach (var tag in _fixture.CreateMany <Tag>(10))
            {
                tag.Name = $"foo{tag.Name}";
                tags.Add(tag);
            }

            Harbor.Setup(h => h.GetTags(It.IsAny <string>())).ReturnsAsync(tags);

            await _sut.Process();

            Harbor.Verify(h => h.DeleteTag(It.IsAny <string>(), It.IsAny <string>()), Times.Exactly(12));
        }