From 6822de713671e36f1e16aa4055279bd2be436ae1 Mon Sep 17 00:00:00 2001 From: Coen van den Munckhof Date: Sun, 9 Jul 2023 10:31:14 +0200 Subject: [PATCH 01/10] small fixes --- .../IO/DefaultRepositoryActionProvider.cs | 2 +- .../IO/GitRepositoryFinderFactory.cs | 9 ++- src/RepoM.Api/IO/Methods/FindFilesMethod.cs | 2 +- .../ActionAssociateFileV1Mapper.cs | 2 +- .../ActionMappers/ActionGitFetchV1Mapper.cs | 2 +- ...JsonDynamicRepositoryActionDeserializer.cs | 15 ---- .../RepositorySpecificConfiguration.cs | 2 +- src/RepoM.App/Bootstrapper.cs | 2 +- src/RepoM.App/InternalsVisisbleTo.cs | 1 + .../DefaultINamedQueryParser.cs | 23 +++--- .../DefaultQueryParserTests.cs | 76 +++++++++++++++++++ 11 files changed, 100 insertions(+), 36 deletions(-) create mode 100644 src/RepoM.App/InternalsVisisbleTo.cs create mode 100644 tests/RepoM.App.Tests/RepositoryFiltering/DefaultQueryParserTests.cs diff --git a/src/RepoM.Api/IO/DefaultRepositoryActionProvider.cs b/src/RepoM.Api/IO/DefaultRepositoryActionProvider.cs index c265bcde..58f19b64 100644 --- a/src/RepoM.Api/IO/DefaultRepositoryActionProvider.cs +++ b/src/RepoM.Api/IO/DefaultRepositoryActionProvider.cs @@ -32,7 +32,7 @@ public DefaultRepositoryActionProvider( public RepositoryActionBase? GetSecondaryAction(Repository repository) { RepositoryActionBase[] actions = GetContextMenuActions(new[] { repository, }).Take(2).ToArray(); - return actions.Length > 1 ? actions.ElementAt(1) : null; + return actions.Length > 1 ? actions[1] : null; } public IEnumerable GetContextMenuActions(IEnumerable repositories) diff --git a/src/RepoM.Api/IO/GitRepositoryFinderFactory.cs b/src/RepoM.Api/IO/GitRepositoryFinderFactory.cs index 9ca39ad0..6de3b056 100644 --- a/src/RepoM.Api/IO/GitRepositoryFinderFactory.cs +++ b/src/RepoM.Api/IO/GitRepositoryFinderFactory.cs @@ -25,9 +25,10 @@ public IGitRepositoryFinder Create() { if (!string.IsNullOrWhiteSpace(enabledSearchProviderName)) { - factory = _factories.FirstOrDefault(searchProviderFactory => searchProviderFactory.IsActive - && - searchProviderFactory.Name.Equals(enabledSearchProviderName, StringComparison.CurrentCultureIgnoreCase)); + factory = _factories.Find(searchProviderFactory => + searchProviderFactory.IsActive + && + searchProviderFactory.Name.Equals(enabledSearchProviderName, StringComparison.CurrentCultureIgnoreCase)); } if (factory != null) @@ -37,7 +38,7 @@ public IGitRepositoryFinder Create() } // Default, fallback - factory = _factories.FirstOrDefault(searchProviderFactory => searchProviderFactory is GravellGitRepositoryFinderFactory); + factory = _factories.Find(searchProviderFactory => searchProviderFactory is GravellGitRepositoryFinderFactory); if (factory != null) { return factory.Create(); diff --git a/src/RepoM.Api/IO/Methods/FindFilesMethod.cs b/src/RepoM.Api/IO/Methods/FindFilesMethod.cs index 1d5aa2e7..00a2d8c1 100644 --- a/src/RepoM.Api/IO/Methods/FindFilesMethod.cs +++ b/src/RepoM.Api/IO/Methods/FindFilesMethod.cs @@ -66,6 +66,6 @@ private static IEnumerable GetFileEnumerator(string path, string searchP return directory .EnumerateFileSystemInfos(searchPattern, SearchOption.AllDirectories) .Select(f => f.FullName) - .Where(f => !f.StartsWith(".")); + .Where(f => !f.StartsWith('.')); } } \ No newline at end of file diff --git a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionAssociateFileV1Mapper.cs b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionAssociateFileV1Mapper.cs index 8a86e3e6..1fa6a7e1 100644 --- a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionAssociateFileV1Mapper.cs +++ b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionAssociateFileV1Mapper.cs @@ -113,6 +113,6 @@ private static IEnumerable GetFileEnumerator(IRepository repository, str return directory .EnumerateFileSystemInfos(searchPattern, SearchOption.AllDirectories) .Select(f => f.FullName) - .Where(f => !f.StartsWith(".")); + .Where(f => !f.StartsWith('.')); } } \ No newline at end of file diff --git a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionGitFetchV1Mapper.cs b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionGitFetchV1Mapper.cs index d01bfa42..4a862a90 100644 --- a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionGitFetchV1Mapper.cs +++ b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionGitFetchV1Mapper.cs @@ -35,7 +35,7 @@ bool IActionToRepositoryActionMapper.CanHandleMultipleRepositories() IEnumerable IActionToRepositoryActionMapper.Map(RepositoryAction action, IEnumerable repositories, ActionMapperComposition actionMapperComposition) { Repository[] repos = repositories as Repository[] ?? repositories.ToArray(); - if (repos.Any(r => !_expressionEvaluator.EvaluateBooleanExpression(action.Active, r))) + if (Array.Exists(repos, r => !_expressionEvaluator.EvaluateBooleanExpression(action.Active, r))) { yield break; } diff --git a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/Deserialization/JsonDynamicRepositoryActionDeserializer.cs b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/Deserialization/JsonDynamicRepositoryActionDeserializer.cs index 041cb651..aef20468 100644 --- a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/Deserialization/JsonDynamicRepositoryActionDeserializer.cs +++ b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/Deserialization/JsonDynamicRepositoryActionDeserializer.cs @@ -60,21 +60,6 @@ public RepositoryActionConfiguration Deserialize(string rawContent) DeserializeRepositoryActions(token, configuration); } - // if (version == 2) - // { - // token = jsonObject["env-files"]; - // configuration.RepositorySpecificEnvironmentFiles.AddRange(TryDeserializeEnumerable(token)); - // - // token = jsonObject["variables"]; - // configuration.Variables.AddRange(TryDeserializeEnumerableVariable(token)); - // - // token = jsonObject["tags"]; - // DeserializeRepositoryTags(token, ref configuration); - // - // token = jsonObject["repository-actions"]; - // DeserializeRepositoryActions(token, configuration); - // } - return configuration; } diff --git a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/RepositorySpecificConfiguration.cs b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/RepositorySpecificConfiguration.cs index d0cfa011..4b316fcd 100644 --- a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/RepositorySpecificConfiguration.cs +++ b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/RepositorySpecificConfiguration.cs @@ -280,7 +280,7 @@ private bool IsEnabled(string? booleanExpression, bool defaultWhenNullOrEmpty, I private RepositoryActionConfiguration Deserialize(string extension, string rawContent) { - if (extension.StartsWith(".")) + if (extension.StartsWith('.')) { extension = extension[1..]; } diff --git a/src/RepoM.App/Bootstrapper.cs b/src/RepoM.App/Bootstrapper.cs index fa7b3452..ddbaa8f1 100644 --- a/src/RepoM.App/Bootstrapper.cs +++ b/src/RepoM.App/Bootstrapper.cs @@ -48,7 +48,7 @@ namespace RepoM.App; using RepoM.App.Plugins; using RepoM.App.Services.HotKey; -public static class Bootstrapper // this should be internal, but made public because of test. +internal static class Bootstrapper { public static readonly Container Container = new(); diff --git a/src/RepoM.App/InternalsVisisbleTo.cs b/src/RepoM.App/InternalsVisisbleTo.cs new file mode 100644 index 00000000..51776b0e --- /dev/null +++ b/src/RepoM.App/InternalsVisisbleTo.cs @@ -0,0 +1 @@ +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("RepoM.App.Tests")] \ No newline at end of file diff --git a/src/RepoM.App/RepositoryFiltering/DefaultINamedQueryParser.cs b/src/RepoM.App/RepositoryFiltering/DefaultINamedQueryParser.cs index bc6a6ee9..14edcea1 100644 --- a/src/RepoM.App/RepositoryFiltering/DefaultINamedQueryParser.cs +++ b/src/RepoM.App/RepositoryFiltering/DefaultINamedQueryParser.cs @@ -14,26 +14,27 @@ internal class DefaultQueryParser : INamedQueryParser public IQuery Parse(string text) { - List q = new(); + IQuery? isPinnedQuery = null; if (text.StartsWith("is:pinned")) { - q.Add(new SimpleTerm("is", "pinned")); - text = text.Replace("is:pinned", " "); + isPinnedQuery = new SimpleTerm("is", "pinned"); + text = text.Replace("is:pinned", string.Empty); } else if(text.StartsWith("is:unpinned")) { - q.Add(new SimpleTerm("is", "unpinned")); - text = text.Replace("is:unpinned", " "); + isPinnedQuery = new SimpleTerm("is", "unpinned"); + text = text.Replace("is:unpinned", string.Empty); } - q.Add(new FreeText(text)); - - if (q.Any()) + if (isPinnedQuery != null && string.IsNullOrWhiteSpace(text)) { - return new AndQuery(q.ToArray()); - + return isPinnedQuery; } - return q.First(); + var freeTextQuery = new FreeText(text); + + return isPinnedQuery == null + ? freeTextQuery + : new AndQuery(isPinnedQuery, freeTextQuery); } } \ No newline at end of file diff --git a/tests/RepoM.App.Tests/RepositoryFiltering/DefaultQueryParserTests.cs b/tests/RepoM.App.Tests/RepositoryFiltering/DefaultQueryParserTests.cs new file mode 100644 index 00000000..f5426865 --- /dev/null +++ b/tests/RepoM.App.Tests/RepositoryFiltering/DefaultQueryParserTests.cs @@ -0,0 +1,76 @@ +namespace RepoM.App.Tests.RepositoryFiltering; + +using FluentAssertions; +using Lucene.Net.Search; +using RepoM.App.RepositoryFiltering; +using RepoM.Core.Plugin.RepositoryFiltering.Clause; +using RepoM.Core.Plugin.RepositoryFiltering.Clause.Terms; +using Xunit; + +public class DefaultQueryParserTests +{ + private readonly DefaultQueryParser _sut; + + public DefaultQueryParserTests() + { + _sut = new DefaultQueryParser(); + } + + [Fact] + public void Name_ShouldBeDefault() + { + _sut.Name.Should().Be("Default"); + } + + [Theory] + [InlineData("")] + [InlineData("abc")] + [InlineData("abc def")] + [InlineData("abc GHI")] + [InlineData(" abc GHI ")] + public void Parse_ShouldReturnFreeTextQuery_WhenInputHasNoSpecialMeaning(string input) + { + // arrange + + // act + IQuery result = _sut.Parse(input); + + // assert + result.Should().BeOfType(); + ((FreeText)result).Value.Should().Be(input); + } + + [Theory] + [InlineData("is:pinned", "pinned")] + [InlineData("is:pinned ", "pinned")] + [InlineData("is:unpinned", "unpinned")] + [InlineData("is:unpinned ", "unpinned")] + public void Parse_ShouldReturnSimpleTerm_WhenInputIsExactPinnedOrUnpinned(string input, string expectedValue) + { + // arrange + + // act + IQuery result = _sut.Parse(input); + + // assert + result.Should().BeOfType(); + ((SimpleTerm)result).Term.Should().Be("is"); + ((SimpleTerm)result).Value.Should().Be(expectedValue); + } + + [Theory] + [InlineData("is:pinned test123")] + [InlineData("is:pinned test123")] + [InlineData("is:unpinned test123")] + [InlineData("is:unpinned test123")] + public void Parse_ShouldReturnAndQuery_WhenInputIsExactPinnedOrUnpinned(string input) + { + // arrange + + // act + IQuery result = _sut.Parse(input); + + // assert + result.Should().BeOfType(); + } +} \ No newline at end of file From 9f34a48defb1dad520967c84fc9cdcaf2126f09b Mon Sep 17 00:00:00 2001 From: Coen van den Munckhof Date: Sun, 9 Jul 2023 10:42:11 +0200 Subject: [PATCH 02/10] update --- .../ActionMappers/ActionGitPullV1Mapper.cs | 2 +- .../ActionMappers/ActionGitPushV1Mapper.cs | 2 +- .../ActionMappers/ActionIgnoreRepositoriesV1Mapper.cs | 2 +- .../RepositorySpecificConfiguration.cs | 2 +- src/RepoM.Api/IO/WindowsPathSkipper.cs | 2 +- .../RepositoryFiltering/QueryMatchers/FreeTextMatcher.cs | 2 +- src/RepoM.App/RepositoryFiltering/QueryMatchers/TagMatcher.cs | 2 +- .../Internal/AzureDevOpsPullRequestService.cs | 2 +- .../RepositoryFiltering/HasPullRequestsMatcher.cs | 3 +-- 9 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionGitPullV1Mapper.cs b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionGitPullV1Mapper.cs index 90fd316b..d3092eca 100644 --- a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionGitPullV1Mapper.cs +++ b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionGitPullV1Mapper.cs @@ -35,7 +35,7 @@ bool IActionToRepositoryActionMapper.CanHandleMultipleRepositories() IEnumerable IActionToRepositoryActionMapper.Map(RepositoryAction action, IEnumerable repositories, ActionMapperComposition actionMapperComposition) { Repository[] repos = repositories as Repository[] ?? repositories.ToArray(); - if (repos.Any(r => !_expressionEvaluator.EvaluateBooleanExpression(action.Active, r))) + if (Array.Exists(repos, r => !_expressionEvaluator.EvaluateBooleanExpression(action.Active, r))) { yield break; } diff --git a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionGitPushV1Mapper.cs b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionGitPushV1Mapper.cs index 92d3801e..cd935750 100644 --- a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionGitPushV1Mapper.cs +++ b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionGitPushV1Mapper.cs @@ -35,7 +35,7 @@ bool IActionToRepositoryActionMapper.CanHandleMultipleRepositories() IEnumerable IActionToRepositoryActionMapper.Map(RepositoryAction action, IEnumerable repositories, ActionMapperComposition actionMapperComposition) { Repository[] repos = repositories as Repository[] ?? repositories.ToArray(); - if (repos.Any(r => !_expressionEvaluator.EvaluateBooleanExpression(action.Active, r))) + if (Array.Exists(repos, r => !_expressionEvaluator.EvaluateBooleanExpression(action.Active, r))) { yield break; } diff --git a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionIgnoreRepositoriesV1Mapper.cs b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionIgnoreRepositoriesV1Mapper.cs index be595f27..f289b5f0 100644 --- a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionIgnoreRepositoriesV1Mapper.cs +++ b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionIgnoreRepositoriesV1Mapper.cs @@ -37,7 +37,7 @@ bool IActionToRepositoryActionMapper.CanHandleMultipleRepositories() IEnumerable IActionToRepositoryActionMapper.Map(Data.RepositoryAction action, IEnumerable repositories, ActionMapperComposition actionMapperComposition) { Repository[] repos = repositories as Repository[] ?? repositories.ToArray(); - if (repos.Any(r => !_expressionEvaluator.EvaluateBooleanExpression(action.Active, r))) + if (Array.Exists(repos, r => !_expressionEvaluator.EvaluateBooleanExpression(action.Active, r))) { yield break; } diff --git a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/RepositorySpecificConfiguration.cs b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/RepositorySpecificConfiguration.cs index 4b316fcd..84563e66 100644 --- a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/RepositorySpecificConfiguration.cs +++ b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/RepositorySpecificConfiguration.cs @@ -473,7 +473,7 @@ public IEnumerable CreateActions(params Repository[] repos if (multiSelectRequired) { - var actionNotCapableForMultipleRepos = repositories.Any(repo => !IsEnabled(action.MultiSelectEnabled, false, repo)); + var actionNotCapableForMultipleRepos = Array.Exists(repositories, repo => !IsEnabled(action.MultiSelectEnabled, false, repo)); if (actionNotCapableForMultipleRepos) { continue; diff --git a/src/RepoM.Api/IO/WindowsPathSkipper.cs b/src/RepoM.Api/IO/WindowsPathSkipper.cs index 60cd094a..3eeff8ce 100644 --- a/src/RepoM.Api/IO/WindowsPathSkipper.cs +++ b/src/RepoM.Api/IO/WindowsPathSkipper.cs @@ -23,6 +23,6 @@ public WindowsPathSkipper() public bool ShouldSkip(string path) { - return _exclusions.Any(ex => path.IndexOf(ex, StringComparison.OrdinalIgnoreCase) > -1); + return _exclusions.Exists(ex => path.IndexOf(ex, StringComparison.OrdinalIgnoreCase) > -1); } } \ No newline at end of file diff --git a/src/RepoM.App/RepositoryFiltering/QueryMatchers/FreeTextMatcher.cs b/src/RepoM.App/RepositoryFiltering/QueryMatchers/FreeTextMatcher.cs index d1889229..e09731bb 100644 --- a/src/RepoM.App/RepositoryFiltering/QueryMatchers/FreeTextMatcher.cs +++ b/src/RepoM.App/RepositoryFiltering/QueryMatchers/FreeTextMatcher.cs @@ -30,7 +30,7 @@ public FreeTextMatcher(bool ignoreCase, bool ignoreCaseTag) return null; } - if (repository.Tags.Any(x => x.Equals(st.Value, _stringComparisonTag))) + if (Array.Exists(repository.Tags, x => x.Equals(st.Value, _stringComparisonTag))) { return true; } diff --git a/src/RepoM.App/RepositoryFiltering/QueryMatchers/TagMatcher.cs b/src/RepoM.App/RepositoryFiltering/QueryMatchers/TagMatcher.cs index c6eed727..7c38be2c 100644 --- a/src/RepoM.App/RepositoryFiltering/QueryMatchers/TagMatcher.cs +++ b/src/RepoM.App/RepositoryFiltering/QueryMatchers/TagMatcher.cs @@ -38,7 +38,7 @@ private static bool CheckTerm(in string term) } var value = term.Value; - return repository.Tags.Any(tag => + return Array.Exists(repository.Tags, tag => tag.Equals(value, StringComparison.CurrentCulture) || tag.StartsWith(value, StringComparison.CurrentCulture)); diff --git a/src/RepoM.Plugin.AzureDevOps/Internal/AzureDevOpsPullRequestService.cs b/src/RepoM.Plugin.AzureDevOps/Internal/AzureDevOpsPullRequestService.cs index 16f61eea..4edd51cf 100644 --- a/src/RepoM.Plugin.AzureDevOps/Internal/AzureDevOpsPullRequestService.cs +++ b/src/RepoM.Plugin.AzureDevOps/Internal/AzureDevOpsPullRequestService.cs @@ -451,7 +451,7 @@ private Guid FindRepositoryGuid(IRepository repository) } GitRepository[] selectedRepos = _devOpsGitRepositories.Values - .Where(x => x.ValidRemoteUrls.Any(u => u.Equals(searchRepoUrl, StringComparison.CurrentCultureIgnoreCase))) + .Where(x => Array.Exists(x.ValidRemoteUrls, u => u.Equals(searchRepoUrl, StringComparison.CurrentCultureIgnoreCase))) .ToArray(); if (selectedRepos.Length == 0) diff --git a/src/RepoM.Plugin.AzureDevOps/RepositoryFiltering/HasPullRequestsMatcher.cs b/src/RepoM.Plugin.AzureDevOps/RepositoryFiltering/HasPullRequestsMatcher.cs index a602b1ce..cbfc04d9 100644 --- a/src/RepoM.Plugin.AzureDevOps/RepositoryFiltering/HasPullRequestsMatcher.cs +++ b/src/RepoM.Plugin.AzureDevOps/RepositoryFiltering/HasPullRequestsMatcher.cs @@ -2,7 +2,6 @@ namespace RepoM.Plugin.AzureDevOps.RepositoryFiltering; using RepoM.Core.Plugin.RepositoryFiltering.Clause.Terms; using RepoM.Core.Plugin.RepositoryFiltering; -using System.Linq; using System; using RepoM.Core.Plugin.Repository; using RepoM.Plugin.AzureDevOps.Internal; @@ -43,7 +42,7 @@ public HasPullRequestsMatcher(IAzureDevOpsPullRequestService azureDevOpsPullRequ return null; } - if (_values.Any(x => x.Equals(st.Value, _stringComparisonValue))) + if (Array.Exists(_values, x => x.Equals(st.Value, _stringComparisonValue))) { return HasPullRequests(repository); } From a54c47914f13e03828b35a67b192043aa71813ce Mon Sep 17 00:00:00 2001 From: Coen van den Munckhof Date: Mon, 10 Jul 2023 22:27:58 +0200 Subject: [PATCH 03/10] update --- .../RepositorySpecificConfiguration.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/RepositorySpecificConfiguration.cs b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/RepositorySpecificConfiguration.cs index 84563e66..de06be6d 100644 --- a/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/RepositorySpecificConfiguration.cs +++ b/src/RepoM.Api/IO/ModuleBasedRepositoryActionProvider/RepositorySpecificConfiguration.cs @@ -353,7 +353,7 @@ List EvaluateVariables(IEnumerable? vars) using IDisposable d1 = RepoMVariableProviderStore.Push(variables ?? new List(0)); using IDisposable d2 = EnvironmentVariableStore.Set(repositoryEnvVars); - foreach (TagsCollection tagsCollection in tags?.Where(t => t != null) ?? Array.Empty()) + foreach (TagsCollection tagsCollection in ((IEnumerable?)tags) ?? Array.Empty()) { using IDisposable d3 = RepoMVariableProviderStore.Push(EvaluateVariables(tagsCollection.Variables)); @@ -463,7 +463,7 @@ public IEnumerable CreateActions(params Repository[] repos using IDisposable d2 = EnvironmentVariableStore.Set(repositoryEnvVars ?? new Dictionary()); // load variables global - foreach (ActionsCollection actionsCollection in actions?.Where(action => action != null) ?? Array.Empty()) + foreach (ActionsCollection actionsCollection in ((IEnumerable?)actions) ?? Array.Empty()) { using IDisposable d3 = RepoMVariableProviderStore.Push(EvaluateVariables(actionsCollection.Variables, singleRepository)); @@ -535,7 +535,7 @@ private IEnumerable CreateFailing(Exception ex, string? filena } } - private object? Evaluate(object? input, Repository repository) + private object? Evaluate(object? input, IRepository repository) { if (input is string s) { @@ -545,7 +545,7 @@ private IEnumerable CreateFailing(Exception ex, string? filena return input; } - private bool IsEnabled(string? booleanExpression, bool defaultWhenNullOrEmpty, Repository repository) + private bool IsEnabled(string? booleanExpression, bool defaultWhenNullOrEmpty, IRepository repository) { return string.IsNullOrWhiteSpace(booleanExpression) ? defaultWhenNullOrEmpty From 31386d56a2bfe97f5491f26eed05b747173e0b9f Mon Sep 17 00:00:00 2001 From: Coen van den Munckhof Date: Tue, 11 Jul 2023 21:20:40 +0200 Subject: [PATCH 04/10] adding some tests --- .../ActionMapperCompositionFactory.cs | 11 +- .../ActionAssociateFileV1MapperTests.cs | 153 ++++++++++++++++++ 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 tests/RepoM.Api.Tests/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionAssociateFileV1MapperTests.cs diff --git a/tests/RepoM.Api.Tests/IO/ModuleBasedRepositoryActionProvider/ActionMapperCompositionFactory.cs b/tests/RepoM.Api.Tests/IO/ModuleBasedRepositoryActionProvider/ActionMapperCompositionFactory.cs index d18af186..6fbad79c 100644 --- a/tests/RepoM.Api.Tests/IO/ModuleBasedRepositoryActionProvider/ActionMapperCompositionFactory.cs +++ b/tests/RepoM.Api.Tests/IO/ModuleBasedRepositoryActionProvider/ActionMapperCompositionFactory.cs @@ -17,7 +17,7 @@ public static ActionMapperComposition Create( IRepositoryWriter repositoryWriter, IRepositoryMonitor repositoryMonitor) { - var list = new List + var mappers = new IActionToRepositoryActionMapper[] { new ActionBrowseRepositoryV1Mapper(expressionEvaluator, translationService), new ActionBrowserV1Mapper(expressionEvaluator, translationService), @@ -35,6 +35,15 @@ public static ActionMapperComposition Create( new ActionJustTextV1Mapper(expressionEvaluator, translationService), }; + return new ActionMapperComposition(mappers, expressionEvaluator); + } + + public static ActionMapperComposition CreateSmall(IRepositoryExpressionEvaluator expressionEvaluator, IActionToRepositoryActionMapper actionToRepositoryActionMapper) + { + var list = new IActionToRepositoryActionMapper[1] + { + actionToRepositoryActionMapper + }; return new ActionMapperComposition(list, expressionEvaluator); } } \ No newline at end of file diff --git a/tests/RepoM.Api.Tests/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionAssociateFileV1MapperTests.cs b/tests/RepoM.Api.Tests/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionAssociateFileV1MapperTests.cs new file mode 100644 index 00000000..3060b795 --- /dev/null +++ b/tests/RepoM.Api.Tests/IO/ModuleBasedRepositoryActionProvider/ActionMappers/ActionAssociateFileV1MapperTests.cs @@ -0,0 +1,153 @@ +namespace RepoM.Api.Tests.IO.ModuleBasedRepositoryActionProvider.ActionMappers; + +using System; +using System.Collections.Generic; +using System.Linq; +using FakeItEasy; +using FluentAssertions; +using LibGit2Sharp; +using RepoM.Api.Common; +using RepoM.Api.Git; +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider; +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider.ActionMappers; +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider.Data.Actions; +using RepoM.Core.Plugin.Expressions; +using Xunit; +using Repository = RepoM.Api.Git.Repository; + +public class ActionAssociateFileV1MapperTests +{ + private readonly IRepositoryExpressionEvaluator _expressionEvaluator; + private readonly ITranslationService _translationService; + private Repository _repository; + + public ActionAssociateFileV1MapperTests() + { + _repository = new Repository(""); + _expressionEvaluator = A.Fake(); + _translationService = A.Fake(); + A.CallTo(() => _translationService.Translate(A._, A._)).ReturnsLazily(call => call.Arguments[0] as string ?? "dummy"); + A.CallTo(() => _translationService.Translate(A._)).ReturnsLazily(call => call.Arguments[0] as string ?? "dummy"); + } + + [Fact] + public void Ctor_ShouldThrown_WhenArgumentIsNull() + { + // arrange + + // act + Action act1 = () => _ = new ActionAssociateFileV1Mapper(_expressionEvaluator, null!); + Action act2 = () => _ = new ActionAssociateFileV1Mapper(null!, _translationService); + + // assert + act1.Should().ThrowExactly(); + act2.Should().ThrowExactly(); + } + + [Fact] + public void CanHandleMultipleRepositories_ShouldReturnFalse() + { + // arrange + var sut = new ActionAssociateFileV1Mapper(_expressionEvaluator, _translationService) as IActionToRepositoryActionMapper; + + // act + var result = sut.CanHandleMultipleRepositories(); + + // assert + result.Should().BeFalse(); + } + + [Fact] + public void CanMap_ShouldReturnFalse_WhenInputNull() + { + // arrange + var sut = new ActionAssociateFileV1Mapper(_expressionEvaluator, _translationService) as IActionToRepositoryActionMapper; + + // act + var result = sut.CanMap(null!); + + // assert + result.Should().BeFalse(); + } + + [Fact] + public void CanMap_ShouldReturnFalse_WhenInputIsNotCorrectType() + { + // arrange + + var sut = new ActionAssociateFileV1Mapper(_expressionEvaluator, _translationService) as IActionToRepositoryActionMapper; + + // act + var result = sut.CanMap(new DummyRepositoryAction()); + + // assert + result.Should().BeFalse(); + } + + [Fact] + public void CanMap_ShouldReturnTrue_WhenInputIsOfTypeRepositoryActionAssociateFileV1() + { + // arrange + var sut = new ActionAssociateFileV1Mapper(_expressionEvaluator, _translationService) as IActionToRepositoryActionMapper; + + // act + var result = sut.CanMap(new RepositoryActionAssociateFileV1()); + + // assert + result.Should().BeTrue(); + } + + [Fact] + public void Map_ShouldReturnEmpty_WhenActionIsNotOfCorrectType() + { + // arrange + var action = new DummyRepositoryAction(); + var sut = new ActionAssociateFileV1Mapper(_expressionEvaluator, _translationService) as IActionToRepositoryActionMapper; + + // act + RepositoryActionBase[] result = sut.Map(action, new [] { _repository, }, ActionMapperCompositionFactory.CreateSmall(_expressionEvaluator, A.Dummy())).ToArray(); + + // assert + result.Should().BeEmpty(); + } + + [Fact] + public void Map_ShouldReturnEmpty_WhenActionActiveIsEvaluatesToFalse() + { + // arrange + var action = new RepositoryActionAssociateFileV1() + { + Active = "dummy active string", + }; + A.CallTo(() => _expressionEvaluator.EvaluateBooleanExpression(action.Active, _repository)).Returns(false); + var sut = new ActionAssociateFileV1Mapper(_expressionEvaluator, _translationService) as IActionToRepositoryActionMapper; + + // act + RepositoryActionBase[] result = sut.Map(action, new [] { _repository, }, ActionMapperCompositionFactory.CreateSmall(_expressionEvaluator, A.Dummy())).ToArray(); + + // assert + result.Should().BeEmpty(); + } + + [Fact] + public void Map_ShouldReturnEmpty_WhenActionExtensionIsNull() + { + // arrange + var action = new RepositoryActionAssociateFileV1() + { + Extension = null, + }; + A.CallTo(() => _expressionEvaluator.EvaluateBooleanExpression(A._, _repository)).Returns(true); + var sut = new ActionAssociateFileV1Mapper(_expressionEvaluator, _translationService) as IActionToRepositoryActionMapper; + + // act + RepositoryActionBase[] result = sut.Map(action, new [] { _repository, }, ActionMapperCompositionFactory.CreateSmall(_expressionEvaluator, A.Dummy())).ToArray(); + + // assert + result.Should().BeEmpty(); + } +} + +file class DummyRepositoryAction : Api.IO.ModuleBasedRepositoryActionProvider.Data.RepositoryAction +{ +} \ No newline at end of file From 27e5e31b3aa3fa4dc117f347190f4be6b75b8ba3 Mon Sep 17 00:00:00 2001 From: Coen van den Munckhof Date: Tue, 11 Jul 2023 21:25:41 +0200 Subject: [PATCH 05/10] add --- .../AzureDevOpsPullRequestServiceTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/RepoM.Plugin.AzureDevOps.Tests/Internal/AzureDevOpsPullRequestServiceTests.cs diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/Internal/AzureDevOpsPullRequestServiceTests.cs b/tests/RepoM.Plugin.AzureDevOps.Tests/Internal/AzureDevOpsPullRequestServiceTests.cs new file mode 100644 index 00000000..aaeb862e --- /dev/null +++ b/tests/RepoM.Plugin.AzureDevOps.Tests/Internal/AzureDevOpsPullRequestServiceTests.cs @@ -0,0 +1,26 @@ +namespace RepoM.Plugin.AzureDevOps.Tests.Internal; + +using System; +using FakeItEasy; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using RepoM.Api.Common; +using RepoM.Plugin.AzureDevOps.Internal; +using Xunit; + +public class AzureDevOpsPullRequestServiceTests +{ + [Fact] + public void Ctor_ShouldThrow_WhenArgumentNull() + { + // arrange + + // act + Func act1 = () => new AzureDevOpsPullRequestService(A.Dummy(), null!); + Func act2 = () => new AzureDevOpsPullRequestService(null!, A.Dummy()); + + // assert + act1.Should().Throw(); + act2.Should().Throw(); + } +} \ No newline at end of file From 16b49fb2ac3d9175928814c689895f53b7239b1c Mon Sep 17 00:00:00 2001 From: Coen van den Munckhof Date: Tue, 11 Jul 2023 21:50:19 +0200 Subject: [PATCH 06/10] adding tests --- .../IO/GitRepositoryFinderFactoryTests.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/RepoM.Api.Tests/IO/GitRepositoryFinderFactoryTests.cs diff --git a/tests/RepoM.Api.Tests/IO/GitRepositoryFinderFactoryTests.cs b/tests/RepoM.Api.Tests/IO/GitRepositoryFinderFactoryTests.cs new file mode 100644 index 00000000..a5a90a76 --- /dev/null +++ b/tests/RepoM.Api.Tests/IO/GitRepositoryFinderFactoryTests.cs @@ -0,0 +1,57 @@ +namespace RepoM.Api.Tests.IO; + +using System; +using System.Collections.Generic; +using FakeItEasy; +using FluentAssertions; +using RepoM.Api.Common; +using RepoM.Api.IO; +using RepoM.Core.Plugin.RepositoryFiltering.Clause.Terms; +using RepoM.Core.Plugin.RepositoryFinder; +using Xunit; + +public class GitRepositoryFinderFactoryTests +{ + private readonly IAppSettingsService _appSettingsService; + private readonly ISingleGitRepositoryFinderFactory _singleGitRepositoryFinderFactory1; + private readonly ISingleGitRepositoryFinderFactory _singleGitRepositoryFinderFactory2; + + public GitRepositoryFinderFactoryTests() + { + _appSettingsService = A.Fake(); + _singleGitRepositoryFinderFactory1 = A.Fake(); + _singleGitRepositoryFinderFactory2 = A.Fake(); + } + + [Fact] + public void Ctor_ShouldThrow_WhenArgumentNull() + { + // arrange + + // act + Func act1 = () => new GitRepositoryFinderFactory(A.Dummy(), null!); + Func act2 = () => new GitRepositoryFinderFactory(null!, new[] { A.Dummy(), }); + + // assert + act1.Should().Throw(); + act2.Should().Throw(); + } + + [Fact] + public void Create_ShouldReturnCreationOfFirstFactory_WhenFirstFactoryIsEnabled() + { + // arrange + IGitRepositoryFinder gitRepositoryFinder = A.Fake(); + A.CallTo(() => _appSettingsService.EnabledSearchProviders).Returns(new List() { "Dummy123", "Dummy555", }); + A.CallTo(() => _singleGitRepositoryFinderFactory1.IsActive).Returns(true); + A.CallTo(() => _singleGitRepositoryFinderFactory1.Name).Returns("Dummy123"); + A.CallTo(() => _singleGitRepositoryFinderFactory1.Create()).Returns(gitRepositoryFinder); + var sut = new GitRepositoryFinderFactory(_appSettingsService, new[] { _singleGitRepositoryFinderFactory1, _singleGitRepositoryFinderFactory2, }); + + // act + IGitRepositoryFinder result = sut.Create(); + + // assert + result.Should().BeSameAs(gitRepositoryFinder); + } +} \ No newline at end of file From 51c77355e829c5685e4825cda1194fb91687b16e Mon Sep 17 00:00:00 2001 From: Coen van den Munckhof Date: Wed, 12 Jul 2023 09:32:17 +0200 Subject: [PATCH 07/10] update --- .../Action/AssociateFileV1Test.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/RepoM.Api.Tests/IO/ModuleBasedRepositoryActionProvider/Action/AssociateFileV1Test.cs b/tests/RepoM.Api.Tests/IO/ModuleBasedRepositoryActionProvider/Action/AssociateFileV1Test.cs index fc466854..b293acb3 100644 --- a/tests/RepoM.Api.Tests/IO/ModuleBasedRepositoryActionProvider/Action/AssociateFileV1Test.cs +++ b/tests/RepoM.Api.Tests/IO/ModuleBasedRepositoryActionProvider/Action/AssociateFileV1Test.cs @@ -43,8 +43,7 @@ public async Task Deserialize_AssociateFile1() // assert await Verifier.Verify(result, _verifySettings); } - - + [Fact] public async Task Deserialize_ShouldBeOfExpectedType() { From 09c6775bcdd2e910f338f307a72db418d091455a Mon Sep 17 00:00:00 2001 From: Coen van den Munckhof Date: Wed, 12 Jul 2023 10:24:44 +0200 Subject: [PATCH 08/10] .. --- src/RepoM.Api/IO/Methods/FindFilesMethod.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/RepoM.Api/IO/Methods/FindFilesMethod.cs b/src/RepoM.Api/IO/Methods/FindFilesMethod.cs index 00a2d8c1..2ee6ef55 100644 --- a/src/RepoM.Api/IO/Methods/FindFilesMethod.cs +++ b/src/RepoM.Api/IO/Methods/FindFilesMethod.cs @@ -3,6 +3,7 @@ namespace RepoM.Api.IO.Methods; using System; using System.Collections.Generic; using System.IO; +using System.IO.Abstractions; using System.Linq; using ExpressionStringEvaluator.Methods; using JetBrains.Annotations; @@ -11,10 +12,12 @@ namespace RepoM.Api.IO.Methods; [UsedImplicitly] public class FindFilesMethod : IMethod { + private readonly IFileSystem _fileSystem; private readonly ILogger _logger; - public FindFilesMethod(ILogger logger) + public FindFilesMethod(IFileSystem fileSystem, ILogger logger) { + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -57,15 +60,15 @@ public bool CanHandle(string method) } } - private static IEnumerable GetFileEnumerator(string path, string searchPattern) + private IEnumerable GetFileEnumerator(string path, string searchPattern) { // prefer EnumerateFileSystemInfos() over EnumerateFiles() to include packaged folders like // .app or .xcodeproj on macOS + IDirectoryInfo directory = _fileSystem.DirectoryInfo.New(path); - var directory = new DirectoryInfo(path); return directory - .EnumerateFileSystemInfos(searchPattern, SearchOption.AllDirectories) - .Select(f => f.FullName) - .Where(f => !f.StartsWith('.')); + .EnumerateFileSystemInfos(searchPattern, SearchOption.AllDirectories) + .Select(f => f.FullName) + .Where(f => !f.StartsWith('.')); } } \ No newline at end of file From a0aa7501c8091d7c6e9dd5965b08c36dfb1a60ec Mon Sep 17 00:00:00 2001 From: Coen van den Munckhof Date: Wed, 12 Jul 2023 10:46:09 +0200 Subject: [PATCH 09/10] update --- .../IO/GitRepositoryFinderFactoryTests.cs | 1 - .../IO/Methods/FindFilesMethodTests.cs | 55 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/RepoM.Api.Tests/IO/Methods/FindFilesMethodTests.cs diff --git a/tests/RepoM.Api.Tests/IO/GitRepositoryFinderFactoryTests.cs b/tests/RepoM.Api.Tests/IO/GitRepositoryFinderFactoryTests.cs index a5a90a76..e4ed077e 100644 --- a/tests/RepoM.Api.Tests/IO/GitRepositoryFinderFactoryTests.cs +++ b/tests/RepoM.Api.Tests/IO/GitRepositoryFinderFactoryTests.cs @@ -6,7 +6,6 @@ namespace RepoM.Api.Tests.IO; using FluentAssertions; using RepoM.Api.Common; using RepoM.Api.IO; -using RepoM.Core.Plugin.RepositoryFiltering.Clause.Terms; using RepoM.Core.Plugin.RepositoryFinder; using Xunit; diff --git a/tests/RepoM.Api.Tests/IO/Methods/FindFilesMethodTests.cs b/tests/RepoM.Api.Tests/IO/Methods/FindFilesMethodTests.cs new file mode 100644 index 00000000..a9fc1a3f --- /dev/null +++ b/tests/RepoM.Api.Tests/IO/Methods/FindFilesMethodTests.cs @@ -0,0 +1,55 @@ +namespace RepoM.Api.Tests.IO.Methods; + +using FakeItEasy; +using RepoM.Api.Common; +using RepoM.Api.IO.Methods; +using RepoM.Core.Plugin.RepositoryFinder; +using System; +using FluentAssertions; +using Xunit; +using System.IO.Abstractions; +using Microsoft.Extensions.Logging; + +public class FindFilesMethodTests +{ + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + + public FindFilesMethodTests() + { + _fileSystem = A.Fake(); + _logger = A.Fake(); + } + + [Fact] + public void Ctor_ShouldThrow_WhenArgumentNull() + { + // arrange + + // act + Func act1 = () => new FindFilesMethod(A.Dummy(), null!); + Func act2 = () => new FindFilesMethod(null!, A.Dummy()); + + // assert + act1.Should().Throw(); + act2.Should().Throw(); + } + + [Theory] + [InlineData("FindFiles", true)] + [InlineData("findfiles", true)] + [InlineData("FindFilesx", false)] + [InlineData("", false)] + [InlineData(null, false)] + public void CanHandle_ShouldReturnExpected(string? method, bool expectedResult) + { + // arrange + var sut = new FindFilesMethod(_fileSystem, _logger); + + // act + var result = sut.CanHandle(method!); + + // assert + _ = result.Should().Be(expectedResult); + } +} \ No newline at end of file From 883981935356b16ec30599639e9ddb7a681db97a Mon Sep 17 00:00:00 2001 From: Coen van den Munckhof Date: Wed, 12 Jul 2023 11:41:16 +0200 Subject: [PATCH 10/10] .. --- .../IO/Methods/FindFilesMethodTests.cs | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/tests/RepoM.Api.Tests/IO/Methods/FindFilesMethodTests.cs b/tests/RepoM.Api.Tests/IO/Methods/FindFilesMethodTests.cs index a9fc1a3f..e394da94 100644 --- a/tests/RepoM.Api.Tests/IO/Methods/FindFilesMethodTests.cs +++ b/tests/RepoM.Api.Tests/IO/Methods/FindFilesMethodTests.cs @@ -1,13 +1,12 @@ namespace RepoM.Api.Tests.IO.Methods; using FakeItEasy; -using RepoM.Api.Common; using RepoM.Api.IO.Methods; -using RepoM.Core.Plugin.RepositoryFinder; using System; using FluentAssertions; using Xunit; using System.IO.Abstractions; +using System.Linq; using Microsoft.Extensions.Logging; public class FindFilesMethodTests @@ -52,4 +51,48 @@ public void CanHandle_ShouldReturnExpected(string? method, bool expectedResult) // assert _ = result.Should().Be(expectedResult); } + + [Theory] + [InlineData(0)] + [InlineData(1)] + public void Handle_ShouldReturnNull_WhenArgsLengthIsLessThenTwo(int length) + { + // arrange + var args = Enumerable.Range(0, length).Select(x => $"{x}").Cast().ToArray(); + var sut = new FindFilesMethod(_fileSystem, _logger); + + // act + object? result = sut.Handle("FindFiles", args); + + // assert + _ = result.Should().BeNull(); + } + + [Fact] + public void Handle_ShouldReturnNull_WhenFirstArgumentIsNotString() + { + // arrange + var args = new object[] { 1, "dummy", }; + var sut = new FindFilesMethod(_fileSystem, _logger); + + // act + object? result = sut.Handle("FindFiles", args); + + // assert + _ = result.Should().BeNull(); + } + + [Fact] + public void Handle_ShouldReturnNull_WhenSecondArgumentIsNotString() + { + // arrange + var args = new object[] {"dummy", 42, }; + var sut = new FindFilesMethod(_fileSystem, _logger); + + // act + object? result = sut.Handle("FindFiles", args); + + // assert + _ = result.Should().BeNull(); + } } \ No newline at end of file