diff --git a/.editorconfig b/.editorconfig index dc8e66ff..7b6b5344 100644 --- a/.editorconfig +++ b/.editorconfig @@ -143,7 +143,7 @@ csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = false csharp_using_directive_placement = outside_namespace:silent csharp_prefer_simple_using_statement = true:suggestion -csharp_style_namespace_declarations = block_scoped:silent +csharp_style_namespace_declarations = file_scoped:warning csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent csharp_style_expression_bodied_methods = false:silent @@ -260,6 +260,9 @@ dotnet_diagnostic.SA1101.severity = none # SA1116: Split parameters should start on line after declaration dotnet_diagnostic.SA1116.severity = silent +# SA1118: Parameter should not span multiple lines +dotnet_diagnostic.SA1118.severity = suggestion + # SA1122: Use string.Empty for empty strings dotnet_diagnostic.SA1122.severity = none diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d081b290..f35df883 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,7 @@ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: @@ -18,6 +18,10 @@ updates: interval: weekly rebase-strategy: auto open-pull-requests-limit: 20 + groups: + github-actions: + patterns: + - "*" - package-ecosystem: "npm" directory: "/" @@ -32,3 +36,13 @@ updates: interval: weekly rebase-strategy: auto open-pull-requests-limit: 20 + groups: + code-analyzers: + patterns: + - "*Analyzer*" + ms-dependencies: + patterns: + - "Microsoft.*" + - "System.*" + exclude-patterns: + - "Microsoft.Playwright" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 621af2e4..260f7f0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,15 +27,15 @@ jobs: with: fetch-depth: 0 # Get all the history so MinGit can compute the version - name: Setup .NET Core (latest) - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - run: dotnet pack NGitLab.sln --configuration Release --output ${{ env.NuGetDirectory }} /bl - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: nuget if-no-files-found: error retention-days: 7 path: ${{ env.NuGetDirectory }}/**/* - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: binlogs if-no-files-found: error @@ -51,8 +51,8 @@ jobs: # Keep in sync with the version in GitLabDockerContainer.cs # Available tags: https://hub.docker.com/r/gitlab/gitlab-ee/tags gitlab: [ - 'gitlab/gitlab-ee:15.4.6-ee.0', 'gitlab/gitlab-ee:15.11.9-ee.0', + 'gitlab/gitlab-ee:15.11.13-ee.0', ] configuration: [ Release ] fail-fast: false @@ -67,7 +67,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup .NET Core (latest) - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - run: | $value = "${{ matrix.gitlab }}-${{ matrix.configuration }}".Replace(':', '-').Replace('/', '-') "artifact_name=test-results-$value" >> $env:GITHUB_OUTPUT @@ -75,7 +75,7 @@ jobs: id: set-artifact-name - run: dotnet test --configuration ${{ matrix.configuration }} --logger trx --results-directory "${{ env.TestResultsDirectory }}" --collect:"XPlat Code Coverage" /p:RunAnalyzers=false name: Run tests - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: ${{ steps.set-artifact-name.outputs.artifact_name }} @@ -96,12 +96,12 @@ jobs: runs-on: 'ubuntu-20.04' needs: [ create_nuget, build_and_test ] steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: nuget path: ${{ env.NuGetDirectory }} - name: Setup .NET Core - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - run: | Write-Host "Current ref: $env:GITHUB_REF" Write-Host "Searching nupkg in folder: ${{ env.NuGetDirectory }}" diff --git a/BannedSymbols.txt b/BannedSymbols.txt index e76045b9..95cf777c 100644 --- a/BannedSymbols.txt +++ b/BannedSymbols.txt @@ -1,4 +1,4 @@ -#https://github.com/dotnet/csharplang/blob/main/spec/documentation-comments.md#id-string-format +// https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d42-id-string-format M:System.String.ToLower;Use ToLowerInvariant instead M:System.String.ToUpper;Use ToUpperInvariant instead diff --git a/Directory.Build.props b/Directory.Build.props index 14b7f1f8..30200ad6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,4 +1,4 @@ - + Ubisoft @@ -8,7 +8,7 @@ $(Company), NGitLab contributors - 10.0 + 11.0 true strict true @@ -27,7 +27,7 @@ - + all @@ -37,17 +37,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/NGitLab.Mock.Tests/BotUserTests.cs b/NGitLab.Mock.Tests/BotUserTests.cs index 97489197..47a119cf 100644 --- a/NGitLab.Mock.Tests/BotUserTests.cs +++ b/NGitLab.Mock.Tests/BotUserTests.cs @@ -1,41 +1,40 @@ using NGitLab.Models; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public sealed class BotUserTests { - public sealed class BotUserTests + [Test] + public void Test_project_bot_user() { - [Test] - public void Test_project_bot_user() - { - using var server = new GitLabServer(); - var group = new Group("test"); - var project = new Project("test-project"); - server.Groups.Add(group); - group.Projects.Add(project); + using var server = new GitLabServer(); + var group = new Group("test"); + var project = new Project("test-project"); + server.Groups.Add(group); + group.Projects.Add(project); - var bot = project.CreateBotUser("token_name", AccessLevel.Maintainer); + var bot = project.CreateBotUser("token_name", AccessLevel.Maintainer); - Assert.That(bot.Bot, Is.True); - Assert.That(bot.Name, Is.EqualTo("token_name")); - var permissions = project.GetEffectivePermissions(); - var botPermission = permissions.GetEffectivePermission(bot); - Assert.That(botPermission.AccessLevel, Is.EqualTo(AccessLevel.Maintainer)); - } + Assert.That(bot.Bot, Is.True); + Assert.That(bot.Name, Is.EqualTo("token_name")); + var permissions = project.GetEffectivePermissions(); + var botPermission = permissions.GetEffectivePermission(bot); + Assert.That(botPermission.AccessLevel, Is.EqualTo(AccessLevel.Maintainer)); + } - [Test] - public void Test_group_bot_user() - { - using var server = new GitLabServer(); - var group = new Group("test"); - server.Groups.Add(group); + [Test] + public void Test_group_bot_user() + { + using var server = new GitLabServer(); + var group = new Group("test"); + server.Groups.Add(group); - var bot = group.CreateBotUser(AccessLevel.Maintainer); + var bot = group.CreateBotUser(AccessLevel.Maintainer); - Assert.That(bot.Bot, Is.True); - var permissions = group.GetEffectivePermissions(); - var botPermission = permissions.GetEffectivePermission(bot); - Assert.That(botPermission.AccessLevel, Is.EqualTo(AccessLevel.Maintainer)); - } + Assert.That(bot.Bot, Is.True); + var permissions = group.GetEffectivePermissions(); + var botPermission = permissions.GetEffectivePermission(bot); + Assert.That(botPermission.AccessLevel, Is.EqualTo(AccessLevel.Maintainer)); } } diff --git a/NGitLab.Mock.Tests/BranchesMockTests.cs b/NGitLab.Mock.Tests/BranchesMockTests.cs index 18c4d03f..c54e7809 100644 --- a/NGitLab.Mock.Tests/BranchesMockTests.cs +++ b/NGitLab.Mock.Tests/BranchesMockTests.cs @@ -2,41 +2,40 @@ using NGitLab.Mock.Config; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class BranchesMockTests { - public class BranchesMockTests + [Test] + public void Test_search_branches() { - [Test] - public void Test_search_branches() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("test-project", id: 1, addDefaultUserAsMaintainer: true, defaultBranch: "main", configure: project => project - .WithCommit("Initial commit") - .WithCommit("Commit for branch_1", sourceBranch: "branch_1")) - .BuildServer(); - - var client = server.CreateClient("user1"); - var branchClient = client.GetRepository(1).Branches; - - var branches = branchClient.Search("main").ToList(); - var expectedBranch = branches.Single(); - Assert.AreEqual("main", expectedBranch.Name); - - branches = branchClient.Search("^main$").ToList(); - expectedBranch = branches.Single(); - Assert.AreEqual("main", expectedBranch.Name); - - branches = branchClient.Search("^branch").ToList(); - expectedBranch = branches.Single(); - Assert.AreEqual("branch_1", expectedBranch.Name); - - branches = branchClient.Search("1$").ToList(); - expectedBranch = branches.Single(); - Assert.AreEqual("branch_1", expectedBranch.Name); - - branches = branchClient.Search("foobar").ToList(); - Assert.IsEmpty(branches); - } + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("test-project", id: 1, addDefaultUserAsMaintainer: true, defaultBranch: "main", configure: project => project + .WithCommit("Initial commit") + .WithCommit("Commit for branch_1", sourceBranch: "branch_1")) + .BuildServer(); + + var client = server.CreateClient("user1"); + var branchClient = client.GetRepository(1).Branches; + + var branches = branchClient.Search("main").ToList(); + var expectedBranch = branches.Single(); + Assert.That(expectedBranch.Name, Is.EqualTo("main")); + + branches = branchClient.Search("^main$").ToList(); + expectedBranch = branches.Single(); + Assert.That(expectedBranch.Name, Is.EqualTo("main")); + + branches = branchClient.Search("^branch").ToList(); + expectedBranch = branches.Single(); + Assert.That(expectedBranch.Name, Is.EqualTo("branch_1")); + + branches = branchClient.Search("1$").ToList(); + expectedBranch = branches.Single(); + Assert.That(expectedBranch.Name, Is.EqualTo("branch_1")); + + branches = branchClient.Search("foobar").ToList(); + Assert.That(branches, Is.Empty); } } diff --git a/NGitLab.Mock.Tests/CommitsMockTests.cs b/NGitLab.Mock.Tests/CommitsMockTests.cs index b071754b..060cf6d7 100644 --- a/NGitLab.Mock.Tests/CommitsMockTests.cs +++ b/NGitLab.Mock.Tests/CommitsMockTests.cs @@ -3,103 +3,128 @@ using NGitLab.Models; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class CommitsMockTests { - public class CommitsMockTests + [Test] + public void Test_commits_added_can_be_found() { - [Test] - public void Test_commits_added_can_be_found() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("test-project", id: 1, configure: project => project - .WithCommit("Initial commit") - .WithCommit("Create branch", sourceBranch: "branch-01")) - .BuildServer(); + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("test-project", id: 1, configure: project => project + .WithCommit("Initial commit") + .WithCommit("Create branch", sourceBranch: "branch-01")) + .BuildServer(); - var client = server.CreateClient(); - var commit = client.GetCommits(1).GetCommit("branch-01"); + var client = server.CreateClient(); + var commit = client.GetCommits(1).GetCommit("branch-01"); - Assert.AreEqual("Create branch", commit.Message.TrimEnd('\r', '\n')); - } + Assert.That(commit.Message.TrimEnd('\r', '\n'), Is.EqualTo("Create branch")); + } - [Test] - public void Test_commits_with_tags_can_be_found() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("test-project", id: 1, configure: project => project - .WithCommit("Initial commit") - .WithCommit("Changes with tag", tags: new[] { "1.0.0" })) - .BuildServer(); + [Test] + public void Test_commits_with_tags_can_be_found() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("test-project", id: 1, configure: project => project + .WithCommit("Initial commit") + .WithCommit("Changes with tag", tags: new[] { "1.0.0" })) + .BuildServer(); - var client = server.CreateClient(); - var commit = client.GetCommits(1).GetCommit("1.0.0"); + var client = server.CreateClient(); + var commit = client.GetCommits(1).GetCommit("1.0.0"); - Assert.AreEqual("Changes with tag", commit.Message.TrimEnd('\r', '\n')); - } + Assert.That(commit.Message.TrimEnd('\r', '\n'), Is.EqualTo("Changes with tag")); + } - [Test] - public void Test_tags_from_commit_can_be_found() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("test-project", id: 1, addDefaultUserAsMaintainer: true, configure: project => project - .WithCommit("Initial commit") - .WithCommit("Changes with tag", tags: new[] { "1.0.0" })) - .BuildServer(); - - var client = server.CreateClient(); - var tags = client.GetRepository(1).Tags.All.ToArray(); - - Assert.That(tags, Has.One.Items); - Assert.AreEqual("1.0.0", tags[0].Name); - } - - [Test] - public void Test_two_branches_can_be_created_from_same_commit() + [Test] + public void Test_tags_from_commit_can_be_found() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("test-project", id: 1, addDefaultUserAsMaintainer: true, configure: project => project + .WithCommit("Initial commit") + .WithCommit("Changes with tag", tags: new[] { "1.0.0" })) + .BuildServer(); + + var client = server.CreateClient(); + var tags = client.GetRepository(1).Tags.All.ToArray(); + + Assert.That(tags, Has.One.Items); + Assert.That(tags[0].Name, Is.EqualTo("1.0.0")); + } + + [Test] + public void Test_two_branches_can_be_created_from_same_commit() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("test-project", id: 1, addDefaultUserAsMaintainer: true, defaultBranch: "main", configure: project => project + .WithCommit("Initial commit") + .WithCommit("Commit for branch_1", sourceBranch: "branch_1") + .WithCommit("Commit for branch_2", sourceBranch: "branch_2", fromBranch: "main")) + .BuildServer(); + + var client = server.CreateClient(); + var repository = client.GetRepository(1); + var commitFromBranch1 = repository.GetCommits("branch_1").FirstOrDefault(); + var commitFromBranch2 = repository.GetCommits("branch_2").FirstOrDefault(); + + Assert.That(commitFromBranch1, Is.Not.Null); + Assert.That(commitFromBranch2, Is.Not.Null); + Assert.That(commitFromBranch1.Parents, Is.Not.Empty); + Assert.That(commitFromBranch2.Parents, Is.Not.Empty); + Assert.That(commitFromBranch2.Parents[0], Is.EqualTo(commitFromBranch1.Parents[0])); + } + + [Test] + public void Test_GetCommitsBetweenTwoRefs() + { + // Arrange + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("test-project", id: 1, addDefaultUserAsMaintainer: true, defaultBranch: "main", configure: project => project + .WithCommit("Initial commit") + .WithCommit("Commit for branch_1", sourceBranch: "branch_1") + .WithCommit("Yet another commit for branch_1")) + .BuildServer(); + + var client = server.CreateClient(); + var repository = client.GetRepository(1); + + // Act + var intermediateCommits = repository.GetCommits("main..branch_1"); + + // Assert + Assert.That(intermediateCommits.Select(c => c.Title), Is.EqualTo(new[] { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("test-project", id: 1, addDefaultUserAsMaintainer: true, defaultBranch: "main", configure: project => project - .WithCommit("Initial commit") - .WithCommit("Commit for branch_1", sourceBranch: "branch_1") - .WithCommit("Commit for branch_2", sourceBranch: "branch_2", fromBranch: "main")) - .BuildServer(); - - var client = server.CreateClient(); - var repository = client.GetRepository(1); - var commitFromBranch1 = repository.GetCommits("branch_1").FirstOrDefault(); - var commitFromBranch2 = repository.GetCommits("branch_2").FirstOrDefault(); - - Assert.NotNull(commitFromBranch1); - Assert.NotNull(commitFromBranch2); - Assert.IsNotEmpty(commitFromBranch1.Parents); - Assert.IsNotEmpty(commitFromBranch2.Parents); - Assert.AreEqual(commitFromBranch1.Parents[0], commitFromBranch2.Parents[0]); - } - - [Test] - public void Test_commits_can_be_cherry_pick() + "Yet another commit for branch_1", + "Commit for branch_1", + }).AsCollection); + } + + [Test] + public void Test_commits_can_be_cherry_pick() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("test-project", id: 1, addDefaultUserAsMaintainer: true, configure: project => project + .WithCommit("Initial commit") + .WithCommit("Changes with tag", sourceBranch: "branch_1")) + .BuildServer(); + + var client = server.CreateClient(); + var repository = client.GetRepository(1); + var commitFromBranch1 = repository.GetCommits("branch_1").FirstOrDefault(); + Assert.That(commitFromBranch1, Is.Not.Null); + + var cherryPicked = client.GetCommits(1).CherryPick(new CommitCherryPick { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("test-project", id: 1, addDefaultUserAsMaintainer: true, configure: project => project - .WithCommit("Initial commit") - .WithCommit("Changes with tag", sourceBranch: "branch_1")) - .BuildServer(); - - var client = server.CreateClient(); - var repository = client.GetRepository(1); - var commitFromBranch1 = repository.GetCommits("branch_1").FirstOrDefault(); - Assert.NotNull(commitFromBranch1); - - var cherryPicked = client.GetCommits(1).CherryPick(new CommitCherryPick - { - Sha = commitFromBranch1.Id, - Branch = "main", - }); - Assert.NotNull(cherryPicked); - } + Sha = commitFromBranch1.Id, + Branch = "main", + }); + Assert.That(cherryPicked, Is.Not.Null); } } diff --git a/NGitLab.Mock.Tests/ConfigTests.cs b/NGitLab.Mock.Tests/ConfigTests.cs index 1316f8e1..32488bda 100644 --- a/NGitLab.Mock.Tests/ConfigTests.cs +++ b/NGitLab.Mock.Tests/ConfigTests.cs @@ -4,138 +4,137 @@ using NGitLab.Models; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class ConfigTests { - public class ConfigTests + [Test] + public void Test_server_can_be_saved_in_config() { - [Test] - public void Test_server_can_be_saved_in_config() + using var server = new GitLabServer(); + var user = new User("user1"); + server.Users.Add(user); + var group = new Group("unit-tests"); + server.Groups.Add(group); + var project = new Project("test-project") + { + Description = "Test project", + DefaultBranch = "default", + Visibility = VisibilityLevel.Public, + }; + group.Projects.Add(project); + project.Labels.Add("label1"); + project.Issues.Add(new Issue + { + Title = "Issue #1", + Description = "My issue", + Author = new UserRef(user), + Labels = new[] { "label1" }, + }); + project.MergeRequests.Add(new MergeRequest { - using var server = new GitLabServer(); - var user = new User("user1"); - server.Users.Add(user); - var group = new Group("unit-tests"); - server.Groups.Add(group); - var project = new Project("test-project") - { - Description = "Test project", - DefaultBranch = "default", - Visibility = VisibilityLevel.Public, - }; - group.Projects.Add(project); - project.Labels.Add("label1"); - project.Issues.Add(new Issue - { - Title = "Issue #1", - Description = "My issue", - Author = new UserRef(user), - Labels = new[] { "label1" }, - }); - project.MergeRequests.Add(new MergeRequest - { - Title = "Merge request #1", - Description = "My merge request", - Author = new UserRef(user), - }); - project.Permissions.Add(new Permission(user, AccessLevel.Owner)); + Title = "Merge request #1", + Description = "My merge request", + Author = new UserRef(user), + }); + project.Permissions.Add(new Permission(user, AccessLevel.Owner)); - var config = server.ToConfig(); - Assert.IsNotNull(config); + var config = server.ToConfig(); + Assert.That(config, Is.Not.Null); - Assert.That(config.Users, Has.One.Items); - Assert.AreEqual("user1", config.Users[0].Username); - Assert.That(config.Groups, Has.One.Items); - Assert.AreEqual("unit-tests", config.Groups[0].Name); - Assert.That(config.Projects, Has.One.Items); - Assert.AreEqual("test-project", config.Projects[0].Name); - Assert.AreEqual("unit-tests", config.Projects[0].Namespace); - Assert.AreEqual("Test project", config.Projects[0].Description); - Assert.AreEqual("default", config.Projects[0].DefaultBranch); - Assert.AreEqual(VisibilityLevel.Public, config.Projects[0].Visibility); - Assert.That(config.Projects[0].Labels, Has.One.Items); - Assert.AreEqual("label1", config.Projects[0].Labels[0].Name); - Assert.That(config.Projects[0].Issues, Has.One.Items); - Assert.AreEqual("Issue #1", config.Projects[0].Issues[0].Title); - Assert.AreEqual("My issue", config.Projects[0].Issues[0].Description); - Assert.AreEqual("user1", config.Projects[0].Issues[0].Author); - Assert.That(config.Projects[0].Issues[0].Labels, Has.One.Items); - Assert.AreEqual("label1", config.Projects[0].Issues[0].Labels[0]); - Assert.That(config.Projects[0].MergeRequests, Has.One.Items); - Assert.AreEqual("Merge request #1", config.Projects[0].MergeRequests[0].Title); - Assert.AreEqual("My merge request", config.Projects[0].MergeRequests[0].Description); - Assert.AreEqual("user1", config.Projects[0].MergeRequests[0].Author); - Assert.That(config.Projects[0].Permissions, Has.One.Items); - Assert.AreEqual("user1", config.Projects[0].Permissions[0].User); - Assert.AreEqual(AccessLevel.Owner, config.Projects[0].Permissions[0].Level); - } + Assert.That(config.Users, Has.One.Items); + Assert.That(config.Users[0].Username, Is.EqualTo("user1")); + Assert.That(config.Groups, Has.One.Items); + Assert.That(config.Groups[0].Name, Is.EqualTo("unit-tests")); + Assert.That(config.Projects, Has.One.Items); + Assert.That(config.Projects[0].Name, Is.EqualTo("test-project")); + Assert.That(config.Projects[0].Namespace, Is.EqualTo("unit-tests")); + Assert.That(config.Projects[0].Description, Is.EqualTo("Test project")); + Assert.That(config.Projects[0].DefaultBranch, Is.EqualTo("default")); + Assert.That(config.Projects[0].Visibility, Is.EqualTo(VisibilityLevel.Public)); + Assert.That(config.Projects[0].Labels, Has.One.Items); + Assert.That(config.Projects[0].Labels[0].Name, Is.EqualTo("label1")); + Assert.That(config.Projects[0].Issues, Has.One.Items); + Assert.That(config.Projects[0].Issues[0].Title, Is.EqualTo("Issue #1")); + Assert.That(config.Projects[0].Issues[0].Description, Is.EqualTo("My issue")); + Assert.That(config.Projects[0].Issues[0].Author, Is.EqualTo("user1")); + Assert.That(config.Projects[0].Issues[0].Labels, Has.One.Items); + Assert.That(config.Projects[0].Issues[0].Labels[0], Is.EqualTo("label1")); + Assert.That(config.Projects[0].MergeRequests, Has.One.Items); + Assert.That(config.Projects[0].MergeRequests[0].Title, Is.EqualTo("Merge request #1")); + Assert.That(config.Projects[0].MergeRequests[0].Description, Is.EqualTo("My merge request")); + Assert.That(config.Projects[0].MergeRequests[0].Author, Is.EqualTo("user1")); + Assert.That(config.Projects[0].Permissions, Has.One.Items); + Assert.That(config.Projects[0].Permissions[0].User, Is.EqualTo("user1")); + Assert.That(config.Projects[0].Permissions[0].Level, Is.EqualTo(AccessLevel.Owner)); + } - [Test] - public void Test_config_can_be_serialized() - { - var config = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("project-1", description: "Project #1", visibility: VisibilityLevel.Public, - configure: project => project - .WithCommit("Initial commit") - .WithCommit("Create branch", sourceBranch: "branch-01") - .WithIssue("Issue #1") - .WithMergeRequest("branch-01", title: "Merge request #1") - .WithUserPermission("user1", AccessLevel.Maintainer)) - .WithProject("project-2"); + [Test] + public void Test_config_can_be_serialized() + { + var config = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("project-1", description: "Project #1", visibility: VisibilityLevel.Public, + configure: project => project + .WithCommit("Initial commit") + .WithCommit("Create branch", sourceBranch: "branch-01") + .WithIssue("Issue #1") + .WithMergeRequest("branch-01", title: "Merge request #1") + .WithUserPermission("user1", AccessLevel.Maintainer)) + .WithProject("project-2"); - var content = config.Serialize(); - Assert.IsNotEmpty(content); + var content = config.Serialize(); + Assert.That(content, Is.Not.Empty); - var config2 = GitLabConfig.Deserialize(content); - Assert.IsNotNull(config2); + var config2 = GitLabConfig.Deserialize(content); + Assert.That(config2, Is.Not.Null); - Assert.That(config2.Users, Has.One.Items); - Assert.AreEqual("user1", config2.Users[0].Username); - Assert.That(config2.Projects, Has.Exactly(2).Items); - Assert.AreEqual("project-1", config2.Projects[0].Name); - Assert.AreEqual("Project #1", config2.Projects[0].Description); - Assert.AreEqual(VisibilityLevel.Public, config2.Projects[0].Visibility); - Assert.That(config2.Projects[0].Commits, Has.Exactly(2).Items); - Assert.AreEqual("Initial commit", config2.Projects[0].Commits[0].Message); - Assert.AreEqual("Create branch", config2.Projects[0].Commits[1].Message); - Assert.That(config2.Projects[0].Issues, Has.One.Items); - Assert.AreEqual("Issue #1", config2.Projects[0].Issues[0].Title); - Assert.That(config2.Projects[0].MergeRequests, Has.One.Items); - Assert.AreEqual("Merge request #1", config2.Projects[0].MergeRequests[0].Title); - Assert.That(config2.Projects[0].Permissions, Has.One.Items); - Assert.AreEqual("user1", config2.Projects[0].Permissions[0].User); - Assert.AreEqual("project-2", config2.Projects[1].Name); + Assert.That(config2.Users, Has.One.Items); + Assert.That(config2.Users[0].Username, Is.EqualTo("user1")); + Assert.That(config2.Projects, Has.Exactly(2).Items); + Assert.That(config2.Projects[0].Name, Is.EqualTo("project-1")); + Assert.That(config2.Projects[0].Description, Is.EqualTo("Project #1")); + Assert.That(config2.Projects[0].Visibility, Is.EqualTo(VisibilityLevel.Public)); + Assert.That(config2.Projects[0].Commits, Has.Exactly(2).Items); + Assert.That(config2.Projects[0].Commits[0].Message, Is.EqualTo("Initial commit")); + Assert.That(config2.Projects[0].Commits[1].Message, Is.EqualTo("Create branch")); + Assert.That(config2.Projects[0].Issues, Has.One.Items); + Assert.That(config2.Projects[0].Issues[0].Title, Is.EqualTo("Issue #1")); + Assert.That(config2.Projects[0].MergeRequests, Has.One.Items); + Assert.That(config2.Projects[0].MergeRequests[0].Title, Is.EqualTo("Merge request #1")); + Assert.That(config2.Projects[0].Permissions, Has.One.Items); + Assert.That(config2.Projects[0].Permissions[0].User, Is.EqualTo("user1")); + Assert.That(config2.Projects[1].Name, Is.EqualTo("project-2")); - using var server = config2.BuildServer(); - Assert.IsNotNull(server); - } + using var server = config2.BuildServer(); + Assert.That(server, Is.Not.Null); + } - [Test] - public void Test_job_ids_are_unique() - { - var config = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("project-1", description: "Project #1", visibility: VisibilityLevel.Public, - configure: project => project - .WithCommit("Initial commit", alias: "C1") - .WithPipeline("C1", p => p.WithJob().WithJob().WithJob())) - .WithProject("project-2", description: "Project #2", visibility: VisibilityLevel.Public, - configure: project => project - .WithCommit("Initial commit", alias: "C1") - .WithPipeline("C1", p => p.WithJob().WithJob())); + [Test] + public void Test_job_ids_are_unique() + { + var config = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("project-1", description: "Project #1", visibility: VisibilityLevel.Public, + configure: project => project + .WithCommit("Initial commit", alias: "C1") + .WithPipeline("C1", p => p.WithJob().WithJob().WithJob())) + .WithProject("project-2", description: "Project #2", visibility: VisibilityLevel.Public, + configure: project => project + .WithCommit("Initial commit", alias: "C1") + .WithPipeline("C1", p => p.WithJob().WithJob())); - using var server = config.BuildServer(); - Assert.IsNotNull(server); + using var server = config.BuildServer(); + Assert.That(server, Is.Not.Null); - var project1 = server.AllProjects.FirstOrDefault(); - Assert.IsNotNull(project1); + var project1 = server.AllProjects.FirstOrDefault(); + Assert.That(project1, Is.Not.Null); - project1.Jobs.Should().BeEquivalentTo(new[] { new { Id = 1 }, new { Id = 2 }, new { Id = 3 } }); + project1.Jobs.Should().BeEquivalentTo(new[] { new { Id = 1 }, new { Id = 2 }, new { Id = 3 } }); - var project2 = server.AllProjects.LastOrDefault(); - Assert.IsNotNull(project2); + var project2 = server.AllProjects.LastOrDefault(); + Assert.That(project2, Is.Not.Null); - project2.Jobs.Should().BeEquivalentTo(new[] { new { Id = 4 }, new { Id = 5 } }); - } + project2.Jobs.Should().BeEquivalentTo(new[] { new { Id = 4 }, new { Id = 5 } }); } } diff --git a/NGitLab.Mock.Tests/GroupsMockTests.cs b/NGitLab.Mock.Tests/GroupsMockTests.cs index d2f08c5d..e76e8326 100644 --- a/NGitLab.Mock.Tests/GroupsMockTests.cs +++ b/NGitLab.Mock.Tests/GroupsMockTests.cs @@ -1,82 +1,430 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; using NGitLab.Mock.Config; +using NGitLab.Models; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class GroupsMockTests { - public class GroupsMockTests + private static GitLabServer CreateGroupHierarchy() => + new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithGroup("tlg", 1) + .WithGroup("sg1", 2, @namespace: "tlg") + .WithGroup("sg1_1", 3, @namespace: "tlg/sg1") + .WithGroup("sg2", 4, @namespace: "tlg") + .WithGroup("sg2_1", 5, @namespace: "tlg/sg2") + .BuildServer(); + + private static GitLabServer CreateProjectHierarchy() => + new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithGroup("tlg", 1) + .WithProject("p1", 2, @namespace: "tlg") + .WithGroup("sg1", 3, @namespace: "tlg") + .WithProject("p2", 4, @namespace: "tlg/sg1") + .WithProject("p3", 5, @namespace: "tlg/sg1") + .WithGroup("sg2", 6, @namespace: "tlg") + .BuildServer(); + + [Test] + public async Task Test_group_get_by_id() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test") + .WithGroup("G1", 1) + .WithGroup("G2", 2) + .WithGroup("G3", 3) + .BuildServer(); + + var client = server.CreateClient("user1"); + var group = await client.Groups.GetByIdAsync(1); + + Assert.That(group.Name, Is.EqualTo("G1"), "Subgroups found are invalid"); + } + + [Test] + public async Task Test_group_get_by_fullpath() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test") + .WithGroup("G1", @namespace: "name1") + .WithGroup("G2", @namespace: "name2") + .WithGroup("G3", @namespace: "name3") + .BuildServer(); + + var client = server.CreateClient("user1"); + var group = await client.Groups.GetByFullPathAsync("name3"); + + Assert.That(group.FullPath, Is.EqualTo("name3"), "Subgroups found are invalid"); + } + + [Test] + public void Test_get_groups_with_top_level_only_ignores_subgroups() + { + using var server = CreateGroupHierarchy(); + + var client = server.CreateClient("user1"); + var groups = client.Groups.GetAsync(new Models.GroupQuery { TopLevelOnly = true }); + + var expected = new string[] { "user1", "tlg" }; + Assert.That(groups.Select(g => g.FullPath), Is.EquivalentTo(expected)); + } + + [Test] + public async Task Test_page_groups_first_page() { - [Test] - public async Task Test_group_get_by_id() + using var server = CreateGroupHierarchy(); + + var client = server.CreateClient("user1"); + (var page, var total) = await client.Groups.PageAsync(new(page: 1, perPage: 3)); + + var expected = new string[] { "user1", "tlg", "tlg/sg1" }; + Assert.Multiple(() => { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test") - .WithGroup("G1", 1) - .WithGroup("G2", 2) - .WithGroup("G3", 3) - .BuildServer(); - - var client = server.CreateClient("user1"); - var group = await client.Groups.GetByIdAsync(1); - - Assert.AreEqual("G1", group.Name, "Subgroups found are invalid"); - } - - [Test] - public async Task Test_group_get_by_fullpath() + Assert.That(page.Select(g => g.FullPath), Is.EquivalentTo(expected)); + Assert.That(total, Is.EqualTo(6)); + }); + } + + [Test] + public async Task Test_page_groups_last_page() + { + using var server = CreateGroupHierarchy(); + + var client = server.CreateClient("user1"); + (var page, var total) = await client.Groups.PageAsync(new(page: 2, perPage: 4)); + + var expected = new string[] { "tlg/sg2", "tlg/sg2/sg2_1" }; + Assert.Multiple(() => { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test") - .WithGroup("G1", @namespace: "name1") - .WithGroup("G2", @namespace: "name2") - .WithGroup("G3", @namespace: "name3") - .BuildServer(); - - var client = server.CreateClient("user1"); - var group = await client.Groups.GetByFullPathAsync("name3"); - - Assert.AreEqual("name3", group.FullPath, "Subgroups found are invalid"); - } - - [Test] - public void Test_get_subgroups_by_id() + Assert.That(page.Select(g => g.FullPath), Is.EquivalentTo(expected)); + Assert.That(total, Is.EqualTo(6)); + }); + } + + [Test] + public async Task Test_page_groups_with_page_0_returns_page_1() + { + using var server = CreateGroupHierarchy(); + + var client = server.CreateClient("user1"); + (var page, var total) = await client.Groups.PageAsync(new(page: 0)); + + Assert.That(total, Is.EqualTo(6)); + } + + [Test] + public void Test_page_groups_with_invalid_perpage_throws() + { + using var server = CreateGroupHierarchy(); + var client = server.CreateClient("user1"); + Assert.ThrowsAsync(() => client.Groups.PageAsync(new(perPage: 0))); + } + + [Test] + public void Test_get_subgroups_by_id() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", addDefaultUserAsMaintainer: true) + .WithGroup("parentGroup1", configure: group => group.Id = 12) + .WithGroup("parentGroup2", configure: group => group.Id = 89) + .WithGroup("G1", 2, @namespace: "parentGroup1") + .WithGroup("G2", 3, @namespace: "parentGroup1") + .WithGroup("G3", 4, @namespace: "parentGroup2") + .BuildServer(); + + var client = server.CreateClient("user1"); + var group = client.Groups.GetSubgroupsByIdAsync(12, new Models.SubgroupQuery { }); + + Assert.That(group.Count(), Is.EqualTo(2), "Subgroups found are invalid"); + } + + [Test] + public void Test_get_subgroups_by_fullpath() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", addDefaultUserAsMaintainer: true) + .WithGroup("parentGroup1", configure: group => group.Id = 12) + .WithGroup("parentGroup2", configure: group => group.Id = 89) + .WithGroup("G1", 2, @namespace: "parentGroup1") + .WithGroup("G2", 3, @namespace: "parentGroup1") + .WithGroup("G3", 4, @namespace: "parentGroup2") + .BuildServer(); + + var client = server.CreateClient("user1"); + var group = client.Groups.GetSubgroupsByFullPathAsync("parentgroup1", new Models.SubgroupQuery { }); + + Assert.That(group.Count(), Is.EqualTo(2), "Subgroups found are invalid"); + } + + [Test] + public void Test_get_subgroups_descendants_by_fullpath() + { + using var server = CreateGroupHierarchy(); + + var client = server.CreateClient("user1"); + var group = client.Groups.GetSubgroupsByFullPathAsync("tlg", new Models.SubgroupQuery { IncludeDescendants = true }); + + var expected = new string[] { "tlg/sg1", "tlg/sg1/sg1_1", "tlg/sg2", "tlg/sg2/sg2_1" }; + Assert.That(group.Select(g => g.FullPath), Is.EquivalentTo(expected)); + } + + [Test] + public void Test_get_subgroups_descendants_by_id() + { + using var server = CreateGroupHierarchy(); + + var client = server.CreateClient("user1"); + var group = client.Groups.GetSubgroupsByIdAsync(1, new Models.SubgroupQuery { IncludeDescendants = true }); + + var expected = new string[] { "tlg/sg1", "tlg/sg1/sg1_1", "tlg/sg2", "tlg/sg2/sg2_1" }; + Assert.That(group.Select(g => g.FullPath), Is.EquivalentTo(expected)); + } + + [Test] + public void Test_get_subgroups_descendants_of_subgroup_by_fullpath() + { + using var server = CreateGroupHierarchy(); + + var client = server.CreateClient("user1"); + var group = client.Groups.GetSubgroupsAsync("tlg/sg2", new Models.SubgroupQuery { IncludeDescendants = true }); + + var expected = new string[] { "tlg/sg2/sg2_1" }; + Assert.That(group.Select(g => g.FullPath), Is.EquivalentTo(expected)); + } + + [Test] + public void Test_get_subgroups_descendants_of_subgroup_by_id() + { + using var server = CreateGroupHierarchy(); + + var client = server.CreateClient("user1"); + var group = client.Groups.GetSubgroupsAsync(2, new Models.SubgroupQuery { IncludeDescendants = true }); + + var expected = new string[] { "tlg/sg1/sg1_1" }; + Assert.That(group.Select(g => g.FullPath), Is.EquivalentTo(expected)); + } + + [Test] + public async Task Test_page_subgroups_with_descendants_first_page() + { + using var server = CreateGroupHierarchy(); + + var client = server.CreateClient("user1"); + (var page, var total) = await client.Groups.PageSubgroupsAsync("tlg", new(page: 1, perPage: 3, new Models.SubgroupQuery { IncludeDescendants = true })); + + var expected = new string[] { "tlg/sg1", "tlg/sg1/sg1_1", "tlg/sg2" }; + Assert.Multiple(() => { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", addDefaultUserAsMaintainer: true) - .WithGroup("parentGroup1", configure: group => group.Id = 12) - .WithGroup("parentGroup2", configure: group => group.Id = 89) - .WithGroup("G1", 2, @namespace: "parentGroup1") - .WithGroup("G2", 3, @namespace: "parentGroup1") - .WithGroup("G3", 4, @namespace: "parentGroup2") - .BuildServer(); - - var client = server.CreateClient("user1"); - var group = client.Groups.GetSubgroupsByIdAsync(12, new Models.SubgroupQuery { }); - - Assert.AreEqual(2, group.Count(), "Subgroups found are invalid"); - } - - [Test] - public void Test_get_subgroups_by_fullpath() + Assert.That(page.Select(g => g.FullPath), Is.EquivalentTo(expected)); + Assert.That(total, Is.EqualTo(4)); + }); + } + + [Test] + public async Task Test_page_subgroups_with_descendants_last_page() + { + using var server = CreateGroupHierarchy(); + + var client = server.CreateClient("user1"); + (var page, var total) = await client.Groups.PageSubgroupsAsync(1, new(page: 2, perPage: 3, new Models.SubgroupQuery { IncludeDescendants = true })); + + var expected = new string[] { "tlg/sg2/sg2_1" }; + Assert.Multiple(() => + { + Assert.That(page.Select(g => g.FullPath), Is.EquivalentTo(expected)); + Assert.That(total, Is.EqualTo(4)); + }); + } + + [Test] + public async Task Test_page_subgroups_with_descendants_after_last_page() + { + using var server = CreateGroupHierarchy(); + + var client = server.CreateClient("user1"); + (var page, var total) = await client.Groups.PageSubgroupsAsync(1, new(page: 100, perPage: 3, new Models.SubgroupQuery { IncludeDescendants = true })); + + Assert.Multiple(() => + { + Assert.That(page.Select(g => g.FullPath), Is.Empty); + Assert.That(total, Is.EqualTo(4)); + }); + } + + [Test] + public async Task Test_page_subgroups_with_page_0_returns_page_1() + { + using var server = CreateGroupHierarchy(); + + var client = server.CreateClient("user1"); + (var page, var total) = await client.Groups.PageSubgroupsAsync("tlg", new(page: 0)); + + Assert.That(total, Is.EqualTo(2)); + } + + [Test] + public void Test_page_subgroups_with_invalid_perpage_throws() + { + using var server = CreateGroupHierarchy(); + var client = server.CreateClient("user1"); + Assert.ThrowsAsync(() => client.Groups.PageSubgroupsAsync(1, new(page: 1, perPage: 0))); + } + + [Test] + public async Task Test_page_projects_first_page() + { + using var server = CreateProjectHierarchy(); + + var client = server.CreateClient("user1"); + (var page, var total) = await client.Groups.PageProjectsAsync("tlg", new(page: 1, perPage: 1000)); + + var expected = new string[] { "tlg/p1" }; + Assert.Multiple(() => + { + Assert.That(page.Select(p => p.PathWithNamespace), Is.EquivalentTo(expected)); + Assert.That(total, Is.EqualTo(1)); + }); + } + + [Test] + public async Task Test_page_projects_in_subgroup() + { + using var server = CreateProjectHierarchy(); + + var client = server.CreateClient("user1"); + (var page, var total) = await client.Groups.PageProjectsAsync("tlg/sg1", new(page: 2, perPage: 1)); + + var expected = new string[] { "tlg/sg1/p3" }; + Assert.Multiple(() => + { + Assert.That(page.Select(p => p.PathWithNamespace), Is.EquivalentTo(expected)); + Assert.That(total, Is.EqualTo(2)); + }); + } + + [Test] + public async Task Test_page_projects_in_subgroup_with_no_projects() + { + using var server = CreateProjectHierarchy(); + + var client = server.CreateClient("user1"); + (var page, var total) = await client.Groups.PageProjectsAsync("tlg/sg2", new()); + + Assert.Multiple(() => + { + Assert.That(page.Select(p => p.PathWithNamespace), Is.Empty); + Assert.That(total, Is.EqualTo(0)); + }); + } + + [Test] + public async Task Test_page_projects_in_subgroup_with_descendants() + { + using var server = CreateProjectHierarchy(); + + var client = server.CreateClient("user1"); + (var page, var total) = await client.Groups.PageProjectsAsync("tlg", new(query: new() { IncludeSubGroups = true })); + + var expected = new string[] { "p1", "p2", "p3" }; + Assert.Multiple(() => + { + Assert.That(page.Select(p => p.Name), Is.EquivalentTo(expected)); + Assert.That(total, Is.EqualTo(3)); + }); + } + + [Test] + public void Test_create_update_delete_group_hooks() + { + // Arrange + var groupId = 1; + + using var server = new GitLabConfig() + .WithUser("user1", isAdmin: true) + .WithGroup("group1", groupId) + .BuildServer(); + + var client = server.CreateClient("user1"); + var groupHooksClient = client.GetGroupHooksClient(groupId); + + var toCreateGroupHook = new GroupHookUpsert + { + Url = new Uri("https://test-create-group-hook.com"), + EnableSslVerification = true, + PushEvents = true, + }; + + // Act + var createdGroupHook = groupHooksClient.Create(toCreateGroupHook); + + // Assert + Assert.That(groupHooksClient.All.ToArray(), Has.Length.EqualTo(1)); + + Assert.That(createdGroupHook.Url, Is.EqualTo(toCreateGroupHook.Url)); + Assert.That(createdGroupHook.EnableSslVerification, Is.EqualTo(toCreateGroupHook.EnableSslVerification)); + Assert.That(createdGroupHook.PushEvents, Is.EqualTo(toCreateGroupHook.PushEvents)); + + var groupHookById = groupHooksClient[createdGroupHook.Id]; + Assert.That(groupHookById.Url, Is.EqualTo(toCreateGroupHook.Url)); + Assert.That(groupHookById.EnableSslVerification, Is.EqualTo(toCreateGroupHook.EnableSslVerification)); + Assert.That(groupHookById.PushEvents, Is.EqualTo(toCreateGroupHook.PushEvents)); + + // Arrange + var toUpdateGroupHook = new GroupHookUpsert { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", addDefaultUserAsMaintainer: true) - .WithGroup("parentGroup1", configure: group => group.Id = 12) - .WithGroup("parentGroup2", configure: group => group.Id = 89) - .WithGroup("G1", 2, @namespace: "parentGroup1") - .WithGroup("G2", 3, @namespace: "parentGroup1") - .WithGroup("G3", 4, @namespace: "parentGroup2") - .BuildServer(); - - var client = server.CreateClient("user1"); - var group = client.Groups.GetSubgroupsByFullPathAsync("parentgroup1", new Models.SubgroupQuery { }); - - Assert.AreEqual(2, group.Count(), "Subgroups found are invalid"); - } + Url = new Uri("https://test-update-group-hook.com"), + PushEvents = false, + }; + + // Act + var updatedGroupHook = groupHooksClient.Update(createdGroupHook.Id, toUpdateGroupHook); + + // Assert + Assert.That(groupHooksClient.All.ToArray(), Has.Length.EqualTo(1)); + + Assert.That(updatedGroupHook.Url, Is.EqualTo(toUpdateGroupHook.Url)); + Assert.That(updatedGroupHook.PushEvents, Is.EqualTo(toUpdateGroupHook.PushEvents)); + Assert.That(updatedGroupHook.EnableSslVerification, Is.False); + + groupHookById = groupHooksClient[updatedGroupHook.Id]; + Assert.That(groupHookById.Url, Is.EqualTo(toUpdateGroupHook.Url)); + Assert.That(groupHookById.PushEvents, Is.EqualTo(toUpdateGroupHook.PushEvents)); + Assert.That(groupHookById.EnableSslVerification, Is.False); + + // Act + groupHooksClient.Delete(updatedGroupHook.Id); + + // Assert + Assert.That(groupHooksClient.All.ToArray(), Is.Empty); + } + + [Test] + public async Task Test_group_created_at_date() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .BuildServer(); + + var client = server.CreateClient("user1"); + + var t1 = DateTime.UtcNow; + var group = await client.Groups.CreateAsync(new Models.GroupCreate + { + Name = "Foo", + Path = "foo", + }); + var t2 = DateTime.UtcNow; + + Assert.That(group.CreatedAt, Is.GreaterThanOrEqualTo(t1)); + Assert.That(group.CreatedAt, Is.LessThanOrEqualTo(t2)); } } diff --git a/NGitLab.Mock.Tests/IssuesMockTests.cs b/NGitLab.Mock.Tests/IssuesMockTests.cs index 6525987c..2f8b595a 100644 --- a/NGitLab.Mock.Tests/IssuesMockTests.cs +++ b/NGitLab.Mock.Tests/IssuesMockTests.cs @@ -3,115 +3,114 @@ using NGitLab.Models; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class IssuesMockTests { - public class IssuesMockTests + [Test] + public void Test_issues_created_by_me_can_be_listed() { - [Test] - public void Test_issues_created_by_me_can_be_listed() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithUser("user2") - .WithProject("Test", configure: project => project - .WithIssue("Issue 1", author: "user1", assignee: "user2") - .WithIssue("Issue 2", author: "user2", assignee: "user1")) - .BuildServer(); - - var client = server.CreateClient("user1"); - var issues = client.Issues.Get(new IssueQuery { Scope = "created_by_me" }).ToArray(); - - Assert.AreEqual(1, issues.Length, "Issues count is invalid"); - Assert.AreEqual("Issue 1", issues[0].Title, "Issue found is invalid"); - } - - [Test] - public void Test_issues_assigned_to_me_can_be_listed() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithUser("user2") - .WithProject("Test", configure: project => project - .WithIssue("Issue 1", author: "user1", assignee: "user2") - .WithIssue("Issue 2", author: "user2", assignee: "user1")) - .BuildServer(); - - var client = server.CreateClient("user1"); - var issues = client.Issues.Get(new IssueQuery { Scope = "assigned_to_me" }).ToArray(); - - Assert.AreEqual(1, issues.Length, "Issues count is invalid"); - Assert.AreEqual("Issue 2", issues[0].Title, "Issue found is invalid"); - } - - [Test] - public void Test_issues_assignee_not_throwing_when_assignees_is_null() - { - using var server = new GitLabConfig() - .WithUser("user", isDefault: true) - .WithProject("Test", configure: project => project - .WithIssue("Issue title", author: "user")) - .BuildServer(); - - var client = server.CreateClient(); - Assert.DoesNotThrow(() => client.Issues.Get(new IssueQuery { Scope = "assigned_to_me" }).ToArray()); - } - - [Test] - public void Test_issue_by_id_can_be_found() - { - using var server = new GitLabConfig() - .WithUser("user", isDefault: true, isAdmin: true) - .WithProject("Test", configure: project => project - .WithIssue("Issue title", author: "user", id: 5)) - .BuildServer(); + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithUser("user2") + .WithProject("Test", configure: project => project + .WithIssue("Issue 1", author: "user1", assignee: "user2") + .WithIssue("Issue 2", author: "user2", assignee: "user1")) + .BuildServer(); + + var client = server.CreateClient("user1"); + var issues = client.Issues.Get(new IssueQuery { Scope = "created_by_me" }).ToArray(); + + Assert.That(issues, Has.Length.EqualTo(1), "Issues count is invalid"); + Assert.That(issues[0].Title, Is.EqualTo("Issue 1"), "Issue found is invalid"); + } + + [Test] + public void Test_issues_assigned_to_me_can_be_listed() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithUser("user2") + .WithProject("Test", configure: project => project + .WithIssue("Issue 1", author: "user1", assignee: "user2") + .WithIssue("Issue 2", author: "user2", assignee: "user1")) + .BuildServer(); + + var client = server.CreateClient("user1"); + var issues = client.Issues.Get(new IssueQuery { Scope = "assigned_to_me" }).ToArray(); + + Assert.That(issues, Has.Length.EqualTo(1), "Issues count is invalid"); + Assert.That(issues[0].Title, Is.EqualTo("Issue 2"), "Issue found is invalid"); + } - var client = server.CreateClient(); + [Test] + public void Test_issues_assignee_not_throwing_when_assignees_is_null() + { + using var server = new GitLabConfig() + .WithUser("user", isDefault: true) + .WithProject("Test", configure: project => project + .WithIssue("Issue title", author: "user")) + .BuildServer(); + + var client = server.CreateClient(); + Assert.DoesNotThrow(() => client.Issues.Get(new IssueQuery { Scope = "assigned_to_me" }).ToArray()); + } + + [Test] + public void Test_issue_by_id_can_be_found() + { + using var server = new GitLabConfig() + .WithUser("user", isDefault: true, isAdmin: true) + .WithProject("Test", configure: project => project + .WithIssue("Issue title", author: "user", id: 5)) + .BuildServer(); + + var client = server.CreateClient(); - var issue = client.Issues.GetById(10001); - Assert.AreEqual(5, issue.IssueId); - Assert.AreEqual("Issue title", issue.Title); - } + var issue = client.Issues.GetById(10001); + Assert.That(issue.IssueId, Is.EqualTo(5)); + Assert.That(issue.Title, Is.EqualTo("Issue title")); + } + + [Test] + public void Test_issue_resource_milestone_events_can_be_found() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", id: 1, configure: project => project + .WithIssue("Issue title", author: "user", id: 5) + .WithMilestone("Milestone 1") + .WithMilestone("Milestone 2")) + .BuildServer(); + + var client = server.CreateClient(); + var issuesClient = client.Issues; + var milestone = client.GetMilestone(1).All.ToArray()[0]; + + issuesClient.Edit(new IssueEdit + { + ProjectId = 1, + IssueId = 5, + MilestoneId = milestone.Id, + }); - [Test] - public void Test_issue_resource_milestone_events_can_be_found() + issuesClient.Edit(new IssueEdit { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", id: 1, configure: project => project - .WithIssue("Issue title", author: "user", id: 5) - .WithMilestone("Milestone 1") - .WithMilestone("Milestone 2")) - .BuildServer(); - - var client = server.CreateClient(); - var issuesClient = client.Issues; - var milestone = client.GetMilestone(1).All.ToArray()[0]; - - issuesClient.Edit(new IssueEdit - { - ProjectId = 1, - IssueId = 5, - MilestoneId = milestone.Id, - }); - - issuesClient.Edit(new IssueEdit - { - ProjectId = 1, - IssueId = 5, - MilestoneId = 2, - }); - - var resourceMilestoneEvents = issuesClient.ResourceMilestoneEvents(projectId: 1, issueIid: 5).ToList(); - Assert.AreEqual(3, resourceMilestoneEvents.Count); - - var removeMilestoneEvents = resourceMilestoneEvents.Where(e => e.Action == ResourceMilestoneEventAction.Remove).ToArray(); - Assert.AreEqual(1, removeMilestoneEvents.Length); - Assert.AreEqual(1, removeMilestoneEvents[0].Milestone.Id); - - var addMilestoneEvents = resourceMilestoneEvents.Where(e => e.Action == ResourceMilestoneEventAction.Add).ToArray(); - Assert.AreEqual(2, addMilestoneEvents.Length); - Assert.AreEqual(1, addMilestoneEvents[0].Milestone.Id); - Assert.AreEqual(2, addMilestoneEvents[1].Milestone.Id); - } + ProjectId = 1, + IssueId = 5, + MilestoneId = 2, + }); + + var resourceMilestoneEvents = issuesClient.ResourceMilestoneEvents(projectId: 1, issueIid: 5).ToList(); + Assert.That(resourceMilestoneEvents, Has.Count.EqualTo(3)); + + var removeMilestoneEvents = resourceMilestoneEvents.Where(e => e.Action == ResourceMilestoneEventAction.Remove).ToArray(); + Assert.That(removeMilestoneEvents, Has.Length.EqualTo(1)); + Assert.That(removeMilestoneEvents[0].Milestone.Id, Is.EqualTo(1)); + + var addMilestoneEvents = resourceMilestoneEvents.Where(e => e.Action == ResourceMilestoneEventAction.Add).ToArray(); + Assert.That(addMilestoneEvents, Has.Length.EqualTo(2)); + Assert.That(addMilestoneEvents[0].Milestone.Id, Is.EqualTo(1)); + Assert.That(addMilestoneEvents[1].Milestone.Id, Is.EqualTo(2)); } } diff --git a/NGitLab.Mock.Tests/LabelsMockTests.cs b/NGitLab.Mock.Tests/LabelsMockTests.cs index 52b760fe..24fa284a 100644 --- a/NGitLab.Mock.Tests/LabelsMockTests.cs +++ b/NGitLab.Mock.Tests/LabelsMockTests.cs @@ -4,126 +4,125 @@ using NGitLab.Models; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class LabelsMockTests { - public class LabelsMockTests + [Test] + public void Test_labels_can_be_found_from_project() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", id: 1, configure: project => project + .WithLabel("test1") + .WithLabel("test2")) + .BuildServer(); + + var client = server.CreateClient(); + var labels = client.Labels.ForProject(1).ToArray(); + + Assert.That(labels, Has.Length.EqualTo(2), "Labels count is invalid"); + Assert.That(labels.Any(x => string.Equals(x.Name, "test1", StringComparison.Ordinal)), Is.True, "Label test1 not found"); + Assert.That(labels.Any(x => string.Equals(x.Name, "test2", StringComparison.Ordinal)), Is.True, "Label test2 not found"); + } + + [Test] + public void Test_labels_can_be_added_to_project() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true) + .BuildServer(); + + var client = server.CreateClient(); + client.Labels.Create(new LabelCreate { Id = 1, Name = "test1" }); + var labels = client.Labels.ForProject(1).ToArray(); + + Assert.That(labels, Has.Length.EqualTo(1), "Labels count is invalid"); + Assert.That(labels[0].Name, Is.EqualTo("test1"), "Label not found"); + } + + [Test] + public void Test_labels_can_be_edited_from_project() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true, configure: project => project + .WithLabel("test1")) + .BuildServer(); + + var client = server.CreateClient(); + client.Labels.Edit(new LabelEdit { Id = 1, Name = "test1", NewName = "test2" }); + var labels = client.Labels.ForProject(1).ToArray(); + + Assert.That(labels, Has.Length.EqualTo(1), "Labels count is invalid"); + Assert.That(labels[0].Name, Is.EqualTo("test2"), "Label not found"); + } + + [Test] + public void Test_labels_can_be_deleted_from_project() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true, configure: project => project + .WithLabel("test1")) + .BuildServer(); + + var client = server.CreateClient(); + client.Labels.Delete(new LabelDelete { Id = 1, Name = "test1" }); + var labels = client.Labels.ForProject(1).ToArray(); + + Assert.That(labels, Is.Empty, "Labels count is invalid"); + } + + [Test] + public void Test_labels_can_be_found_from_group() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithGroup("Test", id: 2, configure: project => project + .WithLabel("test1") + .WithLabel("test2")) + .BuildServer(); + + var client = server.CreateClient(); + var labels = client.Labels.ForGroup(2).ToArray(); + + Assert.That(labels, Has.Length.EqualTo(2), "Labels count is invalid"); + Assert.That(labels.Any(x => string.Equals(x.Name, "test1", StringComparison.Ordinal)), Is.True, "Label test1 not found"); + Assert.That(labels.Any(x => string.Equals(x.Name, "test2", StringComparison.Ordinal)), Is.True, "Label test2 not found"); + } + + [Test] + public void Test_labels_can_be_added_to_group() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithGroup("Test", id: 2, addDefaultUserAsMaintainer: true) + .BuildServer(); + + var client = server.CreateClient(); + client.Labels.CreateGroupLabel(new LabelCreate { Id = 2, Name = "test1" }); + var labels = client.Labels.ForGroup(2).ToArray(); + + Assert.That(labels, Has.Length.EqualTo(1), "Labels count is invalid"); + Assert.That(labels[0].Name, Is.EqualTo("test1"), "Label not found"); + } + + [Test] + public void Test_labels_can_be_edited_from_group() { - [Test] - public void Test_labels_can_be_found_from_project() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", id: 1, configure: project => project - .WithLabel("test1") - .WithLabel("test2")) - .BuildServer(); - - var client = server.CreateClient(); - var labels = client.Labels.ForProject(1).ToArray(); - - Assert.AreEqual(2, labels.Length, "Labels count is invalid"); - Assert.IsTrue(labels.Any(x => string.Equals(x.Name, "test1", StringComparison.Ordinal)), "Label test1 not found"); - Assert.IsTrue(labels.Any(x => string.Equals(x.Name, "test2", StringComparison.Ordinal)), "Label test2 not found"); - } - - [Test] - public void Test_labels_can_be_added_to_project() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true) - .BuildServer(); - - var client = server.CreateClient(); - client.Labels.Create(new LabelCreate { Id = 1, Name = "test1" }); - var labels = client.Labels.ForProject(1).ToArray(); - - Assert.AreEqual(1, labels.Length, "Labels count is invalid"); - Assert.AreEqual("test1", labels[0].Name, "Label not found"); - } - - [Test] - public void Test_labels_can_be_edited_from_project() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true, configure: project => project - .WithLabel("test1")) - .BuildServer(); - - var client = server.CreateClient(); - client.Labels.Edit(new LabelEdit { Id = 1, Name = "test1", NewName = "test2" }); - var labels = client.Labels.ForProject(1).ToArray(); - - Assert.AreEqual(1, labels.Length, "Labels count is invalid"); - Assert.AreEqual("test2", labels[0].Name, "Label not found"); - } - - [Test] - public void Test_labels_can_be_deleted_from_project() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true, configure: project => project - .WithLabel("test1")) - .BuildServer(); - - var client = server.CreateClient(); - client.Labels.Delete(new LabelDelete { Id = 1, Name = "test1" }); - var labels = client.Labels.ForProject(1).ToArray(); - - Assert.AreEqual(0, labels.Length, "Labels count is invalid"); - } - - [Test] - public void Test_labels_can_be_found_from_group() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithGroup("Test", id: 2, configure: project => project - .WithLabel("test1") - .WithLabel("test2")) - .BuildServer(); - - var client = server.CreateClient(); - var labels = client.Labels.ForGroup(2).ToArray(); - - Assert.AreEqual(2, labels.Length, "Labels count is invalid"); - Assert.IsTrue(labels.Any(x => string.Equals(x.Name, "test1", StringComparison.Ordinal)), "Label test1 not found"); - Assert.IsTrue(labels.Any(x => string.Equals(x.Name, "test2", StringComparison.Ordinal)), "Label test2 not found"); - } - - [Test] - public void Test_labels_can_be_added_to_group() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithGroup("Test", id: 2, addDefaultUserAsMaintainer: true) - .BuildServer(); - - var client = server.CreateClient(); - client.Labels.CreateGroupLabel(new LabelCreate { Id = 2, Name = "test1" }); - var labels = client.Labels.ForGroup(2).ToArray(); - - Assert.AreEqual(1, labels.Length, "Labels count is invalid"); - Assert.AreEqual("test1", labels[0].Name, "Label not found"); - } - - [Test] - public void Test_labels_can_be_edited_from_group() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithGroup("Test", id: 2, addDefaultUserAsMaintainer: true, configure: project => project - .WithLabel("test1")) - .BuildServer(); - - var client = server.CreateClient(); - client.Labels.EditGroupLabel(new LabelEdit { Id = 2, Name = "test1", NewName = "test2" }); - var labels = client.Labels.ForGroup(2).ToArray(); - - Assert.AreEqual(1, labels.Length, "Labels count is invalid"); - Assert.AreEqual("test2", labels[0].Name, "Label not found"); - } + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithGroup("Test", id: 2, addDefaultUserAsMaintainer: true, configure: project => project + .WithLabel("test1")) + .BuildServer(); + + var client = server.CreateClient(); + client.Labels.EditGroupLabel(new LabelEdit { Id = 2, Name = "test1", NewName = "test2" }); + var labels = client.Labels.ForGroup(2).ToArray(); + + Assert.That(labels, Has.Length.EqualTo(1), "Labels count is invalid"); + Assert.That(labels[0].Name, Is.EqualTo("test2"), "Label not found"); } } diff --git a/NGitLab.Mock.Tests/LintCITests.cs b/NGitLab.Mock.Tests/LintCITests.cs new file mode 100644 index 00000000..00f558e0 --- /dev/null +++ b/NGitLab.Mock.Tests/LintCITests.cs @@ -0,0 +1,42 @@ +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using NGitLab.Models; +using NUnit.Framework; + +namespace NGitLab.Mock.Tests; + +public class LintCITests +{ + [TestCase(null, "Pipeline filtered out by workflow rules.")] + [TestCase("main", "Pipeline filtered out by workflow rules.")] + [TestCase("dummy", "Reference not found")] + public async Task Test_GetLintCIByRef(string @ref, string expectedError) + { + // Arrange + using var server = new GitLabServer(); + + var user = server.Users.AddNew("user1"); + var client = server.CreateClient(user); + + var group = new Group("SomeGroup"); + server.Groups.Add(group); + + var project = new Project("Project") { Visibility = VisibilityLevel.Internal }; + group.Projects.Add(project); + + // Simulate what GitLab would return if CI was configured not to run pipelines in "main" branch + project.LintCIs.Add(new LintCI(project.DefaultBranch, valid: false, "Pipeline filtered out by workflow rules.")); + + // Act + var result = await client.Lint.ValidateProjectCIConfigurationAsync(project.Id.ToString(CultureInfo.InvariantCulture), new LintCIOptions + { + DryRun = true, + Ref = @ref, + }); + + // Assert + Assert.That(result.Valid, Is.False); + Assert.That(result.Errors.Single(), Is.EqualTo(expectedError)); + } +} diff --git a/NGitLab.Mock.Tests/MembersMockTests.cs b/NGitLab.Mock.Tests/MembersMockTests.cs index f973c2e0..d2fd7726 100644 --- a/NGitLab.Mock.Tests/MembersMockTests.cs +++ b/NGitLab.Mock.Tests/MembersMockTests.cs @@ -1,86 +1,181 @@ -using System.Linq; +using System; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; using NGitLab.Mock.Config; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class MembersMockTests { - public class MembersMockTests + [Test] + public void Test_members_group_all_direct([Values] bool isDefault) + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithUser("user2") + .WithGroup("G1", 1, addDefaultUserAsMaintainer: true) + .WithGroup("G2", 2, @namespace: "G1", configure: g => g.WithUserPermission("user2", Models.AccessLevel.Maintainer)) + .BuildServer(); + + var client = server.CreateClient("user1"); + var members = isDefault + ? client.Members.OfGroup("2") + : client.Members.OfGroup("2", includeInheritedMembers: false); + + Assert.That(members.Count(), Is.EqualTo(1), "Membership found are invalid"); + } + + [Test] + public void Test_members_group_all_inherited() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithUser("user2") + .WithProject("Test") + .WithGroup("G1", 1, configure: g => g.WithUserPermission("user1", Models.AccessLevel.Maintainer)) + .WithGroup("G2", 2, @namespace: "G1", configure: g => g.WithUserPermission("user2", Models.AccessLevel.Maintainer)) + .BuildServer(); + + var client = server.CreateClient("user1"); + var members = client.Members.OfGroup("2", includeInheritedMembers: true); + + Assert.That(members.Count(), Is.EqualTo(2), "Membership found are invalid"); + } + + [Test] + public void Test_members_project_all_direct([Values] bool isDefault) + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithUser("user2") + .WithUser("user3") + .WithGroup("G1", 1, addDefaultUserAsMaintainer: true) + .WithGroup("G2", 2, @namespace: "G1", configure: g => g.WithUserPermission("user2", Models.AccessLevel.Maintainer)) + .WithProject("Project", @namespace: "G1", configure: g => + g.WithUserPermission("user3", Models.AccessLevel.Maintainer) + .WithGroupPermission("G2", Models.AccessLevel.Developer)) + .BuildServer(); + + var client = server.CreateClient("user1"); + var members = isDefault + ? client.Members.OfProject("1") + : client.Members.OfProject("1", includeInheritedMembers: false); + + Assert.That(members.Count(), Is.EqualTo(1), "Membership found are invalid"); + } + + [Test] + public void Test_members_project_all_inherited() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithUser("user2") + .WithUser("user3") + .WithGroup("G1", addDefaultUserAsMaintainer: true) + .WithGroup("G2", @namespace: "G1", configure: g => g.WithUserPermission("user2", Models.AccessLevel.Maintainer)) + .WithProject("Project", 1, @namespace: "G1", configure: g => + g.WithUserPermission("user3", Models.AccessLevel.Maintainer) + .WithGroupPermission("G1/G2", Models.AccessLevel.Developer)) + .BuildServer(); + + var client = server.CreateClient("user1"); + var members = client.Members.OfProject("1", includeInheritedMembers: true); + + Assert.That(members.Count(), Is.EqualTo(3), "Membership found are invalid"); + } + + [Test] + public async Task Test_members_async_methods_simulate_gitlab_behavior() + { + // This test emulates GitLab's behavior. See `MembersClientTests.AsyncMethodsBehaveAsExpected()` + + // Arrange + var user1Name = "user1"; + var ownerName = "owner"; + + using var server = new GitLabConfig() + .WithUser(ownerName, isDefault: true) + .WithUser(user1Name) + .WithGroupOfFullPath("G1", configure: g => + g.WithUserPermission(ownerName, Models.AccessLevel.Owner) + .WithUserPermission(user1Name, Models.AccessLevel.Maintainer)) + .WithGroupOfFullPath("G1/G2", configure: g => + g.WithUserPermission(ownerName, Models.AccessLevel.Owner)) + .WithProject("Project", 1, @namespace: "G1") + .BuildServer(); + + var client = server.CreateClient(ownerName); + + var user1 = await client.Users.GetByUserNameAsync(user1Name); + var user1Id = user1.Id.ToString(); + + // Act + // Assert + const string projectId = "G1/Project"; + const string groupId = "G1/G2"; + + // Does NOT search inherited permission by default... + AssertThrowsGitLabException(() => client.Members.GetMemberOfProjectAsync(projectId, user1.Id), System.Net.HttpStatusCode.NotFound); + AssertThrowsGitLabException(() => client.Members.GetMemberOfGroupAsync(groupId, user1.Id), System.Net.HttpStatusCode.NotFound); + client.Members.OfProjectAsync(projectId).Select(m => m.UserName).Should().BeEmpty(); + client.Members.OfGroupAsync(groupId).Select(m => m.UserName).Should().BeEquivalentTo(new[] { ownerName }); + + // Does search inherited permission when asked... + (await client.Members.GetMemberOfProjectAsync(projectId, user1.Id, includeInheritedMembers: true)).UserName.Should().Be(user1Name); + (await client.Members.GetMemberOfGroupAsync(groupId, user1.Id, includeInheritedMembers: true)).UserName.Should().Be(user1Name); + client.Members.OfProjectAsync(projectId, includeInheritedMembers: true).Select(m => m.UserName).Should().BeEquivalentTo(new[] { ownerName, user1Name }); + client.Members.OfGroupAsync(groupId, includeInheritedMembers: true).Select(m => m.UserName).Should().BeEquivalentTo(new[] { ownerName, user1Name }); + + // Cannot update non-existent membership... + AssertThrowsGitLabException(() => client.Members.UpdateMemberOfProjectAsync(projectId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Owner }), System.Net.HttpStatusCode.NotFound); + AssertThrowsGitLabException(() => client.Members.UpdateMemberOfGroupAsync(groupId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Owner }), System.Net.HttpStatusCode.NotFound); + + // Cannot add membership with an access-level lower than inherited... + AssertThrowsGitLabException(() => client.Members.AddMemberToProjectAsync(projectId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Reporter }), System.Net.HttpStatusCode.BadRequest); + AssertThrowsGitLabException(() => client.Members.AddMemberToGroupAsync(groupId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Reporter }), System.Net.HttpStatusCode.BadRequest); + + // Can add membership with greater than or equal access-level... + await AssertReturnsMembership(() => client.Members.AddMemberToProjectAsync(projectId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Maintainer }), Models.AccessLevel.Maintainer); + await AssertReturnsMembership(() => client.Members.AddMemberToGroupAsync(groupId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Maintainer }), Models.AccessLevel.Maintainer); + + // Cannot add duplicate membership... + AssertThrowsGitLabException(() => client.Members.AddMemberToProjectAsync(projectId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Owner }), System.Net.HttpStatusCode.Conflict); + AssertThrowsGitLabException(() => client.Members.AddMemberToGroupAsync(groupId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Owner }), System.Net.HttpStatusCode.Conflict); + + // Can raise access-level above inherited... + await AssertReturnsMembership(() => client.Members.UpdateMemberOfProjectAsync(projectId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Owner }), Models.AccessLevel.Owner); + await AssertReturnsMembership(() => client.Members.UpdateMemberOfGroupAsync(groupId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Owner }), Models.AccessLevel.Owner); + + // Can decrease access-level to inherited... + await AssertReturnsMembership(() => client.Members.UpdateMemberOfProjectAsync(projectId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Maintainer }), Models.AccessLevel.Maintainer); + await AssertReturnsMembership(() => client.Members.UpdateMemberOfGroupAsync(groupId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Maintainer }), Models.AccessLevel.Maintainer); + + // Cannot decrease access-level lower than inherited... + AssertThrowsGitLabException(() => client.Members.UpdateMemberOfProjectAsync(projectId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Reporter }), System.Net.HttpStatusCode.BadRequest); + AssertThrowsGitLabException(() => client.Members.UpdateMemberOfGroupAsync(groupId, new() { UserId = user1Id, AccessLevel = Models.AccessLevel.Reporter }), System.Net.HttpStatusCode.BadRequest); + + // Can delete... + await client.Members.RemoveMemberFromProjectAsync(projectId, user1.Id); + await client.Members.RemoveMemberFromGroupAsync(groupId, user1.Id); + + // Delete fails when not exist... + AssertThrowsGitLabException(() => client.Members.RemoveMemberFromProjectAsync(projectId, user1.Id), System.Net.HttpStatusCode.NotFound); + AssertThrowsGitLabException(() => client.Members.RemoveMemberFromGroupAsync(groupId, user1.Id), System.Net.HttpStatusCode.NotFound); + } + + private static async Task AssertReturnsMembership(Func> code, Models.AccessLevel expectedAccessLevel) + { + var membership = await code.Invoke().ConfigureAwait(false); + Assert.That(membership, Is.Not.Null); + Assert.That(membership.AccessLevel, Is.EqualTo((int)expectedAccessLevel)); + } + + private static void AssertThrowsGitLabException(AsyncTestDelegate code, System.Net.HttpStatusCode expectedStatusCode) { - [Test] - public void Test_members_group_all_direct([Values] bool isDefault) - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithUser("user2") - .WithGroup("G1", 1, addDefaultUserAsMaintainer: true) - .WithGroup("G2", 2, @namespace: "G1", configure: g => g.WithUserPermission("user2", Models.AccessLevel.Maintainer)) - .BuildServer(); - - var client = server.CreateClient("user1"); - var members = isDefault - ? client.Members.OfGroup("2") - : client.Members.OfGroup("2", includeInheritedMembers: false); - - Assert.AreEqual(1, members.Count(), "Membership found are invalid"); - } - - [Test] - public void Test_members_group_all_inherited() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithUser("user2") - .WithProject("Test") - .WithGroup("G1", 1, configure: g => g.WithUserPermission("user1", Models.AccessLevel.Maintainer)) - .WithGroup("G2", 2, @namespace: "G1", configure: g => g.WithUserPermission("user2", Models.AccessLevel.Maintainer)) - .BuildServer(); - - var client = server.CreateClient("user1"); - var members = client.Members.OfGroup("2", includeInheritedMembers: true); - - Assert.AreEqual(2, members.Count(), "Membership found are invalid"); - } - - [Test] - public void Test_members_project_all_direct([Values] bool isDefault) - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithUser("user2") - .WithUser("user3") - .WithGroup("G1", 1, addDefaultUserAsMaintainer: true) - .WithGroup("G2", 2, @namespace: "G1", configure: g => g.WithUserPermission("user2", Models.AccessLevel.Maintainer)) - .WithProject("Project", @namespace: "G1", configure: g => - g.WithUserPermission("user3", Models.AccessLevel.Maintainer) - .WithGroupPermission("G2", Models.AccessLevel.Developer)) - .BuildServer(); - - var client = server.CreateClient("user1"); - var members = isDefault - ? client.Members.OfProject("1") - : client.Members.OfProject("1", includeInheritedMembers: false); - - Assert.AreEqual(1, members.Count(), "Membership found are invalid"); - } - - [Test] - public void Test_members_project_all_inherited() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithUser("user2") - .WithUser("user3") - .WithGroup("G1", addDefaultUserAsMaintainer: true) - .WithGroup("G2", @namespace: "G1", configure: g => g.WithUserPermission("user2", Models.AccessLevel.Maintainer)) - .WithProject("Project", 1, @namespace: "G1", configure: g => - g.WithUserPermission("user3", Models.AccessLevel.Maintainer) - .WithGroupPermission("G1/G2", Models.AccessLevel.Developer)) - .BuildServer(); - - var client = server.CreateClient("user1"); - var members = client.Members.OfProject("1", includeInheritedMembers: true); - - Assert.AreEqual(3, members.Count(), "Membership found are invalid"); - } + var ex = Assert.CatchAsync(typeof(GitLabException), code) as GitLabException; + Assert.That(ex, Is.Not.Null); + Assert.That(ex.StatusCode, Is.EqualTo(expectedStatusCode)); } } diff --git a/NGitLab.Mock.Tests/MergeRequestsMockTests.cs b/NGitLab.Mock.Tests/MergeRequestsMockTests.cs index 2e35f717..af62b06e 100644 --- a/NGitLab.Mock.Tests/MergeRequestsMockTests.cs +++ b/NGitLab.Mock.Tests/MergeRequestsMockTests.cs @@ -5,290 +5,411 @@ using NGitLab.Models; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class MergeRequestsMockTests { - public class MergeRequestsMockTests + [Test] + public void Test_merge_requests_created_by_me_can_be_listed() { - [Test] - public void Test_merge_requests_created_by_me_can_be_listed() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithUser("user2") - .WithProject("Test", configure: project => project - .WithMergeRequest("branch-01", title: "Merge request 1", author: "user1", assignee: "user2") - .WithMergeRequest("branch-02", title: "Merge request 2", author: "user2", assignee: "user1")) - .BuildServer(); - - var client = server.CreateClient("user1"); - var mergeRequests = client.MergeRequests.Get(new MergeRequestQuery { Scope = "created_by_me" }).ToArray(); - - Assert.AreEqual(1, mergeRequests.Length, "Merge requests count is invalid"); - Assert.AreEqual("Merge request 1", mergeRequests[0].Title, "Merge request found is invalid"); - } - - [Test] - public void Test_merge_requests_assigned_to_me_can_be_listed() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithUser("user2") - .WithProject("Test", configure: project => project - .WithMergeRequest("branch-01", title: "Merge request 1", author: "user1", assignee: "user2") - .WithMergeRequest("branch-02", title: "Merge request 2", author: "user2", assignee: "user1")) - .BuildServer(); - - var client = server.CreateClient("user1"); - var mergeRequests = client.MergeRequests.Get(new MergeRequestQuery { Scope = "assigned_to_me" }).ToArray(); - - Assert.AreEqual(1, mergeRequests.Length, "Merge requests count is invalid"); - Assert.AreEqual("Merge request 2", mergeRequests[0].Title, "Merge request found is invalid"); - } - - [Test] - public void Test_merge_requests_approvable_by_me_can_be_listed() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithUser("user2") - .WithProject("Test", configure: project => project - .WithMergeRequest("branch-01", title: "Merge request 1", author: "user1", approvers: new[] { "user2" }) - .WithMergeRequest("branch-02", title: "Merge request 2", author: "user2", approvers: new[] { "user1" })) - .BuildServer(); - - var client = server.CreateClient("user1"); - var mergeRequests = client.MergeRequests.Get(new MergeRequestQuery { ApproverIds = new[] { 1 } }).ToArray(); - - Assert.AreEqual(1, mergeRequests.Length, "Merge requests count is invalid"); - Assert.AreEqual("Merge request 2", mergeRequests[0].Title, "Merge request found is invalid"); - } - - [Test] - public void Test_merge_requests_can_be_listed_when_assignee_not_set() - { - using var gitLabServer = new GitLabServer(); - var user1 = new User("user1"); - gitLabServer.Users.Add(user1); - var user2 = new User("user2"); - gitLabServer.Users.Add(user2); - var group = new Group("TestGroup"); - gitLabServer.Groups.Add(group); - var project = new Project("Test") { Visibility = VisibilityLevel.Internal }; - group.Projects.Add(project); - var mergeRequest1 = new MergeRequest { Author = new UserRef(user1), Title = "Merge request 1", SourceProject = project }; - project.MergeRequests.Add(mergeRequest1); - var mergeRequest2 = new MergeRequest { Author = new UserRef(user2), Assignee = new UserRef(user1), Title = "Merge request 2", SourceProject = project }; - project.MergeRequests.Add(mergeRequest2); - - var client = gitLabServer.CreateClient(user1); - var mergeRequests = client.MergeRequests.Get(new MergeRequestQuery { Scope = "assigned_to_me" }).ToArray(); - - Assert.AreEqual(1, mergeRequests.Length, "Merge requests count is invalid"); - Assert.AreEqual("Merge request 2", mergeRequests[0].Title, "Merge request found is invalid"); - } - - [Test] - public void Test_merge_requests_assignee_should_update_assignees_and_vice_versa() + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithUser("user2") + .WithProject("Test", configure: project => project + .WithMergeRequest("branch-01", title: "Merge request 1", author: "user1", assignee: "user2") + .WithMergeRequest("branch-02", title: "Merge request 2", author: "user2", assignee: "user1")) + .BuildServer(); + + var client = server.CreateClient("user1"); + var mergeRequests = client.MergeRequests.Get(new MergeRequestQuery { Scope = "created_by_me" }).ToArray(); + + Assert.That(mergeRequests, Has.Length.EqualTo(1), "Merge requests count is invalid"); + Assert.That(mergeRequests[0].Title, Is.EqualTo("Merge request 1"), "Merge request found is invalid"); + } + + [Test] + public void Test_merge_requests_assigned_to_me_can_be_listed() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithUser("user2") + .WithProject("Test", configure: project => project + .WithMergeRequest("branch-01", title: "Merge request 1", author: "user1", assignee: "user2") + .WithMergeRequest("branch-02", title: "Merge request 2", author: "user2", assignee: "user1")) + .BuildServer(); + + var client = server.CreateClient("user1"); + var mergeRequests = client.MergeRequests.Get(new MergeRequestQuery { Scope = "assigned_to_me" }).ToArray(); + + Assert.That(mergeRequests, Has.Length.EqualTo(1), "Merge requests count is invalid"); + Assert.That(mergeRequests[0].Title, Is.EqualTo("Merge request 2"), "Merge request found is invalid"); + } + + [Test] + public void Test_merge_requests_approvable_by_me_can_be_listed() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithUser("user2") + .WithProject("Test", configure: project => project + .WithMergeRequest("branch-01", title: "Merge request 1", author: "user1", approvers: new[] { "user2" }) + .WithMergeRequest("branch-02", title: "Merge request 2", author: "user2", approvers: new[] { "user1" })) + .BuildServer(); + + var client = server.CreateClient("user1"); + var mergeRequests = client.MergeRequests.Get(new MergeRequestQuery { ApproverIds = new[] { 1 } }).ToArray(); + + Assert.That(mergeRequests, Has.Length.EqualTo(1), "Merge requests count is invalid"); + Assert.That(mergeRequests[0].Title, Is.EqualTo("Merge request 2"), "Merge request found is invalid"); + } + + [Test] + public void Test_merge_requests_can_be_listed_when_assignee_not_set() + { + using var gitLabServer = new GitLabServer(); + var user1 = new User("user1"); + gitLabServer.Users.Add(user1); + var user2 = new User("user2"); + gitLabServer.Users.Add(user2); + var group = new Group("TestGroup"); + gitLabServer.Groups.Add(group); + var project = new Project("Test") { Visibility = VisibilityLevel.Internal }; + group.Projects.Add(project); + var mergeRequest1 = new MergeRequest { Author = new UserRef(user1), Title = "Merge request 1", SourceProject = project }; + project.MergeRequests.Add(mergeRequest1); + var mergeRequest2 = new MergeRequest { Author = new UserRef(user2), Assignee = new UserRef(user1), Title = "Merge request 2", SourceProject = project }; + project.MergeRequests.Add(mergeRequest2); + + var client = gitLabServer.CreateClient(user1); + var mergeRequests = client.MergeRequests.Get(new MergeRequestQuery { Scope = "assigned_to_me" }).ToArray(); + + Assert.That(mergeRequests, Has.Length.EqualTo(1), "Merge requests count is invalid"); + Assert.That(mergeRequests[0].Title, Is.EqualTo("Merge request 2"), "Merge request found is invalid"); + } + + [Test] + public void Test_merge_requests_assignee_should_update_assignees_and_vice_versa() + { + var user1 = new User("user1"); + var user2 = new User("user2"); + + var mergeRequestSingle = new MergeRequest { - var user1 = new User("user1"); - var user2 = new User("user2"); - - var mergeRequestSingle = new MergeRequest - { - Assignee = new UserRef(user1), - }; - - var mergeRequestTwo = new MergeRequest - { - Assignees = new[] { new UserRef(user1), new UserRef(user2) }, - }; - - Assert.AreEqual(1, mergeRequestSingle.Assignees.Count, "Merge request assignees count invalid"); - Assert.AreEqual("user1", mergeRequestTwo.Assignee.UserName, "Merge request assignee is invalid"); - } - - [TestCase(false)] - [TestCase(true)] - public void Test_merge_request_with_no_rebase_required_can_be_accepted(bool sourceProjectSameAsTargetProject) + Assignee = new UserRef(user1), + }; + + var mergeRequestTwo = new MergeRequest { - // Arrange - using var server = new GitLabServer(); + Assignees = new[] { new UserRef(user1), new UserRef(user2) }, + }; + + Assert.That(mergeRequestSingle.Assignees, Has.Count.EqualTo(1), "Merge request assignees count invalid"); + Assert.That(mergeRequestTwo.Assignee.UserName, Is.EqualTo("user1"), "Merge request assignee is invalid"); + } + + [TestCase(false)] + [TestCase(true)] + public void Test_merge_request_with_no_rebase_required_can_be_accepted(bool sourceProjectSameAsTargetProject) + { + // Arrange + using var server = new GitLabServer(); + + var contributor = server.Users.AddNew("contributor"); + var maintainer = server.Users.AddNew("maintainer"); - var contributor = server.Users.AddNew("contributor"); - var maintainer = server.Users.AddNew("maintainer"); + var targetGroup = new Group("TheTargetGroup"); + server.Groups.Add(targetGroup); - var targetGroup = new Group("TheTargetGroup"); - server.Groups.Add(targetGroup); + var targetProject = new Project("TheTargetProject") { Visibility = VisibilityLevel.Internal }; + targetGroup.Projects.Add(targetProject); - var targetProject = new Project("TheTargetProject") { Visibility = VisibilityLevel.Internal }; - targetGroup.Projects.Add(targetProject); + targetProject.Permissions.Add(new Permission(maintainer, AccessLevel.Maintainer)); + targetProject.Repository.Commit(maintainer, "A commit"); - targetProject.Permissions.Add(new Permission(maintainer, AccessLevel.Maintainer)); - targetProject.Repository.Commit(maintainer, "A commit"); + var sourceProject = sourceProjectSameAsTargetProject ? + targetProject : + targetProject.Fork(contributor.Namespace, contributor, "TheSourceProject"); + sourceProject.Repository.CreateAndCheckoutBranch("to-be-merged"); + sourceProject.Repository.Commit(contributor, "add a file", new[] { File.CreateFromText(Guid.NewGuid().ToString("N"), "This is the new file's content") }); - var sourceProject = sourceProjectSameAsTargetProject ? - targetProject : - targetProject.Fork(contributor.Namespace, contributor, "TheSourceProject"); - sourceProject.Repository.CreateAndCheckoutBranch("to-be-merged"); - sourceProject.Repository.Commit(contributor, "add a file", new[] { File.CreateFromText(Guid.NewGuid().ToString("N"), "This is the new file's content") }); + var mr = targetProject.CreateMergeRequest(contributor, "A great title", "A great description", targetProject.DefaultBranch, "to-be-merged", sourceProject); + mr.Assignee = new UserRef(maintainer); - var mr = targetProject.CreateMergeRequest(contributor, "A great title", "A great description", targetProject.DefaultBranch, "to-be-merged", sourceProject); - mr.Assignee = new UserRef(maintainer); + targetProject.MergeMethod = "ff"; - targetProject.MergeMethod = "ff"; + var maintainerClient = server.CreateClient("maintainer"); - var maintainerClient = server.CreateClient("maintainer"); + // Act + var modelMr = maintainerClient.GetMergeRequest(mr.Project.Id).Accept(mr.Iid, new MergeRequestMerge + { + MergeWhenPipelineSucceeds = mr.HeadPipeline != null, + ShouldRemoveSourceBranch = true, + Sha = mr.HeadSha, + }); + + // Assert + Assert.That(modelMr.HasConflicts, Is.False); + Assert.That(modelMr.DivergedCommitsCount, Is.EqualTo(0)); + Assert.That(modelMr.DiffRefs?.BaseSha, Is.Not.Null); + Assert.That(modelMr.DiffRefs.StartSha, Is.EqualTo(modelMr.DiffRefs.BaseSha)); + Assert.That(modelMr.State, Is.EqualTo("merged")); + + Assert.That(targetProject.Repository.GetAllBranches().Any(b => b.FriendlyName.EndsWith("to-be-merged", StringComparison.Ordinal)), Is.False, + "Since the merge succeeded and 'ShouldRemoveSourceBranch' was set, 'to-be-merged' branch should be gone"); + + Assert.That(sourceProject.Repository.GetAllBranches().Any(b => b.FriendlyName.EndsWith("to-be-merged", StringComparison.Ordinal)), Is.False, + "Since the merge succeeded and 'ShouldRemoveSourceBranch' was set, 'to-be-merged' branch should be gone"); + } + + [TestCase(false)] + [TestCase(true)] + public void Test_merge_request_with_non_conflicting_rebase_needed_and_merge_method_ff_cannot_be_accepted(bool sourceProjectSameAsTargetProject) + { + // Arrange + using var server = new GitLabServer(); + + var contributor = server.Users.AddNew("contributor"); + var maintainer = server.Users.AddNew("maintainer"); + + var targetGroup = new Group("TheTargetGroup"); + server.Groups.Add(targetGroup); - // Act - var modelMr = maintainerClient.GetMergeRequest(mr.Project.Id).Accept(mr.Iid, new MergeRequestMerge - { - MergeWhenPipelineSucceeds = mr.HeadPipeline != null, - ShouldRemoveSourceBranch = true, - Sha = mr.HeadSha, - }); + var targetProject = new Project("TheTargetProject") { Visibility = VisibilityLevel.Internal }; + targetGroup.Projects.Add(targetProject); - // Assert - Assert.IsFalse(modelMr.HasConflicts); - Assert.AreEqual(0, modelMr.DivergedCommitsCount); - Assert.IsNotNull(modelMr.DiffRefs?.BaseSha); - Assert.AreEqual(modelMr.DiffRefs.BaseSha, modelMr.DiffRefs.StartSha); - Assert.AreEqual("merged", modelMr.State); + targetProject.Permissions.Add(new Permission(maintainer, AccessLevel.Maintainer)); + targetProject.Repository.Commit(maintainer, "A commit"); - Assert.IsFalse(targetProject.Repository.GetAllBranches().Any(b => b.FriendlyName.EndsWith("to-be-merged", StringComparison.Ordinal)), - "Since the merge succeeded and 'ShouldRemoveSourceBranch' was set, 'to-be-merged' branch should be gone"); + var sourceProject = sourceProjectSameAsTargetProject ? + targetProject : + targetProject.Fork(contributor.Namespace, contributor, "TheSourceProject"); + sourceProject.Repository.CreateAndCheckoutBranch("to-be-merged"); + sourceProject.Repository.Commit(contributor, "add a file", new[] { File.CreateFromText(Guid.NewGuid().ToString("N"), "This is the new file's content") }); - Assert.IsFalse(sourceProject.Repository.GetAllBranches().Any(b => b.FriendlyName.EndsWith("to-be-merged", StringComparison.Ordinal)), - "Since the merge succeeded and 'ShouldRemoveSourceBranch' was set, 'to-be-merged' branch should be gone"); - } + targetProject.MergeMethod = "ff"; - [TestCase(false)] - [TestCase(true)] - public void Test_merge_request_with_non_conflicting_rebase_needed_and_merge_method_ff_cannot_be_accepted(bool sourceProjectSameAsTargetProject) + targetProject.Repository.Checkout(targetProject.DefaultBranch); + targetProject.Repository.Commit(maintainer, "add a file", new[] { File.CreateFromText(Guid.NewGuid().ToString("N"), "This is the new file's content") }); + + var mr = targetProject.CreateMergeRequest(contributor, "A great title", "A great description", targetProject.DefaultBranch, "to-be-merged", sourceProject); + mr.Assignee = new UserRef(maintainer); + + var maintainerClient = server.CreateClient("maintainer"); + + // Act/Assert + var exception = Assert.Throws(() => maintainerClient.GetMergeRequest(mr.Project.Id).Accept(mr.Iid, new MergeRequestMerge { - // Arrange - using var server = new GitLabServer(); + MergeWhenPipelineSucceeds = mr.HeadPipeline != null, + ShouldRemoveSourceBranch = true, + })); + Assert.That(exception.StatusCode, Is.EqualTo(HttpStatusCode.MethodNotAllowed)); + Assert.That(exception.Message.Equals("The MR cannot be merged with method 'ff': the source branch must first be rebased", StringComparison.Ordinal), Is.True); - var contributor = server.Users.AddNew("contributor"); - var maintainer = server.Users.AddNew("maintainer"); + Assert.That(mr.HasConflicts, Is.False); + Assert.That(mr.DivergedCommitsCount, Is.EqualTo(1)); + Assert.That(mr.StartSha, Is.Not.EqualTo(mr.BaseSha)); - var targetGroup = new Group("TheTargetGroup"); - server.Groups.Add(targetGroup); + Assert.That(targetProject.Repository.GetAllBranches().Any(b => b.FriendlyName.EndsWith("to-be-merged", StringComparison.Ordinal)), Is.True, + "Since the merge failed, 'to-be-merged' branch should still be there"); - var targetProject = new Project("TheTargetProject") { Visibility = VisibilityLevel.Internal }; - targetGroup.Projects.Add(targetProject); + Assert.That(sourceProject.Repository.GetAllBranches().Any(b => b.FriendlyName.EndsWith("to-be-merged", StringComparison.Ordinal)), Is.True, + "Since the merge failed, 'to-be-merged' branch should still be there"); + } - targetProject.Permissions.Add(new Permission(maintainer, AccessLevel.Maintainer)); - targetProject.Repository.Commit(maintainer, "A commit"); + [TestCase(false)] + [TestCase(true)] + public void Test_merge_request_with_conflicts_cannot_be_accepted(bool sourceProjectSameAsTargetProject) + { + // Arrange + using var server = new GitLabServer(); - var sourceProject = sourceProjectSameAsTargetProject ? - targetProject : - targetProject.Fork(contributor.Namespace, contributor, "TheSourceProject"); - sourceProject.Repository.CreateAndCheckoutBranch("to-be-merged"); - sourceProject.Repository.Commit(contributor, "add a file", new[] { File.CreateFromText(Guid.NewGuid().ToString("N"), "This is the new file's content") }); + var contributor = server.Users.AddNew("contributor"); + var maintainer = server.Users.AddNew("maintainer"); - targetProject.MergeMethod = "ff"; + var targetGroup = new Group("TheTargetGroup"); + server.Groups.Add(targetGroup); - targetProject.Repository.Checkout(targetProject.DefaultBranch); - targetProject.Repository.Commit(maintainer, "add a file", new[] { File.CreateFromText(Guid.NewGuid().ToString("N"), "This is the new file's content") }); + var targetProject = new Project("TheTargetProject") { Visibility = VisibilityLevel.Internal }; + targetGroup.Projects.Add(targetProject); - var mr = targetProject.CreateMergeRequest(contributor, "A great title", "A great description", targetProject.DefaultBranch, "to-be-merged", sourceProject); - mr.Assignee = new UserRef(maintainer); + targetProject.Permissions.Add(new Permission(maintainer, AccessLevel.Maintainer)); + targetProject.Repository.Commit(maintainer, "A commit"); - var maintainerClient = server.CreateClient("maintainer"); + var sourceProject = sourceProjectSameAsTargetProject ? + targetProject : + targetProject.Fork(contributor.Namespace, contributor, "TheSourceProject"); + var conflictingFile = Guid.NewGuid().ToString("N"); + sourceProject.Repository.CreateAndCheckoutBranch("to-be-merged"); + sourceProject.Repository.Commit(contributor, "add a file", new[] { File.CreateFromText(conflictingFile, "This is the new file's content") }); - // Act/Assert - var exception = Assert.Throws(() => maintainerClient.GetMergeRequest(mr.Project.Id).Accept(mr.Iid, new MergeRequestMerge - { - MergeWhenPipelineSucceeds = mr.HeadPipeline != null, - ShouldRemoveSourceBranch = true, - })); - Assert.AreEqual(HttpStatusCode.MethodNotAllowed, exception.StatusCode); - Assert.IsTrue(exception.Message.Equals("The MR cannot be merged with method 'ff': the source branch must first be rebased", StringComparison.Ordinal)); + targetProject.MergeMethod = "ff"; - Assert.IsFalse(mr.HasConflicts); - Assert.AreEqual(1, mr.DivergedCommitsCount); - Assert.AreNotEqual(mr.BaseSha, mr.StartSha); + targetProject.Repository.Checkout(targetProject.DefaultBranch); + targetProject.Repository.Commit(maintainer, "add a file", new[] { File.CreateFromText(conflictingFile, "This is conflicting content") }); - Assert.IsTrue(targetProject.Repository.GetAllBranches().Any(b => b.FriendlyName.EndsWith("to-be-merged", StringComparison.Ordinal)), - "Since the merge failed, 'to-be-merged' branch should still be there"); + var mr = targetProject.CreateMergeRequest(contributor, "A great title", "A great description", targetProject.DefaultBranch, "to-be-merged", sourceProject); + mr.Assignee = new UserRef(maintainer); - Assert.IsTrue(sourceProject.Repository.GetAllBranches().Any(b => b.FriendlyName.EndsWith("to-be-merged", StringComparison.Ordinal)), - "Since the merge failed, 'to-be-merged' branch should still be there"); - } + var maintainerClient = server.CreateClient("maintainer"); - [TestCase(false)] - [TestCase(true)] - public void Test_merge_request_with_conflicts_cannot_be_accepted(bool sourceProjectSameAsTargetProject) + // Act/Assert + var exception = Assert.Throws(() => maintainerClient.GetMergeRequest(mr.Project.Id).Accept(mr.Iid, new MergeRequestMerge { - // Arrange - using var server = new GitLabServer(); + MergeWhenPipelineSucceeds = mr.HeadPipeline != null, + ShouldRemoveSourceBranch = true, + })); + Assert.That(exception.StatusCode, Is.EqualTo(HttpStatusCode.NotAcceptable)); + Assert.That(exception.Message.Equals("The merge request has some conflicts and cannot be merged", StringComparison.Ordinal), Is.True); + + Assert.That(mr.HasConflicts, Is.True); + Assert.That(mr.DivergedCommitsCount, Is.EqualTo(1)); + Assert.That(mr.StartSha, Is.Not.EqualTo(mr.BaseSha)); - var contributor = server.Users.AddNew("contributor"); - var maintainer = server.Users.AddNew("maintainer"); + Assert.That(targetProject.Repository.GetAllBranches().Any(b => b.FriendlyName.EndsWith("to-be-merged", StringComparison.Ordinal)), Is.True, + "Since the merge failed, 'to-be-merged' branch should still be there"); - var targetGroup = new Group("TheTargetGroup"); - server.Groups.Add(targetGroup); + Assert.That(sourceProject.Repository.GetAllBranches().Any(b => b.FriendlyName.EndsWith("to-be-merged", StringComparison.Ordinal)), Is.True, + "Since the merge failed, 'to-be-merged' branch should still be there"); + } - var targetProject = new Project("TheTargetProject") { Visibility = VisibilityLevel.Internal }; - targetGroup.Projects.Add(targetProject); + [Test] + public void Test_merge_request_with_head_pipeline() + { + using var server = new GitLabServer(); + var user = server.Users.AddNew(); + var project = user.Namespace.Projects.AddNew(project => project.Visibility = VisibilityLevel.Internal); + var commit = project.Repository.Commit(user, "test"); - targetProject.Permissions.Add(new Permission(maintainer, AccessLevel.Maintainer)); - targetProject.Repository.Commit(maintainer, "A commit"); + var branch = "my-branch"; + project.Repository.CreateAndCheckoutBranch(branch); + commit = project.Repository.Commit(user, "another test"); - var sourceProject = sourceProjectSameAsTargetProject ? - targetProject : - targetProject.Fork(contributor.Namespace, contributor, "TheSourceProject"); - var conflictingFile = Guid.NewGuid().ToString("N"); - sourceProject.Repository.CreateAndCheckoutBranch("to-be-merged"); - sourceProject.Repository.Commit(contributor, "add a file", new[] { File.CreateFromText(conflictingFile, "This is the new file's content") }); + var mr = project.CreateMergeRequest(user, "A great title", "A great description", project.DefaultBranch, branch); + Assert.That(mr.HeadPipeline, Is.Null, "No pipeline created yet on the source branch"); - targetProject.MergeMethod = "ff"; + var pipeline = project.Pipelines.Add(branch, JobStatus.Success, user); + Assert.That(mr.HeadPipeline, Is.EqualTo(pipeline), "A pipeline was just created on the source branch"); + } - targetProject.Repository.Checkout(targetProject.DefaultBranch); - targetProject.Repository.Commit(maintainer, "add a file", new[] { File.CreateFromText(conflictingFile, "This is conflicting content") }); + [Test] + public void Test_merge_request_resource_state_events_found_on_close_and_reopen() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithUser("user2") + .WithProject("Test", configure: project => project + .WithMergeRequest("branch-01", title: "Merge request 1", author: "user1", assignee: "user2")) + .BuildServer(); - var mr = targetProject.CreateMergeRequest(contributor, "A great title", "A great description", targetProject.DefaultBranch, "to-be-merged", sourceProject); - mr.Assignee = new UserRef(maintainer); + var client = server.CreateClient("user1"); + var projectId = server.AllProjects.First().Id; + var mrClient = client.GetMergeRequest(projectId); + var mergeRequest = mrClient.Get(new MergeRequestQuery { Scope = "created_by_me" }).First(); - var maintainerClient = server.CreateClient("maintainer"); + mrClient.Close(mergeRequest.Iid); + mrClient.Reopen(mergeRequest.Iid); - // Act/Assert - var exception = Assert.Throws(() => maintainerClient.GetMergeRequest(mr.Project.Id).Accept(mr.Iid, new MergeRequestMerge - { - MergeWhenPipelineSucceeds = mr.HeadPipeline != null, - ShouldRemoveSourceBranch = true, - })); - Assert.AreEqual(HttpStatusCode.NotAcceptable, exception.StatusCode); - Assert.IsTrue(exception.Message.Equals("The merge request has some conflicts and cannot be merged", StringComparison.Ordinal)); + var resourceStateEvents = mrClient.ResourceStateEventsAsync(projectId: projectId, mergeRequestIid: mergeRequest.Iid).ToArray(); + Assert.That(resourceStateEvents, Has.Length.EqualTo(2)); - Assert.IsTrue(mr.HasConflicts); - Assert.AreEqual(1, mr.DivergedCommitsCount); - Assert.AreNotEqual(mr.BaseSha, mr.StartSha); + var closeStateEvents = resourceStateEvents.Where(e => string.Equals(e.State, "closed", StringComparison.Ordinal)).ToArray(); + Assert.That(closeStateEvents, Has.Length.EqualTo(1)); - Assert.IsTrue(targetProject.Repository.GetAllBranches().Any(b => b.FriendlyName.EndsWith("to-be-merged", StringComparison.Ordinal)), - "Since the merge failed, 'to-be-merged' branch should still be there"); + var reopenMilestoneEvents = resourceStateEvents.Where(e => string.Equals(e.State, "reopened", StringComparison.Ordinal)).ToArray(); + Assert.That(reopenMilestoneEvents, Has.Length.EqualTo(1)); + } - Assert.IsTrue(sourceProject.Repository.GetAllBranches().Any(b => b.FriendlyName.EndsWith("to-be-merged", StringComparison.Ordinal)), - "Since the merge failed, 'to-be-merged' branch should still be there"); - } + [Test] + public void Test_merge_request_resource_label_events_found() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithUser("user2") + .WithProject("Test", configure: project => project + .WithMergeRequest("branch-01", title: "Merge request 1", author: "user1", assignee: "user2")) + .BuildServer(); + + var client = server.CreateClient("user1"); + var projectId = server.AllProjects.First().Id; + var mrClient = client.GetMergeRequest(projectId); + var mergeRequest = mrClient.Get(new MergeRequestQuery { Scope = "created_by_me" }).First(); + + mrClient.Update(mergeRequest.Iid, new MergeRequestUpdate() + { + AddLabels = "first,second,third", + }); - [Test] - public void Test_merge_request_with_head_pipeline() + mrClient.Update(mergeRequest.Iid, new MergeRequestUpdate() { - using var server = new GitLabServer(); - var user = server.Users.AddNew(); - var project = user.Namespace.Projects.AddNew(project => project.Visibility = VisibilityLevel.Internal); - var commit = project.Repository.Commit(user, "test"); + RemoveLabels = "second", + }); - var branch = "my-branch"; - project.Repository.CreateAndCheckoutBranch(branch); - commit = project.Repository.Commit(user, "another test"); + mrClient.Update(mergeRequest.Iid, new MergeRequestUpdate() + { + Labels = "first,second", + }); + + /* We're expecting this sequence + * 1. Add first + * 1. Add second + * 1. Add third + * 2. Remove second + * 3. Add second + * 3. Remove third + */ + var resourceLabelEvents = mrClient.ResourceLabelEventsAsync(projectId: projectId, mergeRequestIid: mergeRequest.Iid).ToArray(); + Assert.That(resourceLabelEvents, Has.Length.EqualTo(6)); + + var addLabelEvents = resourceLabelEvents.Where(e => e.Action == ResourceLabelEventAction.Add).ToArray(); + Assert.That(addLabelEvents, Has.Length.EqualTo(4)); + + var removeLabelEvents = resourceLabelEvents.Where(e => e.Action == ResourceLabelEventAction.Remove).ToArray(); + Assert.That(removeLabelEvents, Has.Length.EqualTo(2)); + } - var mr = project.CreateMergeRequest(user, "A great title", "A great description", project.DefaultBranch, branch); - Assert.IsNull(mr.HeadPipeline, "No pipeline created yet on the source branch"); + [Test] + public void Test_merge_request_resource_milestone_events_found() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithUser("user2") + .WithProject("Test", configure: project => project + .WithMergeRequest("branch-01", title: "Merge request 1", author: "user1", assignee: "user2") + .WithMilestone("Milestone 1") + .WithMilestone("Milestone 2")) + .BuildServer(); + + var client = server.CreateClient("user1"); + var projectId = server.AllProjects.First().Id; + var mrClient = client.GetMergeRequest(projectId); + var mergeRequest = mrClient.Get(new MergeRequestQuery { Scope = "created_by_me" }).First(); + var milestones = client.GetMilestone(1).All.ToArray(); + + mrClient.Update(mergeRequest.Iid, new MergeRequestUpdate() + { + MilestoneId = milestones[0].Id, + }); - var pipeline = project.Pipelines.Add(branch, JobStatus.Success, user); - Assert.AreEqual(pipeline, mr.HeadPipeline, "A pipeline was just created on the source branch"); - } + mrClient.Update(mergeRequest.Iid, new MergeRequestUpdate() + { + MilestoneId = milestones[1].Id, + }); + + /* We're expecting this sequence + * 1. Add milestone 1 + * 2. Remove milestone 1 + * 2. Add milestone 2 + */ + var resourceMilestoneEvents = mrClient.ResourceMilestoneEventsAsync(projectId: projectId, mergeRequestIid: mergeRequest.Iid).ToArray(); + Assert.That(resourceMilestoneEvents, Has.Length.EqualTo(3)); + + var removeMilestoneEvents = resourceMilestoneEvents.Where(e => e.Action == ResourceMilestoneEventAction.Remove).ToArray(); + Assert.That(removeMilestoneEvents, Has.Length.EqualTo(1)); + Assert.That(removeMilestoneEvents[0].Milestone.Id, Is.EqualTo(milestones[0].Id)); + + var addMilestoneEvents = resourceMilestoneEvents.Where(e => e.Action == ResourceMilestoneEventAction.Add).ToArray(); + Assert.That(addMilestoneEvents, Has.Length.EqualTo(2)); + Assert.That(addMilestoneEvents[0].Milestone.Id, Is.EqualTo(milestones[0].Id)); + Assert.That(addMilestoneEvents[1].Milestone.Id, Is.EqualTo(milestones[1].Id)); } } diff --git a/NGitLab.Mock.Tests/MilestonesMockTests.cs b/NGitLab.Mock.Tests/MilestonesMockTests.cs index 074dd2a7..151a5819 100644 --- a/NGitLab.Mock.Tests/MilestonesMockTests.cs +++ b/NGitLab.Mock.Tests/MilestonesMockTests.cs @@ -4,142 +4,141 @@ using NGitLab.Models; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class MilestonesMockTests { - public class MilestonesMockTests + [Test] + public void Test_milestones_can_be_found_from_project() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", id: 1, configure: project => project + .WithMilestone("Milestone 1") + .WithMilestone("Milestone 2")) + .BuildServer(); + + var client = server.CreateClient(); + var milestones = client.GetMilestone(1).All.ToArray(); + + Assert.That(milestones, Has.Length.EqualTo(2), "Milestones count is invalid"); + Assert.That(milestones.Any(x => string.Equals(x.Title, "Milestone 1", StringComparison.Ordinal)), Is.True, "Milestone 'Milestone 1' not found"); + Assert.That(milestones.Any(x => string.Equals(x.Title, "Milestone 2", StringComparison.Ordinal)), Is.True, "Milestone 'Milestone 2' not found"); + } + + [Test] + public void Test_milestones_can_be_added_to_project() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true) + .BuildServer(); + + var client = server.CreateClient(); + client.GetMilestone(1).Create(new MilestoneCreate { Title = "Milestone 1" }); + var milestones = client.GetMilestone(1).All.ToArray(); + + Assert.That(milestones, Has.Length.EqualTo(1), "Milestones count is invalid"); + Assert.That(milestones[0].Title, Is.EqualTo("Milestone 1"), "Milestone 'Milestone 1' not found"); + } + + [Test] + public void Test_milestones_can_be_edited_from_project() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true, configure: project => project + .WithMilestone("Milestone 1", id: 1)) + .BuildServer(); + + var client = server.CreateClient(); + client.GetMilestone(1).Update(1, new MilestoneUpdate { Title = "Milestone 2" }); + var milestones = client.GetMilestone(1).All.ToArray(); + + Assert.That(milestones, Has.Length.EqualTo(1), "Milestones count is invalid"); + Assert.That(milestones[0].Title, Is.EqualTo("Milestone 2"), "Milestone 'Milestone 2' not found"); + } + + [Test] + public void Test_milestones_can_be_deleted_from_project() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true, configure: project => project + .WithMilestone("Milestone 1", id: 1)) + .BuildServer(); + + var client = server.CreateClient(); + client.GetMilestone(1).Delete(1); + var milestones = client.GetMilestone(1).All.ToArray(); + + Assert.That(milestones, Is.Empty, "Milestones count is invalid"); + } + + [Test] + public void Test_milestones_can_be_closed_and_activated_from_project() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true, configure: project => project + .WithMilestone("Milestone 1", id: 1)) + .BuildServer(); + + var client = server.CreateClient(); + client.GetMilestone(1).Close(1); + var activeMilestones = client.GetMilestone(1).AllInState(Models.MilestoneState.active).ToArray(); + var closedMilestones = client.GetMilestone(1).AllInState(Models.MilestoneState.closed).ToArray(); + + Assert.That(activeMilestones, Is.Empty, "Active milestones count is invalid"); + Assert.That(closedMilestones, Has.Length.EqualTo(1), "Closed milestones count is invalid"); + + client.GetMilestone(1).Activate(1); + activeMilestones = client.GetMilestone(1).AllInState(Models.MilestoneState.active).ToArray(); + closedMilestones = client.GetMilestone(1).AllInState(Models.MilestoneState.closed).ToArray(); + + Assert.That(activeMilestones, Has.Length.EqualTo(1), "Active milestones count is invalid"); + Assert.That(closedMilestones, Is.Empty, "Closed milestones count is invalid"); + } + + [Test] + public void Test_projects_merge_request_can_be_found_from_milestone() + { + const int ProjectId = 1; + const int MilestoneId = 1; + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", id: ProjectId, addDefaultUserAsMaintainer: true, configure: project => project + .WithMilestone("Milestone 1", id: MilestoneId) + .WithMergeRequest("branch-01", title: "Merge request 1", milestone: "Milestone 1") + .WithMergeRequest("branch-02", title: "Merge request 2", milestone: "Milestone 1") + .WithMergeRequest("branch-03", title: "Merge request 3", milestone: "Milestone 2")) + .BuildServer(); + + var client = server.CreateClient(); + var mergeRequests = client.GetMilestone(ProjectId).GetMergeRequests(MilestoneId).ToArray(); + Assert.That(mergeRequests, Has.Length.EqualTo(2), "Merge requests count is invalid"); + } + + [Test] + public void Test_groups_merge_request_can_be_found_from_milestone() { - [Test] - public void Test_milestones_can_be_found_from_project() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", id: 1, configure: project => project - .WithMilestone("Milestone 1") - .WithMilestone("Milestone 2")) - .BuildServer(); - - var client = server.CreateClient(); - var milestones = client.GetMilestone(1).All.ToArray(); - - Assert.AreEqual(2, milestones.Length, "Milestones count is invalid"); - Assert.IsTrue(milestones.Any(x => string.Equals(x.Title, "Milestone 1", StringComparison.Ordinal)), "Milestone 'Milestone 1' not found"); - Assert.IsTrue(milestones.Any(x => string.Equals(x.Title, "Milestone 2", StringComparison.Ordinal)), "Milestone 'Milestone 2' not found"); - } - - [Test] - public void Test_milestones_can_be_added_to_project() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true) - .BuildServer(); - - var client = server.CreateClient(); - client.GetMilestone(1).Create(new MilestoneCreate { Title = "Milestone 1" }); - var milestones = client.GetMilestone(1).All.ToArray(); - - Assert.AreEqual(1, milestones.Length, "Milestones count is invalid"); - Assert.AreEqual("Milestone 1", milestones[0].Title, "Milestone 'Milestone 1' not found"); - } - - [Test] - public void Test_milestones_can_be_edited_from_project() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true, configure: project => project - .WithMilestone("Milestone 1", id: 1)) - .BuildServer(); - - var client = server.CreateClient(); - client.GetMilestone(1).Update(1, new MilestoneUpdate { Title = "Milestone 2" }); - var milestones = client.GetMilestone(1).All.ToArray(); - - Assert.AreEqual(1, milestones.Length, "Milestones count is invalid"); - Assert.AreEqual("Milestone 2", milestones[0].Title, "Milestone 'Milestone 2' not found"); - } - - [Test] - public void Test_milestones_can_be_deleted_from_project() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true, configure: project => project - .WithMilestone("Milestone 1", id: 1)) - .BuildServer(); - - var client = server.CreateClient(); - client.GetMilestone(1).Delete(1); - var milestones = client.GetMilestone(1).All.ToArray(); - - Assert.AreEqual(0, milestones.Length, "Milestones count is invalid"); - } - - [Test] - public void Test_milestones_can_be_closed_and_activated_from_project() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", id: 1, addDefaultUserAsMaintainer: true, configure: project => project - .WithMilestone("Milestone 1", id: 1)) - .BuildServer(); - - var client = server.CreateClient(); - client.GetMilestone(1).Close(1); - var activeMilestones = client.GetMilestone(1).AllInState(Models.MilestoneState.active).ToArray(); - var closedMilestones = client.GetMilestone(1).AllInState(Models.MilestoneState.closed).ToArray(); - - Assert.AreEqual(0, activeMilestones.Length, "Active milestones count is invalid"); - Assert.AreEqual(1, closedMilestones.Length, "Closed milestones count is invalid"); - - client.GetMilestone(1).Activate(1); - activeMilestones = client.GetMilestone(1).AllInState(Models.MilestoneState.active).ToArray(); - closedMilestones = client.GetMilestone(1).AllInState(Models.MilestoneState.closed).ToArray(); - - Assert.AreEqual(1, activeMilestones.Length, "Active milestones count is invalid"); - Assert.AreEqual(0, closedMilestones.Length, "Closed milestones count is invalid"); - } - - [Test] - public void Test_projects_merge_request_can_be_found_from_milestone() - { - const int ProjectId = 1; - const int MilestoneId = 1; - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", id: ProjectId, addDefaultUserAsMaintainer: true, configure: project => project - .WithMilestone("Milestone 1", id: MilestoneId) - .WithMergeRequest("branch-01", title: "Merge request 1", milestone: "Milestone 1") - .WithMergeRequest("branch-02", title: "Merge request 2", milestone: "Milestone 1") - .WithMergeRequest("branch-03", title: "Merge request 3", milestone: "Milestone 2")) - .BuildServer(); - - var client = server.CreateClient(); - var mergeRequests = client.GetMilestone(ProjectId).GetMergeRequests(MilestoneId).ToArray(); - Assert.AreEqual(2, mergeRequests.Length, "Merge requests count is invalid"); - } - - [Test] - public void Test_groups_merge_request_can_be_found_from_milestone() - { - const int projectId = 1; - const int milestoneId = 1; - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithGroup("parentGroup", id: projectId, configure: group => group - .WithMilestone("Milestone 1", id: milestoneId)) - .WithGroup("subGroup1", 2, @namespace: "parentGroup") - .WithGroup("subGroup2", 3, @namespace: "parentGroup") - .WithProject("project1", @namespace: "parentGroup/subGroup1", addDefaultUserAsMaintainer: true, configure: project => project - .WithMergeRequest("branch-01", title: "Merge request 1", milestone: "Milestone 1") - .WithMergeRequest("branch-02", title: "Merge request 2", milestone: "Milestone 2")) - .WithProject("project2", @namespace: "parentGroup/subGroup2", addDefaultUserAsMaintainer: true, configure: project => project - .WithMergeRequest("branch-03", title: "Merge request 3", milestone: "Milestone 1")) - .BuildServer(); - - var client = server.CreateClient(); - var mergeRequests = client.GetGroupMilestone(projectId).GetMergeRequests(milestoneId).ToArray(); - Assert.AreEqual(2, mergeRequests.Length, "Merge requests count is invalid"); - } + const int projectId = 1; + const int milestoneId = 1; + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithGroup("parentGroup", id: projectId, configure: group => group + .WithMilestone("Milestone 1", id: milestoneId)) + .WithGroup("subGroup1", 2, @namespace: "parentGroup") + .WithGroup("subGroup2", 3, @namespace: "parentGroup") + .WithProject("project1", @namespace: "parentGroup/subGroup1", addDefaultUserAsMaintainer: true, configure: project => project + .WithMergeRequest("branch-01", title: "Merge request 1", milestone: "Milestone 1") + .WithMergeRequest("branch-02", title: "Merge request 2", milestone: "Milestone 2")) + .WithProject("project2", @namespace: "parentGroup/subGroup2", addDefaultUserAsMaintainer: true, configure: project => project + .WithMergeRequest("branch-03", title: "Merge request 3", milestone: "Milestone 1")) + .BuildServer(); + + var client = server.CreateClient(); + var mergeRequests = client.GetGroupMilestone(projectId).GetMergeRequests(milestoneId).ToArray(); + Assert.That(mergeRequests, Has.Length.EqualTo(2), "Merge requests count is invalid"); } } diff --git a/NGitLab.Mock.Tests/NGitLab.Mock.Tests.csproj b/NGitLab.Mock.Tests/NGitLab.Mock.Tests.csproj index d7e11b03..b48db878 100644 --- a/NGitLab.Mock.Tests/NGitLab.Mock.Tests.csproj +++ b/NGitLab.Mock.Tests/NGitLab.Mock.Tests.csproj @@ -1,14 +1,18 @@ - net6.0;net472 + net8.0;net472 false - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/NGitLab.Mock.Tests/PipelineTests.cs b/NGitLab.Mock.Tests/PipelineTests.cs index c602d8e0..fb3c7907 100644 --- a/NGitLab.Mock.Tests/PipelineTests.cs +++ b/NGitLab.Mock.Tests/PipelineTests.cs @@ -3,112 +3,111 @@ using NGitLab.Models; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class PipelineTests { - public class PipelineTests + [Test] + public async Task Test_pipelines() { - [Test] - public async Task Test_pipelines() - { - using var server = new GitLabServer(); - var user = server.Users.AddNew(); - var project = user.Namespace.Projects.AddNew(project => project.Visibility = VisibilityLevel.Internal); - var commit = project.Repository.Commit(user, "test"); + using var server = new GitLabServer(); + var user = server.Users.AddNew(); + var project = user.Namespace.Projects.AddNew(project => project.Visibility = VisibilityLevel.Internal); + var commit = project.Repository.Commit(user, "test"); - var pipeline = project.Pipelines.Add(commit.Sha, JobStatus.Success, user); - var job = pipeline.AddNewJob("Test_job", JobStatus.Success); - job.Trace = "This is a trace\nWith Multiple line"; - - var client = server.CreateClient(); - Assert.AreEqual(job.Trace, await client.GetJobs(project.Id).GetTraceAsync(job.Id)); - } + var pipeline = project.Pipelines.Add(commit.Sha, JobStatus.Success, user); + var job = pipeline.AddNewJob("Test_job", JobStatus.Success); + job.Trace = "This is a trace\nWith Multiple line"; - [Test] - public void Test_pipelines_testreport_summary() - { - using var server = new GitLabServer(); - var user = server.Users.AddNew(); - var project = user.Namespace.Projects.AddNew(project => project.Visibility = VisibilityLevel.Internal); - var commit = project.Repository.Commit(user, "test"); + var client = server.CreateClient(); + Assert.That(await client.GetJobs(project.Id).GetTraceAsync(job.Id), Is.EqualTo(job.Trace)); + } - var pipeline = project.Pipelines.Add(commit.Sha, JobStatus.Success, user); - pipeline.TestReportsSummary = new TestReportSummary - { - Total = new TestReportSummaryTotals - { - Time = 60, - Count = 1157, - Success = 1157, - Failed = 0, - Skipped = 0, - Error = 0, - }, - }; - - var client = server.CreateClient(); - var summary = client.GetPipelines(project.Id).GetTestReportsSummary(pipeline.Id); - Assert.AreEqual(60, summary.Total.Time); - Assert.AreEqual(1157, summary.Total.Count); - Assert.AreEqual(1157, summary.Total.Success); - Assert.AreEqual(0, summary.Total.Skipped); - Assert.AreEqual(0, summary.Total.Failed); - Assert.AreEqual(0, summary.Total.Error); - } + [Test] + public void Test_pipelines_testreport_summary() + { + using var server = new GitLabServer(); + var user = server.Users.AddNew(); + var project = user.Namespace.Projects.AddNew(project => project.Visibility = VisibilityLevel.Internal); + var commit = project.Repository.Commit(user, "test"); - [TestCase(false)] - [TestCase(true)] - public void Test_create_pipeline_with_branch_ref_sets_sha(bool addCommitAfterBranching) + var pipeline = project.Pipelines.Add(commit.Sha, JobStatus.Success, user); + pipeline.TestReportsSummary = new TestReportSummary { - using var server = new GitLabServer(); - var user = server.Users.AddNew(); - var project = user.Namespace.Projects.AddNew(project => project.Visibility = VisibilityLevel.Internal); - var commit = project.Repository.Commit(user, "test"); - - var branch = "my-branch"; - if (addCommitAfterBranching) + Total = new TestReportSummaryTotals { - project.Repository.CreateAndCheckoutBranch(branch); - var commit2 = project.Repository.Commit(user, "another test"); - Assert.AreNotEqual(commit.Sha, commit2.Sha); - commit = commit2; - } - else - { - project.Repository.CreateBranch(branch); - } + Time = 60, + Count = 1157, + Success = 1157, + Failed = 0, + Skipped = 0, + Error = 0, + }, + }; + + var client = server.CreateClient(); + var summary = client.GetPipelines(project.Id).GetTestReportsSummary(pipeline.Id); + Assert.That(summary.Total.Time, Is.EqualTo(60)); + Assert.That(summary.Total.Count, Is.EqualTo(1157)); + Assert.That(summary.Total.Success, Is.EqualTo(1157)); + Assert.That(summary.Total.Skipped, Is.EqualTo(0)); + Assert.That(summary.Total.Failed, Is.EqualTo(0)); + Assert.That(summary.Total.Error, Is.EqualTo(0)); + } - var pipeline = project.Pipelines.Add(branch, JobStatus.Success, user); + [TestCase(false)] + [TestCase(true)] + public void Test_create_pipeline_with_branch_ref_sets_sha(bool addCommitAfterBranching) + { + using var server = new GitLabServer(); + var user = server.Users.AddNew(); + var project = user.Namespace.Projects.AddNew(project => project.Visibility = VisibilityLevel.Internal); + var commit = project.Repository.Commit(user, "test"); - Assert.AreEqual(new Sha1(commit.Sha), pipeline.Sha); + var branch = "my-branch"; + if (addCommitAfterBranching) + { + project.Repository.CreateAndCheckoutBranch(branch); + var commit2 = project.Repository.Commit(user, "another test"); + Assert.That(commit2.Sha, Is.Not.EqualTo(commit.Sha)); + commit = commit2; } - - [Test] - public void Test_create_pipeline_with_tag_ref_sets_sha() + else { - using var server = new GitLabServer(); - var user = server.Users.AddNew(); - var project = user.Namespace.Projects.AddNew(project => project.Visibility = VisibilityLevel.Internal); - var commit = project.Repository.Commit(user, "test"); + project.Repository.CreateBranch(branch); + } - var tag = "my-tag"; - project.Repository.CreateTag(tag); + var pipeline = project.Pipelines.Add(branch, JobStatus.Success, user); - var pipeline = project.Pipelines.Add(tag, JobStatus.Success, user); + Assert.That(pipeline.Sha, Is.EqualTo(new Sha1(commit.Sha))); + } - Assert.AreEqual(new Sha1(commit.Sha), pipeline.Sha); - } + [Test] + public void Test_create_pipeline_with_tag_ref_sets_sha() + { + using var server = new GitLabServer(); + var user = server.Users.AddNew(); + var project = user.Namespace.Projects.AddNew(project => project.Visibility = VisibilityLevel.Internal); + var commit = project.Repository.Commit(user, "test"); - [Test] - public void Test_create_pipeline_with_invalid_ref_does_not_set_sha() - { - using var server = new GitLabServer(); - var user = server.Users.AddNew(); - var project = user.Namespace.Projects.AddNew(project => project.Visibility = VisibilityLevel.Internal); - var commit = project.Repository.Commit(user, "test"); + var tag = "my-tag"; + project.Repository.CreateTag(tag); - var pipeline = project.Pipelines.Add("invalid_ref", JobStatus.Success, user); + var pipeline = project.Pipelines.Add(tag, JobStatus.Success, user); - Assert.AreEqual(default(Sha1), pipeline.Sha); - } + Assert.That(pipeline.Sha, Is.EqualTo(new Sha1(commit.Sha))); + } + + [Test] + public void Test_create_pipeline_with_invalid_ref_does_not_set_sha() + { + using var server = new GitLabServer(); + var user = server.Users.AddNew(); + var project = user.Namespace.Projects.AddNew(project => project.Visibility = VisibilityLevel.Internal); + var commit = project.Repository.Commit(user, "test"); + + var pipeline = project.Pipelines.Add("invalid_ref", JobStatus.Success, user); + + Assert.That(pipeline.Sha, Is.EqualTo(default(Sha1))); } } diff --git a/NGitLab.Mock.Tests/ProjectsMockTests.cs b/NGitLab.Mock.Tests/ProjectsMockTests.cs index ff865f98..47b1c359 100644 --- a/NGitLab.Mock.Tests/ProjectsMockTests.cs +++ b/NGitLab.Mock.Tests/ProjectsMockTests.cs @@ -1,136 +1,388 @@ -using System.Globalization; +using System; +using System.Globalization; using System.IO; +using System.Net; +using System.Threading.Tasks; using FluentAssertions; using NGitLab.Mock.Config; using NGitLab.Models; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class ProjectsMockTests { - public class ProjectsMockTests + [Test] + public void WithProjectHelper_WhenPathNotSpecified_ItAutogeneratesPathFromName() { - [Test] - public void Test_projects_created_can_be_found() - { - using var server = new GitLabConfig() - .WithUser("Test", isDefault: true) - .WithProject("Test", @namespace: "testgroup", addDefaultUserAsMaintainer: true) - .BuildServer(); + // Arrange + var config = new GitLabConfig() + .WithUser("Foo", isDefault: true); - var client = server.CreateClient(); - var project = client.Projects["testgroup/Test"]; + // Act + using var server = config + .WithProject("TEST", configure: p => p.@Namespace = "Foo") + .BuildServer(); - Assert.IsNotNull(project); - Assert.AreEqual("Test", project.Name); - Assert.AreEqual("testgroup", project.Namespace.FullPath); - } + // Assert + var client = server.CreateClient(); + var project = client.Projects["Foo/Test"]; - [Test] - public void Test_project_can_be_cloned_by_default() - { - using var tempDir = TemporaryDirectory.Create(); - using var server = new GitLabConfig() - .WithUser("Test", isDefault: true) - .WithProject("Test", clonePath: tempDir.FullPath) - .BuildServer(); + Assert.That(project.Path, Is.EqualTo("test")); + } - Assert.IsTrue(Directory.Exists(tempDir.GetFullPath(".git"))); - } + [Test] + public void Test_projects_created_can_be_found() + { + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProject("Test", @namespace: "testgroup", addDefaultUserAsMaintainer: true) + .BuildServer(); - [Test] - public void Test_projects_created_url_ends_with_namespace_and_name() - { - using var server = new GitLabConfig() - .WithUser("Test", isDefault: true) - .WithProject("Test", @namespace: "testgroup", addDefaultUserAsMaintainer: true) - .BuildServer(); - - var client = server.CreateClient(); - var project = client.Projects["testgroup/Test"]; - - Assert.IsNotNull(project); - StringAssert.EndsWith($"testgroup{Path.DirectorySeparatorChar}test", project.SshUrl); - StringAssert.EndsWith($"testgroup{Path.DirectorySeparatorChar}test", project.HttpUrl); - StringAssert.EndsWith("testgroup/test", project.WebUrl); - } - - [Test] - public void Test_get_languages() - { - using var server = new GitLabServer(); - var user = server.Users.AddNew(); - var project = user.Namespace.Projects.AddNew(); - - var client = server.CreateClient(user); - Assert.IsEmpty(client.Projects.GetLanguages(project.Id.ToString(CultureInfo.InvariantCulture))); - - project.Repository.Commit(user, "dummy", new[] { File.CreateFromText("test.cs", "dummy"), File.CreateFromText("test.js", "dummy") }); - var languages = client.Projects.GetLanguages(project.Id.ToString(CultureInfo.InvariantCulture)); - Assert.AreEqual(2, languages.Count); - Assert.AreEqual(0.5d, languages["C#"]); - Assert.AreEqual(0.5d, languages["JavaScript"]); - } - - [Test] - public void Test_empty_repo() - { - using var server = new GitLabServer(); - var user = server.Users.AddNew(); - var project = user.Namespace.Projects.AddNew(); + var client = server.CreateClient(); + var project = client.Projects["testgroup/Test"]; + + Assert.That(project, Is.Not.Null); + Assert.That(project.Name, Is.EqualTo("Test")); + Assert.That(project.Namespace.FullPath, Is.EqualTo("testgroup")); + } + + [Test] + public void Test_project_can_be_cloned_by_default() + { + using var tempDir = TemporaryDirectory.Create(); + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProject("Test", clonePath: tempDir.FullPath) + .BuildServer(); + + Assert.That(Directory.Exists(tempDir.GetFullPath(".git")), Is.True); + } + + [Test] + public void Test_project_with_submodules() + { + using var tempDir = TemporaryDirectory.Create(); + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProject("ModuleA", configure: x => x.WithCommit(configure: c => c.WithFile("A.txt"))) + .WithProject("ModuleB", configure: x => x.WithCommit(configure: c => c.WithFile("B.txt"))) + .WithProject("Test", clonePath: tempDir.FullPath, configure: x => + x.WithCommit("Init", configure: c + => c.WithSubModule("ModuleA") + .WithSubModule("ModuleB"))) + .BuildServer(); + + Assert.That(Directory.Exists(tempDir.GetFullPath(".git")), Is.True); + Assert.That(System.IO.File.Exists(tempDir.GetFullPath("ModuleA/.git")), Is.True); + Assert.That(System.IO.File.Exists(tempDir.GetFullPath("ModuleA/A.txt")), Is.True); + Assert.That(System.IO.File.Exists(tempDir.GetFullPath("ModuleB/.git")), Is.True); + Assert.That(System.IO.File.Exists(tempDir.GetFullPath("ModuleB/B.txt")), Is.True); + } + + [Test] + public void Test_project_with_nested_submodules() + { + using var tempDir = TemporaryDirectory.Create(); + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProject("ModuleA", configure: x => x.WithCommit(configure: c => c.WithFile("A.txt"))) + .WithProject("ModuleB", configure: x => x.WithCommit(configure: c + => c.WithFile("B.txt") + .WithSubModule("ModuleA"))) + .WithProject("Test", clonePath: tempDir.FullPath, configure: x => + x.WithCommit(configure: c + => c.WithSubModule("ModuleB"))) + .BuildServer(); + + Assert.That(Directory.Exists(tempDir.GetFullPath(".git")), Is.True); + Assert.That(System.IO.File.Exists(tempDir.GetFullPath("ModuleB/.git")), Is.True); + Assert.That(System.IO.File.Exists(tempDir.GetFullPath("ModuleB/B.txt")), Is.True); + Assert.That(System.IO.File.Exists(tempDir.GetFullPath("ModuleB/ModuleA/.git")), Is.True); + Assert.That(System.IO.File.Exists(tempDir.GetFullPath("ModuleB/ModuleA/A.txt")), Is.True); + } + + [Test] + public void Test_projects_created_url_ends_with_namespace_and_name() + { + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProject("Test", @namespace: "testgroup", addDefaultUserAsMaintainer: true) + .BuildServer(); + + var client = server.CreateClient(); + var project = client.Projects["testgroup/Test"]; + + Assert.That(project, Is.Not.Null); + Assert.That(project.SshUrl, Does.EndWith($"testgroup{Path.DirectorySeparatorChar}test")); + Assert.That(project.HttpUrl, Does.EndWith($"testgroup{Path.DirectorySeparatorChar}test")); + Assert.That(project.WebUrl, Does.EndWith("testgroup/test")); + } + + [Test] + public void Test_get_languages() + { + using var server = new GitLabServer(); + var user = server.Users.AddNew(); + var project = user.Namespace.Projects.AddNew(); + + var client = server.CreateClient(user); + Assert.That(client.Projects.GetLanguages(project.Id.ToString(CultureInfo.InvariantCulture)), Is.Empty); + + project.Repository.Commit(user, "dummy", new[] { File.CreateFromText("test.cs", "dummy"), File.CreateFromText("test.js", "dummy") }); + var languages = client.Projects.GetLanguages(project.Id.ToString(CultureInfo.InvariantCulture)); + Assert.That(languages, Has.Count.EqualTo(2)); + Assert.That(languages["C#"], Is.EqualTo(0.5d)); + Assert.That(languages["JavaScript"], Is.EqualTo(0.5d)); + } + + [Test] + public void Test_empty_repo() + { + using var server = new GitLabServer(); + var user = server.Users.AddNew(); + var project = user.Namespace.Projects.AddNew(); + + Assert.That(project.ToClientProject(user).EmptyRepo, Is.True); + + project.Repository.Commit(user, "dummy"); + Assert.That(project.ToClientProject(user).EmptyRepo, Is.False); + } + + [Test] + public void Test_project_permissions_maintainer_with_project_access() + { + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProject("Test", @namespace: "testgroup", addDefaultUserAsMaintainer: true) + .BuildServer(); + + var client = server.CreateClient(); + var project = client.Projects["testgroup/Test"]; + + project.Should().NotBeNull(); + project.Permissions.GroupAccess.Should().BeNull(); + project.Permissions.ProjectAccess.AccessLevel.Should().Be(AccessLevel.Maintainer); + } + + [Test] + public void Test_project_permissions_with_no_access() + { + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProject("Test", @namespace: "testgroup") + .BuildServer(); + + var client = server.CreateClient(); + var project = client.Projects["testgroup/Test"]; + + project.Should().NotBeNull(); + project.Permissions.GroupAccess.Should().BeNull(); + project.Permissions.ProjectAccess.Should().BeNull(); + } + + [Test] + public void Test_project_permissions_with_group_access() + { + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithGroup("testgroup", addDefaultUserAsMaintainer: true) + .WithProject("Test", @namespace: "testgroup") + .BuildServer(); + + var client = server.CreateClient(); + var project = client.Projects["testgroup/Test"]; + + project.Should().NotBeNull(); + project.Permissions.ProjectAccess.Should().BeNull(); + project.Permissions.GroupAccess.AccessLevel.Should().Be(AccessLevel.Maintainer); + } - Assert.IsTrue(project.ToClientProject(user).EmptyRepo); + [Test] + public async Task CreateAsync_WhenMockCreatedWithSupportedOptions_TheyAreAvailableInModel() + { + // Arrange + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithGroupOfFullPath("my-group", name: "MyGroup", addDefaultUserAsMaintainer: true) + .BuildServer(); - project.Repository.Commit(user, "dummy"); - Assert.IsFalse(project.ToClientProject(user).EmptyRepo); - } + var projectClient = server.CreateClient().Projects; - [Test] - public void Test_project_permissions_maintainer_with_project_access() + var expected = new ProjectCreate() { - using var server = new GitLabConfig() - .WithUser("Test", isDefault: true) - .WithProject("Test", @namespace: "testgroup", addDefaultUserAsMaintainer: true) - .BuildServer(); + Name = "MyProject", + Path = "my-project", + NamespaceId = "my-group", + Description = "Description", + DefaultBranch = "foo", + InitializeWithReadme = true, + Topics = new() { "t1", "t2" }, + BuildTimeout = (int)TimeSpan.FromMinutes(15).TotalSeconds, + }; + + // Act + var actual = await projectClient.CreateAsync(expected); + + // Assert + actual.Name.Should().Be(expected.Name); + actual.Path.Should().Be(expected.Path); + actual.Description.Should().Be(expected.Description); + actual.Namespace.FullPath.Should().Be(expected.NamespaceId); + actual.NameWithNamespace.Should().Be($"MyGroup / {expected.Name}"); + actual.PathWithNamespace.Should().Be($"my-group/{expected.Path}"); + actual.DefaultBranch.Should().Be(expected.DefaultBranch); + actual.Topics.Should().BeEquivalentTo(expected.Topics); + actual.BuildTimeout.Should().Be(expected.BuildTimeout); + } - var client = server.CreateClient(); - var project = client.Projects["testgroup/Test"]; + [Test] + public async Task CreateAsync_WhenInitializeWithReadmeIsFalse_ItIgnoresDefaultBranch() + { + // Arrange + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .BuildServer(); - project.Should().NotBeNull(); - project.Permissions.GroupAccess.Should().BeNull(); - project.Permissions.ProjectAccess.AccessLevel.Should().Be(AccessLevel.Maintainer); - } + var projectClient = server.CreateClient().Projects; - [Test] - public void Test_project_permissions_with_no_access() + var expected = new ProjectCreate() { - using var server = new GitLabConfig() - .WithUser("Test", isDefault: true) - .WithProject("Test", @namespace: "testgroup") - .BuildServer(); + Name = "MyProject", + DefaultBranch = "foo", + InitializeWithReadme = false, + }; + + // Act + var actual = await projectClient.CreateAsync(expected); + + // Assert + actual.Name.Should().Be(expected.Name); + actual.DefaultBranch.Should().NotBe(expected.DefaultBranch); + } + + [Test] + public void CreateAsync_WhenProjectPathAlreadyExists_ItThrows() + { + // Arrange + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProjectOfFullPath("Test/duplicate") + .BuildServer(); + + var projectClient = server.CreateClient().Projects; + + // Act + var ex = Assert.CatchAsync(() => + projectClient.CreateAsync(new() + { + Path = "DUPLICATE", // GitLab path is case-INsensitive + Name = "project2", + })); + + // Assert + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + Assert.That(ex.ErrorMessage, Contains.Substring("has already been taken")); + } + + [Test] + public void CreateAsync_WhenProjectNameAlreadyExists_ItThrows() + { + // Arrange + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProjectOfFullPath("Test/duplicate") + .BuildServer(); + + var projectClient = server.CreateClient().Projects; - var client = server.CreateClient(); - var project = client.Projects["testgroup/Test"]; + // Act + var ex = Assert.ThrowsAsync(() => + projectClient.CreateAsync(new() + { + Path = "project2", + Name = "duplicate", // GitLab name is case-Sensitive + })); - project.Should().NotBeNull(); - project.Permissions.GroupAccess.Should().BeNull(); - project.Permissions.ProjectAccess.Should().BeNull(); - } + // Assert + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + Assert.That(ex.ErrorMessage, Contains.Substring("has already been taken")); + } + + [Test] + public async Task CreateAsync_WhenProjectNameOfDifferentCaseAlreadyExists_ItWorks() + { + // Arrange + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProjectOfFullPath("Test/not_duplicate") + .BuildServer(); - [Test] - public void Test_project_permissions_with_group_access() + var projectClient = server.CreateClient().Projects; + + // Act + var newProject = await projectClient.CreateAsync(new() { - using var server = new GitLabConfig() - .WithUser("Test", isDefault: true) - .WithGroup("testgroup", addDefaultUserAsMaintainer: true) - .WithProject("Test", @namespace: "testgroup") - .BuildServer(); - - var client = server.CreateClient(); - var project = client.Projects["testgroup/Test"]; - - project.Should().NotBeNull(); - project.Permissions.ProjectAccess.Should().BeNull(); - project.Permissions.GroupAccess.AccessLevel.Should().Be(AccessLevel.Maintainer); - } + Path = "project2", + Name = "NOT_DUPLICATE", // GitLab name is case-Sensitive + }); + + // Assert + Assert.That(newProject.Name, Is.EqualTo("NOT_DUPLICATE")); + } + + [Test] + public void UpdateAsync_WhenProjectNotFound_ItThrows() + { + // Arrange + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .BuildServer(); + + var projectClient = server.CreateClient().Projects; + + // Act + var ex = Assert.CatchAsync(() => + projectClient.UpdateAsync(int.MaxValue, new() + { + Visibility = VisibilityLevel.Private, + })); + + // Assert + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task DeleteAsync_WhenProjectExists_ItIsDeleted() + { + var projectFullPath = $"Test/{nameof(DeleteAsync_WhenProjectExists_ItIsDeleted)}"; + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProjectOfFullPath(projectFullPath) + .BuildServer(); + + var projectClient = server.CreateClient().Projects; + + // Act + await projectClient.DeleteAsync(projectFullPath); + + // Assert + Assert.CatchAsync(() => projectClient.GetAsync(projectFullPath)); + } + + [Test] + public void DeleteAsync_WhenProjectNotFound_ItThrows() + { + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .BuildServer(); + + var projectClient = server.CreateClient().Projects; + + // Act + var ex = Assert.CatchAsync(() => projectClient.DeleteAsync(int.MaxValue)); + + // Assert + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); } } diff --git a/NGitLab.Mock.Tests/ReleasesMockTests.cs b/NGitLab.Mock.Tests/ReleasesMockTests.cs index 8bf7a803..7a351399 100644 --- a/NGitLab.Mock.Tests/ReleasesMockTests.cs +++ b/NGitLab.Mock.Tests/ReleasesMockTests.cs @@ -3,98 +3,97 @@ using NGitLab.Mock.Config; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class ReleasesMockTests { - public class ReleasesMockTests + [Test] + public void Test_release() { - [Test] - public void Test_release() - { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", configure: project => project - .WithCommit("Changes with tag", tags: new[] { "1.2.3" }) - .WithRelease("user1", "1.2.3")) - .BuildServer(); + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", configure: project => project + .WithCommit("Changes with tag", tags: new[] { "1.2.3" }) + .WithRelease("user1", "1.2.3")) + .BuildServer(); - var client = server.CreateClient("user1"); - var project = client.Projects.Visible.First(); - var releaseClient = client.GetReleases(project.Id); - var singleRelease = releaseClient.All.SingleOrDefault(); + var client = server.CreateClient("user1"); + var project = client.Projects.Visible.First(); + var releaseClient = client.GetReleases(project.Id); + var singleRelease = releaseClient.All.SingleOrDefault(); - Assert.IsNotNull(singleRelease); - Assert.AreEqual("1.2.3", singleRelease.TagName); - Assert.AreEqual($"{project.WebUrl}/-/releases/1.2.3", singleRelease.Links.Self); - } + Assert.That(singleRelease, Is.Not.Null); + Assert.That(singleRelease.TagName, Is.EqualTo("1.2.3")); + Assert.That(singleRelease.Links.Self, Is.EqualTo($"{project.WebUrl}/-/releases/1.2.3")); + } + + [Test] + public void Test_release_page() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", configure: project => project + .WithCommit("Changes with tag", tags: new[] { "1.2.3", "1.2.4" }) + .WithRelease("user1", "1.2.3", createdAt: DateTime.UtcNow.AddHours(-2), releasedAt: DateTime.UtcNow.AddHours(-2)) + .WithRelease("user1", "1.2.4", createdAt: DateTime.UtcNow.AddHours(-1), releasedAt: DateTime.UtcNow.AddHours(-1))) + .BuildServer(); - [Test] - public void Test_release_page() + var client = server.CreateClient("user1"); + var project = client.Projects.Visible.First(); + var releaseClient = client.GetReleases(project.Id); + var firstRelease = releaseClient.GetAsync(new Models.ReleaseQuery { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", configure: project => project - .WithCommit("Changes with tag", tags: new[] { "1.2.3", "1.2.4" }) - .WithRelease("user1", "1.2.3", createdAt: DateTime.UtcNow.AddHours(-2), releasedAt: DateTime.UtcNow.AddHours(-2)) - .WithRelease("user1", "1.2.4", createdAt: DateTime.UtcNow.AddHours(-1), releasedAt: DateTime.UtcNow.AddHours(-1))) - .BuildServer(); + PerPage = 1, + Page = 2, + }).SingleOrDefault(); - var client = server.CreateClient("user1"); - var project = client.Projects.Visible.First(); - var releaseClient = client.GetReleases(project.Id); - var firstRelease = releaseClient.GetAsync(new Models.ReleaseQuery - { - PerPage = 1, - Page = 2, - }).SingleOrDefault(); + Assert.That(firstRelease, Is.Not.Null); + Assert.That(firstRelease.TagName, Is.EqualTo("1.2.3")); + } - Assert.IsNotNull(firstRelease); - Assert.AreEqual("1.2.3", firstRelease.TagName); - } + [Test] + public void Test_release_sort() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", configure: project => project + .WithCommit("Changes with tag", tags: new[] { "1.2.3", "1.2.4" }) + .WithRelease("user1", "1.2.3", createdAt: DateTime.UtcNow.AddHours(-2), releasedAt: DateTime.UtcNow.AddHours(-2)) + .WithRelease("user1", "1.2.4", createdAt: DateTime.UtcNow.AddHours(-1), releasedAt: DateTime.UtcNow.AddHours(-1))) + .BuildServer(); - [Test] - public void Test_release_sort() + var client = server.CreateClient("user1"); + var project = client.Projects.Visible.First(); + var releaseClient = client.GetReleases(project.Id); + var firstRelease = releaseClient.GetAsync(new Models.ReleaseQuery { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", configure: project => project - .WithCommit("Changes with tag", tags: new[] { "1.2.3", "1.2.4" }) - .WithRelease("user1", "1.2.3", createdAt: DateTime.UtcNow.AddHours(-2), releasedAt: DateTime.UtcNow.AddHours(-2)) - .WithRelease("user1", "1.2.4", createdAt: DateTime.UtcNow.AddHours(-1), releasedAt: DateTime.UtcNow.AddHours(-1))) - .BuildServer(); + Sort = "asc", + }).First(); - var client = server.CreateClient("user1"); - var project = client.Projects.Visible.First(); - var releaseClient = client.GetReleases(project.Id); - var firstRelease = releaseClient.GetAsync(new Models.ReleaseQuery - { - Sort = "asc", - }).First(); + Assert.That(firstRelease, Is.Not.Null); + Assert.That(firstRelease.TagName, Is.EqualTo("1.2.3")); + } - Assert.IsNotNull(firstRelease); - Assert.AreEqual("1.2.3", firstRelease.TagName); - } + [Test] + public void Test_release_orderBy() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("Test", configure: project => project + .WithCommit("Changes with tag", tags: new[] { "1.2.3", "1.2.4" }) + .WithRelease("user1", "1.2.3", createdAt: DateTime.UtcNow.AddHours(-1), releasedAt: DateTime.UtcNow.AddHours(-1)) + .WithRelease("user1", "1.2.4", createdAt: DateTime.UtcNow.AddHours(-2), releasedAt: DateTime.UtcNow.AddHours(-2))) + .BuildServer(); - [Test] - public void Test_release_orderBy() + var client = server.CreateClient("user1"); + var project = client.Projects.Visible.First(); + var releaseClient = client.GetReleases(project.Id); + var firstRelease = releaseClient.GetAsync(new Models.ReleaseQuery { - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("Test", configure: project => project - .WithCommit("Changes with tag", tags: new[] { "1.2.3", "1.2.4" }) - .WithRelease("user1", "1.2.3", createdAt: DateTime.UtcNow.AddHours(-1), releasedAt: DateTime.UtcNow.AddHours(-1)) - .WithRelease("user1", "1.2.4", createdAt: DateTime.UtcNow.AddHours(-2), releasedAt: DateTime.UtcNow.AddHours(-2))) - .BuildServer(); - - var client = server.CreateClient("user1"); - var project = client.Projects.Visible.First(); - var releaseClient = client.GetReleases(project.Id); - var firstRelease = releaseClient.GetAsync(new Models.ReleaseQuery - { - OrderBy = "created_at", - }).First(); + OrderBy = "created_at", + }).First(); - Assert.IsNotNull(firstRelease); - Assert.AreEqual("1.2.3", firstRelease.TagName); - } + Assert.That(firstRelease, Is.Not.Null); + Assert.That(firstRelease.TagName, Is.EqualTo("1.2.3")); } } diff --git a/NGitLab.Mock.Tests/TagTests.cs b/NGitLab.Mock.Tests/TagTests.cs index 30e1c985..54f95d51 100644 --- a/NGitLab.Mock.Tests/TagTests.cs +++ b/NGitLab.Mock.Tests/TagTests.cs @@ -3,30 +3,29 @@ using NGitLab.Mock.Config; using NUnit.Framework; -namespace NGitLab.Mock.Tests +namespace NGitLab.Mock.Tests; + +public class TagTests { - public class TagTests + [Test] + public async Task GetTagAsync() { - [Test] - public async Task GetTagAsync() - { - // Arrange - using var server = new GitLabConfig() - .WithUser("user1", isDefault: true) - .WithProject("test-project", id: 1, addDefaultUserAsMaintainer: true, configure: project => project - .WithCommit("Initial commit") - .WithCommit("Changes with tag", tags: new[] { "1.0.0" })) - .BuildServer(); + // Arrange + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("test-project", id: 1, addDefaultUserAsMaintainer: true, configure: project => project + .WithCommit("Initial commit") + .WithCommit("Changes with tag", tags: new[] { "1.0.0" })) + .BuildServer(); - var client = server.CreateClient(); - var tagClient = client.GetRepository(1).Tags; + var client = server.CreateClient(); + var tagClient = client.GetRepository(1).Tags; - // Act/Assert - var tag = await tagClient.GetByNameAsync("1.0.0"); - Assert.AreEqual("1.0.0", tag.Name); + // Act/Assert + var tag = await tagClient.GetByNameAsync("1.0.0"); + Assert.That(tag.Name, Is.EqualTo("1.0.0")); - var ex = Assert.ThrowsAsync(() => tagClient.GetByNameAsync("1.0.1")); - Assert.AreEqual(HttpStatusCode.NotFound, ex.StatusCode); - } + var ex = Assert.ThrowsAsync(() => tagClient.GetByNameAsync("1.0.1")); + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); } } diff --git a/NGitLab.Mock/Badge.cs b/NGitLab.Mock/Badge.cs index de99da4e..2d64f307 100644 --- a/NGitLab.Mock/Badge.cs +++ b/NGitLab.Mock/Badge.cs @@ -1,32 +1,31 @@ using NGitLab.Models; -namespace NGitLab.Mock +namespace NGitLab.Mock; + +public sealed class Badge : GitLabObject { - public sealed class Badge : GitLabObject - { - public int Id { get; set; } + public int Id { get; set; } - public string LinkUrl { get; set; } + public string LinkUrl { get; set; } - public string ImageUrl { get; set; } + public string ImageUrl { get; set; } - public string RenderedLinkUrl { get; set; } + public string RenderedLinkUrl { get; set; } - public string RenderedImageUrl { get; set; } + public string RenderedImageUrl { get; set; } - public BadgeKind Kind => Parent is Project ? BadgeKind.Project : BadgeKind.Group; + public BadgeKind Kind => Parent is Project ? BadgeKind.Project : BadgeKind.Group; - public Models.Badge ToBadgeModel() + public Models.Badge ToBadgeModel() + { + return new Models.Badge { - return new Models.Badge - { - Id = Id, - ImageUrl = ImageUrl, - Kind = Kind, - LinkUrl = LinkUrl, - RenderedImageUrl = RenderedImageUrl, - RenderedLinkUrl = RenderedLinkUrl, - }; - } + Id = Id, + ImageUrl = ImageUrl, + Kind = Kind, + LinkUrl = LinkUrl, + RenderedImageUrl = RenderedImageUrl, + RenderedLinkUrl = RenderedLinkUrl, + }; } } diff --git a/NGitLab.Mock/BadgeCollection.cs b/NGitLab.Mock/BadgeCollection.cs index b3917c75..5ecc312a 100644 --- a/NGitLab.Mock/BadgeCollection.cs +++ b/NGitLab.Mock/BadgeCollection.cs @@ -1,48 +1,47 @@ using System; using System.Linq; -namespace NGitLab.Mock +namespace NGitLab.Mock; + +public sealed class BadgeCollection : Collection { - public sealed class BadgeCollection : Collection + public BadgeCollection(GitLabObject parent) + : base(parent) { - public BadgeCollection(GitLabObject parent) - : base(parent) - { - } + } + + public Badge GetById(int id) + { + return this.FirstOrDefault(badge => badge.Id == id); + } - public Badge GetById(int id) + public override void Add(Badge item) + { + if (item == null) + throw new ArgumentNullException(nameof(item)); + + if (item.Id == default) { - return this.FirstOrDefault(badge => badge.Id == id); + item.Id = Server.GetNewBadgeId(); } - - public override void Add(Badge item) + else if (GetById(item.Id) != null) { - if (item == null) - throw new ArgumentNullException(nameof(item)); - - if (item.Id == default) - { - item.Id = Server.GetNewBadgeId(); - } - else if (GetById(item.Id) != null) - { - throw new GitLabException("Badge already exists."); - } - - base.Add(item); + throw new GitLabException("Badge already exists."); } - public Badge Add(string linkUrl, string imageUrl) + base.Add(item); + } + + public Badge Add(string linkUrl, string imageUrl) + { + var badge = new Badge { - var badge = new Badge - { - LinkUrl = linkUrl, - ImageUrl = imageUrl, - }; + LinkUrl = linkUrl, + ImageUrl = imageUrl, + }; - Add(badge); + Add(badge); - return badge; - } + return badge; } } diff --git a/NGitLab.Mock/Change.cs b/NGitLab.Mock/Change.cs index 7ce4eb20..cecac488 100644 --- a/NGitLab.Mock/Change.cs +++ b/NGitLab.Mock/Change.cs @@ -1,36 +1,35 @@ -namespace NGitLab.Mock +namespace NGitLab.Mock; + +public class Change : GitLabObject { - public class Change : GitLabObject - { - public string OldPath { get; set; } + public string OldPath { get; set; } - public string NewPath { get; set; } + public string NewPath { get; set; } - public long AMode { get; set; } + public long AMode { get; set; } - public long BMode { get; set; } + public long BMode { get; set; } - public bool NewFile { get; set; } + public bool NewFile { get; set; } - public bool RenamedFile { get; set; } + public bool RenamedFile { get; set; } - public bool DeletedFile { get; set; } + public bool DeletedFile { get; set; } - public string Diff { get; set; } + public string Diff { get; set; } - public Models.Change ToChange() + public Models.Change ToChange() + { + return new () { - return new () - { - Diff = Diff, - AMode = AMode, - BMode = BMode, - DeletedFile = DeletedFile, - NewFile = NewFile, - NewPath = NewPath, - OldPath = OldPath, - RenamedFile = RenamedFile, - }; - } + Diff = Diff, + AMode = AMode, + BMode = BMode, + DeletedFile = DeletedFile, + NewFile = NewFile, + NewPath = NewPath, + OldPath = OldPath, + RenamedFile = RenamedFile, + }; } } diff --git a/NGitLab.Mock/Clients/AdvancedSearchClient.cs b/NGitLab.Mock/Clients/AdvancedSearchClient.cs index 3c817d56..ad3834d4 100644 --- a/NGitLab.Mock/Clients/AdvancedSearchClient.cs +++ b/NGitLab.Mock/Clients/AdvancedSearchClient.cs @@ -1,20 +1,19 @@ using System; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class AdvancedSearchClient : ISearchClient { - internal sealed class AdvancedSearchClient : ISearchClient - { - private readonly ClientContext _context; + private readonly ClientContext _context; - public AdvancedSearchClient(ClientContext context) - { - _context = context; - } + public AdvancedSearchClient(ClientContext context) + { + _context = context; + } - public GitLabCollectionResponse GetBlobsAsync(SearchQuery query) - { - throw new NotImplementedException(); - } + public GitLabCollectionResponse GetBlobsAsync(SearchQuery query) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/BranchClient.cs b/NGitLab.Mock/Clients/BranchClient.cs index 194e31c6..e8cc8654 100644 --- a/NGitLab.Mock/Clients/BranchClient.cs +++ b/NGitLab.Mock/Clients/BranchClient.cs @@ -4,121 +4,120 @@ using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class BranchClient : ClientBase, IBranchClient { - internal sealed class BranchClient : ClientBase, IBranchClient + private readonly int _projectId; + + public BranchClient(ClientContext context, int projectId) + : base(context) { - private readonly int _projectId; + _projectId = projectId; + } - public BranchClient(ClientContext context, int projectId) - : base(context) + public IEnumerable Search(string search) + { + Func filterBranch; + switch (search) { - _projectId = projectId; + case null: + case "": + filterBranch = _ => true; + break; + + case not null when search[0] == '^' && search[search.Length - 1] == '$': + search = search.Substring(1, search.Length - 1 - 1); + filterBranch = branch => branch.Equals(search, StringComparison.OrdinalIgnoreCase); + break; + + case not null when search[0] == '^': + search = search.Substring(1); + filterBranch = branch => branch.StartsWith(search, StringComparison.OrdinalIgnoreCase); + break; + + case not null when search[search.Length - 1] == '$': + search = search.Substring(0, search.Length - 1); + filterBranch = branch => branch.EndsWith(search, StringComparison.OrdinalIgnoreCase); + break; + + default: + filterBranch = branch => branch.Contains(search, StringComparison.OrdinalIgnoreCase); + break; } - public IEnumerable Search(string search) + using (Context.BeginOperationScope()) { - Func filterBranch; - switch (search) - { - case null: - case "": - filterBranch = _ => true; - break; - - case not null when search[0] == '^' && search[search.Length - 1] == '$': - search = search.Substring(1, search.Length - 1 - 1); - filterBranch = branch => branch.Equals(search, StringComparison.OrdinalIgnoreCase); - break; - - case not null when search[0] == '^': - search = search.Substring(1); - filterBranch = branch => branch.StartsWith(search, StringComparison.OrdinalIgnoreCase); - break; - - case not null when search[search.Length - 1] == '$': - search = search.Substring(0, search.Length - 1); - filterBranch = branch => branch.EndsWith(search, StringComparison.OrdinalIgnoreCase); - break; - - default: - filterBranch = branch => branch.Contains(search, StringComparison.OrdinalIgnoreCase); - break; - } - - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - return project.Repository.GetAllBranches() - .Where(branch => filterBranch(branch.FriendlyName)) - .Select(branch => branch.ToBranchClient(project)); - } + var project = GetProject(_projectId, ProjectPermission.View); + return project.Repository.GetAllBranches() + .Where(branch => filterBranch(branch.FriendlyName)) + .Select(branch => branch.ToBranchClient(project)); } + } - public GitLabCollectionResponse SearchAsync(string search) - { - return GitLabCollectionResponse.Create(Search(search)); - } + public GitLabCollectionResponse SearchAsync(string search) + { + return GitLabCollectionResponse.Create(Search(search)); + } - public Branch this[string name] + public Branch this[string name] + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var branch = project.Repository.GetBranch(name); - if (branch == null) - throw new GitLabNotFoundException(); - - return branch.ToBranchClient(project); - } - } - } + var project = GetProject(_projectId, ProjectPermission.View); + var branch = project.Repository.GetBranch(name); + if (branch == null) + throw new GitLabNotFoundException(); - public IEnumerable All - { - get - { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - return project.Repository.GetAllBranches().Select(branch => branch.ToBranchClient(project)).ToList(); - } + return branch.ToBranchClient(project); } } + } - public Branch Create(BranchCreate branch) + public IEnumerable All + { + get { using (Context.BeginOperationScope()) { - var project = GetProject(_projectId, ProjectPermission.Contribute); - if (branch.Ref != null) - { - return project.Repository.CreateBranch(branch.Name, branch.Ref).ToBranchClient(project); - } - - return project.Repository.CreateBranch(branch.Name).ToBranchClient(project); + var project = GetProject(_projectId, ProjectPermission.View); + return project.Repository.GetAllBranches().Select(branch => branch.ToBranchClient(project)).ToList(); } } + } - public void Delete(string name) + public Branch Create(BranchCreate branch) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var project = GetProject(_projectId, ProjectPermission.Contribute); + if (branch.Ref != null) { - var project = GetProject(_projectId, ProjectPermission.Contribute); - project.Repository.RemoveBranch(name); + return project.Repository.CreateBranch(branch.Name, branch.Ref).ToBranchClient(project); } - } - public Branch Protect(string name) - { - throw new NotImplementedException(); + return project.Repository.CreateBranch(branch.Name).ToBranchClient(project); } + } - public Branch Unprotect(string name) + public void Delete(string name) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var project = GetProject(_projectId, ProjectPermission.Contribute); + project.Repository.RemoveBranch(name); } } + + public Branch Protect(string name) + { + throw new NotImplementedException(); + } + + public Branch Unprotect(string name) + { + throw new NotImplementedException(); + } } diff --git a/NGitLab.Mock/Clients/ClientBase.cs b/NGitLab.Mock/Clients/ClientBase.cs index da9e6b2e..80c72af7 100644 --- a/NGitLab.Mock/Clients/ClientBase.cs +++ b/NGitLab.Mock/Clients/ClientBase.cs @@ -1,174 +1,178 @@ using System; using System.Net; +using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal abstract class ClientBase { - internal abstract class ClientBase + protected ClientBase(ClientContext context) { - protected ClientBase(ClientContext context) - { - Context = context; - } - - protected GitLabServer Server => Context.Server; + Context = context; + } - protected ClientContext Context { get; } + protected GitLabServer Server => Context.Server; - protected Group GetGroup(object id, GroupPermission permissions) - { - if (id is null) - throw new ArgumentNullException(nameof(id)); + protected ClientContext Context { get; } - if (Context.User.State == UserState.blocked && permissions != GroupPermission.View) - { - throw new GitLabException("403 Forbidden - Your account has been blocked.") - { - StatusCode = HttpStatusCode.Forbidden, - }; - } + protected Group GetGroup(object id, GroupPermission permissions) + { + if (id is null) + throw new ArgumentNullException(nameof(id)); - var group = id switch + if (Context.User.State == UserState.blocked && permissions != GroupPermission.View) + { + throw new GitLabException("403 Forbidden - Your account has been blocked.") { - int idInt => Server.AllGroups.FindGroupById(idInt), - string idStr => Server.AllGroups.FindGroup(idStr), - _ => throw new ArgumentException($"Id of type '{id.GetType()}' is not supported"), + StatusCode = HttpStatusCode.Forbidden, }; + } - if (group == null || !group.CanUserViewGroup(Context.User)) - throw new GitLabNotFoundException("Group does not exist or user doesn't have permission to view it"); - - switch (permissions) - { - case GroupPermission.View: - // Already checked before - break; - - case GroupPermission.Edit: - if (!group.CanUserEditGroup(Context.User)) - throw new GitLabForbiddenException($"User '{Context.User.Name}' does not have the permission to edit the group '{group.Name}'"); - break; - - case GroupPermission.Delete: - if (!group.CanUserDeleteGroup(Context.User)) - throw new GitLabForbiddenException($"User '{Context.User.Name}' does not have the permission to delete the group '{group.Name}'"); - break; + var group = id switch + { + int idInt => Server.AllGroups.FindById(idInt), + string idStr => Server.AllGroups.FindGroup(idStr), + IIdOrPathAddressable gid when gid.Path != null => Server.AllGroups.FindByNamespacedPath(gid.Path), + IIdOrPathAddressable gid => Server.AllGroups.FindById(gid.Id), + _ => throw new ArgumentException($"Id of type '{id.GetType()}' is not supported"), + }; - default: - throw new ArgumentOutOfRangeException(nameof(permissions)); - } + if (group == null || !group.CanUserViewGroup(Context.User)) + throw new GitLabNotFoundException("Group does not exist or user doesn't have permission to view it"); - return group; + switch (permissions) + { + case GroupPermission.View: + // Already checked before + break; + + case GroupPermission.Edit: + if (!group.CanUserEditGroup(Context.User)) + throw new GitLabForbiddenException($"User '{Context.User.Name}' does not have the permission to edit the group '{group.Name}'"); + break; + + case GroupPermission.Delete: + if (!group.CanUserDeleteGroup(Context.User)) + throw new GitLabForbiddenException($"User '{Context.User.Name}' does not have the permission to delete the group '{group.Name}'"); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(permissions)); } - protected Project GetProject(object id, ProjectPermission permissions) - { - if (id is null) - throw new ArgumentNullException(nameof(id)); + return group; + } - if (Context.User.State == UserState.blocked && permissions != ProjectPermission.View) - { - throw new GitLabException("403 Forbidden - Your account has been blocked.") - { - StatusCode = HttpStatusCode.Forbidden, - }; - } + protected Project GetProject(object id, ProjectPermission permissions) + { + if (id is null) + throw new ArgumentNullException(nameof(id)); - var project = id switch + if (Context.User.State == UserState.blocked && permissions != ProjectPermission.View) + { + throw new GitLabException("403 Forbidden - Your account has been blocked.") { - int idInt => Server.AllProjects.FindById(idInt), - string idStr => Server.AllProjects.FindProject(idStr), - _ => throw new ArgumentException($"Id of type '{id.GetType()}' is not supported"), + StatusCode = HttpStatusCode.Forbidden, }; - - if (project == null || !project.CanUserViewProject(Context.User)) - throw new GitLabNotFoundException("Project does not exist or User doesn't have permission to view it"); - - switch (permissions) - { - case ProjectPermission.View: - // Already checked before - break; - - case ProjectPermission.Contribute: - if (!project.CanUserContributeToProject(Context.User)) - throw new GitLabForbiddenException($"User '{Context.User.Name}' does not have the permission to contribute to the project '{project.Name}'"); - break; - - case ProjectPermission.Edit: - if (!project.CanUserEditProject(Context.User)) - throw new GitLabForbiddenException($"User '{Context.User.Name}' does not have the permission to edit the project '{project.Name}'"); - break; - - case ProjectPermission.Delete: - if (!project.CanUserDeleteProject(Context.User)) - throw new GitLabForbiddenException($"User '{Context.User.Name}' does not have the permission to delete the project '{project.Name}'"); - break; - - default: - throw new ArgumentOutOfRangeException(nameof(permissions)); - } - - return project; } - protected User GetUser(int userId) + var project = id switch { - var user = Server.Users.GetById(userId); - if (user == null) - throw new GitLabNotFoundException(); + int idInt => Server.AllProjects.FindById(idInt), + string idStr => Server.AllProjects.FindProject(idStr), + IIdOrPathAddressable pid when pid.Path != null => Server.AllProjects.FindByNamespacedPath(pid.Path), + IIdOrPathAddressable pid => Server.AllProjects.FindById(pid.Id), + _ => throw new ArgumentException($"Id of type '{id.GetType()}' is not supported"), + }; - return user; - } + if (project == null || !project.CanUserViewProject(Context.User)) + throw new GitLabNotFoundException("Project does not exist or User doesn't have permission to view it"); - protected Issue GetIssue(int projectId, int issueId) + switch (permissions) { - var project = GetProject(projectId, ProjectPermission.View); - var issue = project.Issues.GetByIid(issueId); - if (issue == null) - throw new GitLabNotFoundException(); - - return issue; + case ProjectPermission.View: + // Already checked before + break; + + case ProjectPermission.Contribute: + if (!project.CanUserContributeToProject(Context.User)) + throw new GitLabForbiddenException($"User '{Context.User.Name}' does not have the permission to contribute to the project '{project.Name}'"); + break; + + case ProjectPermission.Edit: + if (!project.CanUserEditProject(Context.User)) + throw new GitLabForbiddenException($"User '{Context.User.Name}' does not have the permission to edit the project '{project.Name}'"); + break; + + case ProjectPermission.Delete: + if (!project.CanUserDeleteProject(Context.User)) + throw new GitLabForbiddenException($"User '{Context.User.Name}' does not have the permission to delete the project '{project.Name}'"); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(permissions)); } - protected Milestone GetMilestone(int projectId, int milestoneId) - { - var project = GetProject(projectId, ProjectPermission.View); - var milestone = project.Milestones.GetByIid(milestoneId); - if (milestone == null) - throw new GitLabNotFoundException(); + return project; + } - return milestone; - } + protected User GetUser(int userId) + { + var user = Server.Users.GetById(userId); + if (user == null) + throw new GitLabNotFoundException(); - protected MergeRequest GetMergeRequest(int projectId, int mergeRequestIid) - { - var project = GetProject(projectId, ProjectPermission.View); - var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); - if (mergeRequest == null) - throw new GitLabNotFoundException(); + return user; + } - return mergeRequest; - } + protected Issue GetIssue(int projectId, int issueId) + { + var project = GetProject(projectId, ProjectPermission.View); + var issue = project.Issues.GetByIid(issueId); + if (issue == null) + throw new GitLabNotFoundException(); - protected void EnsureUserIsAuthenticated() - { - if (Context.User == null) - throw new GitLabException { StatusCode = HttpStatusCode.Unauthorized }; - } + return issue; + } - internal enum ProjectPermission - { - View, - Contribute, - Edit, - Delete, - } + protected Milestone GetMilestone(int projectId, int milestoneId) + { + var project = GetProject(projectId, ProjectPermission.View); + var milestone = project.Milestones.GetByIid(milestoneId); + if (milestone == null) + throw new GitLabNotFoundException(); - internal enum GroupPermission - { - View, - Edit, - Delete, - } + return milestone; + } + + protected MergeRequest GetMergeRequest(int projectId, int mergeRequestIid) + { + var project = GetProject(projectId, ProjectPermission.View); + var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); + if (mergeRequest == null) + throw new GitLabNotFoundException(); + + return mergeRequest; + } + + protected void EnsureUserIsAuthenticated() + { + if (Context.User == null) + throw new GitLabException { StatusCode = HttpStatusCode.Unauthorized }; + } + + internal enum ProjectPermission + { + View, + Contribute, + Edit, + Delete, + } + + internal enum GroupPermission + { + View, + Edit, + Delete, } } diff --git a/NGitLab.Mock/Clients/ClientContext.cs b/NGitLab.Mock/Clients/ClientContext.cs index fbd06210..664a5646 100644 --- a/NGitLab.Mock/Clients/ClientContext.cs +++ b/NGitLab.Mock/Clients/ClientContext.cs @@ -1,44 +1,43 @@ using System; using System.Threading; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class ClientContext { - internal sealed class ClientContext + private readonly object _operationLock = new(); + + public ClientContext(GitLabServer server, User user) { - private readonly object _operationLock = new(); + Server = server; + User = user; + } - public ClientContext(GitLabServer server, User user) - { - Server = server; - User = user; - } + public GitLabServer Server { get; } - public GitLabServer Server { get; } + public User User { get; } - public User User { get; } + public bool IsAuthenticated => User != null; - public bool IsAuthenticated => User != null; + public IDisposable BeginOperationScope() + { + Server.RaiseOnClientOperation(); + Monitor.Enter(_operationLock); + return new Releaser(_operationLock); + } + + private sealed class Releaser : IDisposable + { + private readonly object _operationLock; - public IDisposable BeginOperationScope() + public Releaser(object operationLock) { - Server.RaiseOnClientOperation(); - Monitor.Enter(_operationLock); - return new Releaser(_operationLock); + _operationLock = operationLock; } - private sealed class Releaser : IDisposable + public void Dispose() { - private readonly object _operationLock; - - public Releaser(object operationLock) - { - _operationLock = operationLock; - } - - public void Dispose() - { - Monitor.Exit(_operationLock); - } + Monitor.Exit(_operationLock); } } } diff --git a/NGitLab.Mock/Clients/ClusterClient.cs b/NGitLab.Mock/Clients/ClusterClient.cs index 183e477c..22c85344 100644 --- a/NGitLab.Mock/Clients/ClusterClient.cs +++ b/NGitLab.Mock/Clients/ClusterClient.cs @@ -2,18 +2,17 @@ using System.Collections.Generic; using NGitLab.Models; -namespace NGitLab.Mock.Clients -{ - internal sealed class ClusterClient : ClientBase, IClusterClient - { - private readonly int _projectId; +namespace NGitLab.Mock.Clients; - public ClusterClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } +internal sealed class ClusterClient : ClientBase, IClusterClient +{ + private readonly int _projectId; - public IEnumerable All => throw new NotImplementedException(); + public ClusterClient(ClientContext context, ProjectId projectId) + : base(context) + { + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; } + + public IEnumerable All => throw new NotImplementedException(); } diff --git a/NGitLab.Mock/Clients/CommitClient.cs b/NGitLab.Mock/Clients/CommitClient.cs index 62d473a0..6fa09f4d 100644 --- a/NGitLab.Mock/Clients/CommitClient.cs +++ b/NGitLab.Mock/Clients/CommitClient.cs @@ -3,69 +3,68 @@ using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class CommitClient : ClientBase, ICommitClient { - internal sealed class CommitClient : ClientBase, ICommitClient - { - private readonly int _projectId; + private readonly int _projectId; - public CommitClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public CommitClient(ClientContext context, ProjectId projectId) + : base(context) + { + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public Commit Create(CommitCreate commit) + public Commit Create(CommitCreate commit) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var gitCommit = project.Repository.Commit(commit); + var project = GetProject(_projectId, ProjectPermission.Contribute); + var gitCommit = project.Repository.Commit(commit); - return gitCommit.ToCommitClient(project); - } + return gitCommit.ToCommitClient(project); } + } - public Commit GetCommit(string @ref) + public Commit GetCommit(string @ref) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var commit = project.Repository.GetCommit(@ref); + var project = GetProject(_projectId, ProjectPermission.View); + var commit = project.Repository.GetCommit(@ref); - return commit?.ToCommitClient(project); - } + return commit?.ToCommitClient(project); } + } - public Commit CherryPick(CommitCherryPick cherryPick) + public Commit CherryPick(CommitCherryPick cherryPick) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var gitCommit = project.Repository.CherryPick(cherryPick); + var project = GetProject(_projectId, ProjectPermission.Contribute); + var gitCommit = project.Repository.CherryPick(cherryPick); - return gitCommit.ToCommitClient(project); - } + return gitCommit.ToCommitClient(project); } + } - public JobStatus GetJobStatus(string branchName) - { - throw new NotImplementedException(); - } + public JobStatus GetJobStatus(string branchName) + { + throw new NotImplementedException(); + } - public GitLabCollectionResponse GetRelatedMergeRequestsAsync(RelatedMergeRequestsQuery query) + public GitLabCollectionResponse GetRelatedMergeRequestsAsync(RelatedMergeRequestsQuery query) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); + var project = GetProject(_projectId, ProjectPermission.View); - var mergeRequests = project.MergeRequests.Where(mr => mr.Commits.SingleOrDefault( - commit => commit.Sha.Equals(query.Sha.ToString(), StringComparison.OrdinalIgnoreCase)) != null); + var mergeRequests = project.MergeRequests.Where(mr => mr.Commits.SingleOrDefault( + commit => commit.Sha.Equals(query.Sha.ToString(), StringComparison.OrdinalIgnoreCase)) != null); - var relatedMerqueRequests = mergeRequests.Select(mr => mr.ToMergeRequestClient()); + var relatedMerqueRequests = mergeRequests.Select(mr => mr.ToMergeRequestClient()); - return GitLabCollectionResponse.Create(relatedMerqueRequests); - } + return GitLabCollectionResponse.Create(relatedMerqueRequests); } } } diff --git a/NGitLab.Mock/Clients/CommitStatusClient.cs b/NGitLab.Mock/Clients/CommitStatusClient.cs index 0f70a8f6..0de30ba8 100644 --- a/NGitLab.Mock/Clients/CommitStatusClient.cs +++ b/NGitLab.Mock/Clients/CommitStatusClient.cs @@ -3,59 +3,58 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class CommitStatusClient : ClientBase, ICommitStatusClient { - internal sealed class CommitStatusClient : ClientBase, ICommitStatusClient - { - private readonly int _projectId; + private readonly int _projectId; - public CommitStatusClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public CommitStatusClient(ClientContext context, ProjectId projectId) + : base(context) + { + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public CommitStatusCreate AddOrUpdate(CommitStatusCreate status) + public CommitStatusCreate AddOrUpdate(CommitStatusCreate status) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var project = GetProject(_projectId, ProjectPermission.Contribute); + var commitStatus = project.CommitStatuses.FirstOrDefault(cs => Equals(cs, status)); + if (commitStatus == null) { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var commitStatus = project.CommitStatuses.FirstOrDefault(cs => Equals(cs, status)); - if (commitStatus == null) - { - commitStatus = new CommitStatus(); - project.CommitStatuses.Add(commitStatus); - } - - commitStatus.Name = status.Name; - commitStatus.Description = status.Description; - commitStatus.Coverage = status.Coverage; - commitStatus.Ref = status.Ref; - commitStatus.Sha = status.CommitSha; - commitStatus.Status = status.Status; - commitStatus.TargetUrl = status.TargetUrl; - return commitStatus.ToClientCommitStatusCreate(); + commitStatus = new CommitStatus(); + project.CommitStatuses.Add(commitStatus); } - static bool Equals(CommitStatus a, CommitStatusCreate b) - { - return string.Equals(a.Name, b.Name, StringComparison.Ordinal) && - string.Equals(a.Ref, b.Ref, StringComparison.Ordinal) && - string.Equals(a.Sha, b.CommitSha, StringComparison.OrdinalIgnoreCase) && - string.Equals(a.TargetUrl, b.TargetUrl, StringComparison.Ordinal); - } + commitStatus.Name = status.Name; + commitStatus.Description = status.Description; + commitStatus.Coverage = status.Coverage; + commitStatus.Ref = status.Ref; + commitStatus.Sha = status.CommitSha; + commitStatus.Status = status.Status; + commitStatus.TargetUrl = status.TargetUrl; + return commitStatus.ToClientCommitStatusCreate(); } - public IEnumerable AllBySha(string commitSha) + static bool Equals(CommitStatus a, CommitStatusCreate b) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - return project.CommitStatuses - .Where(p => string.Equals(p.Sha, commitSha, StringComparison.OrdinalIgnoreCase)) - .Select(p => p.ToClientCommitStatus()) - .ToList(); - } + return string.Equals(a.Name, b.Name, StringComparison.Ordinal) && + string.Equals(a.Ref, b.Ref, StringComparison.Ordinal) && + string.Equals(a.Sha, b.CommitSha, StringComparison.OrdinalIgnoreCase) && + string.Equals(a.TargetUrl, b.TargetUrl, StringComparison.Ordinal); + } + } + + public IEnumerable AllBySha(string commitSha) + { + using (Context.BeginOperationScope()) + { + var project = GetProject(_projectId, ProjectPermission.View); + return project.CommitStatuses + .Where(p => string.Equals(p.Sha, commitSha, StringComparison.OrdinalIgnoreCase)) + .Select(p => p.ToClientCommitStatus()) + .ToList(); } } } diff --git a/NGitLab.Mock/Clients/ContributorClient.cs b/NGitLab.Mock/Clients/ContributorClient.cs index 177a8b8a..ad5d9c85 100644 --- a/NGitLab.Mock/Clients/ContributorClient.cs +++ b/NGitLab.Mock/Clients/ContributorClient.cs @@ -4,40 +4,39 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class ContributorClient : ClientBase, IContributorClient { - internal sealed class ContributorClient : ClientBase, IContributorClient - { - private readonly int _projectId; + private readonly int _projectId; - public ContributorClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public ContributorClient(ClientContext context, int projectId) + : base(context) + { + _projectId = projectId; + } - public IEnumerable All + public IEnumerable All + { + get { - get + // Note: Counting the number of addition / deletion is too slow. + using (Context.BeginOperationScope()) { - // Note: Counting the number of addition / deletion is too slow. - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - if (project.Repository.IsEmpty) - return Enumerable.Empty(); - - var result = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - foreach (var commit in project.Repository.GetCommits(project.DefaultBranch)) - { - var contributor = result.GetOrAdd(commit.Author.Email, email => new Contributor() { Email = email }); + var project = GetProject(_projectId, ProjectPermission.View); + if (project.Repository.IsEmpty) + return Enumerable.Empty(); - contributor.Name ??= commit.Author.Name; - contributor.Commits++; - } + var result = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + foreach (var commit in project.Repository.GetCommits(project.DefaultBranch)) + { + var contributor = result.GetOrAdd(commit.Author.Email, email => new Contributor() { Email = email }); - return result.Values.ToArray(); + contributor.Name ??= commit.Author.Name; + contributor.Commits++; } + + return result.Values.ToArray(); } } } diff --git a/NGitLab.Mock/Clients/EnvironmentClient.cs b/NGitLab.Mock/Clients/EnvironmentClient.cs index 86a5f7fa..d0fbbd44 100644 --- a/NGitLab.Mock/Clients/EnvironmentClient.cs +++ b/NGitLab.Mock/Clients/EnvironmentClient.cs @@ -2,55 +2,55 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class EnvironmentClient : ClientBase, IEnvironmentClient { - internal sealed class EnvironmentClient : ClientBase, IEnvironmentClient - { - private readonly int _projectId; - - public EnvironmentClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } - - public IEnumerable All => throw new NotImplementedException(); - - public EnvironmentInfo Create(string name, string externalUrl) - { - throw new NotImplementedException(); - } - - public void Delete(int environmentId) - { - throw new NotImplementedException(); - } - - public EnvironmentInfo Edit(int environmentId, string name, string externalUrl) - { - throw new NotImplementedException(); - } - - public EnvironmentInfo Stop(int environmentId) - { - throw new NotImplementedException(); - } - - public GitLabCollectionResponse GetEnvironmentsAsync(EnvironmentQuery query) - { - throw new NotImplementedException(); - } - - public EnvironmentInfo GetById(int environmentId) - { - throw new NotImplementedException(); - } - - public Task GetByIdAsync(int environmentId, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } + private readonly int _projectId; + + public EnvironmentClient(ClientContext context, ProjectId projectId) + : base(context) + { + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } + + public IEnumerable All => throw new NotImplementedException(); + + public EnvironmentInfo Create(string name, string externalUrl) + { + throw new NotImplementedException(); + } + + public void Delete(int environmentId) + { + throw new NotImplementedException(); + } + + public EnvironmentInfo Edit(int environmentId, string name, string externalUrl) + { + throw new NotImplementedException(); + } + + public EnvironmentInfo Stop(int environmentId) + { + throw new NotImplementedException(); + } + + public GitLabCollectionResponse GetEnvironmentsAsync(EnvironmentQuery query) + { + return GitLabCollectionResponse.Create(Array.Empty()); + } + + public EnvironmentInfo GetById(int environmentId) + { + throw new NotImplementedException(); + } + + public Task GetByIdAsync(int environmentId, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/EventClient.cs b/NGitLab.Mock/Clients/EventClient.cs index 5c755ddc..09f82642 100644 --- a/NGitLab.Mock/Clients/EventClient.cs +++ b/NGitLab.Mock/Clients/EventClient.cs @@ -2,31 +2,30 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class EventClient : ClientBase, IEventClient { - internal sealed class EventClient : ClientBase, IEventClient - { - private readonly int? _userId; - private readonly int? _projectId; + private readonly int? _userId; + private readonly int? _projectId; - public EventClient(ClientContext context) - : base(context) - { - } + public EventClient(ClientContext context) + : base(context) + { + } - public EventClient(ClientContext context, int? userId = null, int? projectId = null) - : base(context) - { - _userId = userId; - _projectId = projectId; - } + public EventClient(ClientContext context, int? userId = null, ProjectId? projectId = null) + : base(context) + { + _userId = userId; + _projectId = projectId.HasValue ? Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id : null; + } - IEnumerable IEventClient.Get(EventQuery query) + IEnumerable IEventClient.Get(EventQuery query) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return Server.Events.Get(query, _userId, _projectId).Select(e => e.ToClientEvent()); - } + return Server.Events.Get(query, _userId, _projectId).Select(e => e.ToClientEvent()); } } } diff --git a/NGitLab.Mock/Clients/FileClient.cs b/NGitLab.Mock/Clients/FileClient.cs index dbe7ab82..ff5a138d 100644 --- a/NGitLab.Mock/Clients/FileClient.cs +++ b/NGitLab.Mock/Clients/FileClient.cs @@ -4,125 +4,124 @@ using System.Threading.Tasks; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class FileClient : ClientBase, IFilesClient { - internal sealed class FileClient : ClientBase, IFilesClient - { - private readonly int _projectId; + private readonly int _projectId; - public FileClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public FileClient(ClientContext context, int projectId) + : base(context) + { + _projectId = projectId; + } - public void Create(FileUpsert file) + public void Create(FileUpsert file) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var project = GetProject(_projectId, ProjectPermission.Contribute); + var commitCreate = new CommitCreate { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var commitCreate = new CommitCreate + Branch = file.Branch, + CommitMessage = file.CommitMessage, + AuthorEmail = Context.User.Email, + AuthorName = Context.User.Name, + Actions = new[] { - Branch = file.Branch, - CommitMessage = file.CommitMessage, - AuthorEmail = Context.User.Email, - AuthorName = Context.User.Name, - Actions = new[] + new CreateCommitAction { - new CreateCommitAction - { - Action = nameof(CommitAction.Create), - Content = file.Content, - Encoding = file.Encoding, - FilePath = file.Path, - }, + Action = nameof(CommitAction.Create), + Content = file.Content, + Encoding = file.Encoding, + FilePath = file.Path, }, - }; + }, + }; - project.Repository.Commit(commitCreate); - } + project.Repository.Commit(commitCreate); } + } - public void Delete(FileDelete file) + public void Delete(FileDelete file) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var project = GetProject(_projectId, ProjectPermission.Contribute); + var commitCreate = new CommitCreate { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var commitCreate = new CommitCreate + Branch = file.Branch, + CommitMessage = file.CommitMessage, + AuthorEmail = Context.User.Email, + AuthorName = Context.User.Name, + Actions = new[] { - Branch = file.Branch, - CommitMessage = file.CommitMessage, - AuthorEmail = Context.User.Email, - AuthorName = Context.User.Name, - Actions = new[] + new CreateCommitAction { - new CreateCommitAction - { - Action = nameof(CommitAction.Delete), - FilePath = file.Path, - }, + Action = nameof(CommitAction.Delete), + FilePath = file.Path, }, - }; + }, + }; - project.Repository.Commit(commitCreate); - } + project.Repository.Commit(commitCreate); } + } - public FileData Get(string filePath, string @ref) + public FileData Get(string filePath, string @ref) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var fileSystemPath = WebUtility.UrlDecode(filePath); + var fileSystemPath = WebUtility.UrlDecode(filePath); - var project = GetProject(_projectId, ProjectPermission.View); - return project.Repository.GetFile(fileSystemPath, @ref); - } + var project = GetProject(_projectId, ProjectPermission.View); + return project.Repository.GetFile(fileSystemPath, @ref); } + } - public bool FileExists(string filePath, string @ref) + public bool FileExists(string filePath, string @ref) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return Get(filePath, @ref) != null; - } + return Get(filePath, @ref) != null; } + } - public Blame[] Blame(string filePath, string @ref) - { - throw new NotImplementedException(); - } + public Blame[] Blame(string filePath, string @ref) + { + throw new NotImplementedException(); + } - public void Update(FileUpsert file) + public void Update(FileUpsert file) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var project = GetProject(_projectId, ProjectPermission.Contribute); + var commitCreate = new CommitCreate { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var commitCreate = new CommitCreate + Branch = file.Branch, + CommitMessage = file.CommitMessage, + AuthorEmail = Context.User.Email, + AuthorName = Context.User.Name, + Actions = new[] { - Branch = file.Branch, - CommitMessage = file.CommitMessage, - AuthorEmail = Context.User.Email, - AuthorName = Context.User.Name, - Actions = new[] + new CreateCommitAction { - new CreateCommitAction - { - Action = nameof(CommitAction.Update), - Content = file.Content, - Encoding = file.Encoding, - FilePath = file.Path, - }, + Action = nameof(CommitAction.Update), + Content = file.Content, + Encoding = file.Encoding, + FilePath = file.Path, }, - }; + }, + }; - project.Repository.Commit(commitCreate); - } + project.Repository.Commit(commitCreate); } + } - public async Task GetAsync(string filePath, string @ref, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return Get(filePath, @ref); - } + public async Task GetAsync(string filePath, string @ref, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Get(filePath, @ref); } } diff --git a/NGitLab.Mock/Clients/GitLabBadRequestException.cs b/NGitLab.Mock/Clients/GitLabBadRequestException.cs index 00a6cac5..4d48fdf4 100644 --- a/NGitLab.Mock/Clients/GitLabBadRequestException.cs +++ b/NGitLab.Mock/Clients/GitLabBadRequestException.cs @@ -2,36 +2,35 @@ using System.Net; using System.Runtime.Serialization; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +public sealed class GitLabBadRequestException : GitLabException { - public sealed class GitLabBadRequestException : GitLabException + public GitLabBadRequestException() { - public GitLabBadRequestException() - { - Initialize(); - } + Initialize(); + } - public GitLabBadRequestException(string message) - : base(message) - { - Initialize(); - } + public GitLabBadRequestException(string message) + : base(message) + { + Initialize(); + } - public GitLabBadRequestException(string message, Exception inner) - : base(message, inner) - { - Initialize(); - } + public GitLabBadRequestException(string message, Exception inner) + : base(message, inner) + { + Initialize(); + } - private GitLabBadRequestException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - Initialize(); - } + private GitLabBadRequestException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + Initialize(); + } - private void Initialize() - { - StatusCode = HttpStatusCode.BadRequest; - } + private void Initialize() + { + StatusCode = HttpStatusCode.BadRequest; } } diff --git a/NGitLab.Mock/Clients/GitLabClient.cs b/NGitLab.Mock/Clients/GitLabClient.cs index 9d5283d0..e70ced06 100644 --- a/NGitLab.Mock/Clients/GitLabClient.cs +++ b/NGitLab.Mock/Clients/GitLabClient.cs @@ -1,106 +1,161 @@ using NGitLab.Impl; +using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class GitLabClient : ClientBase, IGitLabClient { - internal sealed class GitLabClient : ClientBase, IGitLabClient + private IGraphQLClient _graphQLClient; + + public GitLabClient(ClientContext context) + : base(context) + { + } + + public IGroupsClient Groups => new GroupClient(Context); + + public IPackageClient Packages => new PackageClient(Context); + + public IUserClient Users => new UserClient(Context); + + public IProjectClient Projects => new ProjectClient(Context); + + public IMembersClient Members => new MembersClient(Context); + + public ICommitClient GetCommits(int projectId) => GetCommits((long)projectId); + + public ICommitClient GetCommits(ProjectId projectId) => new CommitClient(Context, projectId); + + public IIssueClient Issues => new IssueClient(Context); + + public ILabelClient Labels => new LabelClient(Context); + + public IRunnerClient Runners => new RunnerClient(Context); + + public IVersionClient Version => new VersionClient(Context); + + public INamespacesClient Namespaces => new NamespacesClient(Context); + + public ISnippetClient Snippets => new SnippetClient(Context); + + public ISystemHookClient SystemHooks => new SystemHookClient(Context); + + public IDeploymentClient Deployments { get; } + + public IEpicClient Epics { get; } + + public IMergeRequestClient MergeRequests => new MergeRequestClient(Context); + + public IGlobalJobClient Jobs => new GlobalJobsClient(Context); + + public IGraphQLClient GraphQL { - private IGraphQLClient _graphQLClient; + get => _graphQLClient ??= Server.DefaultGraphQLClient ??= new GraphQLClient(Context); + internal set => _graphQLClient = value; + } - public GitLabClient(ClientContext context) - : base(context) - { - } + public ISearchClient AdvancedSearch => new AdvancedSearchClient(Context); - public IGroupsClient Groups => new GroupClient(Context); + public ILintClient Lint => new LintClient(Context); - public IPackageClient Packages => new PackageClient(Context); + public IEventClient GetEvents() => new EventClient(Context); - public IUserClient Users => new UserClient(Context); + public IEventClient GetUserEvents(int userId) => new EventClient(Context, userId); - public IProjectClient Projects => new ProjectClient(Context); + public IEventClient GetProjectEvents(int projectId) => GetProjectEvents((long)projectId); - public IMembersClient Members => new MembersClient(Context); + public IEventClient GetProjectEvents(ProjectId projectId) => new EventClient(Context, null, projectId); - public ICommitClient GetCommits(int projectId) => new CommitClient(Context, projectId); + public ICommitStatusClient GetCommitStatus(int projectId) => GetCommitStatus((long)projectId); - public IIssueClient Issues => new IssueClient(Context); + public ICommitStatusClient GetCommitStatus(ProjectId projectId) => new CommitStatusClient(Context, projectId); - public ILabelClient Labels => new LabelClient(Context); + public IEnvironmentClient GetEnvironmentClient(int projectId) => GetEnvironmentClient((long)projectId); - public IRunnerClient Runners => new RunnerClient(Context); + public IEnvironmentClient GetEnvironmentClient(ProjectId projectId) => new EnvironmentClient(Context, projectId); - public IVersionClient Version => new VersionClient(Context); + public IClusterClient GetClusterClient(int projectId) => GetClusterClient((long)projectId); - public INamespacesClient Namespaces => new NamespacesClient(Context); + public IClusterClient GetClusterClient(ProjectId projectId) => new ClusterClient(Context, projectId); - public ISnippetClient Snippets => new SnippetClient(Context); + public IGroupBadgeClient GetGroupBadgeClient(int groupId) => GetGroupBadgeClient((long)groupId); - public ISystemHookClient SystemHooks => new SystemHookClient(Context); + public IGroupBadgeClient GetGroupBadgeClient(GroupId groupId) => new GroupBadgeClient(Context, groupId); - public IDeploymentClient Deployments { get; } + public IGroupVariableClient GetGroupVariableClient(int groupId) => GetGroupVariableClient((long)groupId); - public IEpicClient Epics { get; } + public IGroupVariableClient GetGroupVariableClient(GroupId groupId) => new GroupVariableClient(Context, groupId); - public IMergeRequestClient MergeRequests => new MergeRequestClient(Context); + public IJobClient GetJobs(int projectId) => GetJobs((long)projectId); - public IGlobalJobClient Jobs => new GlobalJobsClient(Context); + public IJobClient GetJobs(ProjectId projectId) => new JobClient(Context, projectId); - public IGraphQLClient GraphQL - { - get => _graphQLClient ??= Server.DefaultGraphQLClient ??= new GraphQLClient(Context); - internal set => _graphQLClient = value; - } + public IMergeRequestClient GetMergeRequest(int projectId) => GetMergeRequest((long)projectId); - public ISearchClient AdvancedSearch => new AdvancedSearchClient(Context); + public IMergeRequestClient GetMergeRequest(ProjectId projectId) => new MergeRequestClient(Context, projectId); - public ILintClient Lint => new LintClient(Context); + public IMergeRequestClient GetGroupMergeRequest(GroupId groupId) => new MergeRequestClient(Context, groupId); - public IEventClient GetEvents() => new EventClient(Context); + public IMilestoneClient GetMilestone(int projectId) => GetMilestone((long)projectId); - public IEventClient GetUserEvents(int userId) => new EventClient(Context, userId: userId); + public IMilestoneClient GetMilestone(ProjectId projectId) => new MilestoneClient(Context, projectId, MilestoneScope.Projects); - public IEventClient GetProjectEvents(int projectId) => new EventClient(Context, projectId: projectId); + public IMilestoneClient GetGroupMilestone(int groupId) => GetGroupMilestone((long)groupId); - public ICommitStatusClient GetCommitStatus(int projectId) => new CommitStatusClient(Context, projectId); + public IMilestoneClient GetGroupMilestone(GroupId groupId) => new MilestoneClient(Context, groupId, MilestoneScope.Groups); - public IEnvironmentClient GetEnvironmentClient(int projectId) => new EnvironmentClient(Context, projectId); + public IReleaseClient GetReleases(int projectId) => GetReleases((long)projectId); - public IClusterClient GetClusterClient(int projectId) => new ClusterClient(Context, projectId); + public IReleaseClient GetReleases(ProjectId projectId) => new ReleaseClient(Context, projectId); - public IGroupBadgeClient GetGroupBadgeClient(int groupId) => new GroupBadgeClient(Context, groupId); + public IPipelineClient GetPipelines(int projectId) => GetPipelines((long)projectId); - public IGroupVariableClient GetGroupVariableClient(int groupId) => new GroupVariableClient(Context, groupId); + public IPipelineClient GetPipelines(ProjectId projectId) => new PipelineClient(Context, jobClient: GetJobs(projectId), projectId: projectId); - public IJobClient GetJobs(int projectId) => new JobClient(Context, projectId); + public IProjectBadgeClient GetProjectBadgeClient(int projectId) => GetProjectBadgeClient((long)projectId); - public IMergeRequestClient GetMergeRequest(int projectId) => new MergeRequestClient(Context, projectId); + public IProjectBadgeClient GetProjectBadgeClient(ProjectId projectId) => new ProjectBadgeClient(Context, projectId); - public IMilestoneClient GetMilestone(int projectId) => new MilestoneClient(Context, projectId, MilestoneScope.Projects); + public IProjectIssueNoteClient GetProjectIssueNoteClient(int projectId) => GetProjectIssueNoteClient((long)projectId); - public IMilestoneClient GetGroupMilestone(int groupId) => new MilestoneClient(Context, groupId, MilestoneScope.Groups); + public IProjectIssueNoteClient GetProjectIssueNoteClient(ProjectId projectId) => new ProjectIssueNoteClient(Context, projectId); - public IReleaseClient GetReleases(int projectId) => new ReleaseClient(Context, projectId); + public IProjectVariableClient GetProjectVariableClient(int projectId) => GetProjectVariableClient((long)projectId); - public IPipelineClient GetPipelines(int projectId) => new PipelineClient(Context, jobClient: GetJobs(projectId), projectId: projectId); + public IProjectVariableClient GetProjectVariableClient(ProjectId projectId) => new ProjectVariableClient(Context, projectId); - public IProjectBadgeClient GetProjectBadgeClient(int projectId) => new ProjectBadgeClient(Context, projectId); + public IRepositoryClient GetRepository(int projectId) => GetRepository((long)projectId); - public IProjectIssueNoteClient GetProjectIssueNoteClient(int projectId) => new ProjectIssueNoteClient(Context, projectId); + public IRepositoryClient GetRepository(ProjectId projectId) => new RepositoryClient(Context, projectId); - public IProjectVariableClient GetProjectVariableClient(int projectId) => new ProjectVariableClient(Context, projectId); + public ITriggerClient GetTriggers(int projectId) => GetTriggers((long)projectId); - public IRepositoryClient GetRepository(int projectId) => new RepositoryClient(Context, projectId); + public ITriggerClient GetTriggers(ProjectId projectId) => new TriggerClient(Context, projectId); - public ITriggerClient GetTriggers(int projectId) => new TriggerClient(Context, projectId); + public IWikiClient GetWikiClient(int projectId) => GetWikiClient((long)projectId); - public IWikiClient GetWikiClient(int projectId) => new WikiClient(Context, projectId); + public IWikiClient GetWikiClient(ProjectId projectId) => new WikiClient(Context, projectId); - public IProjectLevelApprovalRulesClient GetProjectLevelApprovalRulesClient(int projectId) => new ProjectLevelApprovalRulesClient(Context, projectId); + public IProjectLevelApprovalRulesClient GetProjectLevelApprovalRulesClient(int projectId) => GetProjectLevelApprovalRulesClient((long)projectId); - public IProtectedBranchClient GetProtectedBranchClient(int projectId) => new ProtectedBranchClient(Context, projectId); + public IProjectLevelApprovalRulesClient GetProjectLevelApprovalRulesClient(ProjectId projectId) => new ProjectLevelApprovalRulesClient(Context, projectId); - public ISearchClient GetGroupSearchClient(int groupId) => new GroupSearchClient(Context, groupId); + public IProtectedBranchClient GetProtectedBranchClient(int projectId) => GetProtectedBranchClient((long)projectId); - public ISearchClient GetProjectSearchClient(int projectId) => new ProjectSearchClient(Context, projectId); + public IProtectedBranchClient GetProtectedBranchClient(ProjectId projectId) => new ProtectedBranchClient(Context, projectId); + + public IProtectedTagClient GetProtectedTagClient(ProjectId projectId) + { + throw new System.NotImplementedException(); } + + public ISearchClient GetGroupSearchClient(int groupId) => GetGroupSearchClient((long)groupId); + + public ISearchClient GetGroupSearchClient(GroupId groupId) => new GroupSearchClient(Context, groupId); + + public ISearchClient GetProjectSearchClient(int projectId) => GetProjectSearchClient((long)projectId); + + public ISearchClient GetProjectSearchClient(ProjectId projectId) => new ProjectSearchClient(Context, projectId); + + public IGroupHooksClient GetGroupHooksClient(GroupId groupId) => new GroupHooksClient(Context, groupId); } diff --git a/NGitLab.Mock/Clients/GitLabForbiddenException.cs b/NGitLab.Mock/Clients/GitLabForbiddenException.cs index 2b3962a6..3c350cf6 100644 --- a/NGitLab.Mock/Clients/GitLabForbiddenException.cs +++ b/NGitLab.Mock/Clients/GitLabForbiddenException.cs @@ -2,36 +2,35 @@ using System.Net; using System.Runtime.Serialization; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +public sealed class GitLabForbiddenException : GitLabException { - public sealed class GitLabForbiddenException : GitLabException + public GitLabForbiddenException() { - public GitLabForbiddenException() - { - Initialize(); - } + Initialize(); + } - public GitLabForbiddenException(string message) - : base(message) - { - Initialize(); - } + public GitLabForbiddenException(string message) + : base(message) + { + Initialize(); + } - public GitLabForbiddenException(string message, Exception inner) - : base(message, inner) - { - Initialize(); - } + public GitLabForbiddenException(string message, Exception inner) + : base(message, inner) + { + Initialize(); + } - private GitLabForbiddenException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - Initialize(); - } + private GitLabForbiddenException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + Initialize(); + } - private void Initialize() - { - StatusCode = HttpStatusCode.Forbidden; - } + private void Initialize() + { + StatusCode = HttpStatusCode.Forbidden; } } diff --git a/NGitLab.Mock/Clients/GitLabNotFoundException.cs b/NGitLab.Mock/Clients/GitLabNotFoundException.cs index 12f9c184..7758d90a 100644 --- a/NGitLab.Mock/Clients/GitLabNotFoundException.cs +++ b/NGitLab.Mock/Clients/GitLabNotFoundException.cs @@ -2,36 +2,35 @@ using System.Net; using System.Runtime.Serialization; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +public sealed class GitLabNotFoundException : GitLabException { - public sealed class GitLabNotFoundException : GitLabException + public GitLabNotFoundException() { - public GitLabNotFoundException() - { - Initialize(); - } + Initialize(); + } - public GitLabNotFoundException(string message) - : base(message) - { - Initialize(); - } + public GitLabNotFoundException(string message) + : base(message) + { + Initialize(); + } - public GitLabNotFoundException(string message, Exception inner) - : base(message, inner) - { - Initialize(); - } + public GitLabNotFoundException(string message, Exception inner) + : base(message, inner) + { + Initialize(); + } - private GitLabNotFoundException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - Initialize(); - } + private GitLabNotFoundException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + Initialize(); + } - private void Initialize() - { - StatusCode = HttpStatusCode.NotFound; - } + private void Initialize() + { + StatusCode = HttpStatusCode.NotFound; } } diff --git a/NGitLab.Mock/Clients/GlobalJobsClient.cs b/NGitLab.Mock/Clients/GlobalJobsClient.cs index 2bd434a6..99f4150c 100644 --- a/NGitLab.Mock/Clients/GlobalJobsClient.cs +++ b/NGitLab.Mock/Clients/GlobalJobsClient.cs @@ -3,27 +3,26 @@ using System.Threading; using System.Threading.Tasks; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class GlobalJobsClient : ClientBase, IGlobalJobClient { - internal sealed class GlobalJobsClient : ClientBase, IGlobalJobClient + public GlobalJobsClient(ClientContext context) + : base(context) { - public GlobalJobsClient(ClientContext context) - : base(context) - { - } + } - async Task IGlobalJobClient.GetJobFromJobTokenAsync(string token, CancellationToken cancellationToken) + async Task IGlobalJobClient.GetJobFromJobTokenAsync(string token, CancellationToken cancellationToken) + { + await Task.Yield(); + using (Context.BeginOperationScope()) { - await Task.Yield(); - using (Context.BeginOperationScope()) - { - var job = Server.AllProjects.SelectMany(p => p.Jobs).FirstOrDefault(j => string.Equals(j.JobToken, token, StringComparison.Ordinal)); + var job = Server.AllProjects.SelectMany(p => p.Jobs).FirstOrDefault(j => string.Equals(j.JobToken, token, StringComparison.Ordinal)); - if (job == null) - throw new GitLabNotFoundException(); + if (job == null) + throw new GitLabNotFoundException(); - return job.ToJobClient(); - } + return job.ToJobClient(); } } } diff --git a/NGitLab.Mock/Clients/GraphQLClient.cs b/NGitLab.Mock/Clients/GraphQLClient.cs index 74a97574..671a46a3 100644 --- a/NGitLab.Mock/Clients/GraphQLClient.cs +++ b/NGitLab.Mock/Clients/GraphQLClient.cs @@ -3,18 +3,17 @@ using System.Threading.Tasks; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class GraphQLClient : ClientBase, IGraphQLClient { - internal sealed class GraphQLClient : ClientBase, IGraphQLClient + public GraphQLClient(ClientContext context) + : base(context) { - public GraphQLClient(ClientContext context) - : base(context) - { - } + } - public Task ExecuteAsync(GraphQLQuery query, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } + public Task ExecuteAsync(GraphQLQuery query, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/GroupBadgeClient.cs b/NGitLab.Mock/Clients/GroupBadgeClient.cs index ee1d461c..6deff5dd 100644 --- a/NGitLab.Mock/Clients/GroupBadgeClient.cs +++ b/NGitLab.Mock/Clients/GroupBadgeClient.cs @@ -2,87 +2,86 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class GroupBadgeClient : ClientBase, IGroupBadgeClient { - internal sealed class GroupBadgeClient : ClientBase, IGroupBadgeClient - { - private readonly int _groupId; + private readonly int _groupId; - public GroupBadgeClient(ClientContext context, int groupId) - : base(context) - { - _groupId = groupId; - } + public GroupBadgeClient(ClientContext context, GroupId groupId) + : base(context) + { + _groupId = Server.AllGroups.FindGroup(groupId.ValueAsUriParameter()).Id; + } - public Models.Badge this[int id] + public Models.Badge this[int id] + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var group = GetGroup(_groupId, GroupPermission.View); - var badge = group.Badges.GetById(id); - if (badge == null) - throw new GitLabNotFoundException($"Badge with id '{id}' does not exist in group with id '{_groupId}'"); + var group = GetGroup(_groupId, GroupPermission.View); + var badge = group.Badges.GetById(id); + if (badge == null) + throw new GitLabNotFoundException($"Badge with id '{id}' does not exist in group with id '{_groupId}'"); - return badge.ToBadgeModel(); - } + return badge.ToBadgeModel(); } } + } - public IEnumerable All + public IEnumerable All + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var group = GetGroup(_groupId, GroupPermission.View); - return group.Badges.Select(badge => badge.ToBadgeModel()).ToList(); - } + var group = GetGroup(_groupId, GroupPermission.View); + return group.Badges.Select(badge => badge.ToBadgeModel()).ToList(); } } + } - public Models.Badge Create(BadgeCreate badge) - { - EnsureUserIsAuthenticated(); + public Models.Badge Create(BadgeCreate badge) + { + EnsureUserIsAuthenticated(); - using (Context.BeginOperationScope()) - { - var createdBadge = GetGroup(_groupId, GroupPermission.Edit).Badges.Add(badge.LinkUrl, badge.ImageUrl); - return createdBadge.ToBadgeModel(); - } + using (Context.BeginOperationScope()) + { + var createdBadge = GetGroup(_groupId, GroupPermission.Edit).Badges.Add(badge.LinkUrl, badge.ImageUrl); + return createdBadge.ToBadgeModel(); } + } - public void Delete(int id) - { - EnsureUserIsAuthenticated(); + public void Delete(int id) + { + EnsureUserIsAuthenticated(); - using (Context.BeginOperationScope()) + using (Context.BeginOperationScope()) + { + var badgeToRemove = GetGroup(_groupId, GroupPermission.View).Badges.FirstOrDefault(b => b.Id == id); + if (badgeToRemove == null) { - var badgeToRemove = GetGroup(_groupId, GroupPermission.View).Badges.FirstOrDefault(b => b.Id == id); - if (badgeToRemove == null) - { - throw new GitLabNotFoundException($"Badge with id '{id}' does not exist in group with id '{_groupId}'"); - } - - GetGroup(_groupId, GroupPermission.Edit).Badges.Remove(badgeToRemove); + throw new GitLabNotFoundException($"Badge with id '{id}' does not exist in group with id '{_groupId}'"); } + + GetGroup(_groupId, GroupPermission.Edit).Badges.Remove(badgeToRemove); } + } - public Models.Badge Update(int id, BadgeUpdate badge) + public Models.Badge Update(int id, BadgeUpdate badge) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var badgeToUpdate = GetGroup(_groupId, GroupPermission.Edit).Badges.FirstOrDefault(b => b.Id == id); + if (badgeToUpdate == null) { - var badgeToUpdate = GetGroup(_groupId, GroupPermission.Edit).Badges.FirstOrDefault(b => b.Id == id); - if (badgeToUpdate == null) - { - throw new GitLabNotFoundException($"Badge with id '{id}' does not exist in group with id '{_groupId}'"); - } - - badgeToUpdate.LinkUrl = badge.LinkUrl; - badgeToUpdate.ImageUrl = badge.ImageUrl; - return badgeToUpdate.ToBadgeModel(); + throw new GitLabNotFoundException($"Badge with id '{id}' does not exist in group with id '{_groupId}'"); } + + badgeToUpdate.LinkUrl = badge.LinkUrl; + badgeToUpdate.ImageUrl = badge.ImageUrl; + return badgeToUpdate.ToBadgeModel(); } } } diff --git a/NGitLab.Mock/Clients/GroupClient.cs b/NGitLab.Mock/Clients/GroupClient.cs index 78ebff51..c2917147 100644 --- a/NGitLab.Mock/Clients/GroupClient.cs +++ b/NGitLab.Mock/Clients/GroupClient.cs @@ -7,356 +7,399 @@ using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class GroupClient : ClientBase, IGroupsClient { - internal sealed class GroupClient : ClientBase, IGroupsClient + public GroupClient(ClientContext context) + : base(context) { - public GroupClient(ClientContext context) - : base(context) - { - } + } - public Models.Group this[int id] + public Models.Group Create(GroupCreate group) + { + using (Context.BeginOperationScope()) { - get + Group parentGroup = null; + if (group.ParentId != null) { - using (Context.BeginOperationScope()) - { - var group = Server.AllGroups.FirstOrDefault(g => g.Id == id); - if (group == null || !group.CanUserViewGroup(Context.User)) - throw new GitLabNotFoundException(); + parentGroup = Server.AllGroups.FirstOrDefault(g => g.Id == group.ParentId.Value); + if (parentGroup == null || !parentGroup.CanUserViewGroup(Context.User)) + throw new GitLabNotFoundException(); - return group.ToClientGroup(Context.User); - } + if (!parentGroup.CanUserAddGroup(Context.User)) + throw new GitLabForbiddenException(); } - } - public Models.Group this[string fullPath] - { - get + var newGroup = new Group { - using (Context.BeginOperationScope()) + Name = group.Name, + Path = group.Path, + Description = group.Description, + Visibility = group.Visibility, + LfsEnabled = group.LfsEnabled, + RequestAccessEnabled = group.RequestAccessEnabled, + SharedRunnersLimit = TimeSpan.FromMinutes(group.SharedRunnersMinutesLimit ?? 0), + + Permissions = { - var group = Server.AllGroups.FindGroup(fullPath); - if (group == null || !group.CanUserViewGroup(Context.User)) - throw new GitLabNotFoundException(); + new Permission(Context.User, AccessLevel.Owner), + }, + }; - return group.ToClientGroup(Context.User); - } + if (parentGroup != null) + { + parentGroup.Groups.Add(newGroup); + } + else + { + Server.Groups.Add(newGroup); } + + return newGroup.ToClientGroup(Context.User); } + } - public IEnumerable Accessible + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task CreateAsync(GroupCreate group, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Create(group); + } + + public void Delete(int id) + { + using (Context.BeginOperationScope()) { - get - { - using (Context.BeginOperationScope()) - { - return Server.AllGroups.Where(group => group.CanUserViewGroup(Context.User)).Select(group => group.ToClientGroup(Context.User)).ToList(); - } - } + var group = Server.AllGroups.FirstOrDefault(g => g.Id == id); + if (group == null || !group.CanUserViewGroup(Context.User)) + throw new GitLabNotFoundException(); + + if (!group.CanUserDeleteGroup(Context.User)) + throw new GitLabForbiddenException(); + + group.ToClientGroup(Context.User); } + } + + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task DeleteAsync(int id, CancellationToken cancellationToken = default) + { + await Task.Yield(); + Delete(id); + } + + public IEnumerable Accessible => Get(query: null); - public Models.Group Create(GroupCreate group) + public IEnumerable Get(GroupQuery query) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var groups = Server.AllGroups.Where(group => group.CanUserViewGroup(Context.User)); + + if (query is not null) { - Group parentGroup = null; - if (group.ParentId != null) + if (query.SkipGroups != null && query.SkipGroups.Length > 0) { - parentGroup = Server.AllGroups.FirstOrDefault(g => g.Id == group.ParentId.Value); - if (parentGroup == null || !parentGroup.CanUserViewGroup(Context.User)) - throw new GitLabNotFoundException(); - - if (!parentGroup.CanUserAddGroup(Context.User)) - throw new GitLabForbiddenException(); + groups = groups.Where(g => !query.SkipGroups.Contains(g.Id)); } - var newGroup = new Group + if (query.Owned is true) { - Name = group.Name, - Description = group.Description, - Visibility = group.Visibility, - Permissions = - { - new Permission(Context.User, AccessLevel.Owner), - }, - }; + groups = groups.Where(g => g.IsUserOwner(Context.User)); + } - if (parentGroup != null) + if (query.MinAccessLevel != null) { - parentGroup.Groups.Add(newGroup); + groups = groups.Where(g => g.GetEffectivePermissions().GetAccessLevel(Context.User) >= query.MinAccessLevel); } - else + + if (query.TopLevelOnly is true) { - Server.Groups.Add(newGroup); + groups = groups.Where(g => g.Parent is null); } - return newGroup.ToClientGroup(Context.User); + if (!string.IsNullOrEmpty(query.Search)) + throw new NotImplementedException(); } + + return groups.Select(g => g.ToClientGroup(Context.User)).ToArray(); } + } + + public GitLabCollectionResponse GetAsync(GroupQuery query) => GitLabCollectionResponse.Create(Get(query)); - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task CreateAsync(GroupCreate group, CancellationToken cancellationToken = default) + public async Task> PageAsync(PageQuery query, CancellationToken cancellationToken = default) + { + await Task.Yield(); + var pageNum = Math.Max(1, query?.Page ?? PageQuery.FirstPage); + var perPage = query?.PerPage ?? PageQuery.DefaultPerPage; + if (perPage < PageQuery.MinPerPage) { - await Task.Yield(); - return Create(group); + // Max isn't enforced the same way + throw new GitLabBadRequestException($"per_page value ({perPage}) is invalid: cannot be lower than MinPerPage ({PageQuery.MinPerPage})"); } - public void Delete(int id) - { - using (Context.BeginOperationScope()) - { - var group = Server.AllGroups.FirstOrDefault(g => g.Id == id); - if (group == null || !group.CanUserViewGroup(Context.User)) - throw new GitLabNotFoundException(); + var all = Get(query?.Query).ToArray(); + var page = all + .Skip((pageNum - 1) * perPage) + .Take(perPage) + .ToArray(); + return new(page, all.Length > PagedResponse.MaxQueryCountLimit ? null : all.Length); + } - if (!group.CanUserDeleteGroup(Context.User)) - throw new GitLabForbiddenException(); + public Models.Group this[int id] => GetGroup(id); - group.ToClientGroup(Context.User); - } - } + public Models.Group this[string fullPath] => GetGroup(fullPath); - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task DeleteAsync(int id, CancellationToken cancellationToken = default) + public Models.Group GetGroup(GroupId id) + { + using (Context.BeginOperationScope()) { - await Task.Yield(); - Delete(id); + var group = Server.AllGroups.FirstOrDefault(g => id.Equals(g.PathWithNameSpace, g.Id)); + + if (group == null || !group.CanUserViewGroup(Context.User)) + throw new GitLabNotFoundException(); + + return group.ToClientGroup(Context.User); } + } - public IEnumerable Get(GroupQuery query) - { - using (Context.BeginOperationScope()) - { - var groups = Server.AllGroups; - if (query != null) - { - if (query.SkipGroups != null && query.SkipGroups.Length > 0) - { - groups = groups.Where(g => !query.SkipGroups.Contains(g.Id)); - } + public Task GetByFullPathAsync(string fullPath, CancellationToken cancellationToken = default) => GetGroupAsync(fullPath, cancellationToken); - if (query.Owned is true) - { - groups = groups.Where(g => g.IsUserOwner(Context.User)); - } + public Task GetByIdAsync(int id, CancellationToken cancellationToken = default) => GetGroupAsync(id, cancellationToken); - if (query.MinAccessLevel != null) - { - groups = groups.Where(g => g.GetEffectivePermissions().GetAccessLevel(Context.User) >= query.MinAccessLevel); - } + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task GetGroupAsync(GroupId groupId, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return GetGroup(groupId); + } - if (!string.IsNullOrEmpty(query.Search)) - throw new NotImplementedException(); - } + public void Restore(int id) + { + throw new NotImplementedException(); + } - return groups.Select(g => g.ToClientGroup(Context.User)).ToArray(); - } - } + public async Task RestoreAsync(int id, CancellationToken cancellationToken = default) + { + await Task.Yield(); + Restore(id); + } - public GitLabCollectionResponse GetAsync(GroupQuery query) - { - return GitLabCollectionResponse.Create(Get(query)); - } + public IEnumerable Search(string search) + { + throw new NotImplementedException(); + } - public async Task GetByFullPathAsync(string fullPath, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return this[fullPath]; - } + public GitLabCollectionResponse SearchAsync(string search) + { + return GitLabCollectionResponse.Create(Search(search)); + } - public async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + public IEnumerable SearchProjects(int groupId, string search) => + SearchProjectsAsync(groupId, new GroupProjectsQuery { - await Task.Yield(); - return this[id]; - } + Search = search, + }); - public void Restore(int id) - { - throw new NotImplementedException(); - } + public GitLabCollectionResponse GetProjectsAsync(int groupId, GroupProjectsQuery query) => SearchProjectsAsync(groupId, query); - public IEnumerable Search(string search) + public GitLabCollectionResponse SearchProjectsAsync(GroupId groupId, GroupProjectsQuery query) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); - } + var group = Server.AllGroups.FirstOrDefault(g => groupId.Equals(g.PathWithNameSpace, g.Id)); - public GitLabCollectionResponse SearchAsync(string search) - { - return GitLabCollectionResponse.Create(Search(search)); + if (group == null || !group.CanUserViewGroup(Context.User)) + throw new GitLabNotFoundException(); + + var projects = query?.IncludeSubGroups is true ? group.AllProjects : group.Projects; + + if (query != null) + { + if (query.Archived != null) + { + projects = projects.Where(project => project.Archived == query.Archived); + } + + if (query.Owned != null) + { + projects = projects.Where(project => project.IsUserOwner(Context.User)); + } + + if (query.Visibility != null) + { + projects = projects.Where(project => project.Visibility >= query.Visibility.Value); + } + + if (!string.IsNullOrEmpty(query.Search)) + throw new NotImplementedException(); + + if (query.MinAccessLevel != null) + throw new NotImplementedException(); + } + + projects = projects.Where(project => project.CanUserViewProject(Context.User)); + return GitLabCollectionResponse.Create(projects.Select(project => project.ToClientProject(Context.User)).ToArray()); } + } - public IEnumerable SearchProjects(int groupId, string search) + public async Task> PageProjectsAsync(GroupId groupId, PageQuery query, CancellationToken cancellationToken = default) + { + await Task.Yield(); + var pageNum = Math.Max(1, query?.Page ?? PageQuery.FirstPage); + var perPage = query?.PerPage ?? PageQuery.DefaultPerPage; + if (perPage < 1) { - throw new NotImplementedException(); + throw new GitLabBadRequestException($"per_page value ({perPage}) is invalid: cannot be lower than MinPerPage ({PageQuery.MinPerPage})"); } - public GitLabCollectionResponse GetProjectsAsync(int groupId, GroupProjectsQuery query) + var all = SearchProjectsAsync(groupId, query?.Query).ToArray(); + var page = all + .Skip((pageNum - 1) * perPage) + .Take(perPage) + .ToArray(); + return new(page, all.Length > PagedResponse.MaxQueryCountLimit ? null : all.Length); + } + + public Models.Group Update(int id, GroupUpdate groupUpdate) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var group = Server.AllGroups.FirstOrDefault(g => g.Id == groupId); - if (group == null || !group.CanUserViewGroup(Context.User)) - throw new GitLabNotFoundException(); + var group = Server.AllGroups.FindById(id); + if (group == null || !group.CanUserViewGroup(Context.User)) + throw new GitLabNotFoundException(); - var projects = query?.IncludeSubGroups is true ? group.AllProjects : group.Projects; + if (!group.CanUserEditGroup(Context.User)) + throw new GitLabForbiddenException(); - if (query != null) - { - if (query.Archived != null) - { - projects = projects.Where(project => project.Archived == query.Archived); - } + if (groupUpdate.Description != null) + { + group.Description = groupUpdate.Description; + } - if (query.Owned != null) - { - projects = projects.Where(project => project.IsUserOwner(Context.User)); - } + if (groupUpdate.ExtraSharedRunnersMinutesLimit != null) + { + group.ExtraSharedRunnersLimit = TimeSpan.FromMinutes(groupUpdate.ExtraSharedRunnersMinutesLimit.Value); + } - if (query.Visibility != null) - { - projects = projects.Where(project => project.Visibility >= query.Visibility.Value); - } + if (groupUpdate.LfsEnabled != null) + { + group.LfsEnabled = groupUpdate.LfsEnabled.Value; + } - if (!string.IsNullOrEmpty(query.Search)) - throw new NotImplementedException(); + if (groupUpdate.Name != null) + { + group.Name = groupUpdate.Name; + } - if (query.MinAccessLevel != null) - throw new NotImplementedException(); - } + if (groupUpdate.Path != null) + { + group.Path = groupUpdate.Path; + } - projects = projects.Where(project => project.CanUserViewProject(Context.User)); - return GitLabCollectionResponse.Create(projects.Select(project => project.ToClientProject(Context.User)).ToArray()); + if (groupUpdate.RequestAccessEnabled != null) + { + group.RequestAccessEnabled = groupUpdate.RequestAccessEnabled.Value; } - } - public Models.Group Update(int id, GroupUpdate groupUpdate) - { - using (Context.BeginOperationScope()) + if (groupUpdate.SharedRunnersMinutesLimit != null) { - var group = Server.AllGroups.FindGroupById(id); - if (group == null || !group.CanUserViewGroup(Context.User)) - throw new GitLabNotFoundException(); + group.SharedRunnersLimit = TimeSpan.FromMinutes(groupUpdate.SharedRunnersMinutesLimit.Value); + } - if (!group.CanUserEditGroup(Context.User)) - throw new GitLabForbiddenException(); + if (groupUpdate.Visibility != null) + { + group.Visibility = groupUpdate.Visibility.Value; + } - if (groupUpdate.Description != null) - { - group.Description = groupUpdate.Description; - } + return group.ToClientGroup(Context.User); + } + } - if (groupUpdate.ExtraSharedRunnersMinutesLimit != null) - { - group.ExtraSharedRunnersLimit = TimeSpan.FromMinutes(groupUpdate.ExtraSharedRunnersMinutesLimit.Value); - } + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task UpdateAsync(int id, GroupUpdate groupUpdate, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Update(id, groupUpdate); + } - if (groupUpdate.LfsEnabled != null) - { - group.LfsEnabled = groupUpdate.LfsEnabled.Value; - } + public GitLabCollectionResponse GetSubgroupsByIdAsync(int id, SubgroupQuery query = null) => GetSubgroupsAsync(id, query); - if (groupUpdate.Name != null) - { - group.Name = groupUpdate.Name; - } + public GitLabCollectionResponse GetSubgroupsByFullPathAsync(string fullPath, SubgroupQuery query = null) => GetSubgroupsAsync(fullPath, query); - if (groupUpdate.Path != null) - { - group.Path = groupUpdate.Path; - } + public GitLabCollectionResponse GetSubgroupsAsync(GroupId groupId, SubgroupQuery query = null) + { + using (Context.BeginOperationScope()) + { + var parentGroup = GetGroup(groupId); + var groups = Server.AllGroups.Where(group => group.CanUserViewGroup(Context.User)); - if (groupUpdate.RequestAccessEnabled != null) + if (query is not null) + { + if (query.SkipGroups != null && query.SkipGroups.Length > 0) { - group.RequestAccessEnabled = groupUpdate.RequestAccessEnabled.Value; + groups = groups.Where(g => !query.SkipGroups.Contains(g.Id)); } - if (groupUpdate.SharedRunnersMinutesLimit != null) + if (query.Owned is true) { - group.SharedRunnersLimit = TimeSpan.FromMinutes(groupUpdate.SharedRunnersMinutesLimit.Value); + groups = groups.Where(g => g.IsUserOwner(Context.User)); } - if (groupUpdate.Visibility != null) + if (query.MinAccessLevel != null) { - group.Visibility = groupUpdate.Visibility.Value; + groups = groups.Where(g => g.GetEffectivePermissions().GetAccessLevel(Context.User) >= query.MinAccessLevel); } - return group.ToClientGroup(Context.User); + if (!string.IsNullOrEmpty(query.Search)) + throw new NotImplementedException(); } - } - - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task UpdateAsync(int id, GroupUpdate groupUpdate, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return Update(id, groupUpdate); - } - public GitLabCollectionResponse GetSubgroupsByIdAsync(int id, SubgroupQuery query = null) - { - using (Context.BeginOperationScope()) + groups = groups.Where(g => { - var parentGroup = this[id]; - var groups = Server.AllGroups; - if (query != null) - { - if (query.SkipGroups != null && query.SkipGroups.Length > 0) - { - groups = groups.Where(g => !query.SkipGroups.Contains(g.Id)); - } + if (g.Parent is null) + return false; - if (query.Owned is true) - { - groups = groups.Where(g => g.IsUserOwner(Context.User)); - } + // is it a child of parentGroup... + if (g.Parent.Id == parentGroup.Id) + return true; - if (query.MinAccessLevel != null) + if (query?.IncludeDescendants is true) + { + // is it a descendant of parentGroup... + var ancestor = g.Parent.Parent; + while (ancestor is not null) { - groups = groups.Where(g => g.GetEffectivePermissions().GetAccessLevel(Context.User) >= query.MinAccessLevel); + if (ancestor.Id == parentGroup.Id) + return true; + ancestor = ancestor.Parent; } - - if (!string.IsNullOrEmpty(query.Search)) - throw new NotImplementedException(); } - var clientGroups = groups.Select(g => g.ToClientGroup(Context.User)); + return false; + }); - return GitLabCollectionResponse.Create(clientGroups.Where(g => g.ParentId == parentGroup.Id)); - } + return GitLabCollectionResponse.Create(groups.Select(g => g.ToClientGroup(Context.User))); } + } - public GitLabCollectionResponse GetSubgroupsByFullPathAsync(string fullPath, SubgroupQuery query = null) + public async Task> PageSubgroupsAsync(GroupId groupId, PageQuery query, CancellationToken cancellationToken = default) + { + await Task.Yield(); + var pageNum = Math.Max(1, query?.Page ?? PageQuery.FirstPage); + var perPage = query?.PerPage ?? PageQuery.DefaultPerPage; + if (perPage < 1) { - using (Context.BeginOperationScope()) - { - var parentGroup = this[fullPath]; - var groups = Server.AllGroups; - if (query != null) - { - if (query.SkipGroups != null && query.SkipGroups.Length > 0) - { - groups = groups.Where(g => !query.SkipGroups.Contains(g.Id)); - } - - if (query.Owned is true) - { - groups = groups.Where(g => g.IsUserOwner(Context.User)); - } - - if (query.MinAccessLevel != null) - { - groups = groups.Where(g => g.GetEffectivePermissions().GetAccessLevel(Context.User) >= query.MinAccessLevel); - } - - if (!string.IsNullOrEmpty(query.Search)) - throw new NotImplementedException(); - } - - var clientGroups = groups.Select(g => g.ToClientGroup(Context.User)); - - return GitLabCollectionResponse.Create(clientGroups.Where(g => g.ParentId == parentGroup.Id)); - } + throw new GitLabBadRequestException($"per_page value ({perPage}) is invalid: cannot be lower than MinPerPage ({PageQuery.MinPerPage})"); } + + var all = GetSubgroupsAsync(groupId, query?.Query).ToArray(); + var page = all + .Skip((pageNum - 1) * perPage) + .Take(perPage) + .ToArray(); + return new(page, all.Length > PagedResponse.MaxQueryCountLimit ? null : all.Length); } } diff --git a/NGitLab.Mock/Clients/GroupHooksClient.cs b/NGitLab.Mock/Clients/GroupHooksClient.cs new file mode 100644 index 00000000..4d2e52b3 --- /dev/null +++ b/NGitLab.Mock/Clients/GroupHooksClient.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Linq; +using NGitLab.Models; + +namespace NGitLab.Mock.Clients; + +internal sealed class GroupHooksClient : ClientBase, IGroupHooksClient +{ + public int _groupId { get; } + + public GroupHooksClient(ClientContext context, GroupId groupId) + : base(context) + { + _groupId = Server.AllGroups.FindGroup(groupId.ValueAsUriParameter()).Id; + } + + public IEnumerable All + { + get + { + using (Context.BeginOperationScope()) + { + var hooks = GetGroup(_groupId, GroupPermission.Edit).Hooks; + return ToClientGroupHooks(hooks).ToList(); + } + } + } + + public Models.GroupHook this[int hookId] + { + get + { + using (Context.BeginOperationScope()) + { + var hook = All.FirstOrDefault(h => h.Id == hookId) ?? throw new GitLabNotFoundException(); + return hook; + } + } + } + + public Models.GroupHook Create(GroupHookUpsert hook) + { + using (Context.BeginOperationScope()) + { + var groupHook = UpsertToHook(hook); + + GetGroup(_groupId, GroupPermission.Edit).Hooks.Add(groupHook); + return groupHook.ToClientGroupHook(); + } + } + + public Models.GroupHook Update(int hookId, GroupHookUpsert hook) + { + using (Context.BeginOperationScope()) + { + var currentHook = GetGroup(_groupId, GroupPermission.Edit).Hooks.FirstOrDefault(h => h.Id == hookId) ?? throw new GitLabNotFoundException(); + + currentHook.Url = hook.Url; + currentHook.PushEvents = hook.PushEvents ?? false; + currentHook.MergeRequestsEvents = hook.MergeRequestsEvents ?? false; + currentHook.IssuesEvents = hook.IssuesEvents ?? false; + currentHook.TagPushEvents = hook.TagPushEvents ?? false; + currentHook.NoteEvents = hook.NoteEvents ?? false; + currentHook.JobEvents = hook.JobEvents ?? false; + currentHook.PipelineEvents = hook.PipelineEvents ?? false; + currentHook.WikiPagesEvents = hook.WikiPagesEvents ?? false; + currentHook.EnableSslVerification = hook.EnableSslVerification ?? false; + currentHook.Token = currentHook.Token; + + return currentHook.ToClientGroupHook(); + } + } + + public void Delete(int hookId) + { + using (Context.BeginOperationScope()) + { + var groupHooks = GetGroup(_groupId, GroupPermission.Edit).Hooks; + var hook = groupHooks.FirstOrDefault(h => h.Id == hookId) ?? throw new GitLabNotFoundException(); + + groupHooks.Remove(hook); + } + } + + private static IEnumerable ToClientGroupHooks(IEnumerable hooks) + { + return hooks.Select(hook => hook.ToClientGroupHook()); + } + + private static GroupHook UpsertToHook(GroupHookUpsert hook) + { + var hookFromUpsert = new GroupHook + { + Url = hook.Url, + PushEvents = hook.PushEvents ?? false, + MergeRequestsEvents = hook.MergeRequestsEvents ?? false, + IssuesEvents = hook.IssuesEvents ?? false, + TagPushEvents = hook.TagPushEvents ?? false, + NoteEvents = hook.NoteEvents ?? false, + JobEvents = hook.JobEvents ?? false, + PipelineEvents = hook.PipelineEvents ?? false, + WikiPagesEvents = hook.WikiPagesEvents ?? false, + EnableSslVerification = hook.EnableSslVerification ?? false, + Token = hook.Token, + }; + + return hookFromUpsert; + } +} diff --git a/NGitLab.Mock/Clients/GroupSearchClient.cs b/NGitLab.Mock/Clients/GroupSearchClient.cs index a092214d..4c240d0c 100644 --- a/NGitLab.Mock/Clients/GroupSearchClient.cs +++ b/NGitLab.Mock/Clients/GroupSearchClient.cs @@ -1,22 +1,22 @@ using System; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class GroupSearchClient : ClientBase, ISearchClient { - internal sealed class GroupSearchClient : ISearchClient - { - private readonly ClientContext _context; - private readonly int _groupId; + private readonly ClientContext _context; + private readonly int _groupId; - public GroupSearchClient(ClientContext context, int groupId) - { - _context = context; - _groupId = groupId; - } + public GroupSearchClient(ClientContext context, GroupId groupId) + : base(context) + { + _context = context; + _groupId = Server.AllGroups.FindGroup(groupId.ValueAsUriParameter()).Id; + } - public GitLabCollectionResponse GetBlobsAsync(SearchQuery query) - { - throw new NotImplementedException(); - } + public GitLabCollectionResponse GetBlobsAsync(SearchQuery query) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/GroupVariableClient.cs b/NGitLab.Mock/Clients/GroupVariableClient.cs index 6a0b51b1..1cde7d30 100644 --- a/NGitLab.Mock/Clients/GroupVariableClient.cs +++ b/NGitLab.Mock/Clients/GroupVariableClient.cs @@ -2,35 +2,34 @@ using System.Collections.Generic; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class GroupVariableClient : ClientBase, IGroupVariableClient { - internal sealed class GroupVariableClient : ClientBase, IGroupVariableClient - { - private readonly int _groupId; + private readonly int _groupId; - public GroupVariableClient(ClientContext context, int groupId) - : base(context) - { - _groupId = groupId; - } + public GroupVariableClient(ClientContext context, GroupId groupId) + : base(context) + { + _groupId = Server.AllGroups.FindGroup(groupId.ValueAsUriParameter()).Id; + } - public Variable this[string key] => throw new NotImplementedException(); + public Variable this[string key] => throw new NotImplementedException(); - public IEnumerable All => throw new NotImplementedException(); + public IEnumerable All => throw new NotImplementedException(); - public Variable Create(VariableCreate model) - { - throw new NotImplementedException(); - } + public Variable Create(VariableCreate model) + { + throw new NotImplementedException(); + } - public void Delete(string key) - { - throw new NotImplementedException(); - } + public void Delete(string key) + { + throw new NotImplementedException(); + } - public Variable Update(string key, VariableUpdate model) - { - throw new NotImplementedException(); - } + public Variable Update(string key, VariableUpdate model) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/ICommitActionHandler.cs b/NGitLab.Mock/Clients/ICommitActionHandler.cs index 2f76ac5f..e6bb4496 100644 --- a/NGitLab.Mock/Clients/ICommitActionHandler.cs +++ b/NGitLab.Mock/Clients/ICommitActionHandler.cs @@ -1,9 +1,8 @@ using NGitLab.Models; -namespace NGitLab.Mock +namespace NGitLab.Mock; + +internal interface ICommitActionHandler { - internal interface ICommitActionHandler - { - void Handle(CreateCommitAction action, string repoPath, LibGit2Sharp.Repository repository); - } + void Handle(CreateCommitAction action, string repoPath, LibGit2Sharp.Repository repository); } diff --git a/NGitLab.Mock/Clients/IssueClient.cs b/NGitLab.Mock/Clients/IssueClient.cs index bc1a342b..987b7e4c 100644 --- a/NGitLab.Mock/Clients/IssueClient.cs +++ b/NGitLab.Mock/Clients/IssueClient.cs @@ -8,549 +8,447 @@ using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class IssueClient : ClientBase, IIssueClient { - internal sealed class IssueClient : ClientBase, IIssueClient + public IssueClient(ClientContext context) + : base(context) { - public IssueClient(ClientContext context) - : base(context) - { - } + } - public IEnumerable Owned + public IEnumerable Owned + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var viewableProjects = Server.AllProjects.Where(p => p.CanUserViewProject(Context.User)); - var allIssues = viewableProjects.SelectMany(p => p.Issues); - var assignedOrAuthoredIssues = allIssues.Where(i => i.CanUserViewIssue(Context.User)); - return assignedOrAuthoredIssues.Select(i => i.ToClientIssue()).ToList(); - } + var viewableProjects = Server.AllProjects.Where(p => p.CanUserViewProject(Context.User)); + var allIssues = viewableProjects.SelectMany(p => p.Issues); + var assignedOrAuthoredIssues = allIssues.Where(i => i.CanUserViewIssue(Context.User)); + return assignedOrAuthoredIssues.Select(i => i.ToClientIssue()).ToList(); } } + } - public Models.Issue Create(IssueCreate issueCreate) + public Models.Issue Create(IssueCreate issueCreate) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var project = GetProject(issueCreate.ProjectId, ProjectPermission.View); + + var issue = new Issue { - var project = GetProject(issueCreate.ProjectId, ProjectPermission.View); - - var issue = new Issue - { - Description = issueCreate.Description, - Title = issueCreate.Title, - Author = Context.User, - Confidential = issueCreate.Confidential, - }; - - if (!string.IsNullOrEmpty(issueCreate.Labels)) - { - issue.Labels = issueCreate.Labels.Split(','); - } - - if (issueCreate.AssigneeId != null) - { - issue.Assignee = Server.Users.FirstOrDefault(u => u.Id == issueCreate.AssigneeId); - } - - project.Issues.Add(issue); - return project.Issues.First(i => i.Iid == issue.Iid).ToClientIssue(); - } - } + Description = issueCreate.Description, + Title = issueCreate.Title, + Author = Context.User, + Confidential = issueCreate.Confidential, + }; - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task CreateAsync(IssueCreate issueCreate, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return Create(issueCreate); - } + if (!string.IsNullOrEmpty(issueCreate.Labels)) + { + issue.Labels = issueCreate.Labels.Split(','); + } - public Models.Issue Edit(IssueEdit issueEdit) - { - using (Context.BeginOperationScope()) + if (issueCreate.AssigneeId != null) { - var projectId = issueEdit.ProjectId; - var issueToModify = GetIssue(projectId, issueEdit.IssueId); - - if (issueEdit.AssigneeId.HasValue) - { - issueToModify.Assignee = GetUser(issueEdit.AssigneeId.Value); - } - - var prevMilestone = issueToModify.Milestone; - - if (issueEdit.MilestoneId.HasValue) - { - issueToModify.Milestone = GetMilestone(projectId, issueEdit.MilestoneId.Value); - CreateResourceMilestoneEvents(issueToModify.Id, prevMilestone, issueToModify.Milestone); - } - - issueToModify.Title = issueEdit.Title; - issueToModify.Description = issueEdit.Description; - - string[] labelsEdit; - - if (issueEdit.Labels is null) - { - labelsEdit = null; - } - else if (string.Equals(issueEdit.Labels, string.Empty, StringComparison.Ordinal)) - { - labelsEdit = Array.Empty(); - } - else - { - labelsEdit = issueEdit.Labels.Split(','); - } - - if (labelsEdit is not null) - { - CreateResourceLabelEvents(issueToModify.Labels, labelsEdit, issueToModify.Id); - issueToModify.Labels = labelsEdit; - } - - issueToModify.UpdatedAt = DateTimeOffset.UtcNow; - var isValidState = Enum.TryParse(issueEdit.State, out var requestedState); - if (isValidState) - { - issueToModify.State = (IssueState)requestedState; - } - - return issueToModify.ToClientIssue(); + issue.Assignee = Server.Users.FirstOrDefault(u => u.Id == issueCreate.AssigneeId); } - } - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task EditAsync(IssueEdit issueEdit, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return Edit(issueEdit); + project.Issues.Add(issue); + return project.Issues.First(i => i.Iid == issue.Iid).ToClientIssue(); } + } + + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task CreateAsync(IssueCreate issueCreate, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Create(issueCreate); + } - public IEnumerable ResourceLabelEvents(int projectId, int issueIid) + public Models.Issue Edit(IssueEdit issueEdit) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var projectId = issueEdit.ProjectId; + var issueToModify = GetIssue(projectId, issueEdit.IssueId); + + if (issueEdit.AssigneeId.HasValue) { - var issue = GetIssue(projectId, issueIid); - return Server.ResourceLabelEvents.Get(issue.Id).Select(rle => rle.ToClientResourceLabelEvent()); + issueToModify.Assignee = GetUser(issueEdit.AssigneeId.Value); } - } - public GitLabCollectionResponse ResourceLabelEventsAsync(int projectId, int issueIid) - { - using (Context.BeginOperationScope()) - { - var issue = GetIssue(projectId, issueIid); - var resourceLabelEvents = Server.ResourceLabelEvents.Get(issue.Id); + var prevMilestone = issueToModify.Milestone; - return GitLabCollectionResponse.Create(resourceLabelEvents.Select(rle => rle.ToClientResourceLabelEvent())); + if (issueEdit.MilestoneId.HasValue) + { + issueToModify.Milestone = GetMilestone(projectId, issueEdit.MilestoneId.Value); + Server.ResourceMilestoneEvents.CreateResourceMilestoneEvents(Context.User, issueToModify.Id, prevMilestone, issueToModify.Milestone, "Issue"); } - } - public IEnumerable ResourceMilestoneEvents(int projectId, int issueIid) - { - using (Context.BeginOperationScope()) + issueToModify.Title = issueEdit.Title; + issueToModify.Description = issueEdit.Description; + + string[] labelsEdit; + + if (issueEdit.Labels is null) { - var issue = GetIssue(projectId, issueIid); - return Server.ResourceMilestoneEvents.Get(issue.Id).Select(rme => rme.ToClientResourceMilestoneEvent()); + labelsEdit = null; } - } - - public GitLabCollectionResponse ResourceMilestoneEventsAsync(int projectId, int issueIid) - { - using (Context.BeginOperationScope()) + else if (string.Equals(issueEdit.Labels, string.Empty, StringComparison.Ordinal)) { - var issue = GetIssue(projectId, issueIid); - var resourceMilestoneEvents = Server.ResourceMilestoneEvents.Get(issue.Id); - - return GitLabCollectionResponse.Create(resourceMilestoneEvents.Select(rme => rme.ToClientResourceMilestoneEvent())); + labelsEdit = Array.Empty(); } - } - - public IEnumerable ResourceStateEvents(int projectId, int issueIid) - { - using (Context.BeginOperationScope()) + else { - var issue = GetIssue(projectId, issueIid); - return Server.ResourceStateEvents.Get(issue.Id).Select(rle => rle.ToClientResourceStateEvent()); + labelsEdit = issueEdit.Labels.Split(','); } - } - public GitLabCollectionResponse ResourceStateEventsAsync(int projectId, int issueIid) - { - using (Context.BeginOperationScope()) + if (labelsEdit is not null) { - var issue = GetIssue(projectId, issueIid); - var resourceStateEvents = Server.ResourceStateEvents.Get(issue.Id); + Server.ResourceLabelEvents.CreateResourceLabelEvents(Context.User, issueToModify.Labels, labelsEdit, issueToModify.Id, "issue"); + issueToModify.Labels = labelsEdit; + } - return GitLabCollectionResponse.Create(resourceStateEvents.Select(rle => rle.ToClientResourceStateEvent())); + issueToModify.UpdatedAt = DateTimeOffset.UtcNow; + var isValidState = Enum.TryParse(issueEdit.State, out var requestedState); + if (isValidState) + { + issueToModify.State = (IssueState)requestedState; } - } - public IEnumerable RelatedTo(int projectId, int issueId) - { - throw new NotImplementedException(); + return issueToModify.ToClientIssue(); } + } - public GitLabCollectionResponse RelatedToAsync(int projectId, int issueIid) + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task EditAsync(IssueEdit issueEdit, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Edit(issueEdit); + } + + public IEnumerable ResourceLabelEvents(int projectId, int issueIid) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var issue = GetIssue(projectId, issueIid); + return Server.ResourceLabelEvents.Get(issue.Id).Select(rle => rle.ToClientResourceLabelEvent()); } + } - public IEnumerable ClosedBy(int projectId, int issueId) + public GitLabCollectionResponse ResourceLabelEventsAsync(int projectId, int issueIid) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var issue = GetIssue(projectId, issueIid); + var resourceLabelEvents = Server.ResourceLabelEvents.Get(issue.Id); + + return GitLabCollectionResponse.Create(resourceLabelEvents.Select(rle => rle.ToClientResourceLabelEvent())); } + } - public GitLabCollectionResponse ClosedByAsync(int projectId, int issueIid) + public IEnumerable ResourceMilestoneEvents(int projectId, int issueIid) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var issue = GetIssue(projectId, issueIid); + return Server.ResourceMilestoneEvents.Get(issue.Id).Select(rme => rme.ToClientResourceMilestoneEvent()); } + } - public Task TimeStatsAsync(int projectId, int issueIid, CancellationToken cancellationToken = default) + public GitLabCollectionResponse ResourceMilestoneEventsAsync(int projectId, int issueIid) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var issue = GetIssue(projectId, issueIid); + var resourceMilestoneEvents = Server.ResourceMilestoneEvents.Get(issue.Id); + + return GitLabCollectionResponse.Create(resourceMilestoneEvents.Select(rme => rme.ToClientResourceMilestoneEvent())); } + } - public Task CloneAsync(int projectId, int issueIid, IssueClone issueClone, CancellationToken cancellationToken = default) + public IEnumerable ResourceStateEvents(int projectId, int issueIid) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var issue = GetIssue(projectId, issueIid); + return Server.ResourceStateEvents.Get(issue.Id).Select(rle => rle.ToClientResourceStateEvent()); } + } - public IEnumerable ForProject(int projectId) + public GitLabCollectionResponse ResourceStateEventsAsync(int projectId, int issueIid) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(projectId, ProjectPermission.View); + var issue = GetIssue(projectId, issueIid); + var resourceStateEvents = Server.ResourceStateEvents.Get(issue.Id); - return project - .Issues - .Where(i => i.CanUserViewIssue(Context.User)) - .Select(i => i.ToClientIssue()) - .ToList(); - } + return GitLabCollectionResponse.Create(resourceStateEvents.Select(rle => rle.ToClientResourceStateEvent())); } + } + + public IEnumerable RelatedTo(int projectId, int issueId) + { + throw new NotImplementedException(); + } - public GitLabCollectionResponse ForProjectAsync(int projectId) + public GitLabCollectionResponse RelatedToAsync(int projectId, int issueIid) + { + throw new NotImplementedException(); + } + + public IEnumerable ClosedBy(int projectId, int issueId) + { + throw new NotImplementedException(); + } + + public GitLabCollectionResponse ClosedByAsync(int projectId, int issueIid) + { + throw new NotImplementedException(); + } + + public Task TimeStatsAsync(int projectId, int issueIid, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public Task CloneAsync(int projectId, int issueIid, IssueClone issueClone, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public IEnumerable ForProject(int projectId) + { + using (Context.BeginOperationScope()) { - return GitLabCollectionResponse.Create(ForProject(projectId)); + var project = GetProject(projectId, ProjectPermission.View); + + return project + .Issues + .Where(i => i.CanUserViewIssue(Context.User)) + .Select(i => i.ToClientIssue()) + .ToList(); } + } + + public GitLabCollectionResponse ForProjectAsync(int projectId) + { + return GitLabCollectionResponse.Create(ForProject(projectId)); + } + + public GitLabCollectionResponse ForGroupsAsync(int groupId) + { + throw new NotImplementedException(); + } - public GitLabCollectionResponse ForGroupsAsync(int groupId) + public GitLabCollectionResponse ForGroupsAsync(int groupId, IssueQuery query) + { + throw new NotImplementedException(); + } + + public Models.Issue Get(int projectId, int issueId) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var project = GetProject(projectId, ProjectPermission.View); + return project.Issues.FirstOrDefault(i => i.Iid == issueId && + i.CanUserViewIssue(Context.User))? + .ToClientIssue() ?? throw new GitLabNotFoundException(); } + } - public GitLabCollectionResponse ForGroupsAsync(int groupId, IssueQuery query) + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task GetAsync(int projectId, int issueId, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Get(projectId, issueId); + } + + public IEnumerable Get(IssueQuery query) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var viewableProjects = Server.AllProjects.Where(p => p.CanUserViewProject(Context.User)); + var issues = viewableProjects.SelectMany(p => p.Issues.Where(i => i.CanUserViewIssue(Context.User))); + return FilterByQuery(issues, query).Select(i => i.ToClientIssue()).ToList(); } + } + + public GitLabCollectionResponse GetAsync(IssueQuery query) + { + return GitLabCollectionResponse.Create(Get(query)); + } - public Models.Issue Get(int projectId, int issueId) + public IEnumerable Get(int projectId, IssueQuery query) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(projectId, ProjectPermission.View); - return project.Issues.FirstOrDefault(i => i.Iid == issueId && - i.CanUserViewIssue(Context.User))? - .ToClientIssue() ?? throw new GitLabNotFoundException(); - } + var project = GetProject(projectId, ProjectPermission.View); + var issues = project.Issues.Where(i => i.CanUserViewIssue(Context.User)); + return FilterByQuery(issues, query).Select(i => i.ToClientIssue()).ToList(); } + } - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task GetAsync(int projectId, int issueId, CancellationToken cancellationToken = default) + public GitLabCollectionResponse GetAsync(int projectId, IssueQuery query) + { + return GitLabCollectionResponse.Create(Get(projectId, query)); + } + + public async Task GetByIdAsync(int issueId, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return GetById(issueId); + } + + public GitLabCollectionResponse LinkedToAsync(int projectId, int issueId) + { + throw new NotImplementedException(); + } + + public bool CreateLinkBetweenIssues(int sourceProjectId, int sourceIssueId, int targetProjectId, int targetIssueId) + { + throw new NotImplementedException(); + } + + public Models.Issue GetById(int issueId) + { + using (Context.BeginOperationScope()) { - await Task.Yield(); - return Get(projectId, issueId); + var viewableProjects = Server.AllProjects.Where(p => p.CanUserViewProject(Context.User)); + return viewableProjects + .SelectMany(p => p.Issues.Where(i => i.CanUserViewIssue(Context.User) && i.Id == issueId)) + .FirstOrDefault()? + .ToClientIssue() ?? throw new GitLabNotFoundException(); } + } - public IEnumerable Get(IssueQuery query) + private IEnumerable FilterByQuery(IEnumerable issues, IssueQuery query) + { + if (query.State != null) { - using (Context.BeginOperationScope()) + var isValidState = Enum.TryParse(query.State.ToString(), out var requestedState); + if (isValidState) { - var viewableProjects = Server.AllProjects.Where(p => p.CanUserViewProject(Context.User)); - var issues = viewableProjects.SelectMany(p => p.Issues.Where(i => i.CanUserViewIssue(Context.User))); - return FilterByQuery(issues, query).Select(i => i.ToClientIssue()).ToList(); + issues = issues.Where(i => i.State == requestedState); } } - public GitLabCollectionResponse GetAsync(IssueQuery query) + if (query.Milestone != null) { - return GitLabCollectionResponse.Create(Get(query)); + issues = issues.Where(i => string.Equals(i.Milestone?.Title, query.Milestone, StringComparison.Ordinal)); } - public IEnumerable Get(int projectId, IssueQuery query) + if (!string.IsNullOrEmpty(query.Labels)) { - using (Context.BeginOperationScope()) + foreach (var label in query.Labels.Split(',')) { - var project = GetProject(projectId, ProjectPermission.View); - var issues = project.Issues.Where(i => i.CanUserViewIssue(Context.User)); - return FilterByQuery(issues, query).Select(i => i.ToClientIssue()).ToList(); + issues = issues.Where(i => i.Labels.Contains(label, StringComparer.Ordinal)); } } - public GitLabCollectionResponse GetAsync(int projectId, IssueQuery query) + if (query.CreatedAfter != null) { - return GitLabCollectionResponse.Create(Get(projectId, query)); + issues = issues.Where(i => i.CreatedAt > query.CreatedAfter); } - public async Task GetByIdAsync(int issueId, CancellationToken cancellationToken = default) + if (query.CreatedBefore != null) { - await Task.Yield(); - return GetById(issueId); + issues = issues.Where(i => i.CreatedAt < query.CreatedBefore); } - public GitLabCollectionResponse LinkedToAsync(int projectId, int issueId) + if (query.UpdatedAfter != null) { - throw new NotImplementedException(); + issues = issues.Where(i => i.UpdatedAt > query.UpdatedAfter); } - public bool CreateLinkBetweenIssues(int sourceProjectId, int sourceIssueId, int targetProjectId, int targetIssueId) + if (query.UpdatedBefore != null) { - throw new NotImplementedException(); + issues = issues.Where(i => i.UpdatedAt < query.UpdatedBefore); } - public Models.Issue GetById(int issueId) + if (query.Scope != null) { - using (Context.BeginOperationScope()) + var userId = Context.User.Id; + switch (query.Scope) { - var viewableProjects = Server.AllProjects.Where(p => p.CanUserViewProject(Context.User)); - return viewableProjects - .SelectMany(p => p.Issues.Where(i => i.CanUserViewIssue(Context.User) && i.Id == issueId)) - .FirstOrDefault()? - .ToClientIssue() ?? throw new GitLabNotFoundException(); + case "created_by_me": + case "created-by-me": + issues = issues.Where(i => i.Author.Id == userId); + break; + case "assigned_to_me": + case "assigned-to-me": + issues = issues.Where(i => i.Assignees?.Any(x => x.Id == userId) == true); + break; + case "all": + break; + default: + throw new NotSupportedException($"Scope '{query.Scope}' is not supported"); } } - private IEnumerable FilterByQuery(IEnumerable issues, IssueQuery query) + if (query.AuthorId != null) { - if (query.State != null) - { - var isValidState = Enum.TryParse(query.State.ToString(), out var requestedState); - if (isValidState) - { - issues = issues.Where(i => i.State == requestedState); - } - } - - if (query.Milestone != null) - { - issues = issues.Where(i => string.Equals(i.Milestone?.Title, query.Milestone, StringComparison.Ordinal)); - } - - if (!string.IsNullOrEmpty(query.Labels)) - { - foreach (var label in query.Labels.Split(',')) - { - issues = issues.Where(i => i.Labels.Contains(label, StringComparer.Ordinal)); - } - } - - if (query.CreatedAfter != null) - { - issues = issues.Where(i => i.CreatedAt > query.CreatedAfter); - } - - if (query.CreatedBefore != null) - { - issues = issues.Where(i => i.CreatedAt < query.CreatedBefore); - } - - if (query.UpdatedAfter != null) - { - issues = issues.Where(i => i.UpdatedAt > query.UpdatedAfter); - } - - if (query.UpdatedBefore != null) - { - issues = issues.Where(i => i.UpdatedAt < query.UpdatedBefore); - } - - if (query.Scope != null) - { - var userId = Context.User.Id; - switch (query.Scope) - { - case "created_by_me": - case "created-by-me": - issues = issues.Where(i => i.Author.Id == userId); - break; - case "assigned_to_me": - case "assigned-to-me": - issues = issues.Where(i => i.Assignees?.Any(x => x.Id == userId) == true); - break; - case "all": - break; - default: - throw new NotSupportedException($"Scope '{query.Scope}' is not supported"); - } - } - - if (query.AuthorId != null) - { - issues = issues.Where(i => i.Author.Id == query.AuthorId); - } - - if (query.UpdatedBefore != null) - { - issues = issues.Where(i => i.UpdatedAt < query.UpdatedBefore); - } - - if (query.AssigneeId != null) - { - var isUserId = int.TryParse(query.AssigneeId.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var userId); - - if (isUserId) - { - issues = issues.Where(i => i.Assignee != null && i.Assignee.Id == userId); - } - else if (string.Equals(query.AssigneeId.ToString(), "None", StringComparison.OrdinalIgnoreCase)) - { - issues = issues.Where(i => i.Assignee == null); - } - } - - if (query.Milestone != null) - { - issues = issues.Where(i => string.Equals(i.Milestone?.Title, query.Milestone, StringComparison.Ordinal)); - } - - if (query.Confidential != null) - { - issues = issues.Where(i => i.Confidential == query.Confidential.Value); - } + issues = issues.Where(i => i.Author.Id == query.AuthorId); + } - if (query.Search != null) - { - issues = issues - .Where(i => i.Title.Contains(query.Search, StringComparison.OrdinalIgnoreCase) - || i.Description.Contains(query.Search, StringComparison.OrdinalIgnoreCase)); - } + if (query.UpdatedBefore != null) + { + issues = issues.Where(i => i.UpdatedAt < query.UpdatedBefore); + } - if (query.PerPage != null) - { - issues = issues.Take(query.PerPage.Value); - } + if (query.AssigneeId != null) + { + var isUserId = int.TryParse(query.AssigneeId.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var userId); - if (query.OrderBy != null) + if (isUserId) { - issues = query.OrderBy switch - { - "created_at" => issues.OrderBy(i => i.CreatedAt), - "updated_at" => issues.OrderBy(i => i.UpdatedAt), - _ => throw new NotSupportedException($"OrderBy '{query.OrderBy}' is not supported"), - }; + issues = issues.Where(i => i.Assignee != null && i.Assignee.Id == userId); } - - if (string.Equals(query.Sort, "asc", StringComparison.Ordinal)) + else if (string.Equals(query.AssigneeId.ToString(), "None", StringComparison.OrdinalIgnoreCase)) { - issues = issues.Reverse(); + issues = issues.Where(i => i.Assignee == null); } + } - return issues; + if (query.Milestone != null) + { + issues = issues.Where(i => string.Equals(i.Milestone?.Title, query.Milestone, StringComparison.Ordinal)); } - private void CreateResourceLabelEvents(string[] previousLabels, string[] newLabels, int resourceId) + if (query.Confidential != null) { - var currentUser = Context.User; + issues = issues.Where(i => i.Confidential == query.Confidential.Value); + } - foreach (var label in previousLabels) - { - if (!newLabels.Any(l => string.Equals(l, label, StringComparison.OrdinalIgnoreCase))) - { - Server.ResourceLabelEvents.Add(new ResourceLabelEvent() - { - Action = ResourceLabelEventAction.Remove, - Label = new Label() { Name = label }, - ResourceId = resourceId, - CreatedAt = DateTime.UtcNow, - Id = Server.GetNewResourceLabelEventId(), - User = new Author() - { - Id = currentUser.Id, - Email = currentUser.Email, - AvatarUrl = currentUser.AvatarUrl, - Name = currentUser.Name, - State = currentUser.State.ToString(), - Username = currentUser.UserName, - CreatedAt = currentUser.CreatedAt, - WebUrl = currentUser.WebUrl, - }, - ResourceType = "issue", - }); - } - } + if (query.Search != null) + { + issues = issues + .Where(i => i.Title.Contains(query.Search, StringComparison.OrdinalIgnoreCase) + || i.Description.Contains(query.Search, StringComparison.OrdinalIgnoreCase)); + } - foreach (var label in newLabels) - { - if (!previousLabels.Any(l => string.Equals(l, label, StringComparison.OrdinalIgnoreCase))) - { - Server.ResourceLabelEvents.Add(new ResourceLabelEvent() - { - Action = ResourceLabelEventAction.Add, - Label = new Label() { Name = label }, - ResourceId = resourceId, - CreatedAt = DateTime.UtcNow, - Id = Server.GetNewResourceLabelEventId(), - User = new Author() - { - Id = currentUser.Id, - Email = currentUser.Email, - AvatarUrl = currentUser.AvatarUrl, - Name = currentUser.Name, - State = currentUser.State.ToString(), - Username = currentUser.UserName, - CreatedAt = currentUser.CreatedAt, - WebUrl = currentUser.WebUrl, - }, - ResourceType = "issue", - }); - } - } + if (query.PerPage != null) + { + issues = issues.Take(query.PerPage.Value); } - private void CreateResourceMilestoneEvents(int resourceId, Milestone previousMilestone, Milestone newMilestone) + if (query.OrderBy != null) { - if (previousMilestone is null) + issues = query.OrderBy switch { - CreateResourceMilestoneEvent(resourceId, newMilestone, ResourceMilestoneEventAction.Add); - } - else if (newMilestone is not null && previousMilestone is not null) - { - if (newMilestone.Id != previousMilestone.Id) - { - CreateResourceMilestoneEvent(resourceId, previousMilestone, ResourceMilestoneEventAction.Remove); - } - - CreateResourceMilestoneEvent(resourceId, newMilestone, ResourceMilestoneEventAction.Add); - } + "created_at" => issues.OrderBy(i => i.CreatedAt), + "updated_at" => issues.OrderBy(i => i.UpdatedAt), + _ => throw new NotSupportedException($"OrderBy '{query.OrderBy}' is not supported"), + }; } - private void CreateResourceMilestoneEvent(int resourceId, Milestone milestone, ResourceMilestoneEventAction action) + if (string.Equals(query.Sort, "asc", StringComparison.Ordinal)) { - var currentUser = Context.User; - Server.ResourceMilestoneEvents.Add(new ResourceMilestoneEvent() - { - Action = action, - Milestone = milestone, - ResourceId = resourceId, - CreatedAt = DateTime.UtcNow, - Id = Server.GetNewResourceLabelEventId(), - User = new Author() - { - Id = currentUser.Id, - Email = currentUser.Email, - AvatarUrl = currentUser.AvatarUrl, - Name = currentUser.Name, - State = currentUser.State.ToString(), - Username = currentUser.UserName, - CreatedAt = currentUser.CreatedAt, - WebUrl = currentUser.WebUrl, - }, - ResourceType = "issue", - }); + issues = issues.Reverse(); } + + return issues; } } diff --git a/NGitLab.Mock/Clients/JobClient.cs b/NGitLab.Mock/Clients/JobClient.cs index 913eb43f..89f082c6 100644 --- a/NGitLab.Mock/Clients/JobClient.cs +++ b/NGitLab.Mock/Clients/JobClient.cs @@ -7,133 +7,142 @@ using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class JobClient : ClientBase, IJobClient { - internal sealed class JobClient : ClientBase, IJobClient + private readonly int _projectId; + + public JobClient(ClientContext context, ProjectId projectId) + : base(context) { - private readonly int _projectId; + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public JobClient(ClientContext context, int projectId) - : base(context) + public Models.Job Get(int jobId) + { + using (Context.BeginOperationScope()) { - _projectId = projectId; + var project = GetProject(_projectId, ProjectPermission.View); + var job = project.Jobs.GetById(jobId); + + if (job == null) + throw new GitLabNotFoundException(); + + return job.ToJobClient(); } + } - public Models.Job Get(int jobId) - { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var job = project.Jobs.GetById(jobId); + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task GetAsync(int jobId, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Get(jobId); + } - if (job == null) - throw new GitLabNotFoundException(); + public byte[] GetJobArtifacts(int jobId) + { + throw new NotImplementedException(); + } - return job.ToJobClient(); - } - } + public byte[] GetJobArtifact(int jobId, string path) + { + throw new NotImplementedException(); + } - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task GetAsync(int jobId, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return Get(jobId); - } + public byte[] GetJobArtifact(JobArtifactQuery query) + { + throw new NotImplementedException(); + } - public byte[] GetJobArtifacts(int jobId) - { - throw new NotImplementedException(); - } + public IEnumerable GetJobs(JobScopeMask scope) + { + return GetJobs(new JobQuery { Scope = scope }); + } - public IEnumerable GetJobs(JobScopeMask scope) + public IEnumerable GetJobs(JobQuery query) + { + using (Context.BeginOperationScope()) { - return GetJobs(new JobQuery { Scope = scope }); - } + var project = GetProject(_projectId, ProjectPermission.View); - public IEnumerable GetJobs(JobQuery query) - { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); + if (query.Scope == JobScopeMask.All) + return project.Jobs.Select(j => j.ToJobClient()); - if (query.Scope == JobScopeMask.All) - return project.Jobs.Select(j => j.ToJobClient()); - - var scopes = new List(); - foreach (Enum value in Enum.GetValues(query.Scope.GetType())) + var scopes = new List(); + foreach (Enum value in Enum.GetValues(query.Scope.GetType())) + { + if (query.Scope.HasFlag(value)) { - if (query.Scope.HasFlag(value)) - { - scopes.Add(value.ToString()); - } + scopes.Add(value.ToString()); } - - var jobs = project.Jobs.Where(j => scopes.Any(scope => string.Equals(j.Status.ToString(), scope, StringComparison.OrdinalIgnoreCase))); - return jobs.Select(j => j.ToJobClient()).ToList(); } - } - public GitLabCollectionResponse GetJobsAsync(JobQuery query) - { - return GitLabCollectionResponse.Create(GetJobs(query)); + var jobs = project.Jobs.Where(j => scopes.Any(scope => string.Equals(j.Status.ToString(), scope, StringComparison.OrdinalIgnoreCase))); + return jobs.Select(j => j.ToJobClient()).ToList(); } + } + + public GitLabCollectionResponse GetJobsAsync(JobQuery query) + { + return GitLabCollectionResponse.Create(GetJobs(query)); + } - public string GetTrace(int jobId) + public string GetTrace(int jobId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var job = project.Jobs.GetById(jobId); + var project = GetProject(_projectId, ProjectPermission.View); + var job = project.Jobs.GetById(jobId); - if (job == null) - throw new GitLabNotFoundException(); + if (job == null) + throw new GitLabNotFoundException(); - return job.Trace; - } + return job.Trace; } + } - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task GetTraceAsync(int jobId, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return GetTrace(jobId); - } + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task GetTraceAsync(int jobId, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return GetTrace(jobId); + } - public Models.Job RunAction(int jobId, JobAction action) + public Models.Job RunAction(int jobId, JobAction action) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var job = project.Jobs.GetById(jobId); - - switch (action) - { - case JobAction.Cancel: - job.Status = JobStatus.Canceled; - break; - case JobAction.Erase: - job.Artifacts = null; - job.Trace = null; - break; - case JobAction.Play: - job.Status = JobStatus.Running; - break; - case JobAction.Retry: - var retryJob = job.Clone(); - retryJob.Status = JobStatus.Running; - project.Jobs.Add(retryJob, project.Pipelines.GetById(job.Pipeline.Id)); - return retryJob.ToJobClient(); - } + var project = GetProject(_projectId, ProjectPermission.View); + var job = project.Jobs.GetById(jobId); - return job.ToJobClient(); + switch (action) + { + case JobAction.Cancel: + job.Status = JobStatus.Canceled; + break; + case JobAction.Erase: + job.Artifacts = null; + job.Trace = null; + break; + case JobAction.Play: + job.Status = JobStatus.Running; + break; + case JobAction.Retry: + var retryJob = job.Clone(); + retryJob.Status = JobStatus.Running; + project.Jobs.Add(retryJob, project.Pipelines.GetById(job.Pipeline.Id)); + return retryJob.ToJobClient(); } - } - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task RunActionAsync(int jobId, JobAction action, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return RunAction(jobId, action); + return job.ToJobClient(); } } + + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task RunActionAsync(int jobId, JobAction action, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return RunAction(jobId, action); + } } diff --git a/NGitLab.Mock/Clients/LabelClient.cs b/NGitLab.Mock/Clients/LabelClient.cs index 6a266248..6f172ee6 100644 --- a/NGitLab.Mock/Clients/LabelClient.cs +++ b/NGitLab.Mock/Clients/LabelClient.cs @@ -4,197 +4,196 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class LabelClient : ClientBase, ILabelClient { - internal sealed class LabelClient : ClientBase, ILabelClient + public LabelClient(ClientContext context) + : base(context) { - public LabelClient(ClientContext context) - : base(context) - { - } + } - public Models.Label CreateProjectLabel(int projectId, ProjectLabelCreate label) + public Models.Label CreateProjectLabel(int projectId, ProjectLabelCreate label) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(projectId, ProjectPermission.Edit); - return project.Labels.Add(label.Name, label.Color, label.Description).ToClientLabel(); - } + var project = GetProject(projectId, ProjectPermission.Edit); + return project.Labels.Add(label.Name, label.Color, label.Description).ToClientLabel(); } + } - [EditorBrowsable(EditorBrowsableState.Never)] - public Models.Label Create(LabelCreate label) + [EditorBrowsable(EditorBrowsableState.Never)] + public Models.Label Create(LabelCreate label) + { + return CreateProjectLabel(label.Id, new ProjectLabelCreate { - return CreateProjectLabel(label.Id, new ProjectLabelCreate - { - Name = label.Name, - Color = label.Color, - Description = label.Description, - }); - } + Name = label.Name, + Color = label.Color, + Description = label.Description, + }); + } - public Models.Label CreateGroupLabel(int groupId, GroupLabelCreate label) + public Models.Label CreateGroupLabel(int groupId, GroupLabelCreate label) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var group = GetGroup(groupId, GroupPermission.Edit); - return group.Labels.Add(label.Name, label.Color, label.Description).ToClientLabel(); - } + var group = GetGroup(groupId, GroupPermission.Edit); + return group.Labels.Add(label.Name, label.Color, label.Description).ToClientLabel(); } + } - [EditorBrowsable(EditorBrowsableState.Never)] - public Models.Label CreateGroupLabel(LabelCreate label) + [EditorBrowsable(EditorBrowsableState.Never)] + public Models.Label CreateGroupLabel(LabelCreate label) + { + return CreateGroupLabel(label.Id, new GroupLabelCreate { - return CreateGroupLabel(label.Id, new GroupLabelCreate - { - Name = label.Name, - Color = label.Color, - Description = label.Description, - }); - } + Name = label.Name, + Color = label.Color, + Description = label.Description, + }); + } - public Models.Label DeleteProjectLabel(int projectId, ProjectLabelDelete label) + public Models.Label DeleteProjectLabel(int projectId, ProjectLabelDelete label) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(projectId, ProjectPermission.Edit); - var l = FindLabel(project.Labels, label.Name) ?? throw new GitLabNotFoundException($"Cannot find label '{label.Name}'"); - project.Labels.Remove(l); - return l.ToClientLabel(); - } + var project = GetProject(projectId, ProjectPermission.Edit); + var l = FindLabel(project.Labels, label.Name) ?? throw new GitLabNotFoundException($"Cannot find label '{label.Name}'"); + project.Labels.Remove(l); + return l.ToClientLabel(); } + } - [EditorBrowsable(EditorBrowsableState.Never)] - public Models.Label Delete(LabelDelete label) + [EditorBrowsable(EditorBrowsableState.Never)] + public Models.Label Delete(LabelDelete label) + { + return DeleteProjectLabel(label.Id, new ProjectLabelDelete { - return DeleteProjectLabel(label.Id, new ProjectLabelDelete - { - Id = label.Id, - Name = label.Name, - }); - } + Id = label.Id, + Name = label.Name, + }); + } - public Models.Label EditProjectLabel(int projectId, ProjectLabelEdit label) + public Models.Label EditProjectLabel(int projectId, ProjectLabelEdit label) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(projectId, ProjectPermission.Edit); - var l = FindLabel(project.Labels, label.Name) ?? throw new GitLabNotFoundException($"Cannot find label '{label.Name}'"); + var project = GetProject(projectId, ProjectPermission.Edit); + var l = FindLabel(project.Labels, label.Name) ?? throw new GitLabNotFoundException($"Cannot find label '{label.Name}'"); - if (!string.IsNullOrEmpty(label.NewName)) - { - l.Name = label.NewName; - } - - if (!string.IsNullOrEmpty(label.Color)) - { - l.Color = label.Color; - } - - if (label.Description != null) - { - l.Description = label.Description; - } - - return l.ToClientLabel(); + if (!string.IsNullOrEmpty(label.NewName)) + { + l.Name = label.NewName; } - } - [EditorBrowsable(EditorBrowsableState.Never)] - public Models.Label Edit(LabelEdit label) - { - return EditProjectLabel(label.Id, new ProjectLabelEdit + if (!string.IsNullOrEmpty(label.Color)) { - Name = label.Name, - NewName = label.NewName, - Color = label.Color, - Description = label.Description, - }); - } + l.Color = label.Color; + } - public Models.Label EditGroupLabel(int groupId, GroupLabelEdit label) - { - using (Context.BeginOperationScope()) + if (label.Description != null) { - var group = GetGroup(groupId, GroupPermission.Edit); - var l = FindLabel(group.Labels, label.Name) ?? throw new GitLabNotFoundException($"Cannot find label '{label.Name}'"); + l.Description = label.Description; + } - if (!string.IsNullOrEmpty(label.NewName)) - { - l.Name = label.NewName; - } + return l.ToClientLabel(); + } + } - if (!string.IsNullOrEmpty(label.Color)) - { - l.Color = label.Color; - } + [EditorBrowsable(EditorBrowsableState.Never)] + public Models.Label Edit(LabelEdit label) + { + return EditProjectLabel(label.Id, new ProjectLabelEdit + { + Name = label.Name, + NewName = label.NewName, + Color = label.Color, + Description = label.Description, + }); + } - if (label.Description != null) - { - l.Description = label.Description; - } + public Models.Label EditGroupLabel(int groupId, GroupLabelEdit label) + { + using (Context.BeginOperationScope()) + { + var group = GetGroup(groupId, GroupPermission.Edit); + var l = FindLabel(group.Labels, label.Name) ?? throw new GitLabNotFoundException($"Cannot find label '{label.Name}'"); - return l.ToClientLabel(); + if (!string.IsNullOrEmpty(label.NewName)) + { + l.Name = label.NewName; } - } - [EditorBrowsable(EditorBrowsableState.Never)] - public Models.Label EditGroupLabel(LabelEdit label) - { - return EditGroupLabel(label.Id, new GroupLabelEdit + if (!string.IsNullOrEmpty(label.Color)) { - Name = label.Name, - NewName = label.NewName, - Color = label.Color, - Description = label.Description, - }); - } + l.Color = label.Color; + } - public IEnumerable ForGroup(int groupId) - { - using (Context.BeginOperationScope()) + if (label.Description != null) { - var group = GetGroup(groupId, GroupPermission.View); - return group.Labels.Select(x => x.ToClientLabel()); + l.Description = label.Description; } + + return l.ToClientLabel(); } + } - public IEnumerable ForProject(int projectId) + [EditorBrowsable(EditorBrowsableState.Never)] + public Models.Label EditGroupLabel(LabelEdit label) + { + return EditGroupLabel(label.Id, new GroupLabelEdit { - using (Context.BeginOperationScope()) - { - var project = GetProject(projectId, ProjectPermission.View); - return project.Labels.Select(x => x.ToClientLabel()); - } - } + Name = label.Name, + NewName = label.NewName, + Color = label.Color, + Description = label.Description, + }); + } - public Models.Label GetGroupLabel(int groupId, string name) + public IEnumerable ForGroup(int groupId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var group = GetGroup(groupId, GroupPermission.View); - return FindLabel(group.Labels, name)?.ToClientLabel(); - } + var group = GetGroup(groupId, GroupPermission.View); + return group.Labels.Select(x => x.ToClientLabel()); } + } - public Models.Label GetProjectLabel(int projectId, string name) + public IEnumerable ForProject(int projectId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(projectId, ProjectPermission.View); - return FindLabel(project.Labels, name)?.ToClientLabel(); - } + var project = GetProject(projectId, ProjectPermission.View); + return project.Labels.Select(x => x.ToClientLabel()); } + } - [EditorBrowsable(EditorBrowsableState.Never)] - public Models.Label GetLabel(int projectId, string name) + public Models.Label GetGroupLabel(int groupId, string name) + { + using (Context.BeginOperationScope()) { - return GetProjectLabel(projectId, name); + var group = GetGroup(groupId, GroupPermission.View); + return FindLabel(group.Labels, name)?.ToClientLabel(); } + } - private static Label FindLabel(LabelsCollection collection, string name) + public Models.Label GetProjectLabel(int projectId, string name) + { + using (Context.BeginOperationScope()) { - return collection.FirstOrDefault(x => x.Name.Equals(name, StringComparison.Ordinal)); + var project = GetProject(projectId, ProjectPermission.View); + return FindLabel(project.Labels, name)?.ToClientLabel(); } } + + [EditorBrowsable(EditorBrowsableState.Never)] + public Models.Label GetLabel(int projectId, string name) + { + return GetProjectLabel(projectId, name); + } + + private static Label FindLabel(LabelsCollection collection, string name) + { + return collection.FirstOrDefault(x => x.Name.Equals(name, StringComparison.Ordinal)); + } } diff --git a/NGitLab.Mock/Clients/LibGit2SharpExtensions.cs b/NGitLab.Mock/Clients/LibGit2SharpExtensions.cs index 6b86a8c8..3b494f6d 100644 --- a/NGitLab.Mock/Clients/LibGit2SharpExtensions.cs +++ b/NGitLab.Mock/Clients/LibGit2SharpExtensions.cs @@ -3,85 +3,85 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +public static class LibGit2SharpExtensions { - public static class LibGit2SharpExtensions + public static Commit ToCommitClient(this LibGit2Sharp.Commit commit, Project project) { - public static Commit ToCommitClient(this LibGit2Sharp.Commit commit, Project project) + var commitSha = new Sha1(commit.Sha); + var commitInfo = project.CommitInfos.SingleOrDefault(c => commitSha.Equals(new Sha1(c.Sha))); + return new Commit { - var commitInfo = project.CommitInfos.SingleOrDefault(c => string.Equals(c.Sha, commit.Sha, StringComparison.Ordinal)); - return new Commit - { - AuthoredDate = commit.Author.When.UtcDateTime, - AuthorEmail = commit.Author.Email, - AuthorName = commit.Author.Name, - CommittedDate = commit.Committer.When.UtcDateTime, - CommitterEmail = commit.Committer.Email, - CommitterName = commit.Committer.Name, - CreatedAt = commit.Committer.When.UtcDateTime, - Id = new Sha1(commit.Sha), - Message = commit.Message, - ShortId = commit.Sha.Substring(0, 8), - Title = commit.MessageShort, - Parents = commit.Parents.Select(p => new Sha1(p.Sha)).ToArray(), - Status = commitInfo?.Status ?? "success", - WebUrl = $"{project.WebUrl}/-/commits/{commit.Sha}", - }; - } + AuthoredDate = commit.Author.When.UtcDateTime, + AuthorEmail = commit.Author.Email, + AuthorName = commit.Author.Name, + CommittedDate = commit.Committer.When.UtcDateTime, + CommitterEmail = commit.Committer.Email, + CommitterName = commit.Committer.Name, + CreatedAt = commit.Committer.When.UtcDateTime, + Id = new Sha1(commit.Sha), + Message = commit.Message, + ShortId = commit.Sha.Substring(0, 8), + Title = commit.MessageShort, + Parents = commit.Parents.Select(p => new Sha1(p.Sha)).ToArray(), + Status = commitInfo?.Status ?? "success", + WebUrl = $"{project.WebUrl}/-/commits/{commit.Sha}", + }; + } - public static Branch ToBranchClient(this LibGit2Sharp.Branch branch, Project project) + public static Branch ToBranchClient(this LibGit2Sharp.Branch branch, Project project) + { + var commit = branch.Tip; + return new Branch { - var commit = branch.Tip; - return new Branch - { - CanPush = true, - Protected = false, - DevelopersCanMerge = true, - DevelopersCanPush = true, - Merged = false, - Name = branch.FriendlyName, - Default = string.Equals(branch.FriendlyName, project.DefaultBranch, StringComparison.Ordinal), - Commit = ToCommitInfo(commit), - }; - } + CanPush = true, + Protected = false, + DevelopersCanMerge = true, + DevelopersCanPush = true, + Merged = false, + Name = branch.FriendlyName, + Default = string.Equals(branch.FriendlyName, project.DefaultBranch, StringComparison.Ordinal), + Commit = ToCommitInfo(commit), + }; + } + + public static Models.CommitInfo ToCommitInfo(this LibGit2Sharp.Commit commit) + { + return new Models.CommitInfo + { + Id = new Sha1(commit.Sha), + AuthorName = commit.Author.Name, + AuthorEmail = commit.Author.Email, + AuthoredDate = commit.Author.When.UtcDateTime, + CommitterName = commit.Committer.Name, + CommitterEmail = commit.Committer.Email, + CommittedDate = commit.Committer.When.UtcDateTime, + Message = commit.Message, + Parents = commit.Parents.Select(c => new Sha1(c.Sha)).ToArray(), + }; + } - public static Models.CommitInfo ToCommitInfo(this LibGit2Sharp.Commit commit) + internal static LibGit2Sharp.Commit GetLastCommitForFileChanges(this LibGit2Sharp.Repository repository, string filePath) + { + try { - return new Models.CommitInfo - { - Id = new Sha1(commit.Sha), - AuthorName = commit.Author.Name, - AuthorEmail = commit.Author.Email, - AuthoredDate = commit.Author.When.UtcDateTime, - CommitterName = commit.Committer.Name, - CommitterEmail = commit.Committer.Email, - CommittedDate = commit.Committer.When.UtcDateTime, - Message = commit.Message, - Parents = commit.Parents.Select(c => new Sha1(c.Sha)).ToArray(), - }; + return repository.Commits.QueryBy(filePath).FirstOrDefault()?.Commit; } - - internal static LibGit2Sharp.Commit GetLastCommitForFileChanges(this LibGit2Sharp.Repository repository, string filePath) + catch (KeyNotFoundException) { - try - { - return repository.Commits.QueryBy(filePath).FirstOrDefault()?.Commit; - } - catch (KeyNotFoundException) - { - // LibGit2Sharp sometimes fails with the following exception - // System.Collections.Generic.KeyNotFoundException: The given key '1d08df45e551942eaa70d9f5ab6f5f7665a3f5b3' was not present in the dictionary. - // at System.Collections.Generic.Dictionary`2.get_Item(TKey key) - // at LibGit2Sharp.Core.FileHistory.FullHistory(IRepository repo, String path, CommitFilter filter)+MoveNext() in /_/LibGit2Sharp/Core/FileHistory.cs:line 120 - // at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found) - // at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source) - // at NGitLab.Mock.Clients.LibGit2SharpExtensions.GetLastCommitForFileChanges(Repository repository, String filePath) in /_/NGitLab.Mock/Clients/LibGit2SharpExtensions.cs:line 65 - // at NGitLab.Mock.Repository.GetFile(String filePath, String ref) in /_/NGitLab.Mock/Repository.cs:line 485 - // at NGitLab.Mock.Clients.FileClient.Get(String filePath, String ref) in /_/NGitLab.Mock/Clients/FileClient.cs:line 77 - // at NGitLab.Mock.Clients.FileClient.GetAsync(String filePath, String ref, CancellationToken cancellationToken) in /_/NGitLab.Mock/Clients/FileClient.cs:line 125 - } - - return null; + // LibGit2Sharp sometimes fails with the following exception + // System.Collections.Generic.KeyNotFoundException: The given key '1d08df45e551942eaa70d9f5ab6f5f7665a3f5b3' was not present in the dictionary. + // at System.Collections.Generic.Dictionary`2.get_Item(TKey key) + // at LibGit2Sharp.Core.FileHistory.FullHistory(IRepository repo, String path, CommitFilter filter)+MoveNext() in /_/LibGit2Sharp/Core/FileHistory.cs:line 120 + // at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found) + // at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source) + // at NGitLab.Mock.Clients.LibGit2SharpExtensions.GetLastCommitForFileChanges(Repository repository, String filePath) in /_/NGitLab.Mock/Clients/LibGit2SharpExtensions.cs:line 65 + // at NGitLab.Mock.Repository.GetFile(String filePath, String ref) in /_/NGitLab.Mock/Repository.cs:line 485 + // at NGitLab.Mock.Clients.FileClient.Get(String filePath, String ref) in /_/NGitLab.Mock/Clients/FileClient.cs:line 77 + // at NGitLab.Mock.Clients.FileClient.GetAsync(String filePath, String ref, CancellationToken cancellationToken) in /_/NGitLab.Mock/Clients/FileClient.cs:line 125 } + + return null; } } diff --git a/NGitLab.Mock/Clients/LintClient.cs b/NGitLab.Mock/Clients/LintClient.cs index 300c7781..a9ca8c43 100644 --- a/NGitLab.Mock/Clients/LintClient.cs +++ b/NGitLab.Mock/Clients/LintClient.cs @@ -1,27 +1,42 @@ using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal class LintClient : ClientBase, ILintClient { - internal class LintClient : ILintClient + public LintClient(ClientContext context) + : base(context) { - private readonly ClientContext _context; + } - public LintClient(ClientContext context) - { - _context = context; - } + public Task ValidateCIYamlContentAsync(string projectId, string yamlContent, LintCIOptions options, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } - public Task ValidateCIYamlContentAsync(string projectId, string yamlContent, LintCIOptions options, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } + public async Task ValidateProjectCIConfigurationAsync(string projectId, LintCIOptions options, CancellationToken cancellationToken = default) + { + await Task.Yield(); - public Task ValidateProjectCIConfigurationAsync(string projectId, LintCIOptions options, CancellationToken cancellationToken = default) + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var project = GetProject(projectId, ProjectPermission.View); + var @ref = string.IsNullOrEmpty(options.Ref) ? project.DefaultBranch : options.Ref; + + var lintCi = project.LintCIs.FirstOrDefault(ci => + { + return string.Equals(ci.Ref, @ref, StringComparison.Ordinal); + }) ?? new LintCI(@ref, valid: false, "Reference not found"); + + return new Models.LintCI + { + Valid = lintCi.Valid, + Errors = lintCi.Errors, + }; } } } diff --git a/NGitLab.Mock/Clients/MembersClient.cs b/NGitLab.Mock/Clients/MembersClient.cs index 7b33a3e2..c0cbd26e 100644 --- a/NGitLab.Mock/Clients/MembersClient.cs +++ b/NGitLab.Mock/Clients/MembersClient.cs @@ -1,166 +1,279 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Net; +using System.Threading; +using System.Threading.Tasks; +using NGitLab.Extensions; +using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class MembersClient : ClientBase, IMembersClient { - internal sealed class MembersClient : ClientBase, IMembersClient + public MembersClient(ClientContext context) + : base(context) + { + } + + public IEnumerable OfProject(string projectId) + { + return OfProject(projectId, includeInheritedMembers: false); + } + + public IEnumerable OfProject(string projectId, bool includeInheritedMembers) { - public MembersClient(ClientContext context) - : base(context) + using (Context.BeginOperationScope()) { + var project = GetProject(projectId, ProjectPermission.View); + var members = project.GetEffectivePermissions(includeInheritedMembers).Permissions; + return members.Select(member => member.ToMembershipClient()); } + } + + public GitLabCollectionResponse OfProjectAsync(ProjectId projectId, bool includeInheritedMembers = false) + { + return GitLabCollectionResponse.Create(OfProject(projectId.ValueAsString(), includeInheritedMembers)); + } - public Membership AddMemberToProject(string projectId, ProjectMemberCreate projectMemberCreate) + public Membership AddMemberToProject(string projectId, ProjectMemberCreate projectMemberCreate) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var project = GetProject(projectId, ProjectPermission.Edit); + var user = Server.Users.GetById(projectMemberCreate.UserId); + + if (project.Permissions.Any(p => p.User.Id == user.Id)) { - var project = GetProject(projectId, ProjectPermission.Edit); - var user = Server.Users.GetById(projectMemberCreate.UserId); + throw new GitLabException("Member already exists") + { + // actual GitLab error + StatusCode = HttpStatusCode.Conflict, + ErrorMessage = "Member already exists", + }; + } - CheckUserPermissionOfProject(projectMemberCreate.AccessLevel, user, project); + ValidateNewProjectPermission(projectMemberCreate.AccessLevel, user, project); - var permission = new Permission(user, projectMemberCreate.AccessLevel); - project.Permissions.Add(permission); + project.Permissions.Add(new(user, projectMemberCreate.AccessLevel)); - return project.GetEffectivePermissions().GetEffectivePermission(user).ToMembershipClient(); - } + return project.GetEffectivePermissions().GetEffectivePermission(user).ToMembershipClient(); } + } + + public async Task AddMemberToProjectAsync(ProjectId projectId, ProjectMemberCreate user, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return AddMemberToProject(projectId.ValueAsString(), user); + } - public Membership UpdateMemberOfProject(string projectId, ProjectMemberUpdate projectMemberUpdate) + public Membership UpdateMemberOfProject(string projectId, ProjectMemberUpdate projectMemberUpdate) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(projectId, ProjectPermission.Edit); - var user = Server.Users.GetById(projectMemberUpdate.UserId); + var project = GetProject(projectId, ProjectPermission.Edit); + var user = Server.Users.GetById(projectMemberUpdate.UserId); - CheckUserPermissionOfProject(projectMemberUpdate.AccessLevel, user, project); - return project.GetEffectivePermissions().GetEffectivePermission(user).ToMembershipClient(); - } + var curPermission = project.Permissions.SingleOrDefault(p => p.User.Id == user.Id) + ?? throw new GitLabNotFoundException(); + + ValidateNewProjectPermission(projectMemberUpdate.AccessLevel, user, project); + + project.Permissions.Remove(curPermission); + project.Permissions.Add(new(user, projectMemberUpdate.AccessLevel)); + + return project.GetEffectivePermissions().GetEffectivePermission(user).ToMembershipClient(); } + } - public Membership AddMemberToGroup(string groupId, GroupMemberCreate groupMemberCreate) - { - using (Context.BeginOperationScope()) - { - var group = GetGroup(groupId, GroupPermission.Edit); - var user = Server.Users.GetById(groupMemberCreate.UserId); + public async Task UpdateMemberOfProjectAsync(ProjectId projectId, ProjectMemberUpdate projectMemberUpdate, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return UpdateMemberOfProject(projectId.ValueAsString(), projectMemberUpdate); + } - CheckUserPermissionOfGroup(groupMemberCreate.AccessLevel, user, group); + public Membership GetMemberOfProject(string projectId, string userId) + { + return GetMemberOfProject(projectId, userId, includeInheritedMembers: false); + } - var permission = new Permission(user, groupMemberCreate.AccessLevel); - group.Permissions.Add(permission); + public Membership GetMemberOfProject(string projectId, string userId, bool includeInheritedMembers) + { + return OfProject(projectId, includeInheritedMembers) + .FirstOrDefault(u => string.Equals(u.Id.ToStringInvariant(), userId, StringComparison.Ordinal)) + ?? throw new GitLabNotFoundException(); + } - return group.GetEffectivePermissions().GetEffectivePermission(user).ToMembershipClient(); - } - } + public async Task GetMemberOfProjectAsync(ProjectId projectId, long userId, bool includeInheritedMembers = false, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return GetMemberOfProject(projectId.ValueAsString(), userId.ToStringInvariant(), includeInheritedMembers); + } - public Membership UpdateMemberOfGroup(string groupId, GroupMemberUpdate groupMemberUpdate) + public async Task RemoveMemberFromProjectAsync(ProjectId projectId, long userId, CancellationToken cancellationToken = default) + { + await Task.Yield(); + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var group = GetGroup(groupId, GroupPermission.Edit); - var user = Server.Users.GetById(groupMemberUpdate.UserId); + var project = GetProject(projectId, ProjectPermission.Edit); - CheckUserPermissionOfGroup(groupMemberUpdate.AccessLevel, user, group); - return group.GetEffectivePermissions().GetEffectivePermission(user).ToMembershipClient(); - } - } + var permission = project.Permissions.SingleOrDefault(p => p.User.Id == userId) + ?? throw new GitLabNotFoundException(); - public Membership GetMemberOfGroup(string groupId, string userId) - { - return OfGroup(groupId, includeInheritedMembers: false) - .FirstOrDefault(u => string.Equals(u.Id.ToString(CultureInfo.InvariantCulture), userId, StringComparison.Ordinal)); + project.Permissions.Remove(permission); } + } - public Membership GetMemberOfProject(string projectId, string userId) - { - return OfProject(projectId, includeInheritedMembers: false) - .FirstOrDefault(u => string.Equals(u.Id.ToString(CultureInfo.InvariantCulture), userId, StringComparison.Ordinal)); - } + [Obsolete("Use OfGroup")] + public IEnumerable OfNamespace(string groupId) + { + return OfGroup(groupId); + } - public Membership GetMemberOfProject(string projectId, string userId, bool includeInheritedMembers) - { - return OfProject(projectId, includeInheritedMembers) - .FirstOrDefault(u => string.Equals(u.Id.ToString(CultureInfo.InvariantCulture), userId, StringComparison.Ordinal)); - } + public IEnumerable OfGroup(string groupId) + { + return OfGroup(groupId, includeInheritedMembers: false); + } - public IEnumerable OfGroup(string groupId) + public IEnumerable OfGroup(string groupId, bool includeInheritedMembers) + { + using (Context.BeginOperationScope()) { - return OfGroup(groupId, includeInheritedMembers: false); + var group = GetGroup(groupId, GroupPermission.View); + var members = group.GetEffectivePermissions(includeInheritedMembers).Permissions; + return members.Select(member => member.ToMembershipClient()); } + } - public IEnumerable OfGroup(string groupId, bool includeInheritedMembers) + public GitLabCollectionResponse OfGroupAsync(GroupId groupId, bool includeInheritedMembers = false) + { + return GitLabCollectionResponse.Create(OfGroup(groupId.ValueAsString(), includeInheritedMembers)); + } + + public Membership AddMemberToGroup(string groupId, GroupMemberCreate groupMemberCreate) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var group = GetGroup(groupId, GroupPermission.Edit); + var user = Server.Users.GetById(groupMemberCreate.UserId); + + if (group.Permissions.Any(p => p.User.Id == user.Id)) { - var group = GetGroup(groupId, GroupPermission.View); - var members = group.GetEffectivePermissions(includeInheritedMembers).Permissions; - return members.Select(member => member.ToMembershipClient()); + throw new GitLabException("Member already exists") + { + // actual GitLab error + StatusCode = HttpStatusCode.Conflict, + ErrorMessage = "Member already exists", + }; } - } - public IEnumerable OfNamespace(string groupId) - { - return OfGroup(groupId); + ValidateNewGroupPermission(groupMemberCreate.AccessLevel, user, group); + + group.Permissions.Add(new(user, groupMemberCreate.AccessLevel)); + + return group.GetEffectivePermissions().GetEffectivePermission(user).ToMembershipClient(); } + } + + public async Task AddMemberToGroupAsync(GroupId groupId, GroupMemberCreate user, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return AddMemberToGroup(groupId.ValueAsString(), user); + } - public IEnumerable OfProject(string projectId) + public Membership UpdateMemberOfGroup(string groupId, GroupMemberUpdate groupMemberUpdate) + { + using (Context.BeginOperationScope()) { - return OfProject(projectId, includeInheritedMembers: false); + var group = GetGroup(groupId, GroupPermission.Edit); + var user = Server.Users.GetById(groupMemberUpdate.UserId); + + var curPermission = group.Permissions.SingleOrDefault(p => p.User.Id == user.Id) + ?? throw new GitLabNotFoundException(); + + ValidateNewGroupPermission(groupMemberUpdate.AccessLevel, user, group); + + group.Permissions.Remove(curPermission); + group.Permissions.Add(new(user, groupMemberUpdate.AccessLevel)); + + return group.GetEffectivePermissions().GetEffectivePermission(user).ToMembershipClient(); } + } + + public async Task UpdateMemberOfGroupAsync(GroupId groupId, GroupMemberUpdate groupMemberUpdate, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return UpdateMemberOfGroup(groupId.ValueAsString(), groupMemberUpdate); + } - public IEnumerable OfProject(string projectId, bool includeInheritedMembers) + public Membership GetMemberOfGroup(string groupId, string userId) + { + return GetMemberOfGroup(groupId, userId, false); + } + + public Membership GetMemberOfGroup(string groupId, string userId, bool includeInheritedMembers) + { + return OfGroup(groupId, includeInheritedMembers: includeInheritedMembers) + .FirstOrDefault(u => string.Equals(u.Id.ToStringInvariant(), userId, StringComparison.Ordinal)) + ?? throw new GitLabNotFoundException(); + } + + public async Task GetMemberOfGroupAsync(GroupId groupId, long userId, bool includeInheritedMembers = false, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return GetMemberOfGroup(groupId.ValueAsString(), userId.ToStringInvariant(), includeInheritedMembers); + } + + public async Task RemoveMemberFromGroupAsync(GroupId groupId, long userId, CancellationToken cancellationToken = default) + { + await Task.Yield(); + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(projectId, ProjectPermission.View); - var members = project.GetEffectivePermissions(includeInheritedMembers).Permissions; - return members.Select(member => member.ToMembershipClient()); - } + var group = GetGroup(groupId, GroupPermission.Edit); + + var permission = group.Permissions.SingleOrDefault(p => p.User.Id == userId) + ?? throw new GitLabNotFoundException(); + + group.Permissions.Remove(permission); } + } - private static void CheckUserPermissionOfProject(AccessLevel accessLevel, User user, Project project) + /// + /// Checks if the given permission can be applied to the project and throws an exception if it is invalid. + /// + /// The new access level is less than an inherited membership role. + private static void ValidateNewProjectPermission(AccessLevel newAccessLevel, User user, Project project) + { + // Get the existing permission from the parent, if it exists... + var permission = project.Group?.GetEffectivePermissions().GetEffectivePermission(user); + if (permission?.AccessLevel > newAccessLevel) { - var existingPermission = project.GetEffectivePermissions().GetEffectivePermission(user); - if (existingPermission != null) + throw new GitLabException("access_level should be greater than or equal to inherited membership.") { - if (existingPermission.AccessLevel > accessLevel) - { - throw new GitLabException("{\"access_level\":[\"should be greater than or equal to Owner inherited membership from group Runners\"]}.") - { - StatusCode = HttpStatusCode.BadRequest, - }; - } - - if (existingPermission.AccessLevel == accessLevel) - { - throw new GitLabException { StatusCode = HttpStatusCode.Conflict }; - } - } + // actual GitLab error + StatusCode = HttpStatusCode.BadRequest, + ErrorMessage = $$"""{"access_level":["should be greater than or equal to {{permission.AccessLevel}} inherited membership from project {{project.PathWithNamespace}}"]}.""", + }; } + } - private static void CheckUserPermissionOfGroup(AccessLevel accessLevel, User user, Group group) + /// + /// Checks if the given permission can be applied to the group and throws an exception if it is invalid. + /// + /// The new access level is less than an inherited membership role. + private static void ValidateNewGroupPermission(AccessLevel newAccessLevel, User user, Group group) + { + // Get the existing permission from the parent, if it exists... + var permission = group.Parent?.GetEffectivePermissions().GetEffectivePermission(user); + if (permission?.AccessLevel > newAccessLevel) { - var existingPermission = group.GetEffectivePermissions().GetEffectivePermission(user); - if (existingPermission != null) + throw new GitLabException("access_level should be greater than or equal to inherited membership.") { - if (existingPermission.AccessLevel > accessLevel) - { - throw new GitLabException("{\"access_level\":[\"should be greater than or equal to Owner inherited membership from group Runners\"]}.") - { - StatusCode = HttpStatusCode.BadRequest, - }; - } - - if (existingPermission.AccessLevel == accessLevel) - { - throw new GitLabException { StatusCode = HttpStatusCode.Conflict }; - } - } + // actual GitLab error + StatusCode = HttpStatusCode.BadRequest, + ErrorMessage = $$"""{"access_level":["should be greater than or equal to {{permission.AccessLevel}} inherited membership from group {{group.PathWithNameSpace}}"]}.""", + }; } } } diff --git a/NGitLab.Mock/Clients/MergeRequestApprovalClient.cs b/NGitLab.Mock/Clients/MergeRequestApprovalClient.cs index 7092a281..ad08e8cb 100644 --- a/NGitLab.Mock/Clients/MergeRequestApprovalClient.cs +++ b/NGitLab.Mock/Clients/MergeRequestApprovalClient.cs @@ -1,30 +1,34 @@ using System; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class MergeRequestApprovalClient : ClientBase, IMergeRequestApprovalClient { - internal sealed class MergeRequestApprovalClient : ClientBase, IMergeRequestApprovalClient + private readonly int _projectId; + private readonly int _mergeRequestIid; + + public MergeRequestApprovalClient(ClientContext context, int projectId, int mergeRequestIid) + : base(context) { - private readonly int _projectId; - private readonly int _mergeRequestIid; + _projectId = projectId; + _mergeRequestIid = mergeRequestIid; + } - public MergeRequestApprovalClient(ClientContext context, int projectId, int mergeRequestIid) - : base(context) - { - _projectId = projectId; - _mergeRequestIid = mergeRequestIid; - } + public MergeRequestApprovals Approvals => new(); - public MergeRequestApprovals Approvals => new(); + public MergeRequestApprovals ApproveMergeRequest(MergeRequestApproveRequest request = null) + { + throw new NotImplementedException(); + } - public MergeRequestApprovals ApproveMergeRequest(MergeRequestApproveRequest request = null) - { - throw new NotImplementedException(); - } + public void ResetApprovals() + { + throw new NotImplementedException(); + } - public void ChangeApprovers(MergeRequestApproversChange approversChange) - { - throw new NotImplementedException(); - } + public void ChangeApprovers(MergeRequestApproversChange approversChange) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/MergeRequestChangeClient.cs b/NGitLab.Mock/Clients/MergeRequestChangeClient.cs index bf169d76..0d5e2748 100644 --- a/NGitLab.Mock/Clients/MergeRequestChangeClient.cs +++ b/NGitLab.Mock/Clients/MergeRequestChangeClient.cs @@ -1,32 +1,31 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class MergeRequestChangeClient : ClientBase, IMergeRequestChangeClient { - internal sealed class MergeRequestChangeClient : ClientBase, IMergeRequestChangeClient - { - private readonly int _projectId; - private readonly int _mergeRequestIid; + private readonly int _projectId; + private readonly int _mergeRequestIid; - public MergeRequestChangeClient(ClientContext context, int projectId, int mergeRequestIid) - : base(context) - { - _projectId = projectId; - _mergeRequestIid = mergeRequestIid; - } + public MergeRequestChangeClient(ClientContext context, int projectId, int mergeRequestIid) + : base(context) + { + _projectId = projectId; + _mergeRequestIid = mergeRequestIid; + } - public MergeRequestChange MergeRequestChange + public MergeRequestChange MergeRequestChange + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + return new MergeRequestChange { - return new MergeRequestChange - { - Changes = GetMergeRequest(_projectId, _mergeRequestIid).Changes.Select(a => a.ToChange()) - .ToArray(), - }; - } + Changes = GetMergeRequest(_projectId, _mergeRequestIid).Changes.Select(a => a.ToChange()) + .ToArray(), + }; } } } diff --git a/NGitLab.Mock/Clients/MergeRequestClient.cs b/NGitLab.Mock/Clients/MergeRequestClient.cs index b7354542..b20491e0 100644 --- a/NGitLab.Mock/Clients/MergeRequestClient.cs +++ b/NGitLab.Mock/Clients/MergeRequestClient.cs @@ -5,681 +5,757 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class MergeRequestClient : ClientBase, IMergeRequestClient { - internal sealed class MergeRequestClient : ClientBase, IMergeRequestClient + private readonly int? _projectId; + + public MergeRequestClient(ClientContext context) + : base(context) { - private readonly int? _projectId; + } - public MergeRequestClient(ClientContext context) - : base(context) - { - } + public MergeRequestClient(ClientContext context, ProjectId projectId) + : base(context) + { + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public MergeRequestClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public MergeRequestClient(ClientContext context, GroupId groupId) + : base(context) + { + throw new NotImplementedException(); + } - private void AssertProjectId() - { - if (_projectId == null) - throw new InvalidOperationException("Valid only for a specific project"); - } + private void AssertProjectId() + { + if (_projectId == null) + throw new InvalidOperationException("Valid only for a specific project"); + } - public Models.MergeRequest this[int iid] + public Models.MergeRequest this[int iid] + { + get { - get - { - AssertProjectId(); + AssertProjectId(); - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); - var mergeRequest = project.MergeRequests.GetByIid(iid); - if (mergeRequest == null) - throw new GitLabNotFoundException(); + using (Context.BeginOperationScope()) + { + var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); + var mergeRequest = project.MergeRequests.GetByIid(iid); + if (mergeRequest == null) + throw new GitLabNotFoundException(); - return mergeRequest.ToMergeRequestClient(); - } + return mergeRequest.ToMergeRequestClient(); } } + } - public Task GetByIidAsync(int iid, SingleMergeRequestQuery options, CancellationToken cancellationToken = default) - { - return Task.FromResult(this[iid]); - } + public Task GetByIidAsync(int iid, SingleMergeRequestQuery options, CancellationToken cancellationToken = default) + { + return Task.FromResult(this[iid]); + } - public IEnumerable All + public IEnumerable All + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + if (_projectId == null) { - if (_projectId == null) - { - return Server.AllProjects - .Where(project => project.CanUserViewProject(Context.User)) - .SelectMany(project => project.MergeRequests) - .Select(mr => mr.ToMergeRequestClient()) - .ToList(); - } - - var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); - return project.MergeRequests.Select(mr => mr.ToMergeRequestClient()).ToList(); + return Server.AllProjects + .Where(project => project.CanUserViewProject(Context.User)) + .SelectMany(project => project.MergeRequests) + .Select(mr => mr.ToMergeRequestClient()) + .ToList(); } + + var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); + return project.MergeRequests.Select(mr => mr.ToMergeRequestClient()).ToList(); } } + } - public Models.MergeRequest Accept(int mergeRequestIid, MergeRequestAccept message) + public Models.MergeRequest Accept(int mergeRequestIid, MergeRequestAccept message) + { + return Accept(mergeRequestIid, new MergeRequestMerge { - return Accept(mergeRequestIid, new MergeRequestMerge - { - Sha = message.Sha, - ShouldRemoveSourceBranch = message.ShouldRemoveSourceBranch, - }); - } + Sha = message.Sha, + ShouldRemoveSourceBranch = message.ShouldRemoveSourceBranch, + }); + } - public Models.MergeRequest Accept(int mergeRequestIid, MergeRequestMerge message) + public Models.MergeRequest Accept(int mergeRequestIid, MergeRequestMerge message) + { + AssertProjectId(); + using (Context.BeginOperationScope()) { - AssertProjectId(); - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); - if (mergeRequest == null) - throw new GitLabNotFoundException(); + var project = GetProject(_projectId, ProjectPermission.Contribute); + var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); + if (mergeRequest == null) + throw new GitLabNotFoundException(); - mergeRequest.ShouldRemoveSourceBranch = message.ShouldRemoveSourceBranch ?? false; + mergeRequest.ShouldRemoveSourceBranch = message.ShouldRemoveSourceBranch ?? false; - if (project.ApprovalsBeforeMerge > mergeRequest.Approvers.Count) - { - throw new GitLabException("The merge request needs to be approved before merging") - { - StatusCode = HttpStatusCode.Unauthorized, - }; - } - - if (message.Sha != null) + if (project.ApprovalsBeforeMerge > mergeRequest.Approvers.Count) + { + throw new GitLabException("The merge request needs to be approved before merging") { - var commit = mergeRequest.SourceBranchHeadCommit; - if (!string.Equals(commit.Sha, message.Sha, StringComparison.OrdinalIgnoreCase)) - { - throw new GitLabException("SHA does not match HEAD of source branch") - { - StatusCode = HttpStatusCode.Conflict, - }; - } - } + StatusCode = HttpStatusCode.Unauthorized, + }; + } - if (mergeRequest.HasConflicts) + if (message.Sha != null) + { + var commit = mergeRequest.SourceBranchHeadCommit; + if (!string.Equals(commit.Sha, message.Sha, StringComparison.OrdinalIgnoreCase)) { - throw new GitLabException("The merge request has some conflicts and cannot be merged") + throw new GitLabException("SHA does not match HEAD of source branch") { - StatusCode = HttpStatusCode.NotAcceptable, + StatusCode = HttpStatusCode.Conflict, }; } + } - if (project.MergeMethod != null && - (string.Equals(project.MergeMethod, "ff", StringComparison.Ordinal) || string.Equals(project.MergeMethod, "rebase_merge", StringComparison.Ordinal)) && - project.Repository.IsRebaseNeeded(mergeRequest.ConsolidatedSourceBranch, mergeRequest.TargetBranch)) + if (mergeRequest.HasConflicts) + { + throw new GitLabException("The merge request has some conflicts and cannot be merged") { - throw new GitLabException($"The MR cannot be merged with method '{project.MergeMethod}': the source branch must first be rebased") - { - StatusCode = HttpStatusCode.MethodNotAllowed, - }; - } + StatusCode = HttpStatusCode.NotAcceptable, + }; + } - mergeRequest.Accept(Context.User); - return mergeRequest.ToMergeRequestClient(); + if (project.MergeMethod != null && + (string.Equals(project.MergeMethod, "ff", StringComparison.Ordinal) || string.Equals(project.MergeMethod, "rebase_merge", StringComparison.Ordinal)) && + project.Repository.IsRebaseNeeded(mergeRequest.ConsolidatedSourceBranch, mergeRequest.TargetBranch)) + { + throw new GitLabException($"The MR cannot be merged with method '{project.MergeMethod}': the source branch must first be rebased") + { + StatusCode = HttpStatusCode.MethodNotAllowed, + }; } + + mergeRequest.Accept(Context.User); + return mergeRequest.ToMergeRequestClient(); } + } - public Models.MergeRequest Approve(int mergeRequestIid, MergeRequestApprove message) - { - AssertProjectId(); + public Models.MergeRequest Approve(int mergeRequestIid, MergeRequestApprove message) + { + AssertProjectId(); - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.Contribute); - project.CanUserContributeToProject(Context.User); - var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); + using (Context.BeginOperationScope()) + { + var project = GetProject(_projectId, ProjectPermission.Contribute); + project.CanUserContributeToProject(Context.User); + var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); - if (mergeRequest == null) - throw new GitLabNotFoundException(); + if (mergeRequest == null) + throw new GitLabNotFoundException(); - // Check if user has already aproved the merge request - if (mergeRequest.Approvers.Any(x => x.Id == Context.User.Id)) + // Check if user has already aproved the merge request + if (mergeRequest.Approvers.Any(x => x.Id == Context.User.Id)) + { + throw new GitLabException("GitLab server returned an error (Unauthorized): Empty Response.") { - throw new GitLabException("GitLab server returned an error (Unauthorized): Empty Response.") - { - StatusCode = HttpStatusCode.Unauthorized, - }; - } + StatusCode = HttpStatusCode.Unauthorized, + }; + } - /* To be implemented - * need get configuration for GitLab merge request approval: https://docs.gitlab.com/ee/api/merge_request_approvals.html) - * 1) Check if project approval rules require password input - * 2) Check if project approval rules prevents merge request committers from approving - * 3) Check if project approval rules prevents merge request author from approving - */ + /* To be implemented + * need get configuration for GitLab merge request approval: https://docs.gitlab.com/ee/api/merge_request_approvals.html) + * 1) Check if project approval rules require password input + * 2) Check if project approval rules prevents merge request committers from approving + * 3) Check if project approval rules prevents merge request author from approving + */ - if (message.Sha != null) + if (message.Sha != null) + { + var commit = project.Repository.GetBranchTipCommit(mergeRequest.SourceBranch); + if (!string.Equals(commit.Sha, message.Sha, StringComparison.OrdinalIgnoreCase)) { - var commit = project.Repository.GetBranchTipCommit(mergeRequest.SourceBranch); - if (!string.Equals(commit.Sha, message.Sha, StringComparison.OrdinalIgnoreCase)) + throw new GitLabException("SHA does not match HEAD of source branch") { - throw new GitLabException("SHA does not match HEAD of source branch") - { - StatusCode = HttpStatusCode.Conflict, - }; - } + StatusCode = HttpStatusCode.Conflict, + }; } - - mergeRequest.Approvers.Add(new UserRef(Context.User)); - return mergeRequest.ToMergeRequestClient(); } + + mergeRequest.Approvers.Add(new UserRef(Context.User)); + return mergeRequest.ToMergeRequestClient(); } + } - public RebaseResult Rebase(int mergeRequestIid) + public RebaseResult Rebase(int mergeRequestIid) + { + AssertProjectId(); + using (Context.BeginOperationScope()) { - AssertProjectId(); - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); + var project = GetProject(_projectId, ProjectPermission.Contribute); + var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); - if (mergeRequest == null) - throw new GitLabNotFoundException(); + if (mergeRequest == null) + throw new GitLabNotFoundException(); - return mergeRequest.Rebase(Context.User); - } + return mergeRequest.Rebase(Context.User); } + } - public Task RebaseAsync(int mergeRequestIid, MergeRequestRebase options, CancellationToken cancellationToken = default) - { - return Task.FromResult(Rebase(mergeRequestIid)); - } + public Task RebaseAsync(int mergeRequestIid, MergeRequestRebase options, CancellationToken cancellationToken = default) + { + return Task.FromResult(Rebase(mergeRequestIid)); + } - public IEnumerable AllInState(MergeRequestState state) + public IEnumerable AllInState(MergeRequestState state) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + if (_projectId == null) { - if (_projectId == null) - { - return Server.AllProjects - .Where(project => project.CanUserViewProject(Context.User)) - .SelectMany(project => project.MergeRequests) - .Where(mr => mr.State == state) - .Select(mr => mr.ToMergeRequestClient()) - .ToList(); - } + return Server.AllProjects + .Where(project => project.CanUserViewProject(Context.User)) + .SelectMany(project => project.MergeRequests) + .Where(mr => mr.State == state) + .Select(mr => mr.ToMergeRequestClient()) + .ToList(); + } - var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); + var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); - if (!project.AccessibleMergeRequests) + if (!project.AccessibleMergeRequests) + { + throw new GitLabException("403 Forbidden") { - throw new GitLabException("403 Forbidden") - { - StatusCode = HttpStatusCode.Forbidden, - }; - } - - return project.MergeRequests.Where(mr => mr.State == state).Select(mr => mr.ToMergeRequestClient()).ToList(); + StatusCode = HttpStatusCode.Forbidden, + }; } + + return project.MergeRequests.Where(mr => mr.State == state).Select(mr => mr.ToMergeRequestClient()).ToList(); } + } - public IMergeRequestChangeClient Changes(int mergeRequestIid) - { - AssertProjectId(); + public IMergeRequestChangeClient Changes(int mergeRequestIid) + { + AssertProjectId(); - return new MergeRequestChangeClient(Context, _projectId.GetValueOrDefault(), mergeRequestIid); - } + return new MergeRequestChangeClient(Context, _projectId.GetValueOrDefault(), mergeRequestIid); + } - public IMergeRequestApprovalClient ApprovalClient(int mergeRequestIid) - { - AssertProjectId(); + public IMergeRequestApprovalClient ApprovalClient(int mergeRequestIid) + { + AssertProjectId(); - return new MergeRequestApprovalClient(Context, _projectId.GetValueOrDefault(), mergeRequestIid); - } + return new MergeRequestApprovalClient(Context, _projectId.GetValueOrDefault(), mergeRequestIid); + } - public Models.MergeRequest Close(int mergeRequestIid) + public Models.MergeRequest Close(int mergeRequestIid) + { + AssertProjectId(); + + using (Context.BeginOperationScope()) { - AssertProjectId(); + var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); + var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); + if (mergeRequest == null) + throw new GitLabNotFoundException(); - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); - var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); - if (mergeRequest == null) - throw new GitLabNotFoundException(); + if (mergeRequest.State != MergeRequestState.opened) + throw new GitLabBadRequestException(); - if (mergeRequest.State != MergeRequestState.opened) - throw new GitLabBadRequestException(); + mergeRequest.ClosedAt = DateTimeOffset.UtcNow; + mergeRequest.UpdatedAt = DateTimeOffset.UtcNow; - mergeRequest.ClosedAt = DateTimeOffset.UtcNow; - mergeRequest.UpdatedAt = DateTimeOffset.UtcNow; - return mergeRequest.ToMergeRequestClient(); - } + Server.ResourceStateEvents.CreateResourceStateEvent(Context.User, "closed", mergeRequest.Id, "MergeRequest"); + return mergeRequest.ToMergeRequestClient(); } + } - public IMergeRequestCommentClient Comments(int mergeRequestIid) - { - AssertProjectId(); + public IMergeRequestCommentClient Comments(int mergeRequestIid) + { + AssertProjectId(); - return new MergeRequestCommentClient(Context, _projectId.GetValueOrDefault(), mergeRequestIid); - } + return new MergeRequestCommentClient(Context, _projectId.GetValueOrDefault(), mergeRequestIid); + } + + public IMergeRequestCommitClient Commits(int mergeRequestIid) + { + AssertProjectId(); + + return new MergeRequestCommitClient(Context, _projectId.GetValueOrDefault(), mergeRequestIid); + } + + public Models.MergeRequest CancelMergeWhenPipelineSucceeds(int mergeRequestIid) + { + AssertProjectId(); - public IMergeRequestCommitClient Commits(int mergeRequestIid) + using (Context.BeginOperationScope()) { - AssertProjectId(); + var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); + var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); + if (mergeRequest == null) + throw new GitLabNotFoundException(); + + if (mergeRequest.State != MergeRequestState.opened) + throw new GitLabBadRequestException(); - return new MergeRequestCommitClient(Context, _projectId.GetValueOrDefault(), mergeRequestIid); + mergeRequest.MergeWhenPipelineSucceeds = false; + mergeRequest.UpdatedAt = DateTimeOffset.UtcNow; + return mergeRequest.ToMergeRequestClient(); } + } + + public Models.MergeRequest Create(MergeRequestCreate mergeRequestCreate) + { + AssertProjectId(); + + EnsureUserIsAuthenticated(); - public Models.MergeRequest CancelMergeWhenPipelineSucceeds(int mergeRequestIid) + using (Context.BeginOperationScope()) { - AssertProjectId(); + var sourceProject = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.Contribute); + var targetProject = GetProject(mergeRequestCreate.TargetProjectId, ProjectPermission.View); - using (Context.BeginOperationScope()) + // Ensure the branches exist + _ = sourceProject.Repository.GetBranch(mergeRequestCreate.SourceBranch) ?? throw new GitLabBadRequestException("Source branch not found"); + _ = targetProject.Repository.GetBranch(mergeRequestCreate.TargetBranch) ?? throw new GitLabBadRequestException("Target branch not found"); + + UserRef assignee = null; + if (mergeRequestCreate.AssigneeId != null) { - var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); - var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); - if (mergeRequest == null) - throw new GitLabNotFoundException(); + assignee = Server.Users.GetById(mergeRequestCreate.AssigneeId.Value) ?? throw new GitLabBadRequestException("assignee not found"); + } - if (mergeRequest.State != MergeRequestState.opened) - throw new GitLabBadRequestException(); + var mergeRequest = targetProject.MergeRequests.Add(sourceProject, mergeRequestCreate.SourceBranch, mergeRequestCreate.TargetBranch, mergeRequestCreate.Title, Context.User); + mergeRequest.Assignee = assignee; + mergeRequest.Description = mergeRequestCreate.Description; + mergeRequest.ShouldRemoveSourceBranch = mergeRequestCreate.RemoveSourceBranch; + mergeRequest.Squash = mergeRequestCreate.Squash; + SetLabels(mergeRequest, mergeRequestCreate.Labels, labelsToAdd: null, labelsToRemove: null); - mergeRequest.MergeWhenPipelineSucceeds = false; - mergeRequest.UpdatedAt = DateTimeOffset.UtcNow; - return mergeRequest.ToMergeRequestClient(); - } + return mergeRequest.ToMergeRequestClient(); } + } - public Models.MergeRequest Create(MergeRequestCreate mergeRequestCreate) + private void SetLabels(MergeRequest mergeRequest, string labels, string labelsToAdd, string labelsToRemove) + { + if (labels is not null || labelsToAdd is not null || labelsToRemove is not null) { - AssertProjectId(); - - EnsureUserIsAuthenticated(); - - using (Context.BeginOperationScope()) + var newLabels = mergeRequest.Labels.ToArray(); + if (labels is not null) { - var sourceProject = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.Contribute); - var targetProject = GetProject(mergeRequestCreate.TargetProjectId, ProjectPermission.View); - - // Ensure the branches exist - _ = sourceProject.Repository.GetBranch(mergeRequestCreate.SourceBranch) ?? throw new GitLabBadRequestException("Source branch not found"); - _ = targetProject.Repository.GetBranch(mergeRequestCreate.TargetBranch) ?? throw new GitLabBadRequestException("Target branch not found"); + newLabels = labels.Split(',').Distinct(StringComparer.Ordinal).ToArray(); + } - UserRef assignee = null; - if (mergeRequestCreate.AssigneeId != null) - { - assignee = Server.Users.GetById(mergeRequestCreate.AssigneeId.Value) ?? throw new GitLabBadRequestException("assignee not found"); - } + if (labelsToAdd is not null) + { + newLabels = newLabels.Concat(labelsToAdd.Split(',')).Distinct(StringComparer.Ordinal).ToArray(); + } - var mergeRequest = targetProject.MergeRequests.Add(sourceProject, mergeRequestCreate.SourceBranch, mergeRequestCreate.TargetBranch, mergeRequestCreate.Title, Context.User); - mergeRequest.Assignee = assignee; - mergeRequest.Description = mergeRequestCreate.Description; - mergeRequest.ShouldRemoveSourceBranch = mergeRequestCreate.RemoveSourceBranch; - mergeRequest.Squash = mergeRequestCreate.Squash; - SetLabels(mergeRequest, mergeRequestCreate.Labels); + if (labelsToRemove is not null) + { + newLabels = newLabels.Except(labelsToRemove.Split(','), StringComparer.Ordinal).Distinct(StringComparer.Ordinal).ToArray(); + } - return mergeRequest.ToMergeRequestClient(); + if (newLabels is not null) + { + Server.ResourceLabelEvents.CreateResourceLabelEvents(Context.User, mergeRequest.Labels.ToArray(), newLabels, mergeRequest.Id, "MergeRequest"); } - } - private static void SetLabels(MergeRequest mergeRequest, string labels) - { - if (labels != null) + mergeRequest.Labels.Clear(); + foreach (var newLabel in newLabels) { - mergeRequest.Labels.Clear(); - foreach (var label in labels.Split(',')) + if (!string.IsNullOrEmpty(newLabel)) { - if (!string.IsNullOrEmpty(label)) - { - mergeRequest.Labels.Add(label); - } + mergeRequest.Labels.Add(newLabel); } } } + } + + public void Delete(int mergeRequestIid) + { + AssertProjectId(); - public void Delete(int mergeRequestIid) + using (Context.BeginOperationScope()) { - AssertProjectId(); + var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); + var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); + if (mergeRequest == null) + throw new GitLabNotFoundException(); - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); - var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); - if (mergeRequest == null) - throw new GitLabNotFoundException(); + project.MergeRequests.Remove(mergeRequest); + } + } - project.MergeRequests.Remove(mergeRequest); - } + public IEnumerable Get(MergeRequestQuery query) + { + using (Context.BeginOperationScope()) + { + var projects = _projectId == null + ? Server.AllProjects.Where(project => project.CanUserViewProject(Context.User)) + : new[] { GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View) }; + var mergeRequests = projects.SelectMany(x => x.MergeRequests); + return FilterByQuery(mergeRequests, query); } + } - public IEnumerable Get(MergeRequestQuery query) + [SuppressMessage("Design", "MA0051:Method is too long", Justification = "There are lots of cases to support")] + public IEnumerable FilterByQuery(IEnumerable mergeRequests, MergeRequestQuery query) + { + if (query != null) { - using (Context.BeginOperationScope()) + if (query.ApproverIds != null) { - var projects = _projectId == null - ? Server.AllProjects.Where(project => project.CanUserViewProject(Context.User)) - : new[] { GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View) }; - var mergeRequests = projects.SelectMany(x => x.MergeRequests); - return FilterByQuery(mergeRequests, query); + var approverIds = query.ApproverIds; + mergeRequests = mergeRequests.Where(mr => mr.Approvers.Any(x => approverIds.Contains(x.Id))); } - } - [SuppressMessage("Design", "MA0051:Method is too long", Justification = "There are lots of cases to support")] - public IEnumerable FilterByQuery(IEnumerable mergeRequests, MergeRequestQuery query) - { - if (query != null) + if (query.AssigneeId != null) { - if (query.ApproverIds != null) - { - var approverIds = query.ApproverIds; - mergeRequests = mergeRequests.Where(mr => mr.Approvers.Any(x => approverIds.Contains(x.Id))); - } + var assigneeId = string.Equals(query.AssigneeId.ToString(), "None", StringComparison.OrdinalIgnoreCase) ? (int?)null : int.Parse(query.AssigneeId.ToString()); + mergeRequests = mergeRequests.Where(mr => mr.Assignee?.Id == assigneeId); + } - if (query.AssigneeId != null) + if (query.ReviewerId != null) + { + if (query.ReviewerId == QueryAssigneeId.None) { - var assigneeId = string.Equals(query.AssigneeId.ToString(), "None", StringComparison.OrdinalIgnoreCase) ? (int?)null : int.Parse(query.AssigneeId.ToString()); - mergeRequests = mergeRequests.Where(mr => mr.Assignee?.Id == assigneeId); + mergeRequests = mergeRequests.Where(mr => mr.Reviewers == null || mr.Reviewers.Count == 0); } - - if (query.ReviewerId != null) + else if (query.ReviewerId == QueryAssigneeId.Any) { - if (query.ReviewerId == QueryAssigneeId.None) - { - mergeRequests = mergeRequests.Where(mr => mr.Reviewers == null || mr.Reviewers.Count == 0); - } - else if (query.ReviewerId == QueryAssigneeId.Any) - { - mergeRequests = mergeRequests.Where(mr => mr.Reviewers != null || mr.Reviewers.Any()); - } - else - { - var reviewerId = int.Parse(query.ReviewerId.ToString()); - mergeRequests = mergeRequests.Where(mr => mr.Reviewers.Any(x => reviewerId.Equals(x.Id))); - } + mergeRequests = mergeRequests.Where(mr => mr.Reviewers != null || mr.Reviewers.Any()); } - - if (query.AuthorId != null) + else { - mergeRequests = mergeRequests.Where(mr => mr.Author.Id == query.AuthorId); + var reviewerId = int.Parse(query.ReviewerId.ToString()); + mergeRequests = mergeRequests.Where(mr => mr.Reviewers.Any(x => reviewerId.Equals(x.Id))); } + } - if (query.CreatedAfter != null) - { - mergeRequests = mergeRequests.Where(mr => mr.CreatedAt >= query.CreatedAfter.Value.ToDateTimeOffsetAssumeUtc()); - } + if (query.AuthorId != null) + { + mergeRequests = mergeRequests.Where(mr => mr.Author.Id == query.AuthorId); + } - if (query.CreatedBefore != null) - { - mergeRequests = mergeRequests.Where(mr => mr.CreatedAt <= query.CreatedBefore.Value.ToDateTimeOffsetAssumeUtc()); - } + if (query.CreatedAfter != null) + { + mergeRequests = mergeRequests.Where(mr => mr.CreatedAt >= query.CreatedAfter.Value.ToDateTimeOffsetAssumeUtc()); + } - if (!string.IsNullOrEmpty(query.Labels)) - { - foreach (var label in query.Labels.Split(',')) - { - mergeRequests = mergeRequests.Where(mr => mr.Labels.Contains(label, StringComparer.Ordinal)); - } - } + if (query.CreatedBefore != null) + { + mergeRequests = mergeRequests.Where(mr => mr.CreatedAt <= query.CreatedBefore.Value.ToDateTimeOffsetAssumeUtc()); + } - if (query.Milestone != null) + if (!string.IsNullOrEmpty(query.Labels)) + { + foreach (var label in query.Labels.Split(',')) { - throw new NotImplementedException(); + mergeRequests = mergeRequests.Where(mr => mr.Labels.Contains(label, StringComparer.Ordinal)); } + } - if (query.Scope != null) - { - var userId = Context.User.Id; - switch (query.Scope) - { - case "created_by_me": - case "created-by-me": - mergeRequests = mergeRequests.Where(mr => mr.Author.Id == userId); - break; - case "assigned_to_me": - case "assigned-to-me": - mergeRequests = mergeRequests.Where(mr => mr.Assignee?.Id == userId); - break; - case "all": - break; - default: - throw new NotSupportedException($"Scope '{query.Scope}' is not supported"); - } - } + if (query.Milestone != null) + { + throw new NotImplementedException(); + } - if (query.Search != null) - { - throw new NotImplementedException(); + if (query.Scope != null) + { + var userId = Context.User.Id; + switch (query.Scope) + { + case "created_by_me": + case "created-by-me": + mergeRequests = mergeRequests.Where(mr => mr.Author.Id == userId); + break; + case "assigned_to_me": + case "assigned-to-me": + mergeRequests = mergeRequests.Where(mr => mr.Assignee?.Id == userId); + break; + case "all": + break; + default: + throw new NotSupportedException($"Scope '{query.Scope}' is not supported"); } + } - if (query.SourceBranch != null) - { - mergeRequests = mergeRequests.Where(mr => string.Equals(mr.SourceBranch, query.SourceBranch, StringComparison.Ordinal)); - } + if (query.Search != null) + { + throw new NotImplementedException(); + } - if (query.TargetBranch != null) - { - mergeRequests = mergeRequests.Where(mr => string.Equals(mr.TargetBranch, query.TargetBranch, StringComparison.Ordinal)); - } + if (query.SourceBranch != null) + { + mergeRequests = mergeRequests.Where(mr => string.Equals(mr.SourceBranch, query.SourceBranch, StringComparison.Ordinal)); + } - if (query.UpdatedAfter != null) - { - mergeRequests = mergeRequests.Where(mr => mr.UpdatedAt >= query.UpdatedAfter.Value.ToDateTimeOffsetAssumeUtc()); - } + if (query.TargetBranch != null) + { + mergeRequests = mergeRequests.Where(mr => string.Equals(mr.TargetBranch, query.TargetBranch, StringComparison.Ordinal)); + } - if (query.UpdatedBefore != null) - { - mergeRequests = mergeRequests.Where(mr => mr.UpdatedAt <= query.UpdatedBefore.Value.ToDateTimeOffsetAssumeUtc()); - } + if (query.UpdatedAfter != null) + { + mergeRequests = mergeRequests.Where(mr => mr.UpdatedAt >= query.UpdatedAfter.Value.ToDateTimeOffsetAssumeUtc()); + } - if (query.State != null) - { - mergeRequests = mergeRequests.Where(mr => mr.State == query.State); - } + if (query.UpdatedBefore != null) + { + mergeRequests = mergeRequests.Where(mr => mr.UpdatedAt <= query.UpdatedBefore.Value.ToDateTimeOffsetAssumeUtc()); + } - if (string.Equals(query.Sort, "asc", StringComparison.Ordinal)) - { - mergeRequests = mergeRequests.Reverse(); - } + if (query.State != null) + { + mergeRequests = mergeRequests.Where(mr => mr.State == query.State); + } - if (query.OrderBy != null) - { - mergeRequests = query.OrderBy switch - { - "created_at" => mergeRequests.OrderBy(mr => mr.CreatedAt), - "updated_at" => mergeRequests.OrderBy(mr => mr.UpdatedAt), - _ => throw new NotSupportedException($"OrderBy '{query.OrderBy}' is not supported"), - }; - } + if (string.Equals(query.Sort, "asc", StringComparison.Ordinal)) + { + mergeRequests = mergeRequests.Reverse(); + } - if (query.PerPage != null) + if (query.OrderBy != null) + { + mergeRequests = query.OrderBy switch { - mergeRequests = mergeRequests.Take(query.PerPage.Value); - } + "created_at" => mergeRequests.OrderBy(mr => mr.CreatedAt), + "updated_at" => mergeRequests.OrderBy(mr => mr.UpdatedAt), + _ => throw new NotSupportedException($"OrderBy '{query.OrderBy}' is not supported"), + }; + } - if (query.Wip != null) - { - mergeRequests = mergeRequests.Where(mr => (bool)query.Wip ? mr.WorkInProgress : !mr.WorkInProgress); - } + if (query.PerPage != null) + { + mergeRequests = mergeRequests.Take(query.PerPage.Value); } - return mergeRequests.Select(mr => mr.ToMergeRequestClient()).ToList(); + if (query.Wip != null) + { + mergeRequests = mergeRequests.Where(mr => (bool)query.Wip ? mr.Draft : !mr.Draft); + } } - public IEnumerable GetParticipants(int mergeRequestIid) - { - AssertProjectId(); + return mergeRequests.Select(mr => mr.ToMergeRequestClient()).ToList(); + } - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); - var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); - if (mergeRequest == null) - throw new GitLabNotFoundException(); + public IEnumerable GetParticipants(int mergeRequestIid) + { + AssertProjectId(); - return mergeRequest.Comments.Select(c => c.Author) - .Union(new[] { mergeRequest.Author }) - .Select(u => u.ToClientAuthor()) - .ToList(); - } + using (Context.BeginOperationScope()) + { + var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); + var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); + if (mergeRequest == null) + throw new GitLabNotFoundException(); + + return mergeRequest.Comments.Select(c => c.Author) + .Union(new[] { mergeRequest.Author }) + .Select(u => u.ToClientAuthor()) + .ToList(); } + } - public IEnumerable GetPipelines(int mergeRequestIid) + public IEnumerable GetPipelines(int mergeRequestIid) + { + AssertProjectId(); + + using (Context.BeginOperationScope()) { - AssertProjectId(); + var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); + var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); - var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); + if (mergeRequest == null) + throw new GitLabNotFoundException(); - if (mergeRequest == null) - throw new GitLabNotFoundException(); - - var allSha1 = mergeRequest.Commits.Select(m => new Sha1(m.Sha)); + var allSha1 = mergeRequest.Commits.Select(m => new Sha1(m.Sha)); - return mergeRequest.SourceProject.Pipelines - .Where(p => allSha1.Contains(p.Sha)) - .OrderByDescending(p => p.CreatedAt) - .Select(p => p.ToPipelineBasicClient()) - .ToList(); - } + return mergeRequest.SourceProject.Pipelines + .Where(p => allSha1.Contains(p.Sha)) + .OrderByDescending(p => p.CreatedAt) + .Select(p => p.ToPipelineBasicClient()) + .ToList(); } + } - public Models.MergeRequest Reopen(int mergeRequestIid) + public Models.MergeRequest Reopen(int mergeRequestIid) + { + AssertProjectId(); + + using (Context.BeginOperationScope()) { - AssertProjectId(); + var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); + var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); + if (mergeRequest == null) + throw new GitLabNotFoundException(); - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); - var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); - if (mergeRequest == null) - throw new GitLabNotFoundException(); + if (mergeRequest.State != MergeRequestState.closed) + throw new GitLabBadRequestException(); - if (mergeRequest.State != MergeRequestState.closed) - throw new GitLabBadRequestException(); + mergeRequest.ClosedAt = null; + mergeRequest.UpdatedAt = DateTimeOffset.UtcNow; - mergeRequest.ClosedAt = null; - mergeRequest.UpdatedAt = DateTimeOffset.UtcNow; - return mergeRequest.ToMergeRequestClient(); - } + Server.ResourceStateEvents.CreateResourceStateEvent(Context.User, "reopened", mergeRequest.Id, "MergeRequest"); + return mergeRequest.ToMergeRequestClient(); } + } + + public Models.MergeRequest Update(int mergeRequestIid, MergeRequestUpdate mergeRequestUpdate) + { + AssertProjectId(); - public Models.MergeRequest Update(int mergeRequestIid, MergeRequestUpdate mergeRequestUpdate) + using (Context.BeginOperationScope()) { - AssertProjectId(); + var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); + var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); + if (mergeRequest == null) + throw new GitLabNotFoundException(); - using (Context.BeginOperationScope()) + if (mergeRequestUpdate.AssigneeIds != null) { - var project = GetProject(_projectId.GetValueOrDefault(), ProjectPermission.View); - var mergeRequest = project.MergeRequests.GetByIid(mergeRequestIid); - if (mergeRequest == null) - throw new GitLabNotFoundException(); - - if (mergeRequestUpdate.AssigneeIds != null) + mergeRequest.Assignees.Clear(); + if (mergeRequestUpdate.AssigneeIds.Length == 0) { - mergeRequest.Assignees.Clear(); - if (mergeRequestUpdate.AssigneeIds.Length == 0) - { - mergeRequest.Assignee = null; - } - else - { - foreach (var assigneeId in mergeRequestUpdate.AssigneeIds) - { - var user = Server.Users.GetById(assigneeId); - if (user == null) - throw new GitLabBadRequestException("user not found"); - - mergeRequest.Assignees.Add(new UserRef(user)); - } - } + mergeRequest.Assignee = null; } - else if (mergeRequestUpdate.AssigneeId != null) + else { - if (mergeRequestUpdate.AssigneeId.Value == 0) - { - mergeRequest.Assignee = null; - } - else + foreach (var assigneeId in mergeRequestUpdate.AssigneeIds) { - var user = Server.Users.GetById(mergeRequestUpdate.AssigneeId.Value); + var user = Server.Users.GetById(assigneeId); if (user == null) throw new GitLabBadRequestException("user not found"); - mergeRequest.Assignee = new UserRef(user); + mergeRequest.Assignees.Add(new UserRef(user)); } } - - if (mergeRequestUpdate.ReviewerIds != null) + } + else if (mergeRequestUpdate.AssigneeId != null) + { + if (mergeRequestUpdate.AssigneeId.Value == 0) { - foreach (var reviewerId in mergeRequestUpdate.ReviewerIds) - { - var reviewer = Server.Users.GetById(reviewerId); - if (reviewer == null) - throw new GitLabBadRequestException("user not found"); - - mergeRequest.Reviewers.Add(reviewer); - } + mergeRequest.Assignee = null; } - - if (mergeRequestUpdate.Description != null) + else { - mergeRequest.Description = mergeRequestUpdate.Description; - } + var user = Server.Users.GetById(mergeRequestUpdate.AssigneeId.Value); + if (user == null) + throw new GitLabBadRequestException("user not found"); - if (mergeRequestUpdate.NewState != null) - { - throw new NotImplementedException(); + mergeRequest.Assignee = new UserRef(user); } + } - if (mergeRequestUpdate.SourceBranch != null) - { - mergeRequest.SourceBranch = mergeRequestUpdate.SourceBranch; - } + if (mergeRequestUpdate.MilestoneId != null) + { + var prevMilestone = mergeRequest.Milestone; + mergeRequest.Milestone = GetMilestone(project.Id, mergeRequestUpdate.MilestoneId.Value); + Server.ResourceMilestoneEvents.CreateResourceMilestoneEvents(Context.User, mergeRequest.Id, prevMilestone, mergeRequest.Milestone, "MergeRequest"); + } - if (mergeRequestUpdate.TargetBranch != null) + if (mergeRequestUpdate.ReviewerIds != null) + { + foreach (var reviewerId in mergeRequestUpdate.ReviewerIds) { - mergeRequest.TargetBranch = mergeRequestUpdate.TargetBranch; - } + var reviewer = Server.Users.GetById(reviewerId); + if (reviewer == null) + throw new GitLabBadRequestException("user not found"); - if (mergeRequestUpdate.Title != null) - { - mergeRequest.Title = mergeRequestUpdate.Title; + mergeRequest.Reviewers.Add(reviewer); } + } - SetLabels(mergeRequest, mergeRequestUpdate.Labels); + if (mergeRequestUpdate.Description != null) + { + mergeRequest.Description = mergeRequestUpdate.Description; + } - mergeRequest.UpdatedAt = DateTimeOffset.UtcNow; - return mergeRequest.ToMergeRequestClient(); + if (mergeRequestUpdate.NewState != null) + { + throw new NotImplementedException(); + } + + if (mergeRequestUpdate.SourceBranch != null) + { + mergeRequest.SourceBranch = mergeRequestUpdate.SourceBranch; + } + + if (mergeRequestUpdate.TargetBranch != null) + { + mergeRequest.TargetBranch = mergeRequestUpdate.TargetBranch; + } + + if (mergeRequestUpdate.Title != null) + { + mergeRequest.Title = mergeRequestUpdate.Title; } + + SetLabels(mergeRequest, mergeRequestUpdate.Labels, mergeRequestUpdate.AddLabels, mergeRequestUpdate.RemoveLabels); + + mergeRequest.UpdatedAt = DateTimeOffset.UtcNow; + return mergeRequest.ToMergeRequestClient(); } + } + + public IEnumerable ClosesIssues(int mergeRequestIid) + { + throw new NotImplementedException(); + } + + public GitLabCollectionResponse GetVersionsAsync(int mergeRequestIid) + { + throw new NotImplementedException(); + } + + public Task TimeStatsAsync(int mergeRequestIid, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } - public IEnumerable ClosesIssues(int mergeRequestIid) + public IMergeRequestDiscussionClient Discussions(int mergeRequestIid) + { + AssertProjectId(); + + return new MergeRequestDiscussionClient(Context, _projectId.GetValueOrDefault(), mergeRequestIid); + } + + public GitLabCollectionResponse ResourceLabelEventsAsync(int projectId, int mergeRequestIid) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var mergeRequest = GetMergeRequest(projectId, mergeRequestIid); + var resourceLabelEvents = Server.ResourceLabelEvents.Get(mergeRequest.Id); + + return GitLabCollectionResponse.Create(resourceLabelEvents.Select(rle => rle.ToClientResourceLabelEvent())); } + } - public GitLabCollectionResponse GetVersionsAsync(int mergeRequestIid) + public GitLabCollectionResponse ResourceMilestoneEventsAsync(int projectId, int mergeRequestIid) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var mergeRequest = GetMergeRequest(projectId, mergeRequestIid); + var resourceMilestoneEvents = Server.ResourceMilestoneEvents.Get(mergeRequest.Id); + + return GitLabCollectionResponse.Create(resourceMilestoneEvents.Select(rme => rme.ToClientResourceMilestoneEvent())); } + } - public IMergeRequestDiscussionClient Discussions(int mergeRequestIid) + public GitLabCollectionResponse ResourceStateEventsAsync(int projectId, int mergeRequestIid) + { + using (Context.BeginOperationScope()) { - AssertProjectId(); + var mergeRequest = GetMergeRequest(projectId, mergeRequestIid); + var resourceStateEvents = Server.ResourceStateEvents.Get(mergeRequest.Id); - return new MergeRequestDiscussionClient(Context, _projectId.GetValueOrDefault(), mergeRequestIid); + return GitLabCollectionResponse.Create(resourceStateEvents.Select(rle => rle.ToClientResourceStateEvent())); } } } diff --git a/NGitLab.Mock/Clients/MergeRequestCommentClient.cs b/NGitLab.Mock/Clients/MergeRequestCommentClient.cs index ea8457f5..8bdae201 100644 --- a/NGitLab.Mock/Clients/MergeRequestCommentClient.cs +++ b/NGitLab.Mock/Clients/MergeRequestCommentClient.cs @@ -3,126 +3,146 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class MergeRequestCommentClient : ClientBase, IMergeRequestCommentClient { - internal sealed class MergeRequestCommentClient : ClientBase, IMergeRequestCommentClient - { - private readonly int _projectId; - private readonly int _mergeRequestIid; + private readonly int _projectId; + private readonly int _mergeRequestIid; - public MergeRequestCommentClient(ClientContext context, int projectId, int mergeRequestIid) - : base(context) - { - _projectId = projectId; - _mergeRequestIid = mergeRequestIid; - } + public MergeRequestCommentClient(ClientContext context, int projectId, int mergeRequestIid) + : base(context) + { + _projectId = projectId; + _mergeRequestIid = mergeRequestIid; + } - private MergeRequest GetMergeRequest() => GetMergeRequest(_projectId, _mergeRequestIid); + private MergeRequest GetMergeRequest() => GetMergeRequest(_projectId, _mergeRequestIid); - public IEnumerable All + public IEnumerable All + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return GetMergeRequest().Comments.Select(mr => mr.ToMergeRequestCommentClient()).ToList(); - } + return GetMergeRequest().Comments.Select(mr => mr.ToMergeRequestCommentClient()).ToList(); } } + } - public IEnumerable Discussions + public IEnumerable Discussions + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return GetMergeRequest().GetDiscussions().ToList(); - } + return GetMergeRequest().GetDiscussions().ToList(); } } + } - public Models.MergeRequestComment Add(Models.MergeRequestComment comment) + public Models.MergeRequestComment Add(Models.MergeRequestComment comment) + { + return Add(new MergeRequestCommentCreate { - return Add(new MergeRequestCommentCreate + Body = comment.Body, + CreatedAt = null, + }); + } + + public Models.MergeRequestComment Add(MergeRequestCommentCreate commentCreate) + { + EnsureUserIsAuthenticated(); + + using (Context.BeginOperationScope()) + { + var project = GetProject(_projectId, ProjectPermission.View); + if (project.Archived) + throw new GitLabForbiddenException(); + + var comment = new MergeRequestComment { - Body = comment.Body, - CreatedAt = null, - }); + Author = Context.User, + Body = commentCreate.Body, + }; + + GetMergeRequest().Comments.Add(comment); + return comment.ToMergeRequestCommentClient(); } + } + + public Models.MergeRequestComment Add(string discussionId, MergeRequestCommentCreate commentCreate) + { + EnsureUserIsAuthenticated(); - public Models.MergeRequestComment Add(MergeRequestCommentCreate commentCreate) + using (Context.BeginOperationScope()) { - EnsureUserIsAuthenticated(); + var project = GetProject(_projectId, ProjectPermission.View); + if (project.Archived) + throw new GitLabForbiddenException(); - using (Context.BeginOperationScope()) + var comment = new MergeRequestComment { - var project = GetProject(_projectId, ProjectPermission.View); - if (project.Archived) - throw new GitLabForbiddenException(); - - var comment = new MergeRequestComment - { - Author = Context.User, - Body = commentCreate.Body, - }; - - GetMergeRequest().Comments.Add(comment); - return comment.ToMergeRequestCommentClient(); - } + Author = Context.User, + Body = commentCreate.Body, + }; + + GetMergeRequest().Comments.Add(comment); + return comment.ToMergeRequestCommentClient(); } + } - public Models.MergeRequestComment Edit(long id, MergeRequestCommentEdit edit) + public Models.MergeRequestComment Edit(long id, MergeRequestCommentEdit edit) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - if (project.Archived) - throw new GitLabForbiddenException(); + var project = GetProject(_projectId, ProjectPermission.View); + if (project.Archived) + throw new GitLabForbiddenException(); - var comment = GetMergeRequest().Comments.GetById(id); - if (comment == null) - throw new GitLabNotFoundException(); + var comment = GetMergeRequest().Comments.GetById(id); + if (comment == null) + throw new GitLabNotFoundException(); - comment.Body = edit.Body; - return comment.ToMergeRequestCommentClient(); - } + comment.Body = edit.Body; + return comment.ToMergeRequestCommentClient(); } + } - public void Delete(long id) + public void Delete(long id) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var comments = GetMergeRequest().Comments; - var comment = comments.GetById(id); - if (comment == null) - throw new GitLabNotFoundException(); + var comments = GetMergeRequest().Comments; + var comment = comments.GetById(id); + if (comment == null) + throw new GitLabNotFoundException(); - comments.Remove(comment); - } + comments.Remove(comment); } + } - public IEnumerable Get(MergeRequestCommentQuery query) + public IEnumerable Get(MergeRequestCommentQuery query) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var comments = GetMergeRequest().Comments.Select(mr => mr.ToMergeRequestCommentClient()); + var orderByUpdated = query.OrderBy != null && query.OrderBy.Equals("updated_at", StringComparison.Ordinal); + + if (string.Equals(query.Sort, "asc", StringComparison.Ordinal)) + { + comments = orderByUpdated ? comments.OrderBy(comment => comment.UpdatedAt) : comments.OrderBy(comment => comment.CreatedAt); + } + else { - var comments = GetMergeRequest().Comments.Select(mr => mr.ToMergeRequestCommentClient()); - var orderByUpdated = query.OrderBy != null && query.OrderBy.Equals("updated_at", StringComparison.Ordinal); - - if (string.Equals(query.Sort, "asc", StringComparison.Ordinal)) - { - comments = orderByUpdated ? comments.OrderBy(comment => comment.UpdatedAt) : comments.OrderBy(comment => comment.CreatedAt); - } - else - { - comments = orderByUpdated ? comments.OrderByDescending(comment => comment.UpdatedAt) : comments.OrderByDescending(comment => comment.CreatedAt); - } - - var pageIndex = query.Page.HasValue ? query.Page.Value - 1 : 0; - var perPage = query.PerPage ?? 20; - var lowerBound = pageIndex * perPage; - - return comments.Skip(lowerBound).Take(perPage); + comments = orderByUpdated ? comments.OrderByDescending(comment => comment.UpdatedAt) : comments.OrderByDescending(comment => comment.CreatedAt); } + + var pageIndex = query.Page.HasValue ? query.Page.Value - 1 : 0; + var perPage = query.PerPage ?? 20; + var lowerBound = pageIndex * perPage; + + return comments.Skip(lowerBound).Take(perPage); } } } diff --git a/NGitLab.Mock/Clients/MergeRequestCommitClient.cs b/NGitLab.Mock/Clients/MergeRequestCommitClient.cs index 5c2f4530..7f9e4dd1 100644 --- a/NGitLab.Mock/Clients/MergeRequestCommitClient.cs +++ b/NGitLab.Mock/Clients/MergeRequestCommitClient.cs @@ -2,29 +2,28 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class MergeRequestCommitClient : ClientBase, IMergeRequestCommitClient { - internal sealed class MergeRequestCommitClient : ClientBase, IMergeRequestCommitClient - { - private readonly int _projectId; - private readonly int _mergeRequestIid; + private readonly int _projectId; + private readonly int _mergeRequestIid; - public MergeRequestCommitClient(ClientContext context, int projectId, int mergeRequestIid) - : base(context) - { - _projectId = projectId; - _mergeRequestIid = mergeRequestIid; - } + public MergeRequestCommitClient(ClientContext context, int projectId, int mergeRequestIid) + : base(context) + { + _projectId = projectId; + _mergeRequestIid = mergeRequestIid; + } - public IEnumerable All + public IEnumerable All + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var mergeRequest = GetMergeRequest(_projectId, _mergeRequestIid); - return mergeRequest.Commits.Select(commit => commit.ToCommitClient(mergeRequest.SourceProject)).ToList(); - } + var mergeRequest = GetMergeRequest(_projectId, _mergeRequestIid); + return mergeRequest.Commits.Select(commit => commit.ToCommitClient(mergeRequest.SourceProject)).ToList(); } } } diff --git a/NGitLab.Mock/Clients/MergeRequestDiscussionClient.cs b/NGitLab.Mock/Clients/MergeRequestDiscussionClient.cs index 82ad734f..1008e47a 100644 --- a/NGitLab.Mock/Clients/MergeRequestDiscussionClient.cs +++ b/NGitLab.Mock/Clients/MergeRequestDiscussionClient.cs @@ -5,119 +5,118 @@ using System.Threading.Tasks; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class MergeRequestDiscussionClient : ClientBase, IMergeRequestDiscussionClient { - internal sealed class MergeRequestDiscussionClient : ClientBase, IMergeRequestDiscussionClient - { - private readonly int _projectId; - private readonly int _mergeRequestIid; + private readonly int _projectId; + private readonly int _mergeRequestIid; - public MergeRequestDiscussionClient(ClientContext context, int projectId, int mergeRequestIid) - : base(context) - { - _projectId = projectId; - _mergeRequestIid = mergeRequestIid; - } + public MergeRequestDiscussionClient(ClientContext context, int projectId, int mergeRequestIid) + : base(context) + { + _projectId = projectId; + _mergeRequestIid = mergeRequestIid; + } - private MergeRequest GetMergeRequest() => GetMergeRequest(_projectId, _mergeRequestIid); + private MergeRequest GetMergeRequest() => GetMergeRequest(_projectId, _mergeRequestIid); - public IEnumerable All - { - get - { - using (Context.BeginOperationScope()) - { - return GetMergeRequest().GetDiscussions().ToList(); - } - } - } - - public MergeRequestDiscussion Get(string id) + public IEnumerable All + { + get { using (Context.BeginOperationScope()) { - var discussions = GetMergeRequest().GetDiscussions(); - var discussion = discussions.FirstOrDefault(x => string.Equals(x.Id, id, StringComparison.Ordinal)); - return discussion ?? throw new GitLabNotFoundException(); + return GetMergeRequest().GetDiscussions().ToList(); } } + } - public Task GetAsync(string id, CancellationToken cancellationToken = default) + public MergeRequestDiscussion Get(string id) + { + using (Context.BeginOperationScope()) { - return Task.FromResult(Get(id)); + var discussions = GetMergeRequest().GetDiscussions(); + var discussion = discussions.FirstOrDefault(x => string.Equals(x.Id, id, StringComparison.Ordinal)); + return discussion ?? throw new GitLabNotFoundException(); } + } - public MergeRequestDiscussion Add(Models.MergeRequestComment comment) + public Task GetAsync(string id, CancellationToken cancellationToken = default) + { + return Task.FromResult(Get(id)); + } + + public MergeRequestDiscussion Add(Models.MergeRequestComment comment) + { + return Add(new MergeRequestDiscussionCreate { - return Add(new MergeRequestDiscussionCreate - { - Body = comment.Body, - CreatedAt = null, - }); - } + Body = comment.Body, + CreatedAt = null, + }); + } + + public MergeRequestDiscussion Add(MergeRequestDiscussionCreate commentCreate) + { + EnsureUserIsAuthenticated(); - public MergeRequestDiscussion Add(MergeRequestDiscussionCreate commentCreate) + using (Context.BeginOperationScope()) { - EnsureUserIsAuthenticated(); + var project = GetProject(_projectId, ProjectPermission.View); + if (project.Archived) + throw new GitLabForbiddenException(); - using (Context.BeginOperationScope()) + var comment = new MergeRequestComment { - var project = GetProject(_projectId, ProjectPermission.View); - if (project.Archived) - throw new GitLabForbiddenException(); - - var comment = new MergeRequestComment - { - Author = Context.User, - Body = commentCreate.Body, - }; + Author = Context.User, + Body = commentCreate.Body, + }; - GetMergeRequest().Comments.Add(comment); + GetMergeRequest().Comments.Add(comment); - return new MergeRequestDiscussion - { - Id = comment.ThreadId, - IndividualNote = false, - Notes = new[] { comment.ToMergeRequestCommentClient() }, - }; - } + return new MergeRequestDiscussion + { + Id = comment.ThreadId, + IndividualNote = false, + Notes = new[] { comment.ToMergeRequestCommentClient() }, + }; } + } - public MergeRequestDiscussion Resolve(MergeRequestDiscussionResolve resolve) + public MergeRequestDiscussion Resolve(MergeRequestDiscussionResolve resolve) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var discussions = GetMergeRequest().GetDiscussions(); - var discussion = discussions.FirstOrDefault(x => string.Equals(x.Id, resolve.Id, StringComparison.Ordinal)); - if (discussion == null) - throw new GitLabNotFoundException(); - - foreach (var note in discussion.Notes) - { - note.Resolved = true; - } + var discussions = GetMergeRequest().GetDiscussions(); + var discussion = discussions.FirstOrDefault(x => string.Equals(x.Id, resolve.Id, StringComparison.Ordinal)); + if (discussion == null) + throw new GitLabNotFoundException(); - return discussion; + foreach (var note in discussion.Notes) + { + note.Resolved = true; } + + return discussion; } + } - public void Delete(string discussionId, long noteId) + public void Delete(string discussionId, long noteId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var discussions = GetMergeRequest().GetDiscussions(); - var discussion = discussions.FirstOrDefault(x => string.Equals(x.Id, discussionId, StringComparison.Ordinal)); - if (discussion == null) - throw new GitLabNotFoundException(); + var discussions = GetMergeRequest().GetDiscussions(); + var discussion = discussions.FirstOrDefault(x => string.Equals(x.Id, discussionId, StringComparison.Ordinal)); + if (discussion == null) + throw new GitLabNotFoundException(); - var allComments = GetMergeRequest().Comments; - foreach (var discussionNote in discussion.Notes.Where(x => x.Id == noteId)) + var allComments = GetMergeRequest().Comments; + foreach (var discussionNote in discussion.Notes.Where(x => x.Id == noteId)) + { + var note = allComments.FirstOrDefault(x => x.Id == discussionNote.Id); + if (note != null) { - var note = allComments.FirstOrDefault(x => x.Id == discussionNote.Id); - if (note != null) - { - allComments.Remove(note); - } + allComments.Remove(note); } } } diff --git a/NGitLab.Mock/Clients/MilestoneClient.cs b/NGitLab.Mock/Clients/MilestoneClient.cs index 0e688f6d..8ada014d 100644 --- a/NGitLab.Mock/Clients/MilestoneClient.cs +++ b/NGitLab.Mock/Clients/MilestoneClient.cs @@ -6,215 +6,219 @@ using NGitLab.Mock.Clients; using NGitLab.Models; -namespace NGitLab.Mock +namespace NGitLab.Mock; + +internal sealed class MilestoneClient : ClientBase, IMilestoneClient { - internal sealed class MilestoneClient : ClientBase, IMilestoneClient - { - private readonly int _resourceId; + private readonly int _resourceId; - public MilestoneClient(ClientContext context, int id, MilestoneScope scope) - : base(context) + public MilestoneClient(ClientContext context, IIdOrPathAddressable id, MilestoneScope scope) + : base(context) + { + _resourceId = scope switch { - _resourceId = id; - Scope = scope; - } + MilestoneScope.Groups => Server.AllGroups.FindGroup(id.ValueAsUriParameter()).Id, + MilestoneScope.Projects => Server.AllProjects.FindProject(id.ValueAsUriParameter()).Id, + _ => throw new NotSupportedException($"{scope} milestone is not supported yet."), + }; + Scope = scope; + } - public Models.Milestone this[int id] + public Models.Milestone this[int id] + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return GetMilestone(id, false).ToClientMilestone(); - } + return GetMilestone(id, false).ToClientMilestone(); } } + } - public IEnumerable All => Get(new MilestoneQuery()); + public IEnumerable All => Get(new MilestoneQuery()); - public MilestoneScope Scope { get; } + public MilestoneScope Scope { get; } - public Models.Milestone Activate(int milestoneId) + public Models.Milestone Activate(int milestoneId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var milestone = GetMilestone(milestoneId, true); - milestone.State = MilestoneState.active; - return milestone.ToClientMilestone(); - } + var milestone = GetMilestone(milestoneId, true); + milestone.State = MilestoneState.active; + return milestone.ToClientMilestone(); } + } - public IEnumerable GetMergeRequests(int milestoneId) + public IEnumerable GetMergeRequests(int milestoneId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var milestone = GetMilestone(milestoneId, false); + IEnumerable mergeRequests; + + switch (Scope) { - var milestone = GetMilestone(milestoneId, false); - IEnumerable mergeRequests; - - switch (Scope) - { - case MilestoneScope.Groups: - mergeRequests = milestone.Group.MergeRequests; - break; - case MilestoneScope.Projects: - mergeRequests = milestone.Project.MergeRequests; - break; - default: - throw new NotSupportedException($"{Scope} milestone is not supported yet."); - } - - mergeRequests = mergeRequests.Where(mr => mr.Milestone == milestone); - return mergeRequests.Select(mr => mr.ToMergeRequestClient()); + case MilestoneScope.Groups: + mergeRequests = milestone.Group.MergeRequests; + break; + case MilestoneScope.Projects: + mergeRequests = milestone.Project.MergeRequests; + break; + default: + throw new NotSupportedException($"{Scope} milestone is not supported yet."); } + + mergeRequests = mergeRequests.Where(mr => mr.Milestone == milestone); + return mergeRequests.Select(mr => mr.ToMergeRequestClient()); } + } + + public IEnumerable AllInState(Models.MilestoneState state) + { + return Get(new MilestoneQuery { State = state }); + } - public IEnumerable AllInState(Models.MilestoneState state) + public Models.Milestone Close(int milestoneId) + { + using (Context.BeginOperationScope()) { - return Get(new MilestoneQuery { State = state }); + var milestone = GetMilestone(milestoneId, true); + milestone.State = MilestoneState.closed; + return milestone.ToClientMilestone(); } + } - public Models.Milestone Close(int milestoneId) + public Models.Milestone Create(MilestoneCreate milestone) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var ms = new Milestone { - var milestone = GetMilestone(milestoneId, true); - milestone.State = MilestoneState.closed; - return milestone.ToClientMilestone(); - } - } + Title = milestone.Title, + Description = milestone.Description, + DueDate = string.IsNullOrEmpty(milestone.DueDate) ? DateTimeOffset.UtcNow : DateTimeOffset.Parse(milestone.DueDate), + StartDate = string.IsNullOrEmpty(milestone.StartDate) ? DateTimeOffset.UtcNow : DateTimeOffset.Parse(milestone.StartDate), + }; - public Models.Milestone Create(MilestoneCreate milestone) - { - using (Context.BeginOperationScope()) + switch (Scope) { - var ms = new Milestone - { - Title = milestone.Title, - Description = milestone.Description, - DueDate = string.IsNullOrEmpty(milestone.DueDate) ? DateTimeOffset.UtcNow : DateTimeOffset.Parse(milestone.DueDate), - StartDate = string.IsNullOrEmpty(milestone.StartDate) ? DateTimeOffset.UtcNow : DateTimeOffset.Parse(milestone.StartDate), - }; - - switch (Scope) - { - case MilestoneScope.Groups: - var group = GetGroup(_resourceId, GroupPermission.Edit); - group.Milestones.Add(ms); - break; - case MilestoneScope.Projects: - var project = GetProject(_resourceId, ProjectPermission.Edit); - project.Milestones.Add(ms); - break; - default: - throw new NotSupportedException($"{Scope} milestone is not supported yet."); - } - - return ms.ToClientMilestone(); + case MilestoneScope.Groups: + var group = GetGroup(_resourceId, GroupPermission.Edit); + group.Milestones.Add(ms); + break; + case MilestoneScope.Projects: + var project = GetProject(_resourceId, ProjectPermission.Edit); + project.Milestones.Add(ms); + break; + default: + throw new NotSupportedException($"{Scope} milestone is not supported yet."); } + + return ms.ToClientMilestone(); } + } - public void Delete(int milestoneId) + public void Delete(int milestoneId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var milestone = GetMilestone(milestoneId, true); + switch (Scope) { - var milestone = GetMilestone(milestoneId, true); - switch (Scope) - { - case MilestoneScope.Groups: - milestone.Group.Milestones.Remove(milestone); - break; - case MilestoneScope.Projects: - milestone.Project.Milestones.Remove(milestone); - break; - default: - throw new NotSupportedException($"{Scope} milestone is not supported yet."); - } + case MilestoneScope.Groups: + milestone.Group.Milestones.Remove(milestone); + break; + case MilestoneScope.Projects: + milestone.Project.Milestones.Remove(milestone); + break; + default: + throw new NotSupportedException($"{Scope} milestone is not supported yet."); } } + } - public IEnumerable Get(MilestoneQuery query) + public IEnumerable Get(MilestoneQuery query) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + IEnumerable milestones; + + switch (Scope) { - IEnumerable milestones; - - switch (Scope) - { - case MilestoneScope.Groups: - var group = GetGroup(_resourceId, GroupPermission.View); - milestones = group.Milestones; - break; - case MilestoneScope.Projects: - var project = GetProject(_resourceId, ProjectPermission.View); - milestones = project.Milestones; - break; - default: - throw new NotSupportedException($"{Scope} milestone is not supported yet."); - } - - if (query.State != null) - { - milestones = milestones.Where(m => (int)m.State == (int)query.State); - } - - if (!string.IsNullOrEmpty(query.Search)) - { - milestones = milestones.Where(m => m.Title.Contains(query.Search, StringComparison.OrdinalIgnoreCase)); - } - - return milestones.Select(m => m.ToClientMilestone()); + case MilestoneScope.Groups: + var group = GetGroup(_resourceId, GroupPermission.View); + milestones = group.Milestones; + break; + case MilestoneScope.Projects: + var project = GetProject(_resourceId, ProjectPermission.View); + milestones = project.Milestones; + break; + default: + throw new NotSupportedException($"{Scope} milestone is not supported yet."); } - } - public Models.Milestone Update(int milestoneId, MilestoneUpdate milestone) - { - using (Context.BeginOperationScope()) + if (query.State != null) { - var ms = GetMilestone(milestoneId, true); + milestones = milestones.Where(m => (int)m.State == (int)query.State); + } - if (!string.IsNullOrEmpty(milestone.Title)) - { - ms.Title = milestone.Title; - } + if (!string.IsNullOrEmpty(query.Search)) + { + milestones = milestones.Where(m => m.Title.Contains(query.Search, StringComparison.OrdinalIgnoreCase)); + } - if (milestone.Description != null) - { - ms.Description = milestone.Description; - } + return milestones.Select(m => m.ToClientMilestone()); + } + } - if (!string.IsNullOrEmpty(milestone.DueDate)) - { - ms.DueDate = DateTimeOffset.Parse(milestone.DueDate, CultureInfo.InvariantCulture); - } + public Models.Milestone Update(int milestoneId, MilestoneUpdate milestone) + { + using (Context.BeginOperationScope()) + { + var ms = GetMilestone(milestoneId, true); - if (!string.IsNullOrEmpty(milestone.StartDate)) - { - ms.StartDate = DateTimeOffset.Parse(milestone.StartDate, CultureInfo.InvariantCulture); - } + if (!string.IsNullOrEmpty(milestone.Title)) + { + ms.Title = milestone.Title; + } - return ms.ToClientMilestone(); + if (milestone.Description != null) + { + ms.Description = milestone.Description; } - } - private Milestone GetMilestone(int milestoneId, bool editing) - { - Milestone milestone; + if (!string.IsNullOrEmpty(milestone.DueDate)) + { + ms.DueDate = DateTimeOffset.Parse(milestone.DueDate, CultureInfo.InvariantCulture); + } - switch (Scope) + if (!string.IsNullOrEmpty(milestone.StartDate)) { - case MilestoneScope.Groups: - var group = GetGroup(_resourceId, editing ? GroupPermission.Edit : GroupPermission.View); - milestone = group.Milestones.FirstOrDefault(x => x.Id == milestoneId); - break; - case MilestoneScope.Projects: - var project = GetProject(_resourceId, editing ? ProjectPermission.Edit : ProjectPermission.View); - milestone = project.Milestones.FirstOrDefault(x => x.Id == milestoneId); - break; - default: - throw new NotSupportedException($"{Scope} milestone is not supported yet."); + ms.StartDate = DateTimeOffset.Parse(milestone.StartDate, CultureInfo.InvariantCulture); } - return milestone ?? throw new GitLabNotFoundException($"Cannot find milestone with ID {milestoneId}"); + return ms.ToClientMilestone(); + } + } + + private Milestone GetMilestone(int milestoneId, bool editing) + { + Milestone milestone; + + switch (Scope) + { + case MilestoneScope.Groups: + var group = GetGroup(_resourceId, editing ? GroupPermission.Edit : GroupPermission.View); + milestone = group.Milestones.FirstOrDefault(x => x.Id == milestoneId); + break; + case MilestoneScope.Projects: + var project = GetProject(_resourceId, editing ? ProjectPermission.Edit : ProjectPermission.View); + milestone = project.Milestones.FirstOrDefault(x => x.Id == milestoneId); + break; + default: + throw new NotSupportedException($"{Scope} milestone is not supported yet."); } + + return milestone ?? throw new GitLabNotFoundException($"Cannot find milestone with ID {milestoneId}"); } } diff --git a/NGitLab.Mock/Clients/NamespacesClient.cs b/NGitLab.Mock/Clients/NamespacesClient.cs index 307d57a3..9eac6023 100644 --- a/NGitLab.Mock/Clients/NamespacesClient.cs +++ b/NGitLab.Mock/Clients/NamespacesClient.cs @@ -2,24 +2,23 @@ using System.Collections.Generic; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class NamespacesClient : ClientBase, INamespacesClient { - internal sealed class NamespacesClient : ClientBase, INamespacesClient + public NamespacesClient(ClientContext context) + : base(context) { - public NamespacesClient(ClientContext context) - : base(context) - { - } + } - public Namespace this[int id] => throw new NotImplementedException(); + public Namespace this[int id] => throw new NotImplementedException(); - public Namespace this[string fullPath] => throw new NotImplementedException(); + public Namespace this[string fullPath] => throw new NotImplementedException(); - public IEnumerable Accessible => throw new NotImplementedException(); + public IEnumerable Accessible => throw new NotImplementedException(); - public IEnumerable Search(string search) - { - throw new NotImplementedException(); - } + public IEnumerable Search(string search) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/PackageClient.cs b/NGitLab.Mock/Clients/PackageClient.cs index e34b96f7..cd0e4bd4 100644 --- a/NGitLab.Mock/Clients/PackageClient.cs +++ b/NGitLab.Mock/Clients/PackageClient.cs @@ -12,17 +12,17 @@ public PackageClient(ClientContext context) { } - public Task PublishAsync(int projectId, PackagePublish packagePublish, CancellationToken cancellationToken = default) + public Task PublishGenericPackageAsync(ProjectId projectId, PackagePublish packagePublish, CancellationToken cancellationToken = default) { throw new System.NotImplementedException(); } - public IEnumerable Get(int projectId, PackageQuery packageQuery) + public GitLabCollectionResponse Get(ProjectId projectId, PackageQuery packageQuery) { throw new System.NotImplementedException(); } - public Task GetByIdAsync(int projectId, int packageId, CancellationToken cancellationToken = default) + public Task GetByIdAsync(ProjectId projectId, long packageId, CancellationToken cancellationToken = default) { throw new System.NotImplementedException(); } diff --git a/NGitLab.Mock/Clients/PipelineClient.cs b/NGitLab.Mock/Clients/PipelineClient.cs index 065e3e25..0a7cf0de 100644 --- a/NGitLab.Mock/Clients/PipelineClient.cs +++ b/NGitLab.Mock/Clients/PipelineClient.cs @@ -7,289 +7,302 @@ using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients -{ - internal sealed class PipelineClient : ClientBase, IPipelineClient - { - private readonly int _projectId; - private readonly IJobClient _jobClient; - - public PipelineClient(ClientContext context, IJobClient jobClient, int projectId) - : base(context) - { - _jobClient = jobClient; - _projectId = projectId; - } +namespace NGitLab.Mock.Clients; - public Models.Pipeline this[int id] - { - get - { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var pipeline = project.Pipelines.GetById(id); - if (pipeline == null) - throw new GitLabNotFoundException(); - - return pipeline.ToPipelineClient(); - } - } - } - - public IEnumerable All - { - get - { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - return project.Pipelines.Select(p => p.ToPipelineBasicClient()).ToList(); - } - } - } +internal sealed class PipelineClient : ClientBase, IPipelineClient +{ + private readonly int _projectId; + private readonly IJobClient _jobClient; - public IEnumerable AllJobs => _jobClient.GetJobs(JobScopeMask.All); + public PipelineClient(ClientContext context, IJobClient jobClient, ProjectId projectId) + : base(context) + { + _jobClient = jobClient; + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public Models.Pipeline Create(string @ref) + public Models.Pipeline this[int id] + { + get { using (Context.BeginOperationScope()) { var project = GetProject(_projectId, ProjectPermission.View); - var pipeline = project.Pipelines.Add(@ref, JobStatus.Running, Context.User); + var pipeline = project.Pipelines.GetById(id); + if (pipeline == null) + throw new GitLabNotFoundException(); + return pipeline.ToPipelineClient(); } } + } - public Models.Pipeline Create(PipelineCreate createOptions) + public IEnumerable All + { + get { using (Context.BeginOperationScope()) { var project = GetProject(_projectId, ProjectPermission.View); - var pipeline = project.Pipelines.Add(createOptions.Ref, JobStatus.Running, Context.User); - pipeline.Variables = createOptions.Variables.Select(v => new PipelineVariable { Key = v.Key, Value = v.Value }); - return pipeline.ToPipelineClient(); + return project.Pipelines.Select(p => p.ToPipelineBasicClient()).ToList(); } } + } + + public IEnumerable AllJobs => _jobClient.GetJobs(JobScopeMask.All); - public Models.Pipeline CreatePipelineWithTrigger(string token, string @ref, Dictionary variables) + public Models.Pipeline Create(string @ref) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var pipeline = project.Pipelines.Add(@ref, JobStatus.Running, Context.User); - pipeline.Variables = variables.Select(v => new PipelineVariable { Key = v.Key, Value = v.Value }); - pipeline.CiToken = token; - return pipeline.ToPipelineClient(); - } + var project = GetProject(_projectId, ProjectPermission.View); + var pipeline = project.Pipelines.Add(@ref, JobStatus.Running, Context.User); + return pipeline.ToPipelineClient(); } + } - public void Delete(int pipelineId) + public Models.Pipeline Create(PipelineCreate createOptions) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var pipeline = project.Pipelines.GetById(pipelineId); - project.Pipelines.Remove(pipeline); - } + var project = GetProject(_projectId, ProjectPermission.View); + var pipeline = project.Pipelines.Add(createOptions.Ref, JobStatus.Running, Context.User); + pipeline.Variables = createOptions.Variables.Select(v => new PipelineVariable { Key = v.Key, Value = v.Value }); + return pipeline.ToPipelineClient(); } + } - public IEnumerable GetVariables(int pipelineId) + public Models.Pipeline CreatePipelineWithTrigger(string token, string @ref, Dictionary variables) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var pipeline = project.Pipelines.GetById(pipelineId); - return pipeline.Variables; - } + var project = GetProject(_projectId, ProjectPermission.View); + var pipeline = project.Pipelines.Add(@ref, JobStatus.Running, Context.User); + pipeline.Variables = variables.Select(v => new PipelineVariable { Key = v.Key, Value = v.Value }); + pipeline.CiToken = token; + return pipeline.ToPipelineClient(); } + } - public TestReport GetTestReports(int pipelineId) + public void Delete(int pipelineId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var pipeline = project.Pipelines.GetById(pipelineId); - return pipeline.TestReports; - } + var project = GetProject(_projectId, ProjectPermission.View); + var pipeline = project.Pipelines.GetById(pipelineId); + project.Pipelines.Remove(pipeline); } + } - public TestReportSummary GetTestReportsSummary(int pipelineId) + public IEnumerable GetVariables(int pipelineId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var pipeline = project.Pipelines.GetById(pipelineId); - return pipeline.TestReportsSummary; - } + var project = GetProject(_projectId, ProjectPermission.View); + var pipeline = project.Pipelines.GetById(pipelineId); + return pipeline.Variables; } + } - public GitLabCollectionResponse GetBridgesAsync(PipelineBridgeQuery query) + public TestReport GetTestReports(int pipelineId) + { + using (Context.BeginOperationScope()) { - return GitLabCollectionResponse.Create(GetBridges(query)); + var project = GetProject(_projectId, ProjectPermission.View); + var pipeline = project.Pipelines.GetById(pipelineId); + return pipeline.TestReports; } + } - public IEnumerable GetBridges(PipelineBridgeQuery query) + public TestReportSummary GetTestReportsSummary(int pipelineId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = Context.Server.AllProjects.FindById(_projectId); - var bridges = project.Jobs.Where(j => j.Pipeline.Id == query.PipelineId && j.DownstreamPipeline != null).Select(j => j.ToBridgeClient()); - return (query.Scope == null || !query.Scope.Any()) ? bridges : bridges.Where(j => query.Scope.Contains(j.Status.ToString(), StringComparer.Ordinal)); - } + var project = GetProject(_projectId, ProjectPermission.View); + var pipeline = project.Pipelines.GetById(pipelineId); + return pipeline.TestReportsSummary; } + } - public Models.Job[] GetJobs(int pipelineId) + public GitLabCollectionResponse GetBridgesAsync(PipelineBridgeQuery query) + { + return GitLabCollectionResponse.Create(GetBridges(query)); + } + + public IEnumerable GetBridges(PipelineBridgeQuery query) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return _jobClient.GetJobs(JobScopeMask.All).Where(p => p.Pipeline.Id == pipelineId).ToArray(); - } + var project = Context.Server.AllProjects.FindById(_projectId); + var bridges = project.Jobs.Where(j => j.Pipeline.Id == query.PipelineId && j.DownstreamPipeline != null).Select(j => j.ToBridgeClient()); + return (query.Scope == null || !query.Scope.Any()) ? bridges : bridges.Where(j => query.Scope.Contains(j.Status.ToString(), StringComparer.Ordinal)); } + } - public IEnumerable GetJobs(PipelineJobQuery query) + public Models.Job[] GetJobs(int pipelineId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var jobs = _jobClient.GetJobs(JobScopeMask.All).Where(j => j.Pipeline.Id == query.PipelineId); - return (query.Scope == null || !query.Scope.Any()) ? jobs : jobs.Where(j => query.Scope.Contains(j.Status.ToString(), StringComparer.Ordinal)); - } + return _jobClient.GetJobs(JobScopeMask.All).Where(p => p.Pipeline.Id == pipelineId).ToArray(); } + } - [Obsolete("Use JobClient.GetJobs() instead")] - public IEnumerable GetJobsInProject(JobScope scope) + public IEnumerable GetJobs(PipelineJobQuery query) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var jobs = _jobClient.GetJobs(JobScopeMask.All).Where(j => j.Pipeline.Id == query.PipelineId); + return (query.Scope == null || !query.Scope.Any()) ? jobs : jobs.Where(j => query.Scope.Contains(j.Status.ToString(), StringComparer.Ordinal)); } + } - public IEnumerable Search(PipelineQuery query) + [Obsolete("Use JobClient.GetJobs() instead")] + public IEnumerable GetJobsInProject(JobScope scope) + { + throw new NotImplementedException(); + } + + public IEnumerable Search(PipelineQuery query) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var project = GetProject(_projectId, ProjectPermission.View); + IEnumerable pipelines = project.Pipelines; + if (query.Sha != null) { - var project = GetProject(_projectId, ProjectPermission.View); - IEnumerable pipelines = project.Pipelines; - if (query.Sha != null) - { - var sha = new Sha1(query.Sha); - pipelines = pipelines.Where(pipeline => pipeline.Sha.Equals(sha)); - } + var sha = new Sha1(query.Sha); + pipelines = pipelines.Where(pipeline => pipeline.Sha.Equals(sha)); + } - if (query.Name != null) - { - pipelines = pipelines.Where(pipeline => string.Equals(pipeline.User.Name, query.Name, StringComparison.Ordinal)); - } + if (query.Name != null) + { + pipelines = pipelines.Where(pipeline => string.Equals(pipeline.User.Name, query.Name, StringComparison.Ordinal)); + } - if (query.Ref != null) - { - pipelines = pipelines.Where(pipeline => string.Equals(pipeline.Ref, query.Ref, StringComparison.Ordinal)); - } + if (query.Ref != null) + { + pipelines = pipelines.Where(pipeline => string.Equals(pipeline.Ref, query.Ref, StringComparison.Ordinal)); + } - if (query.Scope.HasValue) + if (query.Scope.HasValue) + { + if (query.Scope.Value == PipelineScope.tags) { - if (query.Scope.Value == PipelineScope.tags) - { - pipelines = pipelines.Where(p => p.Tag); - } - else if (query.Scope.Value == PipelineScope.branches) - { - pipelines = pipelines.Where(p => !p.Tag); - } - else if (query.Scope.Value == PipelineScope.running) - { - pipelines = pipelines.Where(p => p.Status == JobStatus.Running); - } - else if (query.Scope.Value == PipelineScope.pending) - { - pipelines = pipelines.Where(p => p.Status == JobStatus.Pending); - } - else if (query.Scope.Value == PipelineScope.finished) - { - pipelines = pipelines.Where(p => p.FinishedAt.HasValue); - } - else - { - throw new NotImplementedException(); - } + pipelines = pipelines.Where(p => p.Tag); } - - if (query.Status.HasValue) + else if (query.Scope.Value == PipelineScope.branches) { - pipelines = pipelines.Where(pipeline => pipeline.Status == query.Status); + pipelines = pipelines.Where(p => !p.Tag); } - - if (query.Username != null) + else if (query.Scope.Value == PipelineScope.running) { - pipelines = pipelines.Where(pipeline => string.Equals(pipeline.User.UserName, query.Username, StringComparison.Ordinal)); + pipelines = pipelines.Where(p => p.Status == JobStatus.Running); } - - if (query.YamlErrors.HasValue) + else if (query.Scope.Value == PipelineScope.pending) { - pipelines = pipelines.Where(pipeline => !string.IsNullOrEmpty(pipeline.YamlError)); + pipelines = pipelines.Where(p => p.Status == JobStatus.Pending); } - - if (query.OrderBy.HasValue) + else if (query.Scope.Value == PipelineScope.finished) { - pipelines = query.OrderBy.Value switch - { - PipelineOrderBy.id => QuerySort(pipelines, query.Sort, p => p.Id), - PipelineOrderBy.status => QuerySort(pipelines, query.Sort, p => p.Status), - PipelineOrderBy.@ref => QuerySort(pipelines, query.Sort, p => p.Ref), - PipelineOrderBy.user_id => QuerySort(pipelines, query.Sort, p => p.User.Id), - PipelineOrderBy.updated_at => QuerySort(pipelines, query.Sort, p => p.UpdatedAt), - _ => throw new NotImplementedException(), - }; + pipelines = pipelines.Where(p => p.FinishedAt.HasValue); } else { - pipelines = QuerySort(pipelines, query.Sort, p => p.UpdatedAt); + throw new NotImplementedException(); } + } - return pipelines.Where(p => !p.IsDownStreamPipeline).Select(pipeline => pipeline.ToPipelineBasicClient()).ToList(); + if (query.Status.HasValue) + { + pipelines = pipelines.Where(pipeline => pipeline.Status == query.Status); } - } - private static IEnumerable QuerySort(IEnumerable pipelines, PipelineSort? sort, Func expression) - { - if (!sort.HasValue) - sort = PipelineSort.desc; + if (query.Username != null) + { + pipelines = pipelines.Where(pipeline => string.Equals(pipeline.User.UserName, query.Username, StringComparison.Ordinal)); + } - if (sort.Value == PipelineSort.desc) - return pipelines.OrderByDescending(expression); + if (query.YamlErrors.HasValue) + { + pipelines = pipelines.Where(pipeline => !string.IsNullOrEmpty(pipeline.YamlError)); + } - return pipelines.OrderBy(expression); - } + if (query.OrderBy.HasValue) + { + pipelines = query.OrderBy.Value switch + { + PipelineOrderBy.id => QuerySort(pipelines, query.Sort, p => p.Id), + PipelineOrderBy.status => QuerySort(pipelines, query.Sort, p => p.Status), + PipelineOrderBy.@ref => QuerySort(pipelines, query.Sort, p => p.Ref), + PipelineOrderBy.user_id => QuerySort(pipelines, query.Sort, p => p.User.Id), + PipelineOrderBy.updated_at => QuerySort(pipelines, query.Sort, p => p.UpdatedAt), + _ => throw new NotImplementedException(), + }; + } + else + { + pipelines = QuerySort(pipelines, query.Sort, p => p.UpdatedAt); + } - public async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return this[id]; + return pipelines.Where(p => !p.IsDownStreamPipeline).Select(pipeline => pipeline.ToPipelineBasicClient()).ToList(); } + } - public GitLabCollectionResponse GetAllJobsAsync() - { - return GitLabCollectionResponse.Create(AllJobs); - } + private static IEnumerable QuerySort(IEnumerable pipelines, PipelineSort? sort, Func expression) + { + if (!sort.HasValue) + sort = PipelineSort.desc; - public GitLabCollectionResponse GetJobsAsync(PipelineJobQuery query) - { - return GitLabCollectionResponse.Create(GetJobs(query)); - } + if (sort.Value == PipelineSort.desc) + return pipelines.OrderByDescending(expression); - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task CreateAsync(PipelineCreate createOptions, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return Create(createOptions); - } + return pipelines.OrderBy(expression); + } - public GitLabCollectionResponse SearchAsync(PipelineQuery query) - { - return GitLabCollectionResponse.Create(Search(query)); - } + public async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return this[id]; + } - public GitLabCollectionResponse GetVariablesAsync(int pipelineId) + public GitLabCollectionResponse GetAllJobsAsync() + { + return GitLabCollectionResponse.Create(AllJobs); + } + + public GitLabCollectionResponse GetJobsAsync(PipelineJobQuery query) + { + return GitLabCollectionResponse.Create(GetJobs(query)); + } + + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task CreateAsync(PipelineCreate createOptions, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Create(createOptions); + } + + public GitLabCollectionResponse SearchAsync(PipelineQuery query) + { + return GitLabCollectionResponse.Create(Search(query)); + } + + public GitLabCollectionResponse GetVariablesAsync(int pipelineId) + { + return GitLabCollectionResponse.Create(GetVariables(pipelineId)); + } + + public Task RetryAsync(int pipelineId, CancellationToken cancellationToken = default) + { + using (Context.BeginOperationScope()) { - return GitLabCollectionResponse.Create(GetVariables(pipelineId)); + var jobs = _jobClient.GetJobs(JobScopeMask.Failed).Where(j => j.Pipeline.Id == pipelineId); + foreach (var job in jobs) + { + _jobClient.RunAction(job.Id, JobAction.Retry); + } + + return Task.FromResult(this[pipelineId]); } } } diff --git a/NGitLab.Mock/Clients/ProjectBadgeClient.cs b/NGitLab.Mock/Clients/ProjectBadgeClient.cs index 4eb23a6a..3539b575 100644 --- a/NGitLab.Mock/Clients/ProjectBadgeClient.cs +++ b/NGitLab.Mock/Clients/ProjectBadgeClient.cs @@ -3,99 +3,98 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class ProjectBadgeClient : ClientBase, IProjectBadgeClient { - internal sealed class ProjectBadgeClient : ClientBase, IProjectBadgeClient - { - private readonly int _projectId; + private readonly int _projectId; - public ProjectBadgeClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public ProjectBadgeClient(ClientContext context, ProjectId projectId) + : base(context) + { + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public Models.Badge this[int id] + public Models.Badge this[int id] + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var badge = project.Badges.GetById(id); - if (badge == null) - throw new GitLabNotFoundException($"Badge with id '{id}' does not exist in project with id '{_projectId}'"); + var project = GetProject(_projectId, ProjectPermission.View); + var badge = project.Badges.GetById(id); + if (badge == null) + throw new GitLabNotFoundException($"Badge with id '{id}' does not exist in project with id '{_projectId}'"); - return badge.ToBadgeModel(); - } + return badge.ToBadgeModel(); } } + } - public IEnumerable All + public IEnumerable All + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var groupBadges = (project.Group != null) ? GetGroup(project.Group.Id, GroupPermission.View).Badges.Select(badge => badge.ToBadgeModel()) : Array.Empty(); - return groupBadges.Union(project.Badges.Select(badge => badge.ToBadgeModel())).ToList(); - } + var project = GetProject(_projectId, ProjectPermission.View); + var groupBadges = (project.Group != null) ? GetGroup(project.Group.Id, GroupPermission.View).Badges.Select(badge => badge.ToBadgeModel()) : Array.Empty(); + return groupBadges.Union(project.Badges.Select(badge => badge.ToBadgeModel())).ToList(); } } + } - public IEnumerable ProjectsOnly + public IEnumerable ProjectsOnly + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return All.Where(badge => badge.Kind == BadgeKind.Project).ToList(); - } + return All.Where(badge => badge.Kind == BadgeKind.Project).ToList(); } } + } - public Models.Badge Create(BadgeCreate badge) - { - EnsureUserIsAuthenticated(); + public Models.Badge Create(BadgeCreate badge) + { + EnsureUserIsAuthenticated(); - using (Context.BeginOperationScope()) - { - var createdBadge = GetProject(_projectId, ProjectPermission.Edit).Badges.Add(badge.LinkUrl, badge.ImageUrl); - return createdBadge.ToBadgeModel(); - } + using (Context.BeginOperationScope()) + { + var createdBadge = GetProject(_projectId, ProjectPermission.Edit).Badges.Add(badge.LinkUrl, badge.ImageUrl); + return createdBadge.ToBadgeModel(); } + } - public void Delete(int id) - { - EnsureUserIsAuthenticated(); + public void Delete(int id) + { + EnsureUserIsAuthenticated(); - using (Context.BeginOperationScope()) + using (Context.BeginOperationScope()) + { + var badgeToRemove = GetProject(_projectId, ProjectPermission.View).Badges.FirstOrDefault(b => b.Id == id); + if (badgeToRemove == null) { - var badgeToRemove = GetProject(_projectId, ProjectPermission.View).Badges.FirstOrDefault(b => b.Id == id); - if (badgeToRemove == null) - { - throw new GitLabNotFoundException($"Badge with id '{id}' does not exist in project with id '{_projectId}'"); - } - - GetProject(_projectId, ProjectPermission.Edit).Badges.Remove(badgeToRemove); + throw new GitLabNotFoundException($"Badge with id '{id}' does not exist in project with id '{_projectId}'"); } + + GetProject(_projectId, ProjectPermission.Edit).Badges.Remove(badgeToRemove); } + } - public Models.Badge Update(int id, BadgeUpdate badge) + public Models.Badge Update(int id, BadgeUpdate badge) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var badgeToUpdate = GetProject(_projectId, ProjectPermission.Edit).Badges.FirstOrDefault(b => b.Id == id); + if (badgeToUpdate == null) { - var badgeToUpdate = GetProject(_projectId, ProjectPermission.Edit).Badges.FirstOrDefault(b => b.Id == id); - if (badgeToUpdate == null) - { - throw new GitLabNotFoundException($"Badge with id '{id}' does not exist in project with id '{_projectId}'"); - } - - badgeToUpdate.LinkUrl = badge.LinkUrl; - badgeToUpdate.ImageUrl = badge.ImageUrl; - return badgeToUpdate.ToBadgeModel(); + throw new GitLabNotFoundException($"Badge with id '{id}' does not exist in project with id '{_projectId}'"); } + + badgeToUpdate.LinkUrl = badge.LinkUrl; + badgeToUpdate.ImageUrl = badge.ImageUrl; + return badgeToUpdate.ToBadgeModel(); } } } diff --git a/NGitLab.Mock/Clients/ProjectClient.cs b/NGitLab.Mock/Clients/ProjectClient.cs index 5cbcfd5c..d2d72235 100644 --- a/NGitLab.Mock/Clients/ProjectClient.cs +++ b/NGitLab.Mock/Clients/ProjectClient.cs @@ -8,374 +8,400 @@ using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class ProjectClient : ClientBase, IProjectClient { - internal sealed class ProjectClient : ClientBase, IProjectClient + public ProjectClient(ClientContext context) + : base(context) { - public ProjectClient(ClientContext context) - : base(context) - { - } + } - public Models.Project this[int id] + public Models.Project this[int id] + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(id, ProjectPermission.View); - if (project == null || !project.CanUserViewProject(Context.User)) - throw new GitLabNotFoundException(); - - return project.ToClientProject(Context.User); - } + return GetProject(id, ProjectPermission.View).ToClientProject(Context.User); } } + } - public Models.Project this[string fullName] + public Models.Project this[string fullName] + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(fullName, ProjectPermission.View); - return project.ToClientProject(Context.User); - } + return GetProject(fullName, ProjectPermission.View).ToClientProject(Context.User); } } + } - public IEnumerable Accessible + public IEnumerable Accessible + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return Server.AllProjects.Where(project => project.IsUserMember(Context.User)).Select(project => project.ToClientProject(Context.User)).ToList(); - } + return Server.AllProjects.Where(project => project.IsUserMember(Context.User)).Select(project => project.ToClientProject(Context.User)).ToList(); } } + } - public IEnumerable Owned + public IEnumerable Owned + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return Server.AllProjects.Where(project => project.IsUserOwner(Context.User)).Select(project => project.ToClientProject(Context.User)).ToList(); - } + return Server.AllProjects.Where(project => project.IsUserOwner(Context.User)).Select(project => project.ToClientProject(Context.User)).ToList(); } } + } - public IEnumerable Visible + public IEnumerable Visible + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return Server.AllProjects.Where(project => project.CanUserViewProject(Context.User)).Select(project => project.ToClientProject(Context.User)).ToList(); - } + return Server.AllProjects.Where(project => project.CanUserViewProject(Context.User)).Select(project => project.ToClientProject(Context.User)).ToList(); } } + } + + public Models.Project Create(ProjectCreate project) + { + if (project.Topics != null && project.Tags != null) + throw new InvalidOperationException("Cannot specify Topics and Tags. Use Topics only."); - public Models.Project Create(ProjectCreate project) + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var parentGroup = GetParentGroup(project.NamespaceId); + project.Name ??= project.Path; - var newProject = new Project(project.Name) - { - Description = project.Description, - Visibility = project.VisibilityLevel, - Permissions = - { - new Permission(Context.User, AccessLevel.Owner), - }, - }; - - parentGroup.Projects.Add(newProject); - return newProject.ToClientProject(Context.User); - } - } + var parentGroup = GetParentGroup(project.NamespaceId); - private Group GetParentGroup(string namespaceId) - { - Group parentGroup; - if (namespaceId != null) + var newProject = new Project(name: project.Name, path: project.Path) { - parentGroup = Server.AllGroups.FindGroup(namespaceId); - if (parentGroup == null || !parentGroup.CanUserViewGroup(Context.User)) - throw new GitLabNotFoundException(); + Description = project.Description, + Visibility = project.VisibilityLevel, + Permissions = + { + new(Context.User, AccessLevel.Owner), + }, + }; - if (!parentGroup.CanUserAddProject(Context.User)) - throw new GitLabForbiddenException(); - } - else - { - parentGroup = Context.User.Namespace; - } + newProject.Topics = (project.Topics ?? project.Tags)?.ToArray() ?? newProject.Topics; + + // Note: GitLab ignores the DefaultBranch unless InitializeWithReadme = true. + if (!string.IsNullOrEmpty(project.DefaultBranch) && project.InitializeWithReadme) + newProject.DefaultBranch = project.DefaultBranch; + + if (project.BuildTimeout != null) + newProject.BuildTimeout = TimeSpan.FromSeconds(project.BuildTimeout.Value); - return parentGroup; + parentGroup.Projects.Add(newProject); + return newProject.ToClientProject(Context.User); } + } + + public async Task CreateAsync(ProjectCreate project, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Create(project); + } - public void Delete(int id) + private Group GetParentGroup(string namespaceId) + { + Group parentGroup; + if (namespaceId != null) { - using (Context.BeginOperationScope()) - { - var project = GetProject(id, ProjectPermission.Delete); - project.Remove(); - } + parentGroup = Server.AllGroups.FindGroup(namespaceId); + if (parentGroup == null || !parentGroup.CanUserViewGroup(Context.User)) + throw new GitLabNotFoundException(); + + if (!parentGroup.CanUserAddProject(Context.User)) + throw new GitLabForbiddenException(); + } + else + { + parentGroup = Context.User.Namespace; } - public void Archive(int id) + return parentGroup; + } + + public void Delete(int id) + { + using (Context.BeginOperationScope()) { - var project = GetProject(id, ProjectPermission.Edit); - project.Archived = true; + var project = GetProject(id, ProjectPermission.Delete); + project.Remove(); } + } - public void Unarchive(int id) + public async Task DeleteAsync(ProjectId projectId, CancellationToken cancellationToken = default) + { + await Task.Yield(); + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(id, ProjectPermission.Edit); - project.Archived = false; - } + var project = GetProject(projectId, ProjectPermission.Delete); + project.Remove(); } + } + + public void Archive(int id) + { + var project = GetProject(id, ProjectPermission.Edit); + project.Archived = true; + } - public Models.Project Fork(string id, ForkProject forkProject) + public void Unarchive(int id) + { + using (Context.BeginOperationScope()) { - EnsureUserIsAuthenticated(); + var project = GetProject(id, ProjectPermission.Edit); + project.Archived = false; + } + } - using (Context.BeginOperationScope()) - { - var project = GetProject(id, ProjectPermission.View); - var group = forkProject.Namespace != null ? GetParentGroup(forkProject.Namespace) : Context.User.Namespace; - var newProject = project.Fork(group, Context.User, forkProject.Name); - return newProject.ToClientProject(Context.User); - } + public Models.Project Fork(string id, ForkProject forkProject) + { + EnsureUserIsAuthenticated(); + + using (Context.BeginOperationScope()) + { + var project = GetProject(id, ProjectPermission.View); + var group = forkProject.Namespace != null ? GetParentGroup(forkProject.Namespace) : Context.User.Namespace; + var newProject = project.Fork(group, Context.User, forkProject.Name); + return newProject.ToClientProject(Context.User); } + } - public IEnumerable Get(ProjectQuery query) + public IEnumerable Get(ProjectQuery query) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + if (query.MinAccessLevel != null + || query.LastActivityAfter != null + || query.Search != null + || query.Statistics is true) { - if (query.MinAccessLevel != null - || query.LastActivityAfter != null - || query.Search != null - || query.Statistics is true) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } - var projects = Server.AllProjects; + var projects = Server.AllProjects; - if (query.Archived != null) - { - projects = projects.Where(p => query.Archived == p.Archived); - } + if (query.Archived != null) + { + projects = projects.Where(p => query.Archived == p.Archived); + } - if (query.Visibility != null) - { - projects = projects.Where(p => query.Visibility == p.Visibility); - } + if (query.Visibility != null) + { + projects = projects.Where(p => query.Visibility == p.Visibility); + } - switch (query.Scope) - { - case ProjectQueryScope.Accessible: - projects = projects.Where(p => p.IsUserMember(Context.User)); - break; - case ProjectQueryScope.Owned: - projects = projects.Where(p => p.IsUserOwner(Context.User)); - break; - case ProjectQueryScope.Visible: - projects = projects.Where(p => p.CanUserViewProject(Context.User)); - break; - case ProjectQueryScope.All: - break; - } + switch (query.Scope) + { + case ProjectQueryScope.Accessible: + projects = projects.Where(p => p.IsUserMember(Context.User)); + break; + case ProjectQueryScope.Owned: + projects = projects.Where(p => p.IsUserOwner(Context.User)); + break; + case ProjectQueryScope.Visible: + projects = projects.Where(p => p.CanUserViewProject(Context.User)); + break; + case ProjectQueryScope.All: + break; + } - if (query.Topics.Any()) - { - projects = projects.Where(p => query.Topics.All(t => p.Topics.Contains(t, StringComparer.Ordinal))); - } + if (query.Topics.Any()) + { + projects = projects.Where(p => query.Topics.All(t => p.Topics.Contains(t, StringComparer.Ordinal))); + } - if (query.UserId != null) - { - projects = projects.Where(p => p.IsUserOwner(Context.User)); - } + if (query.UserId != null) + { + projects = projects.Where(p => p.IsUserOwner(Context.User)); + } - if (query.OrderBy is "id") + if (query.OrderBy is "id") + { + if (query.Ascending is null or true) { - if (query.Ascending is null or true) - { - projects = projects.OrderBy(p => p.Id); - } - else - { - projects = projects.OrderByDescending(p => p.Id); - } + projects = projects.OrderBy(p => p.Id); } - else if (query.OrderBy is not null) + else { - throw new NotImplementedException(); + projects = projects.OrderByDescending(p => p.Id); } - - return projects.Select(project => project.ToClientProject(Context.User)).ToList(); } - } - - public Models.Project GetById(int id, SingleProjectQuery query) - { - using (Context.BeginOperationScope()) + else if (query.OrderBy is not null) { - return GetProject(id, ProjectPermission.View).ToClientProject(Context.User); + throw new NotImplementedException(); } - } - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task GetByIdAsync(int id, SingleProjectQuery query, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return GetById(id, query); + return projects.Select(project => project.ToClientProject(Context.User)).ToList(); } + } + + public Models.Project GetById(int id, SingleProjectQuery query) => this[id]; - public async Task GetByNamespacedPathAsync(string path, SingleProjectQuery query = null, CancellationToken cancellationToken = default) + public Task GetByIdAsync(int id, SingleProjectQuery query, CancellationToken cancellationToken = default) => + GetAsync(new ProjectId(id), query, cancellationToken); + + public Task GetByNamespacedPathAsync(string path, SingleProjectQuery query = null, CancellationToken cancellationToken = default) => + GetAsync(new ProjectId(path), query, cancellationToken); + + public async Task GetAsync(ProjectId projectId, SingleProjectQuery query = null, CancellationToken cancellationToken = default) + { + await Task.Yield(); + using (Context.BeginOperationScope()) { - await Task.Yield(); - return this[path]; + return GetProject(projectId, ProjectPermission.View).ToClientProject(Context.User); } + } - public IEnumerable GetForks(string id, ForkedProjectQuery query) + public IEnumerable GetForks(string id, ForkedProjectQuery query) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + if (query.Archived != null + || query.Membership != null + || query.MinAccessLevel != null + || query.OrderBy != null + || query.PerPage != null + || query.Search != null + || query.Statistics != null + || query.Visibility != null) { - if (query.Archived != null - || query.Membership != null - || query.MinAccessLevel != null - || query.OrderBy != null - || query.PerPage != null - || query.Search != null - || query.Statistics != null - || query.Visibility != null) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } - var upstream = GetProject(id, ProjectPermission.View); - var matches = Server.AllProjects.Where(project => project.ForkedFrom?.Id == upstream.Id); - matches = matches.Where(project => query.Owned == null || query.Owned == project.IsUserOwner(Context.User)); + var upstream = GetProject(id, ProjectPermission.View); + var matches = Server.AllProjects.Where(project => project.ForkedFrom?.Id == upstream.Id); + matches = matches.Where(project => query.Owned == null || query.Owned == project.IsUserOwner(Context.User)); - return matches.Select(project => project.ToClientProject(Context.User)).ToList(); - } + return matches.Select(project => project.ToClientProject(Context.User)).ToList(); } + } - public Dictionary GetLanguages(string id) + public Dictionary GetLanguages(string id) + { + // Basic implementation, the results are not expected to be accurrate + using (Context.BeginOperationScope()) { - // Basic implementation, the results are not expected to be accurrate - using (Context.BeginOperationScope()) + var project = GetProject(id, ProjectPermission.View); + if (project.Repository.IsEmpty) + return new(StringComparer.Ordinal); + + project.Repository.Checkout(project.DefaultBranch); + + var gitFolder = Path.Combine(project.Repository.FullPath, ".git"); + var files = Directory.GetFiles(project.Repository.FullPath, "*", SearchOption.AllDirectories) + .Where(file => !file.StartsWith(gitFolder, StringComparison.Ordinal)) + .ToArray(); + + Dictionary result = new(StringComparer.Ordinal); + AddByExtension("C#", ".cs"); + AddByExtension("HTML", ".html", ".htm"); + AddByExtension("JavaScript", ".js", ".jsx"); + AddByExtension("PowerShell", ".ps1"); + AddByExtension("TypeScript", ".ts", ".tsx"); + return result; + + void AddByExtension(string name, params string[] expectedExtensions) { - var project = GetProject(id, ProjectPermission.View); - if (project.Repository.IsEmpty) - return new(StringComparer.Ordinal); - - project.Repository.Checkout(project.DefaultBranch); - - var gitFolder = Path.Combine(project.Repository.FullPath, ".git"); - var files = Directory.GetFiles(project.Repository.FullPath, "*", SearchOption.AllDirectories) - .Where(file => !file.StartsWith(gitFolder, StringComparison.Ordinal)) - .ToArray(); - - Dictionary result = new(StringComparer.Ordinal); - AddByExtension("C#", ".cs"); - AddByExtension("HTML", ".html", ".htm"); - AddByExtension("JavaScript", ".js", ".jsx"); - AddByExtension("PowerShell", ".ps1"); - AddByExtension("TypeScript", ".ts", ".tsx"); - return result; - - void AddByExtension(string name, params string[] expectedExtensions) + var count = files.Count(file => expectedExtensions.Any(expectedExtension => file.EndsWith(expectedExtension, StringComparison.OrdinalIgnoreCase))); + if (count > 0) { - var count = files.Count(file => expectedExtensions.Any(expectedExtension => file.EndsWith(expectedExtension, StringComparison.OrdinalIgnoreCase))); - if (count > 0) - { - result.Add(name, count / (double)files.Length); - } + result.Add(name, count / (double)files.Length); } } } + } - public Models.Project Update(string id, ProjectUpdate projectUpdate) + public Models.Project Update(string id, ProjectUpdate projectUpdate) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(id, ProjectPermission.Edit); + var project = GetProject(id, ProjectPermission.Edit); - if (projectUpdate.Name != null) - { - project.Name = projectUpdate.Name; - } + if (projectUpdate.Name != null) + { + project.Name = projectUpdate.Name; + } - if (projectUpdate.DefaultBranch != null) - { - project.DefaultBranch = projectUpdate.DefaultBranch; - } + if (projectUpdate.DefaultBranch != null) + { + project.DefaultBranch = projectUpdate.DefaultBranch; + } - if (projectUpdate.Description != null) - { - project.Description = projectUpdate.Description; - } + if (projectUpdate.Description != null) + { + project.Description = projectUpdate.Description; + } - if (projectUpdate.Visibility.HasValue) - { - project.Visibility = projectUpdate.Visibility.Value; - } + if (projectUpdate.Visibility.HasValue) + { + project.Visibility = projectUpdate.Visibility.Value; + } - if (projectUpdate.BuildTimeout.HasValue) - { - project.BuildTimeout = TimeSpan.FromMinutes(projectUpdate.BuildTimeout.Value); - } + if (projectUpdate.BuildTimeout.HasValue) + { + project.BuildTimeout = TimeSpan.FromSeconds(projectUpdate.BuildTimeout.Value); + } - if (projectUpdate.LfsEnabled.HasValue) - { - project.LfsEnabled = projectUpdate.LfsEnabled.Value; - } + if (projectUpdate.LfsEnabled.HasValue) + { + project.LfsEnabled = projectUpdate.LfsEnabled.Value; + } #pragma warning disable CS0618 // Type or member is obsolete - if (projectUpdate.TagList != null) - { - project.Topics = projectUpdate.TagList.Where(t => !string.IsNullOrEmpty(t)).Distinct(StringComparer.Ordinal).ToArray(); - } + if (projectUpdate.TagList != null) + { + project.Topics = projectUpdate.TagList.Where(t => !string.IsNullOrEmpty(t)).Distinct(StringComparer.Ordinal).ToArray(); + } #pragma warning restore CS0618 // Type or member is obsolete - if (projectUpdate.Topics is { Count: > 0 }) - { - project.Topics = projectUpdate.Topics.Where(t => !string.IsNullOrEmpty(t)).Distinct(StringComparer.Ordinal).ToArray(); - } - - return project.ToClientProject(Context.User); + if (projectUpdate.Topics is { Count: > 0 }) + { + project.Topics = projectUpdate.Topics.Where(t => !string.IsNullOrEmpty(t)).Distinct(StringComparer.Ordinal).ToArray(); } - } - public UploadedProjectFile UploadFile(string id, FormDataContent data) - { - throw new NotImplementedException(); + return project.ToClientProject(Context.User); } + } - public GitLabCollectionResponse GetAsync(ProjectQuery query) - { - return GitLabCollectionResponse.Create(Get(query)); - } + public async Task UpdateAsync(ProjectId projectId, ProjectUpdate projectUpdate, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Update(projectId.ValueAsString(), projectUpdate); + } - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task ForkAsync(string id, ForkProject forkProject, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return Fork(id, forkProject); - } + public UploadedProjectFile UploadFile(string id, FormDataContent data) + { + throw new NotImplementedException(); + } - public GitLabCollectionResponse GetForksAsync(string id, ForkedProjectQuery query) - { - return GitLabCollectionResponse.Create(GetForks(id, query)); - } + public GitLabCollectionResponse GetAsync(ProjectQuery query) + { + return GitLabCollectionResponse.Create(Get(query)); + } + + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task ForkAsync(string id, ForkProject forkProject, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Fork(id, forkProject); + } + + public GitLabCollectionResponse GetForksAsync(string id, ForkedProjectQuery query) + { + return GitLabCollectionResponse.Create(GetForks(id, query)); } } diff --git a/NGitLab.Mock/Clients/ProjectHooksClient.cs b/NGitLab.Mock/Clients/ProjectHooksClient.cs index 7d2cb0c2..a7b730d8 100644 --- a/NGitLab.Mock/Clients/ProjectHooksClient.cs +++ b/NGitLab.Mock/Clients/ProjectHooksClient.cs @@ -2,105 +2,104 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class ProjectHooksClient : ClientBase, IProjectHooksClient { - internal sealed class ProjectHooksClient : ClientBase, IProjectHooksClient - { - public int ProjectId { get; } + public int ProjectId { get; } - public ProjectHooksClient(ClientContext context, int projectId) - : base(context) - { - ProjectId = projectId; - } + public ProjectHooksClient(ClientContext context, int projectId) + : base(context) + { + ProjectId = projectId; + } - public IEnumerable All + public IEnumerable All + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var hooks = GetProject(ProjectId, ProjectPermission.Edit).Hooks; - return ToClientProjectHooks(hooks).ToList(); - } + var hooks = GetProject(ProjectId, ProjectPermission.Edit).Hooks; + return ToClientProjectHooks(hooks).ToList(); } } + } - public Models.ProjectHook this[int hookId] + public Models.ProjectHook this[int hookId] + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var hook = All.FirstOrDefault(h => h.Id == hookId) ?? throw new GitLabNotFoundException(); - return hook; - } + var hook = All.FirstOrDefault(h => h.Id == hookId) ?? throw new GitLabNotFoundException(); + return hook; } } + } - public Models.ProjectHook Create(ProjectHookUpsert hook) + public Models.ProjectHook Create(ProjectHookUpsert hook) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var projectHook = UpsertToHook(hook); + var projectHook = UpsertToHook(hook); - GetProject(ProjectId, ProjectPermission.Edit).Hooks.Add(projectHook); - return projectHook.ToClientProjectHook(); - } + GetProject(ProjectId, ProjectPermission.Edit).Hooks.Add(projectHook); + return projectHook.ToClientProjectHook(); } + } - public Models.ProjectHook Update(int hookId, ProjectHookUpsert hook) + public Models.ProjectHook Update(int hookId, ProjectHookUpsert hook) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var currentHook = GetProject(ProjectId, ProjectPermission.Edit).Hooks.FirstOrDefault(h => h.Id == hookId) ?? throw new GitLabNotFoundException(); + var currentHook = GetProject(ProjectId, ProjectPermission.Edit).Hooks.FirstOrDefault(h => h.Id == hookId) ?? throw new GitLabNotFoundException(); - currentHook.Url = hook.Url; - currentHook.PushEvents = hook.PushEvents; - currentHook.MergeRequestsEvents = hook.MergeRequestsEvents; - currentHook.IssuesEvents = hook.IssuesEvents; - currentHook.TagPushEvents = hook.TagPushEvents; - currentHook.NoteEvents = hook.NoteEvents; - currentHook.JobEvents = hook.JobEvents; - currentHook.PipelineEvents = hook.PipelineEvents; - currentHook.EnableSslVerification = hook.EnableSslVerification; + currentHook.Url = hook.Url; + currentHook.PushEvents = hook.PushEvents; + currentHook.MergeRequestsEvents = hook.MergeRequestsEvents; + currentHook.IssuesEvents = hook.IssuesEvents; + currentHook.TagPushEvents = hook.TagPushEvents; + currentHook.NoteEvents = hook.NoteEvents; + currentHook.JobEvents = hook.JobEvents; + currentHook.PipelineEvents = hook.PipelineEvents; + currentHook.EnableSslVerification = hook.EnableSslVerification; - return currentHook.ToClientProjectHook(); - } + return currentHook.ToClientProjectHook(); } + } - public void Delete(int hookId) + public void Delete(int hookId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var projectHooks = GetProject(ProjectId, ProjectPermission.Edit).Hooks; - var hook = projectHooks.FirstOrDefault(h => h.Id == hookId) ?? throw new GitLabNotFoundException(); + var projectHooks = GetProject(ProjectId, ProjectPermission.Edit).Hooks; + var hook = projectHooks.FirstOrDefault(h => h.Id == hookId) ?? throw new GitLabNotFoundException(); - projectHooks.Remove(hook); - } + projectHooks.Remove(hook); } + } - private static IEnumerable ToClientProjectHooks(IEnumerable hooks) - { - return hooks.Select(hook => hook.ToClientProjectHook()); - } + private static IEnumerable ToClientProjectHooks(IEnumerable hooks) + { + return hooks.Select(hook => hook.ToClientProjectHook()); + } - private static ProjectHook UpsertToHook(ProjectHookUpsert hook) + private static ProjectHook UpsertToHook(ProjectHookUpsert hook) + { + var hookFromUpsert = new ProjectHook { - var hookFromUpsert = new ProjectHook - { - Url = hook.Url, - PushEvents = hook.PushEvents, - MergeRequestsEvents = hook.MergeRequestsEvents, - IssuesEvents = hook.IssuesEvents, - TagPushEvents = hook.TagPushEvents, - NoteEvents = hook.NoteEvents, - JobEvents = hook.JobEvents, - PipelineEvents = hook.PipelineEvents, - EnableSslVerification = hook.EnableSslVerification, - }; + Url = hook.Url, + PushEvents = hook.PushEvents, + MergeRequestsEvents = hook.MergeRequestsEvents, + IssuesEvents = hook.IssuesEvents, + TagPushEvents = hook.TagPushEvents, + NoteEvents = hook.NoteEvents, + JobEvents = hook.JobEvents, + PipelineEvents = hook.PipelineEvents, + EnableSslVerification = hook.EnableSslVerification, + }; - return hookFromUpsert; - } + return hookFromUpsert; } } diff --git a/NGitLab.Mock/Clients/ProjectIssueNoteClient.cs b/NGitLab.Mock/Clients/ProjectIssueNoteClient.cs index 7cd6da56..847a040a 100644 --- a/NGitLab.Mock/Clients/ProjectIssueNoteClient.cs +++ b/NGitLab.Mock/Clients/ProjectIssueNoteClient.cs @@ -2,88 +2,87 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class ProjectIssueNoteClient : ClientBase, IProjectIssueNoteClient { - internal sealed class ProjectIssueNoteClient : ClientBase, IProjectIssueNoteClient + private readonly int _projectId; + + public ProjectIssueNoteClient(ClientContext context, ProjectId projectId) + : base(context) { - private readonly int _projectId; + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public ProjectIssueNoteClient(ClientContext context, int projectId) - : base(context) + public Models.ProjectIssueNote Create(ProjectIssueNoteCreate create) + { + using (Context.BeginOperationScope()) { - _projectId = projectId; - } + var issue = GetIssue(create.IssueId); - public Models.ProjectIssueNote Create(ProjectIssueNoteCreate create) - { - using (Context.BeginOperationScope()) + var projectIssueNote = new ProjectIssueNote { - var issue = GetIssue(create.IssueId); - - var projectIssueNote = new ProjectIssueNote - { - Body = create.Body, - Confidential = create.Confidential, - Author = Context.User, - }; - issue.Notes.Add(projectIssueNote); - - return projectIssueNote.ToProjectIssueNote(); - } + Body = create.Body, + Confidential = create.Confidential, + Author = Context.User, + }; + issue.Notes.Add(projectIssueNote); + + return projectIssueNote.ToProjectIssueNote(); } + } - public Models.ProjectIssueNote Edit(ProjectIssueNoteEdit edit) + public Models.ProjectIssueNote Edit(ProjectIssueNoteEdit edit) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var note = GetIssueNote(edit.IssueId, edit.NoteId); + var note = GetIssueNote(edit.IssueId, edit.NoteId); - note.Body = edit.Body; + note.Body = edit.Body; - return note.ToProjectIssueNote(); - } + return note.ToProjectIssueNote(); } + } - public IEnumerable ForIssue(int issueIid) + public IEnumerable ForIssue(int issueIid) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return GetIssue(issueIid).Notes.Select(n => n.ToProjectIssueNote()); - } + return GetIssue(issueIid).Notes.Select(n => n.ToProjectIssueNote()); } + } - public Models.ProjectIssueNote Get(int issueIid, int noteId) + public Models.ProjectIssueNote Get(int issueIid, int noteId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return GetIssueNote(issueIid, noteId).ToProjectIssueNote(); - } + return GetIssueNote(issueIid, noteId).ToProjectIssueNote(); } + } - private Issue GetIssue(int issueIid) - { - var project = GetProject(_projectId, ProjectPermission.View); - var issue = project.Issues.FirstOrDefault(iss => iss.Iid == issueIid); - - if (issue == null) - { - throw new GitLabNotFoundException("Issue does not exist."); - } + private Issue GetIssue(int issueIid) + { + var project = GetProject(_projectId, ProjectPermission.View); + var issue = project.Issues.FirstOrDefault(iss => iss.Iid == issueIid); - return issue; + if (issue == null) + { + throw new GitLabNotFoundException("Issue does not exist."); } - private ProjectIssueNote GetIssueNote(int issueIid, int issueNoteId) - { - var issue = GetIssue(issueIid); - var note = issue.Notes.FirstOrDefault(n => n.Id == issueNoteId); + return issue; + } - if (note == null) - { - throw new GitLabNotFoundException("Issue Note does not exist."); - } + private ProjectIssueNote GetIssueNote(int issueIid, int issueNoteId) + { + var issue = GetIssue(issueIid); + var note = issue.Notes.FirstOrDefault(n => n.Id == issueNoteId); - return note; + if (note == null) + { + throw new GitLabNotFoundException("Issue Note does not exist."); } + + return note; } } diff --git a/NGitLab.Mock/Clients/ProjectLevelApprovalRulesClient.cs b/NGitLab.Mock/Clients/ProjectLevelApprovalRulesClient.cs index 172a027a..b9c9841f 100644 --- a/NGitLab.Mock/Clients/ProjectLevelApprovalRulesClient.cs +++ b/NGitLab.Mock/Clients/ProjectLevelApprovalRulesClient.cs @@ -2,36 +2,35 @@ using System.Collections.Generic; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class ProjectLevelApprovalRulesClient : ClientBase, IProjectLevelApprovalRulesClient { - internal sealed class ProjectLevelApprovalRulesClient : ClientBase, IProjectLevelApprovalRulesClient - { - private readonly int _projectId; + private readonly int _projectId; - public ProjectLevelApprovalRulesClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public ProjectLevelApprovalRulesClient(ClientContext context, ProjectId projectId) + : base(context) + { + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public List GetProjectLevelApprovalRules() - { - throw new NotImplementedException(); - } + public List GetProjectLevelApprovalRules() + { + throw new NotImplementedException(); + } - public ApprovalRule UpdateProjectLevelApprovalRule(int approvalRuleIdToUpdate, ApprovalRuleUpdate approvalRuleUpdate) - { - throw new NotImplementedException(); - } + public ApprovalRule UpdateProjectLevelApprovalRule(int approvalRuleIdToUpdate, ApprovalRuleUpdate approvalRuleUpdate) + { + throw new NotImplementedException(); + } - public ApprovalRule CreateProjectLevelRule(ApprovalRuleCreate approvalRuleCreate) - { - throw new NotImplementedException(); - } + public ApprovalRule CreateProjectLevelRule(ApprovalRuleCreate approvalRuleCreate) + { + throw new NotImplementedException(); + } - public void DeleteProjectLevelRule(int approvalRuleIdToDelete) - { - throw new NotImplementedException(); - } + public void DeleteProjectLevelRule(int approvalRuleIdToDelete) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/ProjectSearchClient.cs b/NGitLab.Mock/Clients/ProjectSearchClient.cs index 06af83f7..9066f60f 100644 --- a/NGitLab.Mock/Clients/ProjectSearchClient.cs +++ b/NGitLab.Mock/Clients/ProjectSearchClient.cs @@ -1,22 +1,22 @@ using System; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class ProjectSearchClient : ClientBase, ISearchClient { - internal sealed class ProjectSearchClient : ISearchClient - { - private readonly ClientContext _context; - private readonly int _projectId; + private readonly ClientContext _context; + private readonly int _projectId; - public ProjectSearchClient(ClientContext context, int projectId) - { - _context = context; - _projectId = projectId; - } + public ProjectSearchClient(ClientContext context, ProjectId projectId) + : base(context) + { + _context = context; + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public GitLabCollectionResponse GetBlobsAsync(SearchQuery query) - { - throw new NotImplementedException(); - } + public GitLabCollectionResponse GetBlobsAsync(SearchQuery query) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/ProjectVariableClient.cs b/NGitLab.Mock/Clients/ProjectVariableClient.cs index a911d077..5d66d1d2 100644 --- a/NGitLab.Mock/Clients/ProjectVariableClient.cs +++ b/NGitLab.Mock/Clients/ProjectVariableClient.cs @@ -2,35 +2,34 @@ using System.Collections.Generic; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class ProjectVariableClient : ClientBase, IProjectVariableClient { - internal sealed class ProjectVariableClient : ClientBase, IProjectVariableClient - { - private readonly int _projectId; + private readonly int _projectId; - public ProjectVariableClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public ProjectVariableClient(ClientContext context, ProjectId projectId) + : base(context) + { + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public Variable this[string key] => throw new NotImplementedException(); + public Variable this[string key] => throw new NotImplementedException(); - public IEnumerable All => throw new NotImplementedException(); + public IEnumerable All => throw new NotImplementedException(); - public Variable Create(VariableCreate model) - { - throw new NotImplementedException(); - } + public Variable Create(VariableCreate model) + { + throw new NotImplementedException(); + } - public void Delete(string key) - { - throw new NotImplementedException(); - } + public void Delete(string key) + { + throw new NotImplementedException(); + } - public Variable Update(string key, VariableUpdate model) - { - throw new NotImplementedException(); - } + public Variable Update(string key, VariableUpdate model) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/ProtectedBranchClient.cs b/NGitLab.Mock/Clients/ProtectedBranchClient.cs index 491a9b54..93d46482 100644 --- a/NGitLab.Mock/Clients/ProtectedBranchClient.cs +++ b/NGitLab.Mock/Clients/ProtectedBranchClient.cs @@ -2,69 +2,68 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class ProtectedBranchClient : ClientBase, IProtectedBranchClient { - internal sealed class ProtectedBranchClient : ClientBase, IProtectedBranchClient + private readonly int _projectId; + + public ProtectedBranchClient(ClientContext context, ProjectId projectId) + : base(context) { - private readonly int _projectId; + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public ProtectedBranchClient(ClientContext context, int projectId) - : base(context) + public Models.ProtectedBranch GetProtectedBranch(string branchName) + { + using (Context.BeginOperationScope()) { - _projectId = projectId; + var project = GetProject(_projectId, ProjectPermission.Edit); + return project.ProtectedBranches.First(b => b.Name.Equals(branchName, StringComparison.Ordinal)).ToProtectedBranchClient(); } + } - public Models.ProtectedBranch GetProtectedBranch(string branchName) + public Models.ProtectedBranch[] GetProtectedBranches(string search = null) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.Edit); - return project.ProtectedBranches.First(b => b.Name.Equals(branchName, StringComparison.Ordinal)).ToProtectedBranchClient(); - } + var project = GetProject(_projectId, ProjectPermission.Edit); + return project.ProtectedBranches + .Where(b => b.Name.Contains(search ?? "", StringComparison.Ordinal)) + .Select(b => b.ToProtectedBranchClient()) + .ToArray(); } + } - public Models.ProtectedBranch[] GetProtectedBranches(string search = null) + public Models.ProtectedBranch ProtectBranch(BranchProtect branchProtect) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var project = GetProject(_projectId, ProjectPermission.Edit); + var protectedBranch = project.ProtectedBranches.FirstOrDefault(b => b.Name.Equals(branchProtect.BranchName, StringComparison.Ordinal)); + if (protectedBranch == null) { - var project = GetProject(_projectId, ProjectPermission.Edit); - return project.ProtectedBranches - .Where(b => b.Name.Contains(search ?? "", StringComparison.Ordinal)) - .Select(b => b.ToProtectedBranchClient()) - .ToArray(); + protectedBranch = new(); + project.ProtectedBranches.Add(protectedBranch); } - } - - public Models.ProtectedBranch ProtectBranch(BranchProtect branchProtect) - { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.Edit); - var protectedBranch = project.ProtectedBranches.FirstOrDefault(b => b.Name.Equals(branchProtect.BranchName, StringComparison.Ordinal)); - if (protectedBranch == null) - { - protectedBranch = new(); - project.ProtectedBranches.Add(protectedBranch); - } - protectedBranch.Name = branchProtect.BranchName; - protectedBranch.AllowForcePush = branchProtect.AllowForcePush; - protectedBranch.CodeOwnerApprovalRequired = branchProtect.CodeOwnerApprovalRequired; + protectedBranch.Name = branchProtect.BranchName; + protectedBranch.AllowForcePush = branchProtect.AllowForcePush; + protectedBranch.CodeOwnerApprovalRequired = branchProtect.CodeOwnerApprovalRequired; - return protectedBranch.ToProtectedBranchClient(); - } + return protectedBranch.ToProtectedBranchClient(); } + } - public void UnprotectBranch(string branchName) + public void UnprotectBranch(string branchName) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var project = GetProject(_projectId, ProjectPermission.Edit); + var protectedBranch = project.ProtectedBranches.FirstOrDefault(b => b.Name.Equals(branchName, StringComparison.Ordinal)); + if (protectedBranch != null) { - var project = GetProject(_projectId, ProjectPermission.Edit); - var protectedBranch = project.ProtectedBranches.FirstOrDefault(b => b.Name.Equals(branchName, StringComparison.Ordinal)); - if (protectedBranch != null) - { - project.ProtectedBranches.Remove(protectedBranch); - } + project.ProtectedBranches.Remove(protectedBranch); } } } diff --git a/NGitLab.Mock/Clients/ReleaseClient.cs b/NGitLab.Mock/Clients/ReleaseClient.cs index 6bf6c2f5..7b585257 100644 --- a/NGitLab.Mock/Clients/ReleaseClient.cs +++ b/NGitLab.Mock/Clients/ReleaseClient.cs @@ -7,137 +7,136 @@ using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class ReleaseClient : ClientBase, IReleaseClient { - internal sealed class ReleaseClient : ClientBase, IReleaseClient - { - private readonly int _projectId; + private readonly int _projectId; - public ReleaseClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public ReleaseClient(ClientContext context, ProjectId projectId) + : base(context) + { + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public IEnumerable All + public IEnumerable All + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - return project.Releases.Select(r => r.ToReleaseClient()); - } + var project = GetProject(_projectId, ProjectPermission.View); + return project.Releases.Select(r => r.ToReleaseClient()); } } + } - public Models.ReleaseInfo this[string tagName] + public Models.ReleaseInfo this[string tagName] + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var release = project.Releases.FirstOrDefault(r => r.TagName.Equals(tagName, StringComparison.Ordinal)); + var project = GetProject(_projectId, ProjectPermission.View); + var release = project.Releases.FirstOrDefault(r => r.TagName.Equals(tagName, StringComparison.Ordinal)); - return release.ToReleaseClient(); - } + return release.ToReleaseClient(); } } + } - public Models.ReleaseInfo Create(ReleaseCreate data) + public Models.ReleaseInfo Create(ReleaseCreate data) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var release = project.Releases.Add(data.TagName, data.Name, data.Ref, data.Description, Context.User); - return release.ToReleaseClient(); - } + var project = GetProject(_projectId, ProjectPermission.Contribute); + var release = project.Releases.Add(data.TagName, data.Name, data.Ref, data.Description, Context.User); + return release.ToReleaseClient(); } + } - public Models.ReleaseInfo Update(ReleaseUpdate data) + public Models.ReleaseInfo Update(ReleaseUpdate data) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var project = GetProject(_projectId, ProjectPermission.Contribute); + var release = project.Releases.GetByTagName(data.TagName); + if (release == null) { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var release = project.Releases.GetByTagName(data.TagName); - if (release == null) - { - throw new GitLabNotFoundException(); - } - - if (data.Name != null) - { - release.Name = data.Name; - } - - if (data.Description != null) - { - release.Description = data.Description; - } - - if (data.ReleasedAt.HasValue) - { - release.ReleasedAt = data.ReleasedAt.Value; - } + throw new GitLabNotFoundException(); + } - return release.ToReleaseClient(); + if (data.Name != null) + { + release.Name = data.Name; } - } - public void Delete(string tagName) - { - using (Context.BeginOperationScope()) + if (data.Description != null) { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var release = project.Releases.FirstOrDefault(r => r.TagName.Equals(tagName, StringComparison.Ordinal)); - if (release == null) - throw new GitLabNotFoundException(); + release.Description = data.Description; + } - project.Releases.Remove(release); + if (data.ReleasedAt.HasValue) + { + release.ReleasedAt = data.ReleasedAt.Value; } - } - public IReleaseLinkClient ReleaseLinks(string tagName) - { - throw new NotImplementedException(); + return release.ToReleaseClient(); } + } - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task CreateAsync(ReleaseCreate data, CancellationToken cancellationToken = default) + public void Delete(string tagName) + { + using (Context.BeginOperationScope()) { - await Task.Yield(); - return Create(data); + var project = GetProject(_projectId, ProjectPermission.Contribute); + var release = project.Releases.FirstOrDefault(r => r.TagName.Equals(tagName, StringComparison.Ordinal)); + if (release == null) + throw new GitLabNotFoundException(); + + project.Releases.Remove(release); } + } + + public IReleaseLinkClient ReleaseLinks(string tagName) + { + throw new NotImplementedException(); + } + + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task CreateAsync(ReleaseCreate data, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Create(data); + } - public GitLabCollectionResponse GetAsync(ReleaseQuery query = null) + public GitLabCollectionResponse GetAsync(ReleaseQuery query = null) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var project = GetProject(_projectId, ProjectPermission.View); + var result = project.Releases.AsEnumerable(); + if (query != null) { - var project = GetProject(_projectId, ProjectPermission.View); - var result = project.Releases.AsEnumerable(); - if (query != null) + var orderBy = !string.IsNullOrEmpty(query.OrderBy) && string.Equals(query.OrderBy, "created_at", StringComparison.Ordinal) + ? new Func(r => r.CreatedAt) + : new Func(r => r.ReleasedAt); + + var sortAsc = !string.IsNullOrEmpty(query.Sort) && string.Equals(query.Sort, "asc", StringComparison.Ordinal); + result = sortAsc ? result.OrderBy(orderBy) : result.OrderByDescending(orderBy); + + if (query.Page.HasValue) { - var orderBy = !string.IsNullOrEmpty(query.OrderBy) && string.Equals(query.OrderBy, "created_at", StringComparison.Ordinal) - ? new Func(r => r.CreatedAt) - : new Func(r => r.ReleasedAt); - - var sortAsc = !string.IsNullOrEmpty(query.Sort) && string.Equals(query.Sort, "asc", StringComparison.Ordinal); - result = sortAsc ? result.OrderBy(orderBy) : result.OrderByDescending(orderBy); - - if (query.Page.HasValue) - { - var perPage = query.PerPage ?? 20; - var page = Math.Max(0, query.Page.Value - 1); - result = result.Skip(perPage * page); - } - - if (query.IncludeHtmlDescription == true) - throw new NotImplementedException(); + var perPage = query.PerPage ?? 20; + var page = Math.Max(0, query.Page.Value - 1); + result = result.Skip(perPage * page); } - return GitLabCollectionResponse.Create(result.Select(r => r.ToReleaseClient()).ToArray()); + if (query.IncludeHtmlDescription == true) + throw new NotImplementedException(); } + + return GitLabCollectionResponse.Create(result.Select(r => r.ToReleaseClient()).ToArray()); } } } diff --git a/NGitLab.Mock/Clients/ReleaseLinkClient.cs b/NGitLab.Mock/Clients/ReleaseLinkClient.cs index 6f89286b..469ff781 100644 --- a/NGitLab.Mock/Clients/ReleaseLinkClient.cs +++ b/NGitLab.Mock/Clients/ReleaseLinkClient.cs @@ -2,37 +2,36 @@ using System.Collections.Generic; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class ReleaseLinkClient : ClientBase, IReleaseLinkClient { - internal sealed class ReleaseLinkClient : ClientBase, IReleaseLinkClient + private readonly int _projectId; + private readonly string _tagName; + + public ReleaseLinkClient(ClientContext context, int projectId, string tagName) + : base(context) + { + _projectId = projectId; + _tagName = tagName; + } + + public ReleaseLink this[int id] => throw new NotImplementedException(); + + public IEnumerable All => throw new NotImplementedException(); + + public ReleaseLink Create(ReleaseLinkCreate data) + { + throw new NotImplementedException(); + } + + public void Delete(int id) + { + throw new NotImplementedException(); + } + + public ReleaseLink Update(int id, ReleaseLinkUpdate data) { - private readonly int _projectId; - private readonly string _tagName; - - public ReleaseLinkClient(ClientContext context, int projectId, string tagName) - : base(context) - { - _projectId = projectId; - _tagName = tagName; - } - - public ReleaseLink this[int id] => throw new NotImplementedException(); - - public IEnumerable All => throw new NotImplementedException(); - - public ReleaseLink Create(ReleaseLinkCreate data) - { - throw new NotImplementedException(); - } - - public void Delete(int id) - { - throw new NotImplementedException(); - } - - public ReleaseLink Update(int id, ReleaseLinkUpdate data) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/RepositoryClient.cs b/NGitLab.Mock/Clients/RepositoryClient.cs index 2d28c937..d0ecef6f 100644 --- a/NGitLab.Mock/Clients/RepositoryClient.cs +++ b/NGitLab.Mock/Clients/RepositoryClient.cs @@ -5,122 +5,126 @@ using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class RepositoryClient : ClientBase, IRepositoryClient { - internal sealed class RepositoryClient : ClientBase, IRepositoryClient - { - private readonly int _projectId; + private readonly int _projectId; - public RepositoryClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public RepositoryClient(ClientContext context, ProjectId projectId) + : base(context) + { + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public ITagClient Tags => new TagClient(Context, _projectId); + public ITagClient Tags => new TagClient(Context, _projectId); - public IFilesClient Files => new FileClient(Context, _projectId); + public IFilesClient Files => new FileClient(Context, _projectId); - public IBranchClient Branches => new BranchClient(Context, _projectId); + public IBranchClient Branches => new BranchClient(Context, _projectId); - public IProjectHooksClient ProjectHooks => new ProjectHooksClient(Context, _projectId); + public IProjectHooksClient ProjectHooks => new ProjectHooksClient(Context, _projectId); - public IContributorClient Contributors => new ContributorClient(Context, _projectId); + public IContributorClient Contributors => new ContributorClient(Context, _projectId); - public IEnumerable Tree + public IEnumerable Tree + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - return project.Repository.GetTree().ToList(); - } + var project = GetProject(_projectId, ProjectPermission.View); + return project.Repository.GetTree().ToList(); } } + } - public IEnumerable Commits + public IEnumerable Commits + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - return project.Repository.GetCommits().Select(commit => ConvertToNGitLabCommit(commit, project)).ToList(); - } + var project = GetProject(_projectId, ProjectPermission.View); + return project.Repository.GetCommits().Select(commit => ConvertToNGitLabCommit(commit, project)).ToList(); } } + } - public IEnumerable GetTree(string path) - { - throw new NotImplementedException(); - } + public IEnumerable GetTree(string path) + { + throw new NotImplementedException(); + } - public IEnumerable GetTree(string path, string @ref, bool recursive) - { - throw new NotImplementedException(); - } + public IEnumerable GetTree(string path, string @ref, bool recursive) + { + throw new NotImplementedException(); + } - public GitLabCollectionResponse GetTreeAsync(RepositoryGetTreeOptions options) - { - return GitLabCollectionResponse.Create(GetTree(options)); - } + public GitLabCollectionResponse GetTreeAsync(RepositoryGetTreeOptions options) + { + return GitLabCollectionResponse.Create(GetTree(options)); + } - public IEnumerable GetTree(RepositoryGetTreeOptions options) + public IEnumerable GetTree(RepositoryGetTreeOptions options) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - return project.Repository.GetTree(options).ToList(); - } + var project = GetProject(_projectId, ProjectPermission.View); + return project.Repository.GetTree(options).ToList(); } + } - public void GetRawBlob(string sha, Action parser) - { - throw new NotImplementedException(); - } + public void GetRawBlob(string sha, Action parser) + { + throw new NotImplementedException(); + } - public void GetArchive(Action parser) - { - throw new NotImplementedException(); - } + public void GetArchive(Action parser) + { + throw new NotImplementedException(); + } - public IEnumerable GetCommits(string refName, int maxResults = 0) + public IEnumerable GetCommits(string refName, int maxResults = 0) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - return project.Repository.GetCommits(refName).Select(commit => ConvertToNGitLabCommit(commit, project)).ToList(); - } + var project = GetProject(_projectId, ProjectPermission.View); + return project.Repository.GetCommits(refName).Select(commit => ConvertToNGitLabCommit(commit, project)).ToList(); } + } - public IEnumerable GetCommits(GetCommitsRequest request) + public IEnumerable GetCommits(GetCommitsRequest request) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - return project.Repository.GetCommits(request).Select(commit => ConvertToNGitLabCommit(commit, project)).ToList(); - } + var project = GetProject(_projectId, ProjectPermission.View); + return project.Repository.GetCommits(request).Select(commit => ConvertToNGitLabCommit(commit, project)).ToList(); } + } - public Commit GetCommit(Sha1 sha) - { - throw new NotImplementedException(); - } + public Commit GetCommit(Sha1 sha) + { + throw new NotImplementedException(); + } - public IEnumerable GetCommitDiff(Sha1 sha) - { - throw new NotImplementedException(); - } + public IEnumerable GetCommitDiff(Sha1 sha) + { + throw new NotImplementedException(); + } - public IEnumerable GetCommitRefs(Sha1 sha, CommitRefType type = CommitRefType.All) - { - throw new NotImplementedException(); - } + public IEnumerable GetCommitRefs(Sha1 sha, CommitRefType type = CommitRefType.All) + { + throw new NotImplementedException(); + } - private static Commit ConvertToNGitLabCommit(LibGit2Sharp.Commit commit, Project project) - { - return commit.ToCommitClient(project); - } + private static Commit ConvertToNGitLabCommit(LibGit2Sharp.Commit commit, Project project) + { + return commit.ToCommitClient(project); + } + + public CompareResults Compare(CompareQuery query) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/RunnerClient.cs b/NGitLab.Mock/Clients/RunnerClient.cs index 492d9619..d1906e38 100644 --- a/NGitLab.Mock/Clients/RunnerClient.cs +++ b/NGitLab.Mock/Clients/RunnerClient.cs @@ -7,176 +7,192 @@ using NGitLab.Mock.Internals; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class RunnerClient : ClientBase, IRunnerClient { - internal sealed class RunnerClient : ClientBase, IRunnerClient + public IEnumerable Accessible { - public IEnumerable Accessible + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return GetOwnedRunners().Select(r => r.ToClientRunner(Context.User)); - } + return GetOwnedRunners().Select(r => r.ToClientRunner(Context.User)); } } + } - public IEnumerable All + public IEnumerable All + { + get { - get + if (!Context.User.IsAdmin) { - if (!Context.User.IsAdmin) - { - throw new GitLabForbiddenException(); - } - - using (Context.BeginOperationScope()) - { - var runners = Server.AllProjects.SelectMany(p => p.RegisteredRunners); - var clientRunners = runners.Select(r => r.ToClientRunner(Context.User)).ToList(); - return clientRunners; - } + throw new GitLabForbiddenException(); } - } - public RunnerClient(ClientContext context) - : base(context) - { - } - - public Models.Runner this[int id] - { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var runner = Accessible.FirstOrDefault(r => r.Id == id) ?? throw new GitLabNotFoundException(); - return runner; - } + var runners = Server.AllProjects.SelectMany(p => p.RegisteredRunners); + var clientRunners = runners.Select(r => r.ToClientRunner(Context.User)).ToList(); + return clientRunners; } } + } - public void Delete(Models.Runner runner) - { - throw new NotImplementedException(); - } - - public void Delete(int runnerId) - { - throw new NotImplementedException(); - } + public RunnerClient(ClientContext context) + : base(context) + { + } - public Models.Runner Update(int runnerId, RunnerUpdate runnerUpdate) + public Models.Runner this[int id] + { + get { using (Context.BeginOperationScope()) { - var runner = this[runnerId] ?? throw new GitLabNotFoundException(); - var runnerOnServer = GetServerRunner(runnerId); - - runnerOnServer.Active = runnerUpdate.Active ?? runnerOnServer.Active; - runnerOnServer.TagList = runnerUpdate.TagList ?? runnerOnServer.TagList; - runnerOnServer.Description = !string.IsNullOrEmpty(runnerUpdate.Description) ? runnerUpdate.Description : runnerOnServer.Description; - runnerOnServer.Locked = runnerUpdate.Locked ?? runnerOnServer.Locked; - runnerOnServer.RunUntagged = runnerUpdate.RunUntagged ?? runnerOnServer.RunUntagged; - + var runner = Accessible.FirstOrDefault(r => r.Id == id) ?? throw new GitLabNotFoundException(); return runner; } } + } + + public void Delete(Models.Runner runner) => Delete(runner.Id); - public IEnumerable OfProject(int projectId) + public void Delete(int runnerId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var projects = Server.AllProjects.Where(p => p.EnabledRunners.Any(r => r.Id == runnerId)); + if (!projects.Any()) { - var runnerRefs = GetProject(projectId, ProjectPermission.Edit).EnabledRunners; - return runnerRefs.Select(r => this[r.Id]).ToList(); + throw new GitLabBadRequestException("Runner is not found in any project"); } - } - public GitLabCollectionResponse OfProjectAsync(int projectId) - { - return GitLabCollectionResponse.Create(OfProject(projectId)); - } + if (projects.Skip(1).Any()) + { + throw new GitLabBadRequestException("Runner is enabled in multiple projects"); + } - public IEnumerable GetJobs(int runnerId, JobScope jobScope) - { - throw new NotImplementedException(); + var project = GetProject(projects.Single().Id, ProjectPermission.Edit); + project.RemoveRunner(runnerId); } + } - public IEnumerable GetJobs(int runnerId, JobStatus? status = null) + public Models.Runner Update(int runnerId, RunnerUpdate runnerUpdate) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); - } + var runner = this[runnerId] ?? throw new GitLabNotFoundException(); + var runnerOnServer = GetServerRunner(runnerId); - // Seems like an old method... In the actual code, the method is the same as OfProject. - IEnumerable IRunnerClient.GetAvailableRunners(int projectId) - { - return OfProject(projectId); + runnerOnServer.Active = runnerUpdate.Active ?? runnerOnServer.Active; + runnerOnServer.TagList = runnerUpdate.TagList ?? runnerOnServer.TagList; + runnerOnServer.Description = !string.IsNullOrEmpty(runnerUpdate.Description) ? runnerUpdate.Description : runnerOnServer.Description; + runnerOnServer.Locked = runnerUpdate.Locked ?? runnerOnServer.Locked; + runnerOnServer.RunUntagged = runnerUpdate.RunUntagged ?? runnerOnServer.RunUntagged; + + return runner; } + } - public IEnumerable GetAllRunnersWithScope(RunnerScope scope) + public IEnumerable OfProject(int projectId) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var runnerRefs = GetProject(projectId, ProjectPermission.Edit).EnabledRunners; + return runnerRefs.Select(r => this[r.Id]).ToList(); } + } - public Models.Runner EnableRunner(int projectId, RunnerId runnerId) - { - using (Context.BeginOperationScope()) - { - var project = GetProject(projectId, ProjectPermission.Edit); - var runner = GetServerRunner(runnerId.Id); + public GitLabCollectionResponse OfProjectAsync(int projectId) + { + return GitLabCollectionResponse.Create(OfProject(projectId)); + } - var runnerReference = new RunnerRef(runner); + public IEnumerable GetJobs(int runnerId, JobScope jobScope) + { + throw new NotImplementedException(); + } - if (project.EnabledRunners.Contains(runnerReference)) - { - throw new GitLabException("Bad Request. Runner has already been taken"); - } + public IEnumerable GetJobs(int runnerId, JobStatus? status = null) + { + throw new NotImplementedException(); + } - project.EnabledRunners.Add(runnerReference); - return runner.ToClientRunner(Context.User); - } - } + // Seems like an old method... In the actual code, the method is the same as OfProject. + IEnumerable IRunnerClient.GetAvailableRunners(int projectId) + { + return OfProject(projectId); + } - [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] - public async Task EnableRunnerAsync(int projectId, RunnerId runnerId, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return EnableRunner(projectId, runnerId); - } + public IEnumerable GetAllRunnersWithScope(RunnerScope scope) + { + throw new NotImplementedException(); + } - public void DisableRunner(int projectId, RunnerId runnerId) + public Models.Runner EnableRunner(int projectId, RunnerId runnerId) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(projectId, ProjectPermission.Edit); - var runner = GetServerRunner(runnerId.Id); + var project = GetProject(projectId, ProjectPermission.Edit); + var runner = GetServerRunner(runnerId.Id); - if (project.EnabledRunners.All(r => r.Id != runnerId.Id)) - { - throw new GitLabNotFoundException(); - } + var runnerReference = new RunnerRef(runner); - var runnerReference = new RunnerRef(runner); - project.EnabledRunners.Remove(runnerReference); + if (project.EnabledRunners.Contains(runnerReference)) + { + throw new GitLabBadRequestException("Runner has already been taken"); } - } - private IEnumerable GetOwnedRunners() - { - var projects = Server.AllProjects.Where(project => project.CanUserEditProject(Context.User)); - var runners = projects.SelectMany(p => p.RegisteredRunners); - return runners; + project.EnabledRunners.Add(runnerReference); + return runner.ToClientRunner(Context.User); } + } + + [SuppressMessage("Design", "MA0042:Do not use blocking calls in an async method", Justification = "Would be an infinite recursion")] + public async Task EnableRunnerAsync(int projectId, RunnerId runnerId, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return EnableRunner(projectId, runnerId); + } - private Runner GetServerRunner(int id) + public void DisableRunner(int projectId, RunnerId runnerId) + { + using (Context.BeginOperationScope()) { - return GetOwnedRunners().FirstOrDefault(runner => runner.Id == id) ?? throw new GitLabNotFoundException(); + var project = GetProject(projectId, ProjectPermission.Edit); + var runner = GetServerRunner(runnerId.Id); + + if (project.EnabledRunners.All(r => r.Id != runnerId.Id)) + { + throw new GitLabNotFoundException(); + } + + var runnerReference = new RunnerRef(runner); + project.EnabledRunners.Remove(runnerReference); } + } + + private IEnumerable GetOwnedRunners() + { + var projects = Server.AllProjects.Where(project => project.CanUserEditProject(Context.User)); + var runners = projects.SelectMany(p => p.RegisteredRunners); + return runners; + } - Models.Runner IRunnerClient.Register(RunnerRegister request) + private Runner GetServerRunner(int id) + { + return GetOwnedRunners().FirstOrDefault(runner => runner.Id == id) ?? throw new GitLabNotFoundException(); + } + + public Models.Runner Register(RunnerRegister request) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + var project = Server.AllProjects.SingleOrDefault(p => string.Equals(p.RunnersToken, request.Token, StringComparison.Ordinal)); + var runner = project.AddRunner(null, request.Description, request.Active ?? false, request.Locked ?? true, false, request.RunUntagged ?? false); + return runner.ToClientRunner(Context.User); } } } diff --git a/NGitLab.Mock/Clients/SnippetClient.cs b/NGitLab.Mock/Clients/SnippetClient.cs index 30fb1aff..b156bc44 100644 --- a/NGitLab.Mock/Clients/SnippetClient.cs +++ b/NGitLab.Mock/Clients/SnippetClient.cs @@ -3,62 +3,61 @@ using System.IO; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class SnippetClient : ClientBase, ISnippetClient { - internal sealed class SnippetClient : ClientBase, ISnippetClient + public SnippetClient(ClientContext context) + : base(context) { - public SnippetClient(ClientContext context) - : base(context) - { - } + } - public IEnumerable All => throw new NotImplementedException(); + public IEnumerable All => throw new NotImplementedException(); - public IEnumerable User => throw new NotImplementedException(); + public IEnumerable User => throw new NotImplementedException(); - public void Create(SnippetCreate snippet) - { - throw new NotImplementedException(); - } + public void Create(SnippetCreate snippet) + { + throw new NotImplementedException(); + } - public void Create(SnippetProjectCreate snippet) - { - throw new NotImplementedException(); - } + public void Create(SnippetProjectCreate snippet) + { + throw new NotImplementedException(); + } - public void Update(SnippetUpdate snippet) - { - throw new NotImplementedException(); - } + public void Update(SnippetUpdate snippet) + { + throw new NotImplementedException(); + } - public void Update(SnippetProjectUpdate snippet) - { - throw new NotImplementedException(); - } + public void Update(SnippetProjectUpdate snippet) + { + throw new NotImplementedException(); + } - public void Delete(int snippetId) - { - throw new NotImplementedException(); - } + public void Delete(int snippetId) + { + throw new NotImplementedException(); + } - public void Delete(int projectId, int snippetId) - { - throw new NotImplementedException(); - } + public void Delete(int projectId, int snippetId) + { + throw new NotImplementedException(); + } - public IEnumerable ForProject(int projectId) - { - throw new NotImplementedException(); - } + public IEnumerable ForProject(int projectId) + { + throw new NotImplementedException(); + } - public Snippet Get(int projectId, int snippetId) - { - throw new NotImplementedException(); - } + public Snippet Get(int projectId, int snippetId) + { + throw new NotImplementedException(); + } - public void GetContent(int projectId, int snippetId, Action parser) - { - throw new NotImplementedException(); - } + public void GetContent(int projectId, int snippetId, Action parser) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/SystemHookClient.cs b/NGitLab.Mock/Clients/SystemHookClient.cs index e19ed33a..f8d8f5df 100644 --- a/NGitLab.Mock/Clients/SystemHookClient.cs +++ b/NGitLab.Mock/Clients/SystemHookClient.cs @@ -3,83 +3,82 @@ using System.Linq; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class SystemHookClient : ClientBase, ISystemHookClient { - internal sealed class SystemHookClient : ClientBase, ISystemHookClient + public SystemHookClient(ClientContext context) + : base(context) { - public SystemHookClient(ClientContext context) - : base(context) - { - } + } - public Models.SystemHook this[int hookId] + public Models.SystemHook this[int hookId] + { + get { - get + AssertIsAdmin(); + using (Context.BeginOperationScope()) { - AssertIsAdmin(); - using (Context.BeginOperationScope()) - { - var result = Server.SystemHooks.FirstOrDefault(hook => hook.Id == hookId); - if (result == null) - throw new GitLabNotFoundException(); - - return result.ToClientSystemHook(); - } - } - } + var result = Server.SystemHooks.FirstOrDefault(hook => hook.Id == hookId); + if (result == null) + throw new GitLabNotFoundException(); - public IEnumerable All - { - get - { - AssertIsAdmin(); - using (Context.BeginOperationScope()) - { - return Server.SystemHooks.Select(hook => hook.ToClientSystemHook()).ToList(); - } + return result.ToClientSystemHook(); } } + } - public Models.SystemHook Create(SystemHookUpsert hook) + public IEnumerable All + { + get { AssertIsAdmin(); using (Context.BeginOperationScope()) { - var newHook = new SystemHook - { - CreatedAt = DateTime.UtcNow, - EnableSslVerification = hook.EnableSslVerification, - MergeRequestsEvents = hook.MergeRequestsEvents, - PushEvents = hook.PushEvents, - TagPushEvents = hook.TagPushEvents, - Url = hook.Url, - RepositoryUpdateEvents = hook.RepositoryUpdateEvents, - }; - Server.SystemHooks.Add(newHook); - - return newHook.ToClientSystemHook(); + return Server.SystemHooks.Select(hook => hook.ToClientSystemHook()).ToList(); } } + } - public void Delete(int hookId) + public Models.SystemHook Create(SystemHookUpsert hook) + { + AssertIsAdmin(); + using (Context.BeginOperationScope()) { - AssertIsAdmin(); - using (Context.BeginOperationScope()) + var newHook = new SystemHook { - var result = Server.SystemHooks.FirstOrDefault(hook => hook.Id == hookId); - if (result == null) - throw new GitLabNotFoundException(); + CreatedAt = DateTime.UtcNow, + EnableSslVerification = hook.EnableSslVerification, + MergeRequestsEvents = hook.MergeRequestsEvents, + PushEvents = hook.PushEvents, + TagPushEvents = hook.TagPushEvents, + Url = hook.Url, + RepositoryUpdateEvents = hook.RepositoryUpdateEvents, + }; + Server.SystemHooks.Add(newHook); - Server.SystemHooks.Remove(result); - } + return newHook.ToClientSystemHook(); } + } - private void AssertIsAdmin() + public void Delete(int hookId) + { + AssertIsAdmin(); + using (Context.BeginOperationScope()) { - if (Context.IsAuthenticated && Context.User.IsAdmin) - return; + var result = Server.SystemHooks.FirstOrDefault(hook => hook.Id == hookId); + if (result == null) + throw new GitLabNotFoundException(); - throw new GitLabException("User must be admin"); + Server.SystemHooks.Remove(result); } } + + private void AssertIsAdmin() + { + if (Context.IsAuthenticated && Context.User.IsAdmin) + return; + + throw new GitLabException("User must be admin"); + } } diff --git a/NGitLab.Mock/Clients/TagClient.cs b/NGitLab.Mock/Clients/TagClient.cs index 78f8fc7a..5f6f978f 100644 --- a/NGitLab.Mock/Clients/TagClient.cs +++ b/NGitLab.Mock/Clients/TagClient.cs @@ -8,95 +8,94 @@ using NGitLab.Models; using Commit = LibGit2Sharp.Commit; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class TagClient : ClientBase, ITagClient { - internal sealed class TagClient : ClientBase, ITagClient - { - private readonly int _projectId; + private readonly int _projectId; - public TagClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public TagClient(ClientContext context, int projectId) + : base(context) + { + _projectId = projectId; + } - public IEnumerable All + public IEnumerable All + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return GetProject(_projectId, ProjectPermission.View).Repository.GetTags().Select(t => ToTagClient(t)).ToList(); - } + return GetProject(_projectId, ProjectPermission.View).Repository.GetTags().Select(t => ToTagClient(t)).ToList(); } } + } - public Tag Create(TagCreate tag) + public Tag Create(TagCreate tag) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var createdTag = project.Repository.CreateTag(Context.User, tag.Name, tag.Ref, tag.Message, tag.ReleaseDescription); + var project = GetProject(_projectId, ProjectPermission.Contribute); + var createdTag = project.Repository.CreateTag(Context.User, tag.Name, tag.Ref, tag.Message, tag.ReleaseDescription); - return ToTagClient(createdTag); - } + return ToTagClient(createdTag); } + } - public Task GetByNameAsync(string name, CancellationToken cancellationToken = default) + public Task GetByNameAsync(string name, CancellationToken cancellationToken = default) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.View); - var mockTag = project.Repository.GetTags().FirstOrDefault(t => t.FriendlyName.Equals(name, StringComparison.Ordinal)); - if (mockTag is null) - throw new GitLabException() { StatusCode = HttpStatusCode.NotFound }; - return Task.FromResult(ToTagClient(mockTag)); - } + var project = GetProject(_projectId, ProjectPermission.View); + var mockTag = project.Repository.GetTags().FirstOrDefault(t => t.FriendlyName.Equals(name, StringComparison.Ordinal)); + if (mockTag is null) + throw new GitLabException() { StatusCode = HttpStatusCode.NotFound }; + return Task.FromResult(ToTagClient(mockTag)); } + } - public void Delete(string name) + public void Delete(string name) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var project = GetProject(_projectId, ProjectPermission.Contribute); - project.Repository.DeleteTag(name); - } + var project = GetProject(_projectId, ProjectPermission.Contribute); + project.Repository.DeleteTag(name); } + } - public Tag ToTagClient(LibGit2Sharp.Tag tag) - { - var project = GetProject(_projectId, ProjectPermission.Contribute); - var commit = (Commit)tag.PeeledTarget; + public Tag ToTagClient(LibGit2Sharp.Tag tag) + { + var project = GetProject(_projectId, ProjectPermission.Contribute); + var commit = (Commit)tag.PeeledTarget; - return new Tag + return new Tag + { + Commit = commit.ToCommitInfo(), + Name = tag.FriendlyName, + Release = new Models.ReleaseInfo { - Commit = commit.ToCommitInfo(), - Name = tag.FriendlyName, - Release = new Models.ReleaseInfo - { - Description = project.Releases.GetByTagName(tag.FriendlyName)?.Description, - TagName = tag.FriendlyName, - }, - Message = tag.Annotation?.Message, - }; - } + Description = project.Releases.GetByTagName(tag.FriendlyName)?.Description, + TagName = tag.FriendlyName, + }, + Message = tag.Annotation?.Message, + }; + } - public GitLabCollectionResponse GetAsync(TagQuery query) + public GitLabCollectionResponse GetAsync(TagQuery query) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var result = GetProject(_projectId, ProjectPermission.View).Repository.GetTags(); + if (query != null) { - var result = GetProject(_projectId, ProjectPermission.View).Repository.GetTags(); - if (query != null) - { - if (!string.IsNullOrEmpty(query.Sort)) - throw new NotImplementedException(); + if (!string.IsNullOrEmpty(query.Sort)) + throw new NotImplementedException(); - if (!string.IsNullOrEmpty(query.OrderBy)) - throw new NotImplementedException(); - } - - return GitLabCollectionResponse.Create(result.Select(tag => ToTagClient(tag)).ToArray()); + if (!string.IsNullOrEmpty(query.OrderBy)) + throw new NotImplementedException(); } + + return GitLabCollectionResponse.Create(result.Select(tag => ToTagClient(tag)).ToArray()); } } } diff --git a/NGitLab.Mock/Clients/TriggerClient.cs b/NGitLab.Mock/Clients/TriggerClient.cs index 560b686d..a06ae661 100644 --- a/NGitLab.Mock/Clients/TriggerClient.cs +++ b/NGitLab.Mock/Clients/TriggerClient.cs @@ -2,25 +2,24 @@ using System.Collections.Generic; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class TriggerClient : ClientBase, ITriggerClient { - internal sealed class TriggerClient : ClientBase, ITriggerClient - { - private readonly int _projectId; + private readonly int _projectId; - public TriggerClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public TriggerClient(ClientContext context, ProjectId projectId) + : base(context) + { + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public Trigger this[int id] => throw new NotImplementedException(); + public Trigger this[int id] => throw new NotImplementedException(); - public IEnumerable All => throw new NotImplementedException(); + public IEnumerable All => throw new NotImplementedException(); - public Trigger Create(string description) - { - throw new NotImplementedException(); - } + public Trigger Create(string description) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/UserClient.cs b/NGitLab.Mock/Clients/UserClient.cs index 899258a4..89fde676 100644 --- a/NGitLab.Mock/Clients/UserClient.cs +++ b/NGitLab.Mock/Clients/UserClient.cs @@ -5,178 +5,204 @@ using System.Threading.Tasks; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class UserClient : ClientBase, IUserClient { - internal sealed class UserClient : ClientBase, IUserClient + public UserClient(ClientContext context) + : base(context) { - public UserClient(ClientContext context) - : base(context) - { - } + } - public Models.User this[int id] + public Models.User this[int id] + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return Server.Users.GetById(id)?.ToClientUser() ?? throw new GitLabNotFoundException(); - } + return Server.Users.GetById(id)?.ToClientUser() ?? throw new GitLabNotFoundException(); } } + } - public IEnumerable All + public IEnumerable All + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return Server.Users.Select(user => user.ToClientUser()).ToList(); - } + return Server.Users.Select(user => user.ToClientUser()).ToList(); } } + } - public Session Current + public Session Current + { + get { - get + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return Context.User?.ToClientSession(); - } + return Context.User?.ToClientSession(); } } + } - public ISshKeyClient CurrentUserSShKeys => throw new NotSupportedException(); + public ISshKeyClient CurrentUserSShKeys => throw new NotSupportedException(); - public Models.User Create(UserUpsert user) + public Models.User Create(UserUpsert user) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var u = new User(user.Username) { - var u = new User(user.Username) - { - Name = user.Name, - Email = user.Email, - }; - - Server.Users.Add(u); - return u.ToClientUser(); - } - } + Name = user.Name, + Email = user.Email, + State = UserState.active, + IsAdmin = user.IsAdmin ?? false, + }; - public UserToken CreateToken(UserTokenCreate tokenRequest) - { - throw new NotSupportedException(); + Server.Users.Add(u); + return u.ToClientUser(); } + } - public void Delete(int id) - { - using (Context.BeginOperationScope()) - { - var user = Server.Users.GetById(id); + public async Task CreateAsync(UserUpsert user, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Create(user); + } - if (user == null) - { - throw new GitLabNotFoundException($"User '{id}' is not found. Cannot be deleted"); - } + public UserToken CreateToken(UserTokenCreate tokenRequest) + { + throw new NotSupportedException(); + } - Server.Users.Remove(user); - } - } + public async Task CreateTokenAsync(UserTokenCreate tokenRequest, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return CreateToken(tokenRequest); + } - public void Activate(int id) + public void Delete(int id) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var user = Server.Users.GetById(id); - - if (user == null) - { - throw new GitLabNotFoundException($"User '{id}' is not found. Cannot be activated"); - } + var user = Server.Users.GetById(id); - user.State = UserState.active; + if (user == null) + { + throw new GitLabNotFoundException($"User '{id}' is not found. Cannot be deleted"); } + + Server.Users.Remove(user); } + } - public void Deactivate(int id) + public void Activate(int id) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - var user = Server.Users.GetById(id); + var user = Server.Users.GetById(id); - if (user == null) - { - throw new GitLabNotFoundException($"User '{id}' is not found. Cannot be deactivated"); - } - - user.State = UserState.deactivated; + if (user == null) + { + throw new GitLabNotFoundException($"User '{id}' is not found. Cannot be activated"); } + + user.State = UserState.active; } + } - public IEnumerable Get(string username) + public void Deactivate(int id) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var user = Server.Users.GetById(id); + + if (user == null) { - return Server.Users.SearchByUsername(username).Select(user => user.ToClientUser()).ToList(); + throw new GitLabNotFoundException($"User '{id}' is not found. Cannot be deactivated"); } + + user.State = UserState.deactivated; } + } - public IEnumerable Get(UserQuery query) + public IEnumerable Get(string username) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return Server.Users.Get(query).Select(user => user.ToClientUser()); - } + return Server.Users.SearchByUsername(username).Select(user => user.ToClientUser()).ToList(); } + } - public IEnumerable Search(string query) + public IEnumerable Get(UserQuery query) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) - { - return Server.Users - .Where(user => string.Equals(user.Email, query, StringComparison.OrdinalIgnoreCase) || string.Equals(user.UserName, query, StringComparison.OrdinalIgnoreCase)) - .Select(user => user.ToClientUser()).ToList(); - } + return Server.Users.Get(query).Select(user => user.ToClientUser()); } + } - public ISshKeyClient SShKeys(int userId) + public IEnumerable Search(string query) + { + using (Context.BeginOperationScope()) { - throw new NotImplementedException(); + return Server.Users + .Where(user => string.Equals(user.Email, query, StringComparison.OrdinalIgnoreCase) || string.Equals(user.UserName, query, StringComparison.OrdinalIgnoreCase)) + .Select(user => user.ToClientUser()).ToList(); } + } + + public ISshKeyClient SShKeys(int userId) + { + throw new NotImplementedException(); + } - public Models.User Update(int id, UserUpsert userUpsert) + public Models.User Update(int id, UserUpsert userUpsert) + { + using (Context.BeginOperationScope()) { - using (Context.BeginOperationScope()) + var user = Server.Users.GetById(id); + if (user != null) { - var user = Server.Users.GetById(id); - if (user != null) - { - user.Name = userUpsert.Name; - user.Email = userUpsert.Email; + // user.UserName = userUpsert.Username ?? user.UserName; // TODO + user.Name = userUpsert.Name ?? user.Name; + user.Email = userUpsert.Email ?? user.Email; + user.IsAdmin = userUpsert.IsAdmin ?? user.IsAdmin; - return user.ToClientUser(); - } - - throw new GitLabNotFoundException(); + return user.ToClientUser(); } - } - public async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) - { - await Task.Yield(); - return this[id]; + throw new GitLabNotFoundException(); } + } - public async Task GetCurrentUserAsync(CancellationToken cancellationToken = default) - { - await Task.Yield(); - return Current; - } + public async Task GetByIdAsync(int id, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return this[id]; + } - public GitLabCollectionResponse GetLastActivityDatesAsync(DateTimeOffset? from = null) - { - throw new NotImplementedException(); - } + public async Task GetByUserNameAsync(string username, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Get(username).SingleOrDefault() + ?? throw new GitLabException("User not found.") + { + StatusCode = System.Net.HttpStatusCode.NotFound, + ErrorMessage = "User not found.", + }; + } + + public async Task GetCurrentUserAsync(CancellationToken cancellationToken = default) + { + await Task.Yield(); + return Current; + } + + public GitLabCollectionResponse GetLastActivityDatesAsync(DateTimeOffset? from = null) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Clients/VersionClient.cs b/NGitLab.Mock/Clients/VersionClient.cs index 039438c2..fbb87559 100644 --- a/NGitLab.Mock/Clients/VersionClient.cs +++ b/NGitLab.Mock/Clients/VersionClient.cs @@ -1,17 +1,16 @@ using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class VersionClient : ClientBase, IVersionClient { - internal sealed class VersionClient : ClientBase, IVersionClient + public VersionClient(ClientContext context) + : base(context) { - public VersionClient(ClientContext context) - : base(context) - { - } + } - public GitLabVersion Get() - { - return Server.Version; - } + public GitLabVersion Get() + { + return Server.Version; } } diff --git a/NGitLab.Mock/Clients/WikiClient.cs b/NGitLab.Mock/Clients/WikiClient.cs index 35231d0f..923d735f 100644 --- a/NGitLab.Mock/Clients/WikiClient.cs +++ b/NGitLab.Mock/Clients/WikiClient.cs @@ -2,35 +2,34 @@ using System.Collections.Generic; using NGitLab.Models; -namespace NGitLab.Mock.Clients +namespace NGitLab.Mock.Clients; + +internal sealed class WikiClient : ClientBase, IWikiClient { - internal sealed class WikiClient : ClientBase, IWikiClient - { - private readonly int _projectId; + private readonly int _projectId; - public WikiClient(ClientContext context, int projectId) - : base(context) - { - _projectId = projectId; - } + public WikiClient(ClientContext context, ProjectId projectId) + : base(context) + { + _projectId = Server.AllProjects.FindProject(projectId.ValueAsUriParameter()).Id; + } - public WikiPage this[string slug] => throw new NotImplementedException(); + public WikiPage this[string slug] => throw new NotImplementedException(); - public IEnumerable All => throw new NotImplementedException(); + public IEnumerable All => throw new NotImplementedException(); - public WikiPage Create(WikiPageCreate wikiPage) - { - throw new NotImplementedException(); - } + public WikiPage Create(WikiPageCreate wikiPage) + { + throw new NotImplementedException(); + } - public void Delete(string slug) - { - throw new NotImplementedException(); - } + public void Delete(string slug) + { + throw new NotImplementedException(); + } - public WikiPage Update(string slug, WikiPageUpdate wikiPage) - { - throw new NotImplementedException(); - } + public WikiPage Update(string slug, WikiPageUpdate wikiPage) + { + throw new NotImplementedException(); } } diff --git a/NGitLab.Mock/Collection.cs b/NGitLab.Mock/Collection.cs index 153161ac..506c66bb 100644 --- a/NGitLab.Mock/Collection.cs +++ b/NGitLab.Mock/Collection.cs @@ -4,85 +4,84 @@ using System.Collections.Specialized; using System.ComponentModel; -namespace NGitLab.Mock +namespace NGitLab.Mock; + +public abstract class Collection : IReadOnlyCollection, INotifyCollectionChanged, INotifyPropertyChanged + where T : GitLabObject { - public abstract class Collection : IReadOnlyCollection, INotifyCollectionChanged, INotifyPropertyChanged - where T : GitLabObject - { - private readonly List _items = new(); + private readonly List _items = new(); - public event NotifyCollectionChangedEventHandler CollectionChanged; + public event NotifyCollectionChangedEventHandler CollectionChanged; - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler PropertyChanged; - protected Collection(GitLabObject parent) - { - Parent = parent ?? throw new ArgumentNullException(nameof(parent)); - } + protected Collection(GitLabObject parent) + { + Parent = parent ?? throw new ArgumentNullException(nameof(parent)); + } - protected GitLabServer Server => Parent.Server; + protected GitLabServer Server => Parent.Server; - protected GitLabObject Parent { get; } + protected GitLabObject Parent { get; } - public int Count => _items.Count; + public int Count => _items.Count; - protected void Clear() - { - _items.Clear(); + protected void Clear() + { + _items.Clear(); - CollectionChanged?.Invoke(this, EventArgsCache.ResetCollectionChanged); - PropertyChanged?.Invoke(this, EventArgsCache.IndexerPropertyChanged); - PropertyChanged?.Invoke(this, EventArgsCache.CountPropertyChanged); - } + CollectionChanged?.Invoke(this, EventArgsCache.ResetCollectionChanged); + PropertyChanged?.Invoke(this, EventArgsCache.IndexerPropertyChanged); + PropertyChanged?.Invoke(this, EventArgsCache.CountPropertyChanged); + } - public IEnumerator GetEnumerator() => _items.GetEnumerator(); + public IEnumerator GetEnumerator() => _items.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public bool Contains(T item) - { - if (item is null) - throw new ArgumentNullException(nameof(item)); + public bool Contains(T item) + { + if (item is null) + throw new ArgumentNullException(nameof(item)); - return _items.Contains(item); - } + return _items.Contains(item); + } - public virtual void Add(T item) - { - if (item is null) - throw new ArgumentNullException(nameof(item)); + public virtual void Add(T item) + { + if (item is null) + throw new ArgumentNullException(nameof(item)); - item.Parent = Parent; - _items.Add(item); + item.Parent = Parent; + _items.Add(item); - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _items.Count - 1)); - PropertyChanged?.Invoke(this, EventArgsCache.IndexerPropertyChanged); - PropertyChanged?.Invoke(this, EventArgsCache.CountPropertyChanged); - } + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _items.Count - 1)); + PropertyChanged?.Invoke(this, EventArgsCache.IndexerPropertyChanged); + PropertyChanged?.Invoke(this, EventArgsCache.CountPropertyChanged); + } - public bool Remove(T item) - { - if (item is null) - throw new ArgumentNullException(nameof(item)); + public bool Remove(T item) + { + if (item is null) + throw new ArgumentNullException(nameof(item)); - var index = _items.IndexOf(item); - if (index < 0) - return false; + var index = _items.IndexOf(item); + if (index < 0) + return false; - item.Parent = null; - _items.RemoveAt(index); + item.Parent = null; + _items.RemoveAt(index); - CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); - PropertyChanged?.Invoke(this, EventArgsCache.IndexerPropertyChanged); - PropertyChanged?.Invoke(this, EventArgsCache.CountPropertyChanged); - return true; - } + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); + PropertyChanged?.Invoke(this, EventArgsCache.IndexerPropertyChanged); + PropertyChanged?.Invoke(this, EventArgsCache.CountPropertyChanged); + return true; + } - private static class EventArgsCache - { - internal static readonly PropertyChangedEventArgs CountPropertyChanged = new("Count"); - internal static readonly PropertyChangedEventArgs IndexerPropertyChanged = new("Item[]"); - internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new(NotifyCollectionChangedAction.Reset); - } + private static class EventArgsCache + { + internal static readonly PropertyChangedEventArgs CountPropertyChanged = new("Count"); + internal static readonly PropertyChangedEventArgs IndexerPropertyChanged = new("Item[]"); + internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new(NotifyCollectionChangedAction.Reset); } } diff --git a/NGitLab.Mock/CommitAction.cs b/NGitLab.Mock/CommitAction.cs index 894f07b9..71af5e82 100644 --- a/NGitLab.Mock/CommitAction.cs +++ b/NGitLab.Mock/CommitAction.cs @@ -1,11 +1,10 @@ -namespace NGitLab.Mock +namespace NGitLab.Mock; + +public enum CommitAction { - public enum CommitAction - { - Create, - Delete, - Move, - Update, - chmod, - } + Create, + Delete, + Move, + Update, + chmod, } diff --git a/NGitLab.Mock/CommitActionCreateHandler.cs b/NGitLab.Mock/CommitActionCreateHandler.cs index 0bd06ceb..622996a7 100644 --- a/NGitLab.Mock/CommitActionCreateHandler.cs +++ b/NGitLab.Mock/CommitActionCreateHandler.cs @@ -2,32 +2,31 @@ using System.IO; using NGitLab.Models; -namespace NGitLab.Mock +namespace NGitLab.Mock; + +internal sealed class CommitActionCreateHandler : ICommitActionHandler { - internal sealed class CommitActionCreateHandler : ICommitActionHandler + public void Handle(CreateCommitAction action, string repoPath, LibGit2Sharp.Repository repository) { - public void Handle(CreateCommitAction action, string repoPath, LibGit2Sharp.Repository repository) - { - if (!Directory.Exists(repoPath)) - throw new DirectoryNotFoundException(); + if (!Directory.Exists(repoPath)) + throw new DirectoryNotFoundException(); - var filePath = Path.Combine(repoPath, action.FilePath); + var filePath = Path.Combine(repoPath, action.FilePath); - if (System.IO.File.Exists(filePath)) - throw new GitLabException("File already exists."); + if (System.IO.File.Exists(filePath)) + throw new GitLabException("File already exists."); - if (string.Equals(action.Encoding, "base64", StringComparison.OrdinalIgnoreCase)) - { - var content = Convert.FromBase64String(action.Content); - System.IO.File.WriteAllBytes(filePath, content); - } - else - { - System.IO.File.WriteAllText(filePath, action.Content); - } - - repository.Index.Add(action.FilePath); - repository.Index.Write(); + if (string.Equals(action.Encoding, "base64", StringComparison.OrdinalIgnoreCase)) + { + var content = Convert.FromBase64String(action.Content); + System.IO.File.WriteAllBytes(filePath, content); } + else + { + System.IO.File.WriteAllText(filePath, action.Content); + } + + repository.Index.Add(action.FilePath); + repository.Index.Write(); } } diff --git a/NGitLab.Mock/CommitActionDeleteHandler.cs b/NGitLab.Mock/CommitActionDeleteHandler.cs index 6fa67963..27b5acf7 100644 --- a/NGitLab.Mock/CommitActionDeleteHandler.cs +++ b/NGitLab.Mock/CommitActionDeleteHandler.cs @@ -1,21 +1,20 @@ using System.IO; using NGitLab.Models; -namespace NGitLab.Mock +namespace NGitLab.Mock; + +internal sealed class CommitActionDeleteHandler : ICommitActionHandler { - internal sealed class CommitActionDeleteHandler : ICommitActionHandler + public void Handle(CreateCommitAction action, string repoPath, LibGit2Sharp.Repository repository) { - public void Handle(CreateCommitAction action, string repoPath, LibGit2Sharp.Repository repository) - { - var fullPath = Path.Combine(repoPath, action.FilePath); + var fullPath = Path.Combine(repoPath, action.FilePath); - if (!System.IO.File.Exists(fullPath)) - throw new FileNotFoundException("Could not find file", fullPath); + if (!System.IO.File.Exists(fullPath)) + throw new FileNotFoundException("Could not find file", fullPath); - System.IO.File.Delete(fullPath); + System.IO.File.Delete(fullPath); - repository.Index.Remove(action.FilePath); - repository.Index.Write(); - } + repository.Index.Remove(action.FilePath); + repository.Index.Write(); } } diff --git a/NGitLab.Mock/CommitActionMoveHandler.cs b/NGitLab.Mock/CommitActionMoveHandler.cs index deae46f7..5de8bd79 100644 --- a/NGitLab.Mock/CommitActionMoveHandler.cs +++ b/NGitLab.Mock/CommitActionMoveHandler.cs @@ -1,22 +1,21 @@ using System.IO; using NGitLab.Models; -namespace NGitLab.Mock +namespace NGitLab.Mock; + +internal sealed class CommitActionMoveHandler : ICommitActionHandler { - internal sealed class CommitActionMoveHandler : ICommitActionHandler + public void Handle(CreateCommitAction action, string repoPath, LibGit2Sharp.Repository repository) { - public void Handle(CreateCommitAction action, string repoPath, LibGit2Sharp.Repository repository) - { - var oldPath = Path.Combine(repoPath, action.PreviousPath); - if (!System.IO.File.Exists(oldPath)) - throw new FileNotFoundException("Could not find file", nameof(oldPath)); + var oldPath = Path.Combine(repoPath, action.PreviousPath); + if (!System.IO.File.Exists(oldPath)) + throw new FileNotFoundException("Could not find file", nameof(oldPath)); - var newPath = Path.Combine(repoPath, action.FilePath); - System.IO.File.Move(oldPath, newPath); + var newPath = Path.Combine(repoPath, action.FilePath); + System.IO.File.Move(oldPath, newPath); - repository.Index.Add(action.FilePath); - repository.Index.Add(action.PreviousPath); - repository.Index.Write(); - } + repository.Index.Add(action.FilePath); + repository.Index.Add(action.PreviousPath); + repository.Index.Write(); } } diff --git a/NGitLab.Mock/CommitActionUpdateHandler.cs b/NGitLab.Mock/CommitActionUpdateHandler.cs index 07f7f97b..3893b3d0 100644 --- a/NGitLab.Mock/CommitActionUpdateHandler.cs +++ b/NGitLab.Mock/CommitActionUpdateHandler.cs @@ -2,32 +2,31 @@ using System.IO; using NGitLab.Models; -namespace NGitLab.Mock +namespace NGitLab.Mock; + +internal sealed class CommitActionUpdateHandler : ICommitActionHandler { - internal sealed class CommitActionUpdateHandler : ICommitActionHandler + public void Handle(CreateCommitAction action, string repoPath, LibGit2Sharp.Repository repository) { - public void Handle(CreateCommitAction action, string repoPath, LibGit2Sharp.Repository repository) - { - if (!Directory.Exists(repoPath)) - throw new DirectoryNotFoundException(); + if (!Directory.Exists(repoPath)) + throw new DirectoryNotFoundException(); - var filePath = Path.Combine(repoPath, action.FilePath); + var filePath = Path.Combine(repoPath, action.FilePath); - if (!System.IO.File.Exists(filePath)) - throw new FileNotFoundException("File does not exist."); + if (!System.IO.File.Exists(filePath)) + throw new FileNotFoundException("File does not exist."); - if (string.Equals(action.Encoding, "base64", StringComparison.OrdinalIgnoreCase)) - { - var content = Convert.FromBase64String(action.Content); - System.IO.File.WriteAllBytes(filePath, content); - } - else - { - System.IO.File.WriteAllText(filePath, action.Content); - } - - repository.Index.Add(action.FilePath); - repository.Index.Write(); + if (string.Equals(action.Encoding, "base64", StringComparison.OrdinalIgnoreCase)) + { + var content = Convert.FromBase64String(action.Content); + System.IO.File.WriteAllBytes(filePath, content); } + else + { + System.IO.File.WriteAllText(filePath, action.Content); + } + + repository.Index.Add(action.FilePath); + repository.Index.Write(); } } diff --git a/NGitLab.Mock/CommitInfo.cs b/NGitLab.Mock/CommitInfo.cs index c4bc3c45..82859e9a 100644 --- a/NGitLab.Mock/CommitInfo.cs +++ b/NGitLab.Mock/CommitInfo.cs @@ -1,9 +1,8 @@ -namespace NGitLab.Mock +namespace NGitLab.Mock; + +public sealed class CommitInfo : GitLabObject { - public sealed class CommitInfo : GitLabObject - { - public string Sha { get; set; } + public string Sha { get; set; } - public string Status { get; set; } - } + public string Status { get; set; } } diff --git a/NGitLab.Mock/CommitInfoCollection.cs b/NGitLab.Mock/CommitInfoCollection.cs index 917457bf..8e50c59e 100644 --- a/NGitLab.Mock/CommitInfoCollection.cs +++ b/NGitLab.Mock/CommitInfoCollection.cs @@ -1,29 +1,28 @@ using System; using System.Linq; -namespace NGitLab.Mock +namespace NGitLab.Mock; + +public sealed class CommitInfoCollection : Collection { - public sealed class CommitInfoCollection : Collection + public CommitInfoCollection(GitLabObject parent) + : base(parent) { - public CommitInfoCollection(GitLabObject parent) - : base(parent) - { - } + } - public CommitInfo GetOrAdd(string sha) + public CommitInfo GetOrAdd(string sha) + { + var commitInfo = this.FirstOrDefault(commit => string.Equals(commit.Sha, sha, StringComparison.OrdinalIgnoreCase)); + if (commitInfo == null) { - var commitInfo = this.FirstOrDefault(commit => string.Equals(commit.Sha, sha, StringComparison.OrdinalIgnoreCase)); - if (commitInfo == null) + commitInfo = new CommitInfo { - commitInfo = new CommitInfo - { - Sha = sha, - }; - - Add(commitInfo); - } + Sha = sha, + }; - return commitInfo; + Add(commitInfo); } + + return commitInfo; } } diff --git a/NGitLab.Mock/CommitStatus.cs b/NGitLab.Mock/CommitStatus.cs index a739386c..7ff5b8cb 100644 --- a/NGitLab.Mock/CommitStatus.cs +++ b/NGitLab.Mock/CommitStatus.cs @@ -1,49 +1,48 @@ using NGitLab.Models; -namespace NGitLab.Mock +namespace NGitLab.Mock; + +public sealed class CommitStatus : GitLabObject { - public sealed class CommitStatus : GitLabObject - { - public new Project Parent => (Project)base.Parent; + public new Project Parent => (Project)base.Parent; - public string Sha { get; set; } + public string Sha { get; set; } - public string Status { get; set; } + public string Status { get; set; } - public string Ref { get; set; } + public string Ref { get; set; } - public string Name { get; set; } + public string Name { get; set; } - public string TargetUrl { get; set; } + public string TargetUrl { get; set; } - public string Description { get; set; } + public string Description { get; set; } - public int? Coverage { get; set; } + public int? Coverage { get; set; } - public CommitStatusCreate ToClientCommitStatusCreate() + public CommitStatusCreate ToClientCommitStatusCreate() + { + return new CommitStatusCreate { - return new CommitStatusCreate - { - Name = Name, - TargetUrl = TargetUrl, - Status = Status, - Ref = Ref, - CommitSha = Sha, - Description = Description, - State = Status, - Coverage = Coverage, - }; - } - - public Models.CommitStatus ToClientCommitStatus() + Name = Name, + TargetUrl = TargetUrl, + Status = Status, + Ref = Ref, + CommitSha = Sha, + Description = Description, + State = Status, + Coverage = Coverage, + }; + } + + public Models.CommitStatus ToClientCommitStatus() + { + return new Models.CommitStatus { - return new Models.CommitStatus - { - CommitSha = Sha, - Name = Name, - Ref = Ref, - Status = Status, - }; - } + CommitSha = Sha, + Name = Name, + Ref = Ref, + Status = Status, + }; } } diff --git a/NGitLab.Mock/CommitStatusCollection.cs b/NGitLab.Mock/CommitStatusCollection.cs index fc270446..770b541b 100644 --- a/NGitLab.Mock/CommitStatusCollection.cs +++ b/NGitLab.Mock/CommitStatusCollection.cs @@ -1,10 +1,9 @@ -namespace NGitLab.Mock +namespace NGitLab.Mock; + +public sealed class CommitStatusCollection : Collection { - public sealed class CommitStatusCollection : Collection + public CommitStatusCollection(GitLabObject parent) + : base(parent) { - public CommitStatusCollection(GitLabObject parent) - : base(parent) - { - } } } diff --git a/NGitLab.Mock/Config/ConfigSerializer.cs b/NGitLab.Mock/Config/ConfigSerializer.cs index 89932ac1..b0f5566b 100644 --- a/NGitLab.Mock/Config/ConfigSerializer.cs +++ b/NGitLab.Mock/Config/ConfigSerializer.cs @@ -9,418 +9,417 @@ #pragma warning disable MA0009 // Add regex evaluation timeout -namespace NGitLab.Mock.Config +namespace NGitLab.Mock.Config; + +internal sealed class ConfigSerializer { - internal sealed class ConfigSerializer - { - private static readonly Regex s_variableTemplateRegex = new(@"{{|}}|{[a-zA-Z_][\w\.-]+}", RegexOptions.Compiled); + private static readonly Regex s_variableTemplateRegex = new(@"{{|}}|{[a-zA-Z_][\w\.-]+}", RegexOptions.Compiled); - private readonly Dictionary _configs = new(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary _variables = new(StringComparer.Ordinal); - private readonly Dictionary _resolvedDictionary = new(StringComparer.Ordinal); + private readonly Dictionary _configs = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _variables = new(StringComparer.Ordinal); + private readonly Dictionary _resolvedDictionary = new(StringComparer.Ordinal); - public void DefineVariable(string name, object value) - { - _variables[name] = value; - } + public void DefineVariable(string name, object value) + { + _variables[name] = value; + } - public static string Serialize(GitLabConfig config) + public static string Serialize(GitLabConfig config) + { + var serializer = new SerializerBuilder().DisableAliases().Build(); + return serializer.Serialize(new Dictionary(StringComparer.OrdinalIgnoreCase) { - var serializer = new SerializerBuilder().DisableAliases().Build(); - return serializer.Serialize(new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "gitlab", config }, - }); - } + { "gitlab", config }, + }); + } - public void Deserialize(string content) + public void Deserialize(string content) + { + var deserializer = new DeserializerBuilder().Build(); + var data = deserializer.Deserialize>(content); + foreach (var config in data) { - var deserializer = new DeserializerBuilder().Build(); - var data = deserializer.Deserialize>(content); - foreach (var config in data) + if (string.Equals(config.Key, "variables", StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(config.Key, "variables", StringComparison.OrdinalIgnoreCase)) - { - if (config.Value is not Dictionary dict) - throw new InvalidOperationException("Variables config is invalid, expected dictionary"); + if (config.Value is not Dictionary dict) + throw new InvalidOperationException("Variables config is invalid, expected dictionary"); - foreach (var pair in dict) - { - if (pair.Key is not string key) - throw new InvalidOperationException("Variables config is invalid, expected keys as string"); - - DefineVariable(key, pair.Value); - } - } - else + foreach (var pair in dict) { - _configs[config.Key] = config.Value; + if (pair.Key is not string key) + throw new InvalidOperationException("Variables config is invalid, expected keys as string"); + + DefineVariable(key, pair.Value); } } + else + { + _configs[config.Key] = config.Value; + } } + } - public bool TryGet(string name, ref T value) - where T : class - { - if (!_configs.TryGetValue(name, out var config)) - return true; - - object v = value; - config = ExpandVariables(config); - if (!TryConvert(typeof(T), config, ref v)) - return false; - - value = (T)v; + public bool TryGet(string name, ref T value) + where T : class + { + if (!_configs.TryGetValue(name, out var config)) return true; - } - private object ExpandVariables(object value) - { - return value == null ? null : ExpandVariables(value, new Stack()); - } + object v = value; + config = ExpandVariables(config); + if (!TryConvert(typeof(T), config, ref v)) + return false; - private object ExpandVariables(object value, Stack stack) - { - if (value is string str) - { - var matches = s_variableTemplateRegex.Matches(str).Cast().ToArray(); - if (matches.Length == 0) - return Environment.ExpandEnvironmentVariables(str); + value = (T)v; + return true; + } - if (matches.Length == 1 && matches[0].Length == str.Length) - return ResolveVariable(str.Trim('{', '}'), stack); + private object ExpandVariables(object value) + { + return value == null ? null : ExpandVariables(value, new Stack()); + } - var bld = new StringBuilder(); - var index = 0; - foreach (var match in matches) - { - if (index < match.Index) - bld.Append(Environment.ExpandEnvironmentVariables(str.Substring(index, match.Index - index))); + private object ExpandVariables(object value, Stack stack) + { + if (value is string str) + { + var matches = s_variableTemplateRegex.Matches(str).Cast().ToArray(); + if (matches.Length == 0) + return Environment.ExpandEnvironmentVariables(str); - if (string.Equals(match.Value, "{{", StringComparison.Ordinal)) - bld.Append('{'); - else if (string.Equals(match.Value, "}}", StringComparison.Ordinal)) - bld.Append('}'); - else - bld.Append(ResolveVariable(match.Value.Trim('{', '}'), stack)); + if (matches.Length == 1 && matches[0].Length == str.Length) + return ResolveVariable(str.Trim('{', '}'), stack); - index = match.Index + match.Length; - } + var bld = new StringBuilder(); + var index = 0; + foreach (var match in matches) + { + if (index < match.Index) + bld.Append(Environment.ExpandEnvironmentVariables(str.Substring(index, match.Index - index))); - if (index < str.Length) - bld.Append(Environment.ExpandEnvironmentVariables(str.Substring(index))); + if (string.Equals(match.Value, "{{", StringComparison.Ordinal)) + bld.Append('{'); + else if (string.Equals(match.Value, "}}", StringComparison.Ordinal)) + bld.Append('}'); + else + bld.Append(ResolveVariable(match.Value.Trim('{', '}'), stack)); - return bld.ToString(); + index = match.Index + match.Length; } - if (value is List list) - { - for (var i = 0; i < list.Count; i++) - { - list[i] = ExpandVariables(list[i], stack); - } + if (index < str.Length) + bld.Append(Environment.ExpandEnvironmentVariables(str.Substring(index))); - return list; - } + return bld.ToString(); + } - if (value is Dictionary dictionary) + if (value is List list) + { + for (var i = 0; i < list.Count; i++) { - foreach (var key in dictionary.Keys.ToArray()) - { - dictionary[key] = ExpandVariables(dictionary[key], stack); - } - - return dictionary; + list[i] = ExpandVariables(list[i], stack); } - return value; + return list; } - private object ResolveVariable(string name, Stack stack) + if (value is Dictionary dictionary) { - if (stack.Contains(name, StringComparer.Ordinal)) - throw new InvalidOperationException($"Cyclic variable resolution of '{name}'"); - - switch (name.ToLowerInvariant()) + foreach (var key in dictionary.Keys.ToArray()) { - case "new_guid": - return Guid.NewGuid().ToString("D"); + dictionary[key] = ExpandVariables(dictionary[key], stack); } - if (_resolvedDictionary.TryGetValue(name, out var value)) - return value; + return dictionary; + } + + return value; + } - if (!_variables.TryGetValue(name, out value)) - throw new InvalidOperationException($"Variable '{name}' not found"); + private object ResolveVariable(string name, Stack stack) + { + if (stack.Contains(name, StringComparer.Ordinal)) + throw new InvalidOperationException($"Cyclic variable resolution of '{name}'"); - stack.Push(name); - value = ExpandVariables(value, stack); - stack.Pop(); - _resolvedDictionary[name] = value; + switch (name.ToLowerInvariant()) + { + case "new_guid": + return Guid.NewGuid().ToString("D"); + } + if (_resolvedDictionary.TryGetValue(name, out var value)) return value; + + if (!_variables.TryGetValue(name, out value)) + throw new InvalidOperationException($"Variable '{name}' not found"); + + stack.Push(name); + value = ExpandVariables(value, stack); + stack.Pop(); + _resolvedDictionary[name] = value; + + return value; + } + + public static bool TryConvert(object valueObj, out T value) + { + object v = null; + if (TryConvert(typeof(T), valueObj, ref v)) + { + value = (T)v; + return true; + } + + value = default; + return false; + } + + public static bool TryConvert(Type type, object valueObj, ref object value) + { + if (type == typeof(object) || type.IsInstanceOfType(valueObj)) + { + value = valueObj; + return true; } - public static bool TryConvert(object valueObj, out T value) + if (type == typeof(bool)) { - object v = null; - if (TryConvert(typeof(T), valueObj, ref v)) + if (valueObj is string valueString && bool.TryParse(string.IsNullOrEmpty(valueString) ? "false" : valueString, out var valueBool)) { - value = (T)v; + value = valueBool; return true; } - value = default; return false; } - public static bool TryConvert(Type type, object valueObj, ref object value) + if (type == typeof(int)) { - if (type == typeof(object) || type.IsInstanceOfType(valueObj)) + if (valueObj is string valueString && int.TryParse(valueString, out var valueInt)) { - value = valueObj; + value = valueInt; return true; } - if (type == typeof(bool)) - { - if (valueObj is string valueString && bool.TryParse(string.IsNullOrEmpty(valueString) ? "false" : valueString, out var valueBool)) - { - value = valueBool; - return true; - } + return false; + } - return false; + if (type == typeof(string)) + { + if (valueObj == null) + { + value = null; + return true; } - if (type == typeof(int)) + if (valueObj is string valueString) { - if (valueObj is string valueString && int.TryParse(valueString, out var valueInt)) - { - value = valueInt; - return true; - } - - return false; + value = valueString; + return true; } - if (type == typeof(string)) + return false; + } + + if (type.IsEnum) + { + if (valueObj is string valueString && TryParseEnum(type, valueString, out var valueEnum)) { - if (valueObj == null) - { - value = null; - return true; - } + value = valueEnum; + return true; + } - if (valueObj is string valueString) - { - value = valueString; - return true; - } + return false; + } - return false; + if (type == typeof(DateTime)) + { + if (valueObj is string valueString && (DateTime.TryParseExact(valueString, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var valueDate) || DateTime.TryParse(valueString, CultureInfo.InvariantCulture, DateTimeStyles.None, out valueDate))) + { + value = valueDate; + return true; } - if (type.IsEnum) - { - if (valueObj is string valueString && TryParseEnum(type, valueString, out var valueEnum)) - { - value = valueEnum; - return true; - } + return false; + } - return false; + if (type == typeof(DateTimeOffset)) + { + if (valueObj is string valueString && (DateTimeOffset.TryParseExact(valueString, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var valueDate) || DateTimeOffset.TryParse(valueString, CultureInfo.InvariantCulture, DateTimeStyles.None, out valueDate))) + { + value = valueDate; + return true; } - if (type == typeof(DateTime)) - { - if (valueObj is string valueString && (DateTime.TryParseExact(valueString, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var valueDate) || DateTime.TryParse(valueString, CultureInfo.InvariantCulture, DateTimeStyles.None, out valueDate))) - { - value = valueDate; - return true; - } + return false; + } - return false; - } + if (valueObj == null) + { + value = null; + return true; + } - if (type == typeof(DateTimeOffset)) + // Nullable + if (type.IsGenericType) + { + var genericArgs = type.GetGenericArguments(); + if (genericArgs.Length == 1 && genericArgs[0].IsValueType) { - if (valueObj is string valueString && (DateTimeOffset.TryParseExact(valueString, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var valueDate) || DateTimeOffset.TryParse(valueString, CultureInfo.InvariantCulture, DateTimeStyles.None, out valueDate))) - { - value = valueDate; - return true; - } + var nullableType = typeof(Nullable<>).MakeGenericType(genericArgs); + if (!type.IsAssignableFrom(nullableType)) + return false; - return false; - } + object v = null; + if (!TryConvert(genericArgs[0], valueObj, ref v)) + return false; - if (valueObj == null) - { - value = null; + value = Activator.CreateInstance(nullableType, v); return true; } + } - // Nullable - if (type.IsGenericType) + if (value == null && type.IsClass && !type.IsAbstract && !type.IsArray) + { + value = Activator.CreateInstance(type); + } + + var success = false; + if (typeof(IDictionary).IsAssignableFrom(type)) + { + var itf = type.IsInterface && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>) + ? type + : type.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>)); + + if (itf != null) { - var genericArgs = type.GetGenericArguments(); - if (genericArgs.Length == 1 && genericArgs[0].IsValueType) + var args = itf.GetGenericArguments(); + if (args.Length != 2 || args[0] != typeof(string) || valueObj is not Dictionary valueDict) + return false; + + var itemType = args[1]; + var dict = value as IDictionary; + if (dict == null) { - var nullableType = typeof(Nullable<>).MakeGenericType(genericArgs); - if (!type.IsAssignableFrom(nullableType)) + var dictType = typeof(Dictionary<,>).MakeGenericType(args); + if (!type.IsAssignableFrom(dictType)) return false; + dict = (IDictionary)Activator.CreateInstance(dictType); + } + + foreach (var key in valueDict.Keys.Select(x => x as string ?? throw new InvalidOperationException($"Not supported key: {x}")).ToArray()) + { object v = null; - if (!TryConvert(genericArgs[0], valueObj, ref v)) + if (!TryConvert(itemType, valueDict[key], ref v)) return false; - value = Activator.CreateInstance(nullableType, v); - return true; + dict[key] = v; } - } - if (value == null && type.IsClass && !type.IsAbstract && !type.IsArray) - { - value = Activator.CreateInstance(type); + value = dict; + success = true; } - - var success = false; - if (typeof(IDictionary).IsAssignableFrom(type)) + } + else if (typeof(IEnumerable).IsAssignableFrom(type)) + { + if (type.IsArray) { - var itf = type.IsInterface && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>) - ? type - : type.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>)); + if (type.GetArrayRank() != 1) + return false; - if (itf != null) + var itemType = type.GetElementType(); + if (itemType == null || valueObj is not List valueArr) + return false; + + var arr = Array.CreateInstance(itemType, valueArr.Count); + for (var i = 0; i < valueArr.Count; i++) { - var args = itf.GetGenericArguments(); - if (args.Length != 2 || args[0] != typeof(string) || valueObj is not Dictionary valueDict) + object v = null; + if (!TryConvert(itemType, valueArr[i], ref v)) return false; - var itemType = args[1]; - var dict = value as IDictionary; - if (dict == null) - { - var dictType = typeof(Dictionary<,>).MakeGenericType(args); - if (!type.IsAssignableFrom(dictType)) - return false; - - dict = (IDictionary)Activator.CreateInstance(dictType); - } + arr.SetValue(v, i); + } - foreach (var key in valueDict.Keys.Select(x => x as string ?? throw new InvalidOperationException($"Not supported key: {x}")).ToArray()) - { - object v = null; - if (!TryConvert(itemType, valueDict[key], ref v)) - return false; + value = arr; + return true; + } - dict[key] = v; - } + var itf = type.IsInterface && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IList<>) + ? type + : type.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IList<>)); - value = dict; - success = true; - } - } - else if (typeof(IEnumerable).IsAssignableFrom(type)) + if (itf != null) { - if (type.IsArray) - { - if (type.GetArrayRank() != 1) - return false; + var args = itf.GetGenericArguments(); + if (args.Length != 1 || valueObj is not List valueArr) + return false; - var itemType = type.GetElementType(); - if (itemType == null || valueObj is not List valueArr) + var itemType = args[0]; + var list = value as IList; + if (list == null) + { + var listType = typeof(List<>).MakeGenericType(itemType); + if (!type.IsAssignableFrom(listType)) return false; - var arr = Array.CreateInstance(itemType, valueArr.Count); - for (var i = 0; i < valueArr.Count; i++) - { - object v = null; - if (!TryConvert(itemType, valueArr[i], ref v)) - return false; - - arr.SetValue(v, i); - } - - value = arr; - return true; + list = (IList)Activator.CreateInstance(listType); } - var itf = type.IsInterface && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IList<>) - ? type - : type.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IList<>)); - - if (itf != null) + foreach (var item in valueArr) { - var args = itf.GetGenericArguments(); - if (args.Length != 1 || valueObj is not List valueArr) + object v = null; + if (!TryConvert(itemType, item, ref v)) return false; - var itemType = args[0]; - var list = value as IList; - if (list == null) - { - var listType = typeof(List<>).MakeGenericType(itemType); - if (!type.IsAssignableFrom(listType)) - return false; - - list = (IList)Activator.CreateInstance(listType); - } - - foreach (var item in valueArr) - { - object v = null; - if (!TryConvert(itemType, item, ref v)) - return false; - - list.Add(v); - } - - value = list; - success = true; + list.Add(v); } - } - if (type.IsClass) - { - if (valueObj is not Dictionary tempDict) - return success; + value = list; + success = true; + } + } - var valueDict = tempDict.ToDictionary(x => (string)x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase); - foreach (var property in type.GetProperties()) - { - if (!valueDict.TryGetValue(property.Name, out var vObj)) - continue; + if (type.IsClass) + { + if (valueObj is not Dictionary tempDict) + return success; - var v = property.GetValue(value); - if (v == null && !property.CanWrite) - return false; + var valueDict = tempDict.ToDictionary(x => (string)x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase); + foreach (var property in type.GetProperties()) + { + if (!valueDict.TryGetValue(property.Name, out var vObj)) + continue; - if (!TryConvert(property.PropertyType, vObj, ref v)) - return false; + var v = property.GetValue(value); + if (v == null && !property.CanWrite) + return false; - if (property.CanWrite) - property.SetValue(value, v); - } + if (!TryConvert(property.PropertyType, vObj, ref v)) + return false; - return true; + if (property.CanWrite) + property.SetValue(value, v); } - return success; + return true; } - private static bool TryParseEnum(Type type, string value, out object result) + return success; + } + + private static bool TryParseEnum(Type type, string value, out object result) + { + try { - try - { - result = Enum.Parse(type, value, ignoreCase: true); - return true; - } - catch - { - result = default; - return false; - } + result = Enum.Parse(type, value, ignoreCase: true); + return true; + } + catch + { + result = default; + return false; } } } diff --git a/NGitLab.Mock/Config/GitLabCollection.cs b/NGitLab.Mock/Config/GitLabCollection.cs index 62b7d355..7a553392 100644 --- a/NGitLab.Mock/Config/GitLabCollection.cs +++ b/NGitLab.Mock/Config/GitLabCollection.cs @@ -1,47 +1,46 @@ using System.Linq; -namespace NGitLab.Mock.Config +namespace NGitLab.Mock.Config; + +public abstract class GitLabCollection : System.Collections.ObjectModel.Collection + where TItem : GitLabObject { - public abstract class GitLabCollection : System.Collections.ObjectModel.Collection - where TItem : GitLabObject + internal readonly object _parent; + + protected internal GitLabCollection(object parent) + { + _parent = parent; + } + + protected override void InsertItem(int index, TItem item) + { + SetItem(item); + base.InsertItem(index, item); + } + + protected override void SetItem(int index, TItem item) { - internal readonly object _parent; - - protected internal GitLabCollection(object parent) - { - _parent = parent; - } - - protected override void InsertItem(int index, TItem item) - { - SetItem(item); - base.InsertItem(index, item); - } - - protected override void SetItem(int index, TItem item) - { - SetItem(item); - base.SetItem(index, item); - } - - internal virtual void SetItem(TItem item) - { - if (item == null) - return; - - item.Parent = _parent; - - if (item.Id == default) - item.Id = Items.Select(x => x.Id).DefaultIfEmpty().Max() + 1; - } + SetItem(item); + base.SetItem(index, item); } - public abstract class GitLabCollection : GitLabCollection - where TItem : GitLabObject + internal virtual void SetItem(TItem item) + { + if (item == null) + return; + + item.Parent = _parent; + + if (item.Id == default) + item.Id = Items.Select(x => x.Id).DefaultIfEmpty().Max() + 1; + } +} + +public abstract class GitLabCollection : GitLabCollection + where TItem : GitLabObject +{ + protected internal GitLabCollection(TParent parent) + : base(parent) { - protected internal GitLabCollection(TParent parent) - : base(parent) - { - } } } diff --git a/NGitLab.Mock/Config/GitLabComment.cs b/NGitLab.Mock/Config/GitLabComment.cs index 47b5cd0f..0f8b0621 100644 --- a/NGitLab.Mock/Config/GitLabComment.cs +++ b/NGitLab.Mock/Config/GitLabComment.cs @@ -1,51 +1,50 @@ using System; -namespace NGitLab.Mock.Config +namespace NGitLab.Mock.Config; + +public class GitLabComment : GitLabObject { - public class GitLabComment : GitLabObject - { - /// - /// Author username (required if default user not defined) - /// - public string Author { get; set; } + /// + /// Author username (required if default user not defined) + /// + public string Author { get; set; } + + public string Message { get; set; } - public string Message { get; set; } + /// + /// Indicates if comment is from GitLab system + /// + public bool System { get; set; } - /// - /// Indicates if comment is from GitLab system - /// - public bool System { get; set; } + public DateTime? CreatedAt { get; set; } - public DateTime? CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } - public DateTime? UpdatedAt { get; set; } + /// + /// Comment thread (all comments with same thread are grouped) + /// + public string Thread { get; set; } - /// - /// Comment thread (all comments with same thread are grouped) - /// - public string Thread { get; set; } + /// + /// Indicates if comment is resolvable (only for merge request comments) + /// + public bool Resolvable { get; set; } - /// - /// Indicates if comment is resolvable (only for merge request comments) - /// - public bool Resolvable { get; set; } + /// + /// Indicates if comment is resolved (only for merge request comments and resolvable) + /// + public bool Resolved { get; set; } +} - /// - /// Indicates if comment is resolved (only for merge request comments and resolvable) - /// - public bool Resolved { get; set; } +public class GitLabCommentsCollection : GitLabCollection +{ + internal GitLabCommentsCollection(GitLabIssue parent) + : base(parent) + { } - public class GitLabCommentsCollection : GitLabCollection + internal GitLabCommentsCollection(GitLabMergeRequest parent) + : base(parent) { - internal GitLabCommentsCollection(GitLabIssue parent) - : base(parent) - { - } - - internal GitLabCommentsCollection(GitLabMergeRequest parent) - : base(parent) - { - } } } diff --git a/NGitLab.Mock/Config/GitLabCommit.cs b/NGitLab.Mock/Config/GitLabCommit.cs index 585e879e..fb2eb4db 100644 --- a/NGitLab.Mock/Config/GitLabCommit.cs +++ b/NGitLab.Mock/Config/GitLabCommit.cs @@ -1,57 +1,61 @@ using System.Collections.Generic; -namespace NGitLab.Mock.Config +namespace NGitLab.Mock.Config; + +/// +/// Describe a commit in a GitLab project +/// +public class GitLabCommit : GitLabObject { /// - /// Describe a commit in a GitLab project + /// Author username (required if default user not defined) /// - public class GitLabCommit : GitLabObject - { - /// - /// Author username (required if default user not defined) - /// - public string User { get; set; } - - public string Message { get; set; } - - /// - /// Alias to reference it in pipeline - /// - public string Alias { get; set; } - - /// - /// Files in the repository at this commit - /// - public IList Files { get; } = new List(); - - public IList Tags { get; } = new List(); - - /// - /// Source branch if a checkout or for a merge commit (required for merge commit) - /// - public string SourceBranch { get; set; } - - /// - /// Target branch for a merge commit (required for merge commit) - /// - public string TargetBranch { get; set; } - - /// - /// Branch to checkout before make more operation - /// - public string FromBranch { get; set; } - - /// - /// Indicates if source branch must be deleted after merge - /// - public bool DeleteSourceBranch { get; set; } - } + public string User { get; set; } + + public string Message { get; set; } + + /// + /// Alias to reference it in pipeline + /// + public string Alias { get; set; } + + /// + /// Files in the repository at this commit + /// + public IList Files { get; } = new List(); + + /// + /// Submodules added at this commit + /// + public IList SubModules { get; } = new List(); + + public IList Tags { get; } = new List(); - public class GitLabCommitsCollection : GitLabCollection + /// + /// Source branch if a checkout or for a merge commit (required for merge commit) + /// + public string SourceBranch { get; set; } + + /// + /// Target branch for a merge commit (required for merge commit) + /// + public string TargetBranch { get; set; } + + /// + /// Branch to checkout before make more operation + /// + public string FromBranch { get; set; } + + /// + /// Indicates if source branch must be deleted after merge + /// + public bool DeleteSourceBranch { get; set; } +} + +public class GitLabCommitsCollection : GitLabCollection +{ + internal GitLabCommitsCollection(GitLabProject parent) + : base(parent) { - internal GitLabCommitsCollection(GitLabProject parent) - : base(parent) - { - } } } diff --git a/NGitLab.Mock/Config/GitLabConfig.cs b/NGitLab.Mock/Config/GitLabConfig.cs index debefe77..57776da0 100644 --- a/NGitLab.Mock/Config/GitLabConfig.cs +++ b/NGitLab.Mock/Config/GitLabConfig.cs @@ -1,67 +1,66 @@ using NGitLab.Models; -namespace NGitLab.Mock.Config +namespace NGitLab.Mock.Config; + +/// +/// Describe content of a GitLab server +/// +public class GitLabConfig { - /// - /// Describe content of a GitLab server - /// - public class GitLabConfig + public GitLabConfig() { - public GitLabConfig() - { - Users = new GitLabUsersCollection(this); - Groups = new GitLabGroupsCollection(this); - Projects = new GitLabProjectsCollection(this); - } + Users = new GitLabUsersCollection(this); + Groups = new GitLabGroupsCollection(this); + Projects = new GitLabProjectsCollection(this); + } - /// - /// Server url - /// - public string Url { get; set; } + /// + /// Server url + /// + public string Url { get; set; } - /// - /// User used by default when not specified in extensions methods - /// - public string DefaultUser { get; set; } + /// + /// User used by default when not specified in extensions methods + /// + public string DefaultUser { get; set; } - /// - /// Branch name by default when not specified in extensions methods or deserialize - /// - public string DefaultBranch { get; set; } + /// + /// Branch name by default when not specified in extensions methods or deserialize + /// + public string DefaultBranch { get; set; } - /// - /// Groups and projects default visibility - /// - public VisibilityLevel DefaultVisibility { get; set; } = VisibilityLevel.Internal; + /// + /// Groups and projects default visibility + /// + public VisibilityLevel DefaultVisibility { get; set; } = VisibilityLevel.Internal; - public GitLabUsersCollection Users { get; } + public GitLabUsersCollection Users { get; } - /// - /// Explicit groups - /// - public GitLabGroupsCollection Groups { get; } + /// + /// Explicit groups + /// + public GitLabGroupsCollection Groups { get; } - public GitLabProjectsCollection Projects { get; } + public GitLabProjectsCollection Projects { get; } - /// - /// Serialize config to YAML format - /// - public string Serialize() - { - return ConfigSerializer.Serialize(this); - } + /// + /// Serialize config to YAML format + /// + public string Serialize() + { + return ConfigSerializer.Serialize(this); + } - /// - /// Deserialize YAML content to config - /// - public static GitLabConfig Deserialize(string content) - { - var serializer = new ConfigSerializer(); - serializer.Deserialize(content); - var config = new GitLabConfig(); - return serializer.TryGet("gitlab", ref config) - ? config - : throw new GitLabException("Cannot deserialize YAML config"); - } + /// + /// Deserialize YAML content to config + /// + public static GitLabConfig Deserialize(string content) + { + var serializer = new ConfigSerializer(); + serializer.Deserialize(content); + var config = new GitLabConfig(); + return serializer.TryGet("gitlab", ref config) + ? config + : throw new GitLabException("Cannot deserialize YAML config"); } } diff --git a/NGitLab.Mock/Config/GitLabFileDescriptor.cs b/NGitLab.Mock/Config/GitLabFileDescriptor.cs index 418487b5..29fea54d 100644 --- a/NGitLab.Mock/Config/GitLabFileDescriptor.cs +++ b/NGitLab.Mock/Config/GitLabFileDescriptor.cs @@ -1,18 +1,17 @@ -namespace NGitLab.Mock.Config +namespace NGitLab.Mock.Config; + +/// +/// Describe a file in project repository +/// +public class GitLabFileDescriptor { /// - /// Describe a file in project repository + /// Relative path to file (required) /// - public class GitLabFileDescriptor - { - /// - /// Relative path to file (required) - /// - public string Path { get; set; } + public string Path { get; set; } - /// - /// File content - /// - public string Content { get; set; } - } + /// + /// File content + /// + public string Content { get; set; } } diff --git a/NGitLab.Mock/Config/GitLabGroup.cs b/NGitLab.Mock/Config/GitLabGroup.cs index 06746af5..bd05d0e4 100644 --- a/NGitLab.Mock/Config/GitLabGroup.cs +++ b/NGitLab.Mock/Config/GitLabGroup.cs @@ -1,45 +1,49 @@ using NGitLab.Models; -namespace NGitLab.Mock.Config +namespace NGitLab.Mock.Config; + +/// +/// Describe a GitLab group +/// +public class GitLabGroup : GitLabObject { + public GitLabGroup() + { + Labels = new GitLabLabelsCollection(this); + Permissions = new GitLabPermissionsCollection(this); + Milestones = new GitLabMilestonesCollection(this); + } + /// - /// Describe a GitLab group + /// Name (required) /// - public class GitLabGroup : GitLabObject - { - public GitLabGroup() - { - Labels = new GitLabLabelsCollection(this); - Permissions = new GitLabPermissionsCollection(this); - Milestones = new GitLabMilestonesCollection(this); - } + public string Name { get; set; } - /// - /// Name (required) - /// - public string Name { get; set; } + /// + /// Path/slug. Defaults to . + /// + public string Path { get; set; } - /// - /// Parent namespace - /// - public string Namespace { get; set; } + /// + /// Parent namespace + /// + public string Namespace { get; set; } - public string Description { get; set; } + public string Description { get; set; } - public VisibilityLevel? Visibility { get; set; } + public VisibilityLevel? Visibility { get; set; } - public GitLabLabelsCollection Labels { get; } + public GitLabLabelsCollection Labels { get; } - public GitLabPermissionsCollection Permissions { get; } + public GitLabPermissionsCollection Permissions { get; } - public GitLabMilestonesCollection Milestones { get; } - } + public GitLabMilestonesCollection Milestones { get; } +} - public class GitLabGroupsCollection : GitLabCollection +public class GitLabGroupsCollection : GitLabCollection +{ + internal GitLabGroupsCollection(GitLabConfig parent) + : base(parent) { - internal GitLabGroupsCollection(GitLabConfig parent) - : base(parent) - { - } } } diff --git a/NGitLab.Mock/Config/GitLabHelpers.cs b/NGitLab.Mock/Config/GitLabHelpers.cs index f73a8833..638088e7 100644 --- a/NGitLab.Mock/Config/GitLabHelpers.cs +++ b/NGitLab.Mock/Config/GitLabHelpers.cs @@ -11,1951 +11,2083 @@ #pragma warning disable RS0026 // Adding optional parameters to public methods #pragma warning disable RS0037 // Activate nullable values in public API -namespace NGitLab.Mock.Config +namespace NGitLab.Mock.Config; + +public static class GitLabHelpers { - public static class GitLabHelpers + public static GitLabConfig Configure(this GitLabConfig config, Action configure) { - public static GitLabConfig Configure(this GitLabConfig config, Action configure) - { - configure(config); - return config; - } + configure(config); + return config; + } - public static GitLabUser Configure(this GitLabUser user, Action configure) - { - return Configure(user, configure); - } + public static GitLabUser Configure(this GitLabUser user, Action configure) + { + return Configure(user, configure); + } - public static GitLabGroup Configure(this GitLabGroup group, Action configure) - { - return Configure(group, configure); - } + public static GitLabGroup Configure(this GitLabGroup group, Action configure) + { + return Configure(group, configure); + } - public static GitLabProject Configure(this GitLabProject project, Action configure) - { - return Configure(project, configure); - } + public static GitLabProject Configure(this GitLabProject project, Action configure) + { + return Configure(project, configure); + } - public static GitLabCommit Configure(this GitLabCommit commit, Action configure) - { - return Configure(commit, configure); - } + public static GitLabCommit Configure(this GitLabCommit commit, Action configure) + { + return Configure(commit, configure); + } - public static GitLabIssue Configure(this GitLabIssue issue, Action configure) - { - return Configure(issue, configure); - } + public static GitLabIssue Configure(this GitLabIssue issue, Action configure) + { + return Configure(issue, configure); + } - public static GitLabMergeRequest Configure(this GitLabMergeRequest mergeRequest, Action configure) - { - return Configure(mergeRequest, configure); - } + public static GitLabMergeRequest Configure(this GitLabMergeRequest mergeRequest, Action configure) + { + return Configure(mergeRequest, configure); + } - private static T Configure(this T gitLabObject, Action configure) - where T : GitLabObject - { - configure(gitLabObject); - return gitLabObject; - } + private static T Configure(this T gitLabObject, Action configure) + where T : GitLabObject + { + configure(gitLabObject); + return gitLabObject; + } - public static GitLabPipeline Configure(this GitLabPipeline pipeline, Action configure) - { - configure(pipeline); - return pipeline; - } + public static GitLabPipeline Configure(this GitLabPipeline pipeline, Action configure) + { + configure(pipeline); + return pipeline; + } - /// - /// Add a user description in config - /// - /// Config. - /// Username (required) - /// Configuration method - public static GitLabConfig WithUser(this GitLabConfig config, string username, Action configure) + /// + /// Add a user description in config + /// + /// Config. + /// Username (required) + /// Configuration method + public static GitLabConfig WithUser(this GitLabConfig config, string username, Action configure) + { + return Configure(config, _ => { - return Configure(config, _ => + var user = new GitLabUser { - var user = new GitLabUser - { - Username = username ?? throw new ArgumentNullException(nameof(username)), - }; + Username = username ?? throw new ArgumentNullException(nameof(username)), + }; - config.Users.Add(user); - configure(user); - }); - } + config.Users.Add(user); + configure(user); + }); + } - /// - /// Add a user description in config - /// - /// Config. - /// Username (required) - /// Name. - /// Email. - /// Avatar URL. - /// Indicates if user is admin in GitLab server - /// Define as default user for next descriptions - /// Configuration method - public static GitLabConfig WithUser(this GitLabConfig config, string username, string? name = null, string? email = null, string? avatarUrl = null, bool isAdmin = false, bool isDefault = false, Action? configure = null) - { - return WithUser(config, username, user => + /// + /// Add a user description in config + /// + /// Config. + /// Username (required) + /// Name. + /// Email. + /// Avatar URL. + /// Indicates if user is admin in GitLab server + /// Define as default user for next descriptions + /// Configuration method + public static GitLabConfig WithUser(this GitLabConfig config, string username, string? name = null, string? email = null, string? avatarUrl = null, bool isAdmin = false, bool isDefault = false, Action? configure = null) + { + return WithUser(config, username, user => + { + user.Name = name; + user.Email = email; + user.AvatarUrl = avatarUrl; + user.IsAdmin = isAdmin; + if (isDefault) { - user.Name = name; - user.Email = email; - user.AvatarUrl = avatarUrl; - user.IsAdmin = isAdmin; - if (isDefault) - { - user.AsDefaultUser(); - } + user.AsDefaultUser(); + } - configure?.Invoke(user); - }); - } + configure?.Invoke(user); + }); + } - /// - /// Define a user as default for next descriptions - /// - /// User. - public static GitLabUser AsDefaultUser(this GitLabUser user) + /// + /// Define a user as default for next descriptions + /// + /// User. + public static GitLabUser AsDefaultUser(this GitLabUser user) + { + return Configure(user, _ => { - return Configure(user, _ => - { - user.Parent.DefaultUser = user.Username; - }); - } + user.Parent.DefaultUser = user.Username; + }); + } - /// - /// Define a user as default for next descriptions - /// - /// Config. - /// Username. - public static GitLabConfig SetDefaultUser(this GitLabConfig config, string username) + /// + /// Define a user as default for next descriptions + /// + /// Config. + /// Username. + public static GitLabConfig SetDefaultUser(this GitLabConfig config, string username) + { + return Configure(config, _ => { - return Configure(config, _ => - { - config.DefaultUser = username; - }); - } + config.DefaultUser = username; + }); + } - /// - /// Define default branch for next descriptions - /// - /// Config. - /// Branch name. - /// - public static GitLabConfig SetDefaultBranch(this GitLabConfig config, string branchName) + /// + /// Define default branch for next descriptions + /// + /// Config. + /// Branch name. + /// + public static GitLabConfig SetDefaultBranch(this GitLabConfig config, string branchName) + { + return Configure(config, _ => { - return Configure(config, _ => - { - config.DefaultBranch = branchName; - }); - } + config.DefaultBranch = branchName; + }); + } - /// - /// Define default groups and projects visibility - /// - /// Config. - /// Visibility. - /// - public static GitLabConfig SetDefaultVisibility(this GitLabConfig config, VisibilityLevel visibility) + /// + /// Define default groups and projects visibility + /// + /// Config. + /// Visibility. + /// + public static GitLabConfig SetDefaultVisibility(this GitLabConfig config, VisibilityLevel visibility) + { + return Configure(config, _ => { - return Configure(config, _ => - { - config.DefaultVisibility = visibility; - }); - } + config.DefaultVisibility = visibility; + }); + } - /// - /// Add an explicit group description in config - /// - /// Config. - /// Name. - /// Configuration method - public static GitLabConfig WithGroup(this GitLabConfig config, string? name, Action configure) + /// + /// Add an explicit group description in config + /// + /// Config. + /// Name. + /// Configuration method + public static GitLabConfig WithGroup(this GitLabConfig config, string? name, Action configure) + { + return Configure(config, _ => { - return Configure(config, _ => + var group = new GitLabGroup { - var group = new GitLabGroup - { - Name = name ?? Guid.NewGuid().ToString("D"), - }; - - config.Groups.Add(group); - configure(group); - }); - } - - /// - /// Add an explicit group description in config - /// - /// Config. - /// Name. - /// Explicit ID (config increment) - /// Parent namespace. - /// Description. - /// Visibility. - /// Define default user as maintainer. - /// Configuration method - public static GitLabConfig WithGroup(this GitLabConfig config, string? name = null, int id = default, string? @namespace = null, string? description = null, VisibilityLevel? visibility = null, bool addDefaultUserAsMaintainer = false, Action? configure = null) - { - return WithGroup(config, name, group => - { - if (id != default) - group.Id = id; - - group.Namespace = @namespace; - group.Description = description; - group.Visibility = visibility; - - if (addDefaultUserAsMaintainer) - { - if (string.IsNullOrEmpty(group.Parent.DefaultUser)) - throw new InvalidOperationException("Default user not configured"); - - WithUserPermission(group, group.Parent.DefaultUser, AccessLevel.Maintainer); - } + Name = name ?? Guid.NewGuid().ToString("D"), + }; - configure?.Invoke(group); - }); - } + config.Groups.Add(group); + configure(group); + }); + } - /// - /// Add a project description in config - /// - /// Config. - /// Name. - /// Configuration method - public static GitLabConfig WithProject(this GitLabConfig config, string? name, Action configure) + /// + /// Add an explicit group description in config + /// + /// Config. + /// Name. + /// Explicit ID (config increment) + /// Parent namespace. + /// Description. + /// Visibility. + /// Define default user as maintainer. + /// Configuration method + public static GitLabConfig WithGroup(this GitLabConfig config, string? name = null, int id = default, string? @namespace = null, string? description = null, VisibilityLevel? visibility = null, bool addDefaultUserAsMaintainer = false, Action? configure = null) + { + return WithGroup(config, name, group => { - return Configure(config, _ => - { - var project = new GitLabProject - { - Name = name, - }; + if (id != default) + group.Id = id; - config.Projects.Add(project); - configure(project); - }); - } + group.Namespace = @namespace; + group.Description = description; + group.Visibility = visibility; - /// - /// Add a project description in config - /// - /// Config. - /// Name. - /// Explicit ID (config increment) - /// Group fullname. - /// Description. - /// Repository default branch. - /// Visibility. - /// Indicates if an initial commit is added. - /// Define default user as maintainer. - /// Path where to clone repository after server resolving - /// Parameters passed to clone command - /// Configuration method - public static GitLabConfig WithProject(this GitLabConfig config, string? name = null, int id = default, string? @namespace = null, string? description = null, - string? defaultBranch = null, VisibilityLevel visibility = VisibilityLevel.Internal, bool initialCommit = false, - bool addDefaultUserAsMaintainer = false, string? clonePath = null, string? cloneParameters = null, Action? configure = null) - { - return WithProject(config, name, project => + if (addDefaultUserAsMaintainer) { - if (id != default) - project.Id = id; + if (string.IsNullOrEmpty(group.Parent.DefaultUser)) + throw new InvalidOperationException("Default user not configured"); - project.Namespace = @namespace; - project.Description = description; - project.DefaultBranch = defaultBranch ?? config.DefaultBranch ?? "main"; - project.Visibility = visibility; - project.ClonePath = clonePath; - project.CloneParameters = cloneParameters; + WithUserPermission(group, group.Parent.DefaultUser, AccessLevel.Maintainer); + } - if (initialCommit) - { - WithCommit(project, "Initial commit", user: null, commit => - { - WithFile(commit, "README.md", $"# {name}{Environment.NewLine}"); - }); - } + configure?.Invoke(group); + }); + } - if (addDefaultUserAsMaintainer) - { - if (string.IsNullOrEmpty(project.Parent.DefaultUser)) - throw new InvalidOperationException("Default user not configured"); + /// + /// Add a group with the given full path, i.e., "{namespace}/{path}". + /// The namespace is optional, and leading or trailing slashes are ignored. + /// + /// Config. + /// The fully qualified path of the group. + /// Optional name. Defaults to the path. + /// Optional explicit ID (config increment) + /// Optional description. + /// Optional visibility. + /// Optionally define default user as maintainer. + /// Optional configuration method + public static GitLabConfig WithGroupOfFullPath(this GitLabConfig config, string fullPath, string? name = null, int id = default, string? description = null, VisibilityLevel? visibility = null, bool addDefaultUserAsMaintainer = false, Action? configure = null) + { + if (string.IsNullOrWhiteSpace(fullPath)) + throw new ArgumentNullException(nameof(fullPath)); - WithUserPermission(project, project.Parent.DefaultUser, AccessLevel.Maintainer); - } + var span = fullPath.AsSpan().Trim('/'); + var slash = span.LastIndexOf('/'); + var path = slash == -1 ? span.ToString() : span.Slice(slash + 1).ToString(); + var @namespace = slash == -1 ? null : span.Slice(0, slash).ToString(); - configure?.Invoke(project); - }); - } + return WithGroup(config, name ?? path, id, @namespace, description, visibility, addDefaultUserAsMaintainer, configure: group => + { + group.Path = path; + configure?.Invoke(group); + }); + } - /// - /// Add a group description in group - /// - /// Group. - /// Name (required) - /// Color in RGB hex format (example: #A1F8C3) - /// Description. - public static GitLabGroup WithLabel(this GitLabGroup group, string name, string? color = null, string? description = null) + /// + /// Add a project description in config + /// + /// Config. + /// Name. + /// Configuration method + public static GitLabConfig WithProject(this GitLabConfig config, string? name, Action configure) + { + return Configure(config, _ => { - return Configure(group, _ => + var project = new GitLabProject { - var label = new GitLabLabel - { - Name = name ?? throw new ArgumentNullException(nameof(name)), - Color = color, - Description = description, - }; + Name = name, + }; - group.Labels.Add(label); - }); - } + config.Projects.Add(project); + configure(project); + }); + } - /// - /// Add a label description in project - /// - /// Project. - /// Name (required) - /// Color in RGB hex format (example: #A1F8C3) - /// Description. - public static GitLabProject WithLabel(this GitLabProject project, string name, string? color = null, string? description = null) + /// + /// Add a project description in config + /// + /// Config. + /// Name. + /// Explicit ID (config increment) + /// Group fullname. + /// Description. + /// Repository default branch. + /// Visibility. + /// Indicates if an initial commit is added. + /// Define default user as maintainer. + /// Path where to clone repository after server resolving + /// Parameters passed to clone command + /// Configuration method + public static GitLabConfig WithProject(this GitLabConfig config, string? name = null, int id = default, string? @namespace = null, string? description = null, + string? defaultBranch = null, VisibilityLevel visibility = VisibilityLevel.Internal, bool initialCommit = false, + bool addDefaultUserAsMaintainer = false, string? clonePath = null, string? cloneParameters = null, Action? configure = null) + { + return WithProject(config, name, project => { - return Configure(project, _ => - { - var label = new GitLabLabel - { - Name = name ?? throw new ArgumentNullException(nameof(name)), - Color = color, - Description = description, - }; + if (id != default) + project.Id = id; - project.Labels.Add(label); - }); - } + project.Namespace = @namespace; + project.Description = description; + project.DefaultBranch = defaultBranch ?? config.DefaultBranch ?? "main"; + project.Visibility = visibility; + project.ClonePath = clonePath; + project.CloneParameters = cloneParameters; - /// - /// Add a commit description in project - /// - /// Project. - /// Message (required) - /// Author username (required if default user not defined) - /// Configuration method - public static GitLabProject WithCommit(this GitLabProject project, string? message, string? user, Action configure) - { - return Configure(project, _ => + if (initialCommit) { - var commit = new GitLabCommit + WithCommit(project, "Initial commit", user: null, commit => { - Message = message, - User = user, - }; - - project.Commits.Add(commit); - configure(commit); - }); - } + WithFile(commit, "README.md", $"# {name}{Environment.NewLine}"); + }); + } - /// - /// Add a commit description in project - /// - /// Project. - /// Message (required) - /// Author username (required if default user not defined) - /// Source branch (required if checkout or merge) - /// Target branch (required if merge) - /// From branch - /// Tags. - /// Alias to reference it in pipeline. - /// Configuration method - public static GitLabProject WithCommit(this GitLabProject project, string? message = null, string? user = null, string? sourceBranch = null, string? targetBranch = null, string? fromBranch = null, IEnumerable? tags = null, string? alias = null, Action? configure = null) - { - return WithCommit(project, message, user, commit => + if (addDefaultUserAsMaintainer) { - commit.SourceBranch = sourceBranch; - commit.TargetBranch = targetBranch; - commit.FromBranch = fromBranch; - commit.Alias = alias; - if (tags != null) - { - foreach (var tag in tags) - { - commit.Tags.Add(tag); - } - } + if (string.IsNullOrEmpty(project.Parent.DefaultUser)) + throw new InvalidOperationException("Default user not configured"); - configure?.Invoke(commit); - }); - } + WithUserPermission(project, project.Parent.DefaultUser, AccessLevel.Maintainer); + } - /// - /// Add a merge commit in project - /// - /// Project. - /// Source branch (required) - /// Target branch - /// Author username (required if default user not defined) - /// Indicates if source branch must be deleted after merge. - /// Tags. - /// Configuration method - public static GitLabProject WithMergeCommit(this GitLabProject project, string sourceBranch, string? targetBranch = null, string? user = null, bool deleteSourceBranch = false, IEnumerable? tags = null, Action? configure = null) - { - return WithCommit(project, $"Merge '{sourceBranch}' into '{targetBranch ?? project.DefaultBranch}'", user, commit => - { - commit.SourceBranch = sourceBranch ?? throw new ArgumentNullException(nameof(sourceBranch)); - commit.TargetBranch = targetBranch ?? project.DefaultBranch; - commit.DeleteSourceBranch = deleteSourceBranch; - if (tags != null) - { - foreach (var tag in tags) - { - commit.Tags.Add(tag); - } - } + configure?.Invoke(project); + }); + } - configure?.Invoke(commit); - }); - } + /// + /// Add a project with the given full path, i.e., "{namespace}/{path}". + /// If the namespace is not specified, the project will be created in a new top-level group. + /// Leading or trailing slashes are ignored. + /// + /// Config. + /// The fully qualified path of the project. + /// Optional name. Defaults to the path. + /// Optional explicit ID (config increment) + /// Optional description. + /// Optional repository default branch. + /// Optional visibility. + /// Indicates if an initial commit is added. + /// Define default user as maintainer. + /// Path where to clone repository after server resolving + /// Parameters passed to clone command + /// Configuration method + public static GitLabConfig WithProjectOfFullPath(this GitLabConfig config, string? fullPath, string? name = null, int id = default, string? description = null, + string? defaultBranch = null, VisibilityLevel visibility = VisibilityLevel.Internal, bool initialCommit = false, + bool addDefaultUserAsMaintainer = false, string? clonePath = null, string? cloneParameters = null, Action? configure = null) + { + if (string.IsNullOrWhiteSpace(fullPath)) + throw new ArgumentNullException(nameof(fullPath)); + + var span = fullPath.AsSpan().Trim('/'); + var slash = span.LastIndexOf('/'); + var path = slash == -1 ? span.ToString() : span.Slice(slash + 1).ToString(); + var @namespace = slash == -1 ? null : span.Slice(0, slash).ToString(); + + return config.WithProject( + name: name ?? path, + id: id, + @namespace: @namespace, + description: description, + defaultBranch: defaultBranch, + visibility: visibility, + initialCommit: initialCommit, + addDefaultUserAsMaintainer: addDefaultUserAsMaintainer, + clonePath: clonePath, + cloneParameters: cloneParameters, + configure: project => + { + project.Path = path; + configure?.Invoke(project); + }); + } - /// - /// Add a tag description in commit - /// - /// Commit. - /// Name (required) - public static GitLabCommit WithTag(this GitLabCommit commit, string name) + /// + /// Add a group description in group + /// + /// Group. + /// Name (required) + /// Color in RGB hex format (example: #A1F8C3) + /// Description. + public static GitLabGroup WithLabel(this GitLabGroup group, string name, string? color = null, string? description = null) + { + return Configure(group, _ => { - return Configure(commit, _ => + var label = new GitLabLabel { - commit.Tags.Add(name ?? throw new ArgumentNullException(nameof(name))); - }); - } + Name = name ?? throw new ArgumentNullException(nameof(name)), + Color = color, + Description = description, + }; - /// - /// Add a file description in commit - /// - /// Commit. - /// Relative path (required) - /// File Content - public static GitLabCommit WithFile(this GitLabCommit commit, string relativePath, string content = "") - { - return Configure(commit, _ => - { - commit.Files.Add(new GitLabFileDescriptor - { - Path = relativePath ?? throw new ArgumentNullException(nameof(relativePath)), - Content = content, - }); - }); - } + group.Labels.Add(label); + }); + } - /// - /// Add an issue description in project - /// - /// Project. - /// Title. - /// Author username (required if default user not defined) - /// Configuration method - public static GitLabProject WithIssue(this GitLabProject project, string? title, string? author, Action configure) + /// + /// Add a label description in project + /// + /// Project. + /// Name (required) + /// Color in RGB hex format (example: #A1F8C3) + /// Description. + public static GitLabProject WithLabel(this GitLabProject project, string name, string? color = null, string? description = null) + { + return Configure(project, _ => { - return Configure(project, _ => + var label = new GitLabLabel { - var issue = new GitLabIssue - { - Title = title, - Author = author, - }; + Name = name ?? throw new ArgumentNullException(nameof(name)), + Color = color, + Description = description, + }; - project.Issues.Add(issue); - configure(issue); - }); - } + project.Labels.Add(label); + }); + } - /// - /// Add an issue description in project - /// - /// Project. - /// Title. - /// Explicit ID (project increment) - /// Description. - /// Author username (required if default user nor defined) - /// Assignee username (allow multiple separated by ',') - /// Milestone title (create it if not exists) - /// Creation date time. - /// Update date time. - /// Close date time. - /// Labels names. - /// Configuration method - public static GitLabProject WithIssue(this GitLabProject project, string? title = null, int id = default, string? description = null, string? author = null, string? assignee = null, string? milestone = null, DateTime? createdAt = null, DateTime? updatedAt = null, DateTime? closedAt = null, IEnumerable? labels = null, Action? configure = null) - { - return WithIssue(project, title, author, issue => + /// + /// Add a commit description in project + /// + /// Project. + /// Message (required) + /// Author username (required if default user not defined) + /// Configuration method + public static GitLabProject WithCommit(this GitLabProject project, string? message, string? user, Action configure) + { + return Configure(project, _ => + { + var commit = new GitLabCommit { - if (id != default) - issue.Id = id; - - issue.Description = description; - issue.Assignee = assignee; - issue.Milestone = milestone; - issue.CreatedAt = createdAt; - issue.UpdatedAt = updatedAt; - issue.ClosedAt = closedAt; - if (labels != null) - { - foreach (var label in labels) - { - WithLabel(issue, label); - } - } + Message = message, + User = user, + }; - configure?.Invoke(issue); - }); - } + project.Commits.Add(commit); + configure(commit); + }); + } - public static GitLabProject WithRelease(this GitLabProject project, string author, string tagName, DateTime? createdAt = null, DateTime? releasedAt = null) + /// + /// Add a commit description in project + /// + /// Project. + /// Message (required) + /// Author username (required if default user not defined) + /// Source branch (required if checkout or merge) + /// Target branch (required if merge) + /// From branch + /// Tags. + /// Alias to reference it in pipeline. + /// Configuration method + public static GitLabProject WithCommit(this GitLabProject project, string? message = null, string? user = null, string? sourceBranch = null, string? targetBranch = null, string? fromBranch = null, IEnumerable? tags = null, string? alias = null, Action? configure = null) + { + return WithCommit(project, message, user, commit => { - return Configure(project, _ => + commit.SourceBranch = sourceBranch; + commit.TargetBranch = targetBranch; + commit.FromBranch = fromBranch; + commit.Alias = alias; + if (tags != null) { - var date = DateTime.UtcNow; - var release = new GitLabReleaseInfo + foreach (var tag in tags) { - Author = author, - TagName = tagName, - CreatedAt = createdAt ?? date, - ReleasedAt = releasedAt ?? date, - }; + commit.Tags.Add(tag); + } + } - project.Releases.Add(release); - }); - } + configure?.Invoke(commit); + }); + } - /// - /// Add label in issue (create it if not exists) - /// - /// Issue. - /// Label name (required) - public static GitLabIssue WithLabel(this GitLabIssue issue, string label) + /// + /// Add a merge commit in project + /// + /// Project. + /// Source branch (required) + /// Target branch + /// Author username (required if default user not defined) + /// Indicates if source branch must be deleted after merge. + /// Tags. + /// Configuration method + public static GitLabProject WithMergeCommit(this GitLabProject project, string sourceBranch, string? targetBranch = null, string? user = null, bool deleteSourceBranch = false, IEnumerable? tags = null, Action? configure = null) + { + return WithCommit(project, $"Merge '{sourceBranch}' into '{targetBranch ?? project.DefaultBranch}'", user, commit => { - return Configure(issue, _ => - { - issue.Labels.Add(label ?? throw new ArgumentNullException(nameof(label))); - }); - } - - /// - /// Add merge request description in project - /// - /// Project. - /// Source branch. - /// Title. - /// Author username (required if default user not defined) - /// Configuration method - public static GitLabProject WithMergeRequest(this GitLabProject project, string? sourceBranch, string? title, string? author, Action configure) - { - return Configure(project, _ => - { - var mergeRequest = new GitLabMergeRequest - { - Title = title, - Author = author, - SourceBranch = sourceBranch, - TargetBranch = project.DefaultBranch, - }; - - project.MergeRequests.Add(mergeRequest); - configure(mergeRequest); - }); - } - - /// - /// Add merge request description in project - /// - /// Project. - /// Source branch (required) - /// Title. - /// Explicit ID (project increment) - /// Target branch (use default branch if null) - /// Description. - /// Author username (required if default user not defined) - /// Assignee username. - /// Creation date time. - /// Update date time. - /// Close date time. - /// Merge date time. - /// Approvers usernames. - /// Labels names. - /// Milestone name. - /// Configuration method - public static GitLabProject WithMergeRequest(this GitLabProject project, string? sourceBranch = null, string? title = null, int id = default, string? targetBranch = null, string? description = null, string? author = null, string? assignee = null, DateTime? createdAt = null, DateTime? updatedAt = null, DateTime? closedAt = null, DateTime? mergedAt = null, IEnumerable? approvers = null, IEnumerable? labels = null, string? milestone = null, Action? configure = null) - { - return WithMergeRequest(project, sourceBranch, title, author, mergeRequest => + commit.SourceBranch = sourceBranch ?? throw new ArgumentNullException(nameof(sourceBranch)); + commit.TargetBranch = targetBranch ?? project.DefaultBranch; + commit.DeleteSourceBranch = deleteSourceBranch; + if (tags != null) { - if (id != default) - mergeRequest.Id = id; - - mergeRequest.Description = description; - mergeRequest.Assignee = assignee; - mergeRequest.TargetBranch = targetBranch; - mergeRequest.CreatedAt = createdAt; - mergeRequest.UpdatedAt = updatedAt; - mergeRequest.ClosedAt = closedAt; - mergeRequest.MergedAt = mergedAt; - mergeRequest.Milestone = milestone; - - if (labels != null) - { - foreach (var label in labels) - { - WithLabel(mergeRequest, label); - } - } - - if (approvers != null) + foreach (var tag in tags) { - foreach (var approver in approvers) - { - WithApprover(mergeRequest, approver); - } + commit.Tags.Add(tag); } + } - configure?.Invoke(mergeRequest); - }); - } + configure?.Invoke(commit); + }); + } - /// - /// Add label in merge request (create it if not exists) - /// - /// Merge request. - /// Label name (required) - public static GitLabMergeRequest WithLabel(this GitLabMergeRequest mergeRequest, string label) + /// + /// Add a tag description in commit + /// + /// Commit. + /// Name (required) + public static GitLabCommit WithTag(this GitLabCommit commit, string name) + { + return Configure(commit, _ => { - return Configure(mergeRequest, _ => - { - mergeRequest.Labels.Add(label ?? throw new ArgumentNullException(nameof(label))); - }); - } + commit.Tags.Add(name ?? throw new ArgumentNullException(nameof(name))); + }); + } - /// - /// Add approver in merge request - /// - /// Merge request. - /// Approver username (required) - public static GitLabMergeRequest WithApprover(this GitLabMergeRequest mergeRequest, string approver) + /// + /// Add a file description in commit + /// + /// Commit. + /// Relative path (required) + /// File Content + public static GitLabCommit WithFile(this GitLabCommit commit, string relativePath, string content = "") + { + return Configure(commit, _ => { - return Configure(mergeRequest, _ => + commit.Files.Add(new GitLabFileDescriptor { - mergeRequest.Approvers.Add(approver ?? throw new ArgumentNullException(nameof(approver))); + Path = relativePath ?? throw new ArgumentNullException(nameof(relativePath)), + Content = content, }); - } + }); + } - /// - /// Add user permission in group - /// - /// Group. - /// Username (required) - /// Access level (required) - public static GitLabGroup WithUserPermission(this GitLabGroup group, string user, AccessLevel level) + /// + /// Add a submodule in commit + /// + public static GitLabCommit WithSubModule(this GitLabCommit commit, string projectName) + { + return Configure(commit, _ => { - return Configure(group, _ => + commit.SubModules.Add(new GitLabSubModuleDescriptor { - var permission = new GitLabPermission - { - User = user ?? throw new ArgumentNullException(nameof(user)), - Level = level, - }; - - group.Permissions.Add(permission); + ProjectName = projectName, }); - } + }); + } - /// - /// Add user permission in project - /// - /// Project. - /// Username (required) - /// Access level (required) - public static GitLabProject WithUserPermission(this GitLabProject project, string user, AccessLevel level) + /// + /// Add an issue description in project + /// + /// Project. + /// Title. + /// Author username (required if default user not defined) + /// Configuration method + public static GitLabProject WithIssue(this GitLabProject project, string? title, string? author, Action configure) + { + return Configure(project, _ => { - return Configure(project, _ => + var issue = new GitLabIssue { - var permission = new GitLabPermission - { - User = user ?? throw new ArgumentNullException(nameof(user)), - Level = level, - }; + Title = title, + Author = author, + }; - project.Permissions.Add(permission); - }); - } + project.Issues.Add(issue); + configure(issue); + }); + } - /// - /// Add group permission in group - /// - /// Group. - /// Group fullname (required) - /// Access level (required) - public static GitLabGroup WithGroupPermission(this GitLabGroup grp, string groupName, AccessLevel level) + /// + /// Add an issue description in project + /// + /// Project. + /// Title. + /// Explicit ID (project increment) + /// Description. + /// Author username (required if default user nor defined) + /// Assignee username (allow multiple separated by ',') + /// Milestone title (create it if not exists) + /// Creation date time. + /// Update date time. + /// Close date time. + /// Labels names. + /// Configuration method + public static GitLabProject WithIssue(this GitLabProject project, string? title = null, int id = default, string? description = null, string? author = null, string? assignee = null, string? milestone = null, DateTime? createdAt = null, DateTime? updatedAt = null, DateTime? closedAt = null, IEnumerable? labels = null, Action? configure = null) + { + return WithIssue(project, title, author, issue => { - return Configure(grp, _ => - { - var permission = new GitLabPermission - { - Group = groupName ?? throw new ArgumentNullException(nameof(groupName)), - Level = level, - }; + if (id != default) + issue.Id = id; - grp.Permissions.Add(permission); - }); - } - - /// - /// Add group permission in project - /// - /// Project. - /// Group fullname (required) - /// Access level (required) - public static GitLabProject WithGroupPermission(this GitLabProject project, string groupName, AccessLevel level) - { - return Configure(project, _ => + issue.Description = description; + issue.Assignee = assignee; + issue.Milestone = milestone; + issue.CreatedAt = createdAt; + issue.UpdatedAt = updatedAt; + issue.ClosedAt = closedAt; + if (labels != null) { - var permission = new GitLabPermission + foreach (var label in labels) { - Group = groupName ?? throw new ArgumentNullException(nameof(groupName)), - Level = level, - }; + WithLabel(issue, label); + } + } - project.Permissions.Add(permission); - }); - } + configure?.Invoke(issue); + }); + } - /// - /// Add milestone in group - /// - /// Group. - /// Title (required) - /// Configuration method - public static GitLabGroup WithMilestone(this GitLabGroup group, string title, Action configure) + public static GitLabProject WithRelease(this GitLabProject project, string author, string tagName, DateTime? createdAt = null, DateTime? releasedAt = null) + { + return Configure(project, _ => { - return Configure(group, _ => - { - var milestone = new GitLabMilestone - { - Title = title ?? throw new ArgumentNullException(nameof(title)), - }; - - group.Milestones.Add(milestone); - configure(milestone); - }); - } - - /// - /// Add milestone in group - /// - /// Group. - /// Title (required) - /// Explicit ID (config increment) - /// Description. - /// Due date time. - /// Start date time. - /// Creation date time. - /// Update date time. - /// Close date time. - /// Configuration method - public static GitLabGroup WithMilestone(this GitLabGroup group, string title, int id = default, string? description = null, DateTime? dueDate = null, DateTime? startDate = null, DateTime? createdAt = null, DateTime? updatedAt = null, DateTime? closedAt = null, Action? configure = null) - { - return WithMilestone(group, title, milestone => + var date = DateTime.UtcNow; + var release = new GitLabReleaseInfo { - if (id != default) - milestone.Id = id; - - milestone.Description = description; - milestone.DueDate = dueDate; - milestone.StartDate = startDate; - milestone.CreatedAt = createdAt; - milestone.UpdatedAt = updatedAt; - milestone.ClosedAt = closedAt; + Author = author, + TagName = tagName, + CreatedAt = createdAt ?? date, + ReleasedAt = releasedAt ?? date, + }; - configure?.Invoke(milestone); - }); - } + project.Releases.Add(release); + }); + } - /// - /// Add milestone in project - /// - /// Project. - /// Title (required) - /// Configuration method - public static GitLabProject WithMilestone(this GitLabProject project, string title, Action configure) + /// + /// Add label in issue (create it if not exists) + /// + /// Issue. + /// Label name (required) + public static GitLabIssue WithLabel(this GitLabIssue issue, string label) + { + return Configure(issue, _ => { - return Configure(project, _ => - { - var milestone = new GitLabMilestone - { - Title = title ?? throw new ArgumentNullException(nameof(title)), - }; - - project.Milestones.Add(milestone); - configure(milestone); - }); - } + issue.Labels.Add(label ?? throw new ArgumentNullException(nameof(label))); + }); + } - /// - /// Add milestone in project - /// - /// Project. - /// Title (required) - /// Explicit ID (config increment) - /// Description. - /// Due date time. - /// Start date time. - /// Creation date time. - /// Update date time. - /// Close date time. - /// Configuration method - public static GitLabProject WithMilestone(this GitLabProject project, string title, int id = default, string? description = null, DateTime? dueDate = null, DateTime? startDate = null, DateTime? createdAt = null, DateTime? updatedAt = null, DateTime? closedAt = null, Action? configure = null) - { - return WithMilestone(project, title, milestone => + /// + /// Add merge request description in project + /// + /// Project. + /// Source branch. + /// Title. + /// Author username (required if default user not defined) + /// Configuration method + public static GitLabProject WithMergeRequest(this GitLabProject project, string? sourceBranch, string? title, string? author, Action configure) + { + return Configure(project, _ => + { + var mergeRequest = new GitLabMergeRequest { - if (id != default) - milestone.Id = id; - - milestone.Description = description; - milestone.DueDate = dueDate; - milestone.StartDate = startDate; - milestone.CreatedAt = createdAt; - milestone.UpdatedAt = updatedAt; - milestone.ClosedAt = closedAt; + Title = title, + Author = author, + SourceBranch = sourceBranch, + TargetBranch = project.DefaultBranch, + }; - configure?.Invoke(milestone); - }); - } + project.MergeRequests.Add(mergeRequest); + configure(mergeRequest); + }); + } - /// - /// Add comment in issue - /// - /// Issue. - /// Message. - /// Configuration method - public static GitLabIssue WithComment(this GitLabIssue issue, string? message, Action configure) + /// + /// Add merge request description in project + /// + /// Project. + /// Source branch (required) + /// Title. + /// Explicit ID (project increment) + /// Target branch (use default branch if null) + /// Description. + /// Author username (required if default user not defined) + /// Assignee username. + /// Creation date time. + /// Update date time. + /// Close date time. + /// Merge date time. + /// Approvers usernames. + /// Labels names. + /// Milestone name. + /// Configuration method + public static GitLabProject WithMergeRequest(this GitLabProject project, string? sourceBranch = null, string? title = null, int id = default, string? targetBranch = null, string? description = null, string? author = null, string? assignee = null, DateTime? createdAt = null, DateTime? updatedAt = null, DateTime? closedAt = null, DateTime? mergedAt = null, IEnumerable? approvers = null, IEnumerable? labels = null, string? milestone = null, Action? configure = null) + { + return WithMergeRequest(project, sourceBranch, title, author, mergeRequest => { - return WithComment(issue, message, configure); - } + if (id != default) + mergeRequest.Id = id; - /// - /// Add comment in merge request - /// - /// Merge request. - /// Message. - /// Configuration method - public static GitLabMergeRequest WithComment(this GitLabMergeRequest mergeRequest, string? message, Action configure) - { - return WithComment(mergeRequest, message, configure); - } + mergeRequest.Description = description; + mergeRequest.Assignee = assignee; + mergeRequest.TargetBranch = targetBranch; + mergeRequest.CreatedAt = createdAt; + mergeRequest.UpdatedAt = updatedAt; + mergeRequest.ClosedAt = closedAt; + mergeRequest.MergedAt = mergedAt; + mergeRequest.Milestone = milestone; - private static T WithComment(this T obj, string? message, Action configure) - where T : GitLabObject - { - return Configure(obj, _ => + if (labels != null) { - var comment = new GitLabComment + foreach (var label in labels) { - Message = message, - }; + WithLabel(mergeRequest, label); + } + } - switch (obj) + if (approvers != null) + { + foreach (var approver in approvers) { - case GitLabIssue issue: - issue.Comments.Add(comment); - break; - case GitLabMergeRequest mergeRequest: - mergeRequest.Comments.Add(comment); - break; - default: - throw new InvalidOperationException($"Cannot add comment in {typeof(T).Name}"); + WithApprover(mergeRequest, approver); } + } - configure(comment); - }); - } - - /// - /// Add comment in issue - /// - /// Issue. - /// Message (required) - /// Explicit ID (issue increment) - /// Author username (required if default user not defined) - /// Indicates if comment is from GitLab system. - /// Creation date time. - /// Update date time. - /// Comment thread (all comments with same thread are grouped) - /// Indicates if comment is resolvable. - /// Indicates if comment is resolved. - /// Configuration method - public static GitLabIssue WithComment(this GitLabIssue issue, string? message = null, int id = default, string? author = null, bool system = false, DateTime? createdAt = null, DateTime? updatedAt = null, string? thread = null, bool resolvable = false, bool resolved = false, Action? configure = null) - { - return WithComment(issue, message, id, author, system, createdAt, updatedAt, thread, resolvable, resolved, configure); - } - - /// - /// Add comment in issue - /// - /// Merge request. - /// Message (required) - /// Explicit ID (merge request increment) - /// Author username (required if default user not defined) - /// Indicates if comment is from GitLab system. - /// Creation date time. - /// Update date time. - /// Comment thread (all comments with same thread are grouped) - /// Indicates if comment is resolvable. - /// Indicates if comment is resolved. - /// Configuration method - public static GitLabMergeRequest WithComment(this GitLabMergeRequest mergeRequest, string? message = null, int id = default, string? author = null, bool system = false, DateTime? createdAt = null, DateTime? updatedAt = null, string? thread = null, bool resolvable = false, bool resolved = false, Action? configure = null) - { - return WithComment(mergeRequest, message, id, author, system, createdAt, updatedAt, thread, resolvable, resolved, configure); - } + configure?.Invoke(mergeRequest); + }); + } - private static T WithComment(this T obj, string? message, int id = default, string? author = null, bool system = false, DateTime? createdAt = null, DateTime? updatedAt = null, string? thread = null, bool resolvable = false, bool resolved = false, Action? configure = null) - where T : GitLabObject + /// + /// Add label in merge request (create it if not exists) + /// + /// Merge request. + /// Label name (required) + public static GitLabMergeRequest WithLabel(this GitLabMergeRequest mergeRequest, string label) + { + return Configure(mergeRequest, _ => { - return WithComment(obj, message, comment => - { - if (id != default) - comment.Id = id; - - comment.Author = author; - comment.System = system; - comment.CreatedAt = createdAt; - comment.UpdatedAt = updatedAt; - comment.Thread = thread; - comment.Resolvable = resolvable; - comment.Resolved = resolved; - - configure?.Invoke(comment); - }); - } - - /// - /// Add commit mention comment in issue - /// - /// Issue. - /// Message. - /// Inner HTML. - /// Explicit ID (issue increment) - /// Author username (required if default user not defined) - /// Creation date time. - /// Update date time. - public static GitLabIssue WithSystemComment(this GitLabIssue issue, string? message = null, string? innerHtml = null, int id = default, string? author = null, DateTime? createdAt = null, DateTime? updatedAt = null) - { - return WithSystemComment(issue, message, innerHtml, id, author, createdAt, updatedAt); - } - - /// - /// Add commit mention comment in merge request - /// - /// Merge request. - /// Message. - /// Inner HTML. - /// Explicit ID (merge request increment) - /// Author username (required if default user not defined) - /// Creation date time. - /// Update date time. - public static GitLabMergeRequest WithSystemComment(this GitLabMergeRequest mergeRequest, string? message = null, string? innerHtml = null, int id = default, string? author = null, DateTime? createdAt = null, DateTime? updatedAt = null) - { - return WithSystemComment(mergeRequest, message, innerHtml, id, author, createdAt, updatedAt); - } + mergeRequest.Labels.Add(label ?? throw new ArgumentNullException(nameof(label))); + }); + } - private static T WithSystemComment(this T obj, string? message, string? innerHtml, int id, string? author, DateTime? createdAt, DateTime? updatedAt) - where T : GitLabObject + /// + /// Add approver in merge request + /// + /// Merge request. + /// Approver username (required) + public static GitLabMergeRequest WithApprover(this GitLabMergeRequest mergeRequest, string approver) + { + return Configure(mergeRequest, _ => { - var body = innerHtml == null ? message : $"{message}\n\n{innerHtml}"; - return WithComment(obj, body, id: id, author: author, system: true, createdAt: createdAt, updatedAt: updatedAt); - } + mergeRequest.Approvers.Add(approver ?? throw new ArgumentNullException(nameof(approver))); + }); + } - /// - /// Add pipeline in project - /// - /// Project. - /// Commit alias reference. - /// Configuration method - public static GitLabProject WithPipeline(this GitLabProject project, string @ref, Action configure) + /// + /// Add user permission in group + /// + /// Group. + /// Username (required) + /// Access level (required) + public static GitLabGroup WithUserPermission(this GitLabGroup group, string user, AccessLevel level) + { + return Configure(group, _ => { - return Configure(project, _ => + var permission = new GitLabPermission { - var pipeline = new GitLabPipeline(project) - { - Commit = @ref ?? throw new ArgumentNullException(nameof(@ref)), - }; + User = user ?? throw new ArgumentNullException(nameof(user)), + Level = level, + }; - project.Pipelines.Add(pipeline); - configure(pipeline); - }); - } + group.Permissions.Add(permission); + }); + } - /// - /// Add job in pipeline - /// - /// Pipeline. - /// Name. - /// Stage (build by default) - /// Status (Manual by default) - /// Creation date time. - /// Start date time. - /// Finish date time. - /// Indicates if failure is allowed. - /// Downstream pipeline. - /// Configuration method - public static GitLabPipeline WithJob(this GitLabPipeline pipeline, string? name = null, string? stage = null, JobStatus status = JobStatus.Unknown, DateTime? createdAt = null, DateTime? startedAt = null, DateTime? finishedAt = null, bool allowFailure = false, GitLabPipeline? downstreamPipeline = null, Action? configure = null) - { - return Configure(pipeline, _ => + /// + /// Add user permission in project + /// + /// Project. + /// Username (required) + /// Access level (required) + public static GitLabProject WithUserPermission(this GitLabProject project, string user, AccessLevel level) + { + return Configure(project, _ => + { + var permission = new GitLabPermission { - if (pipeline.Parent == null) - throw new InvalidOperationException($"Parent project not set on pipeline {pipeline}"); + User = user ?? throw new ArgumentNullException(nameof(user)), + Level = level, + }; - var job = new GitLabJob - { - Name = name, - Stage = stage, - Status = status, - CreatedAt = createdAt, - StartedAt = startedAt, - FinishedAt = finishedAt, - AllowFailure = allowFailure, - DownstreamPipeline = downstreamPipeline, - }; - - pipeline.Jobs.Add(job); - configure?.Invoke(job); - }); - } + project.Permissions.Add(permission); + }); + } - /// - /// Create and fill server from config - /// - /// Config. - public static GitLabServer BuildServer(this GitLabConfig config) + /// + /// Add group permission in group + /// + /// Group. + /// Group fullname (required) + /// Access level (required) + public static GitLabGroup WithGroupPermission(this GitLabGroup grp, string groupName, AccessLevel level) + { + return Configure(grp, _ => { - var server = CreateServer(config); - foreach (var user in config.Users) + var permission = new GitLabPermission { - CreateUser(server, user); - } + Group = groupName ?? throw new ArgumentNullException(nameof(groupName)), + Level = level, + }; - foreach (var group in config.Groups.OrderBy(x => - string.IsNullOrEmpty(x.Namespace) ? x.Name : $"{x.Namespace}/{x.Name}", StringComparer.Ordinal)) - { - CreateGroup(server, group); - } + grp.Permissions.Add(permission); + }); + } - foreach (var project in config.Projects) + /// + /// Add group permission in project + /// + /// Project. + /// Group fullname (required) + /// Access level (required) + public static GitLabProject WithGroupPermission(this GitLabProject project, string groupName, AccessLevel level) + { + return Configure(project, _ => + { + var permission = new GitLabPermission { - CreateProject(server, project); - } + Group = groupName ?? throw new ArgumentNullException(nameof(groupName)), + Level = level, + }; - return server; - } + project.Permissions.Add(permission); + }); + } - /// - /// Create client from config - /// - /// Config. - /// Username that use client (if not defined, use default user or the first in the list) - public static IGitLabClient BuildClient(this GitLabConfig config, string? username = null) + /// + /// Add milestone in group + /// + /// Group. + /// Title (required) + /// Configuration method + public static GitLabGroup WithMilestone(this GitLabGroup group, string title, Action configure) + { + return Configure(group, _ => { - return CreateClient(BuildServer(config), username ?? config.DefaultUser ?? config.Users.FirstOrDefault()?.Username ?? throw new InvalidOperationException("No user configured")); - } + var milestone = new GitLabMilestone + { + Title = title ?? throw new ArgumentNullException(nameof(title)), + }; - /// - /// Create client from server - /// - /// Server. - /// Username that use client (if not defined, use the first in the list) - public static IGitLabClient CreateClient(this GitLabServer server, string? username = null) - { - username ??= server.Users.FirstOrDefault()?.UserName ?? throw new InvalidOperationException("No user configured"); - return server.CreateClient(server.Users.First(x => string.Equals(x.UserName, username, StringComparison.Ordinal))); - } + group.Milestones.Add(milestone); + configure(milestone); + }); + } - /// - /// Create config from server - /// - /// Server. - public static GitLabConfig ToConfig(this GitLabServer server) + /// + /// Add milestone in group + /// + /// Group. + /// Title (required) + /// Explicit ID (config increment) + /// Description. + /// Due date time. + /// Start date time. + /// Creation date time. + /// Update date time. + /// Close date time. + /// Configuration method + public static GitLabGroup WithMilestone(this GitLabGroup group, string title, int id = default, string? description = null, DateTime? dueDate = null, DateTime? startDate = null, DateTime? createdAt = null, DateTime? updatedAt = null, DateTime? closedAt = null, Action? configure = null) + { + return WithMilestone(group, title, milestone => { - var config = new GitLabConfig(); - foreach (var user in server.Users) - { - config.Users.Add(ToConfig(user)); - } + if (id != default) + milestone.Id = id; - foreach (var group in server.AllGroups.Where(x => !x.IsUserNamespace)) - { - config.Groups.Add(ToConfig(group)); - } - - foreach (var project in server.AllProjects) - { - config.Projects.Add(ToConfig(project)); - } + milestone.Description = description; + milestone.DueDate = dueDate; + milestone.StartDate = startDate; + milestone.CreatedAt = createdAt; + milestone.UpdatedAt = updatedAt; + milestone.ClosedAt = closedAt; - return config; - } + configure?.Invoke(milestone); + }); + } - private static GitLabServer CreateServer(GitLabConfig config) + /// + /// Add milestone in project + /// + /// Project. + /// Title (required) + /// Configuration method + public static GitLabProject WithMilestone(this GitLabProject project, string title, Action configure) + { + return Configure(project, _ => { - return new GitLabServer + var milestone = new GitLabMilestone { - DefaultBranchName = config.DefaultBranch ?? "main", - DefaultForkVisibilityLevel = config.DefaultVisibility, - Url = new Uri(config.Url ?? Path.GetTempPath()), + Title = title ?? throw new ArgumentNullException(nameof(title)), }; - } - private static void CreateUser(GitLabServer server, GitLabUser user) - { - server.Users.Add(new User(user.Username ?? throw new ArgumentException(@"user.Username == null", nameof(user))) - { - Id = user.Id, - Name = user.Name ?? user.Username, - Email = user.Email ?? $"{user.Username}@example.com", - AvatarUrl = user.AvatarUrl, - IsAdmin = user.IsAdmin, - }); - } + project.Milestones.Add(milestone); + configure(milestone); + }); + } - private static void CreateGroup(GitLabServer server, GitLabGroup group) + /// + /// Add milestone in project + /// + /// Project. + /// Title (required) + /// Explicit ID (config increment) + /// Description. + /// Due date time. + /// Start date time. + /// Creation date time. + /// Update date time. + /// Close date time. + /// Configuration method + public static GitLabProject WithMilestone(this GitLabProject project, string title, int id = default, string? description = null, DateTime? dueDate = null, DateTime? startDate = null, DateTime? createdAt = null, DateTime? updatedAt = null, DateTime? closedAt = null, Action? configure = null) + { + return WithMilestone(project, title, milestone => { - var grp = new Group(group.Name ?? throw new ArgumentException(@"group.Name == null", nameof(group))) - { - Id = group.Id, - Description = group.Description, - Visibility = group.Visibility ?? group.Parent.DefaultVisibility, - }; + if (id != default) + milestone.Id = id; - if (string.IsNullOrEmpty(group.Namespace)) - { - server.Groups.Add(grp); - } - else - { - var parent = GetOrCreateGroup(server, group.Namespace); - parent.Groups.Add(grp); - } + milestone.Description = description; + milestone.DueDate = dueDate; + milestone.StartDate = startDate; + milestone.CreatedAt = createdAt; + milestone.UpdatedAt = updatedAt; + milestone.ClosedAt = closedAt; - foreach (var label in group.Labels) - { - CreateLabel(grp, label); - } + configure?.Invoke(milestone); + }); + } - foreach (var permission in group.Permissions) - { - CreatePermission(server, grp, permission); - } + /// + /// Add comment in issue + /// + /// Issue. + /// Message. + /// Configuration method + public static GitLabIssue WithComment(this GitLabIssue issue, string? message, Action configure) + { + return WithComment(issue, message, configure); + } - foreach (var milestone in group.Milestones) - { - CreateMilestone(grp, milestone); - } - } + /// + /// Add comment in merge request + /// + /// Merge request. + /// Message. + /// Configuration method + public static GitLabMergeRequest WithComment(this GitLabMergeRequest mergeRequest, string? message, Action configure) + { + return WithComment(mergeRequest, message, configure); + } - private static void CreateProject(GitLabServer server, GitLabProject project) + private static T WithComment(this T obj, string? message, Action configure) + where T : GitLabObject + { + return Configure(obj, _ => { - var prj = new Project(project.Name ?? Guid.NewGuid().ToString("D")) + var comment = new GitLabComment { - Id = project.Id, - Description = project.Description, - DefaultBranch = project.DefaultBranch ?? server.DefaultBranchName, - Visibility = project.Visibility ?? project.Parent.DefaultVisibility, - ForkingAccessLevel = project.ForkingAccessLevel, + Message = message, }; - var group = GetOrCreateGroup(server, project.Namespace ?? Guid.NewGuid().ToString("D")); - group.Projects.Add(prj); - - var aliases = new Dictionary(StringComparer.Ordinal); - foreach (var commit in project.Commits) + switch (obj) { - var cmt = CreateCommit(server, prj, commit); - if (!string.IsNullOrEmpty(commit.Alias)) - aliases[commit.Alias] = cmt; + case GitLabIssue issue: + issue.Comments.Add(comment); + break; + case GitLabMergeRequest mergeRequest: + mergeRequest.Comments.Add(comment); + break; + default: + throw new InvalidOperationException($"Cannot add comment in {typeof(T).Name}"); } - foreach (var label in project.Labels) - { - CreateLabel(prj, label); - } - - foreach (var milestone in project.Milestones) - { - CreateMilestone(prj, milestone); - } + configure(comment); + }); + } - foreach (var issue in project.Issues) - { - CreateIssue(server, prj, issue); - } + /// + /// Add comment in issue + /// + /// Issue. + /// Message (required) + /// Explicit ID (issue increment) + /// Author username (required if default user not defined) + /// Indicates if comment is from GitLab system. + /// Creation date time. + /// Update date time. + /// Comment thread (all comments with same thread are grouped) + /// Indicates if comment is resolvable. + /// Indicates if comment is resolved. + /// Configuration method + public static GitLabIssue WithComment(this GitLabIssue issue, string? message = null, int id = default, string? author = null, bool system = false, DateTime? createdAt = null, DateTime? updatedAt = null, string? thread = null, bool resolvable = false, bool resolved = false, Action? configure = null) + { + return WithComment(issue, message, id, author, system, createdAt, updatedAt, thread, resolvable, resolved, configure); + } - foreach (var release in project.Releases) - { - CreateRelease(server, prj, release); - } + /// + /// Add comment in issue + /// + /// Merge request. + /// Message (required) + /// Explicit ID (merge request increment) + /// Author username (required if default user not defined) + /// Indicates if comment is from GitLab system. + /// Creation date time. + /// Update date time. + /// Comment thread (all comments with same thread are grouped) + /// Indicates if comment is resolvable. + /// Indicates if comment is resolved. + /// Configuration method + public static GitLabMergeRequest WithComment(this GitLabMergeRequest mergeRequest, string? message = null, int id = default, string? author = null, bool system = false, DateTime? createdAt = null, DateTime? updatedAt = null, string? thread = null, bool resolvable = false, bool resolved = false, Action? configure = null) + { + return WithComment(mergeRequest, message, id, author, system, createdAt, updatedAt, thread, resolvable, resolved, configure); + } - for (var i = 0; i < project.MergeRequests.Count; i++) - { - var mergeRequest = project.MergeRequests[i]; - var maxCreatedAt = project.MergeRequests - .Skip(i + 1) - .SelectMany(x => new[] { x.CreatedAt ?? default, x.UpdatedAt ?? default, x.MergedAt ?? default, x.ClosedAt ?? default } - .Concat(x.Comments.SelectMany(c => new[] { c.CreatedAt ?? default, c.UpdatedAt ?? default }))) - .Where(x => x != default) - .DefaultIfEmpty(DateTime.UtcNow).Min(); + private static T WithComment(this T obj, string? message, int id = default, string? author = null, bool system = false, DateTime? createdAt = null, DateTime? updatedAt = null, string? thread = null, bool resolvable = false, bool resolved = false, Action? configure = null) + where T : GitLabObject + { + return WithComment(obj, message, comment => + { + if (id != default) + comment.Id = id; - CreateMergeRequest(server, prj, mergeRequest, maxCreatedAt); - } + comment.Author = author; + comment.System = system; + comment.CreatedAt = createdAt; + comment.UpdatedAt = updatedAt; + comment.Thread = thread; + comment.Resolvable = resolvable; + comment.Resolved = resolved; - foreach (var permission in project.Permissions) - { - CreatePermission(server, prj, permission); - } + configure?.Invoke(comment); + }); + } - foreach (var pipeline in project.Pipelines) - { - CreatePipeline(server, prj, pipeline, aliases); - } + /// + /// Add commit mention comment in issue + /// + /// Issue. + /// Message. + /// Inner HTML. + /// Explicit ID (issue increment) + /// Author username (required if default user not defined) + /// Creation date time. + /// Update date time. + public static GitLabIssue WithSystemComment(this GitLabIssue issue, string? message = null, string? innerHtml = null, int id = default, string? author = null, DateTime? createdAt = null, DateTime? updatedAt = null) + { + return WithSystemComment(issue, message, innerHtml, id, author, createdAt, updatedAt); + } - if (!string.IsNullOrEmpty(project.ClonePath)) - { - var folderPath = Path.GetDirectoryName(Path.GetFullPath(project.ClonePath)); - if (!Directory.Exists(folderPath)) - Directory.CreateDirectory(folderPath!); + /// + /// Add commit mention comment in merge request + /// + /// Merge request. + /// Message. + /// Inner HTML. + /// Explicit ID (merge request increment) + /// Author username (required if default user not defined) + /// Creation date time. + /// Update date time. + public static GitLabMergeRequest WithSystemComment(this GitLabMergeRequest mergeRequest, string? message = null, string? innerHtml = null, int id = default, string? author = null, DateTime? createdAt = null, DateTime? updatedAt = null) + { + return WithSystemComment(mergeRequest, message, innerHtml, id, author, createdAt, updatedAt); + } - // libgit2sharp cannot clone with an other folder name - using var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = "git", - Arguments = $"clone {project.CloneParameters} \"{prj.SshUrl}\" \"{Path.GetFileName(project.ClonePath)}\"", - RedirectStandardError = true, - UseShellExecute = false, - WorkingDirectory = folderPath, - }, - }; - - process.Start(); - process.WaitForExit(); - if (process.ExitCode != 0) - { - var error = process.StandardError.ReadToEnd(); - throw new GitLabException($"Cannot clone '{prj.PathWithNamespace}' in '{project.ClonePath}': {error}"); - } - } - } + private static T WithSystemComment(this T obj, string? message, string? innerHtml, int id, string? author, DateTime? createdAt, DateTime? updatedAt) + where T : GitLabObject + { + var body = innerHtml == null ? message : $"{message}\n\n{innerHtml}"; + return WithComment(obj, body, id: id, author: author, system: true, createdAt: createdAt, updatedAt: updatedAt); + } - private static void CreateRelease(GitLabServer server, Project project, GitLabReleaseInfo gitLabRelease) + /// + /// Add pipeline in project + /// + /// Project. + /// Commit alias reference. + /// Configuration method + public static GitLabProject WithPipeline(this GitLabProject project, string @ref, Action configure) + { + return Configure(project, _ => { - var user = GetOrCreateUser(server, gitLabRelease.Author); - var release = new ReleaseInfo + var pipeline = new GitLabPipeline(project) { - Author = new UserRef(user), - TagName = gitLabRelease.TagName, - CreatedAt = gitLabRelease.CreatedAt, - ReleasedAt = gitLabRelease.ReleasedAt, + Commit = @ref ?? throw new ArgumentNullException(nameof(@ref)), }; - project.Releases.Add(release); - } + project.Pipelines.Add(pipeline); + configure(pipeline); + }); + } - private static Commit CreateCommit(GitLabServer server, Project prj, GitLabCommit commit) + /// + /// Add job in pipeline + /// + /// Pipeline. + /// Name. + /// Stage (build by default) + /// Status (Manual by default) + /// Creation date time. + /// Start date time. + /// Finish date time. + /// Indicates if failure is allowed. + /// Downstream pipeline. + /// Configuration method + public static GitLabPipeline WithJob(this GitLabPipeline pipeline, string? name = null, string? stage = null, JobStatus status = JobStatus.Unknown, DateTime? createdAt = null, DateTime? startedAt = null, DateTime? finishedAt = null, bool allowFailure = false, GitLabPipeline? downstreamPipeline = null, Action? configure = null) + { + return Configure(pipeline, _ => { - var username = commit.User ?? commit.Parent.Parent.DefaultUser ?? throw new InvalidOperationException("Default user is required when author not set"); - var user = GetOrCreateUser(server, username); - var targetBranch = commit.TargetBranch; - Commit cmt; - if (!string.IsNullOrEmpty(commit.FromBranch)) - { - prj.Repository.Checkout(commit.FromBranch); - } + if (pipeline.Parent == null) + throw new InvalidOperationException($"Parent project not set on pipeline {pipeline}"); - if (string.IsNullOrEmpty(targetBranch)) + var job = new GitLabJob { - var branchExists = string.IsNullOrEmpty(commit.SourceBranch) || prj.Repository.GetAllBranches().Any(x => string.Equals(x.FriendlyName, commit.SourceBranch, StringComparison.Ordinal)); - if (!branchExists) - prj.Repository.CreateBranch(commit.SourceBranch); - - var files = commit.Files.Count == 0 - ? new[] { File.CreateFromText("test.txt", Guid.NewGuid().ToString()) } - : commit.Files.Select(x => File.CreateFromText(x.Path, x.Content ?? string.Empty)); - cmt = prj.Repository.Commit(user, commit.Message ?? Guid.NewGuid().ToString("D"), commit.SourceBranch, files); - } - else - { - cmt = prj.Repository.Merge(user, commit.SourceBranch, targetBranch); - if (commit.DeleteSourceBranch) - prj.Repository.RemoveBranch(commit.SourceBranch); - } + Name = name, + Stage = stage, + Status = status, + CreatedAt = createdAt, + StartedAt = startedAt, + FinishedAt = finishedAt, + AllowFailure = allowFailure, + DownstreamPipeline = downstreamPipeline, + }; - foreach (var tag in commit.Tags) - { - prj.Repository.CreateTag(tag); - } + pipeline.Jobs.Add(job); + configure?.Invoke(job); + }); + } - return cmt; + /// + /// Create and fill server from config + /// + /// Config. + public static GitLabServer BuildServer(this GitLabConfig config) + { + var server = CreateServer(config); + foreach (var user in config.Users) + { + CreateUser(server, user); } - private static void CreateLabel(Group group, GitLabLabel label) + foreach (var group in config.Groups.OrderBy(x => + string.IsNullOrEmpty(x.Namespace) ? x.Name : $"{x.Namespace}/{x.Name}", StringComparer.Ordinal)) { - group.Labels.Add(new Label - { - Name = label.Name ?? throw new ArgumentException(@"label.Name == null", nameof(label)), - Color = label.Color ?? "#d9534f", - Description = label.Description, - }); + CreateGroup(server, group); } - private static void CreateLabel(Project project, GitLabLabel label) + foreach (var project in config.Projects) { - project.Labels.Add(new Label - { - Name = label.Name ?? throw new ArgumentException(@"label.Name == null", nameof(label)), - Color = label.Color ?? "#d9534f", - Description = label.Description, - }); + CreateProject(server, project); } - private static void CreateLabel(Project project, string label) + return server; + } + + /// + /// Create client from config + /// + /// Config. + /// Username that use client (if not defined, use default user or the first in the list) + public static IGitLabClient BuildClient(this GitLabConfig config, string? username = null) + { + return CreateClient(BuildServer(config), username ?? config.DefaultUser ?? config.Users.FirstOrDefault()?.Username ?? throw new InvalidOperationException("No user configured")); + } + + /// + /// Create client from server + /// + /// Server. + /// Username that use client (if not defined, use the first in the list) + public static IGitLabClient CreateClient(this GitLabServer server, string? username = null) + { + username ??= server.Users.FirstOrDefault()?.UserName ?? throw new InvalidOperationException("No user configured"); + return server.CreateClient(server.Users.First(x => string.Equals(x.UserName, username, StringComparison.Ordinal))); + } + + /// + /// Create config from server + /// + /// Server. + public static GitLabConfig ToConfig(this GitLabServer server) + { + var config = new GitLabConfig(); + foreach (var user in server.Users) { - var model = project.Labels.Concat(GetLabels(project.Group)).FirstOrDefault(x => string.Equals(x.Name, label, StringComparison.Ordinal)); - if (model == null) - project.Labels.Add(label); + config.Users.Add(ToConfig(user)); + } - static IEnumerable