diff --git a/Mohaymen.GiteaClient.IntegrationTests/Gitea/Branch/CreateBranch/CreateBranchTests.cs b/Mohaymen.GiteaClient.IntegrationTests/Gitea/Branch/CreateBranch/CreateBranchTests.cs index 9715abf..adb9def 100644 --- a/Mohaymen.GiteaClient.IntegrationTests/Gitea/Branch/CreateBranch/CreateBranchTests.cs +++ b/Mohaymen.GiteaClient.IntegrationTests/Gitea/Branch/CreateBranch/CreateBranchTests.cs @@ -30,15 +30,15 @@ public CreateBranchTests(GiteaCollectionFixture giteaCollectionFixture) public async Task CreateBranch_ShouldCreateBranchWithCreatedStatusCode_WhenInputsAreProvidedProperly() { // Arrange - const string repositoryName = GiteaTestConstants.RepositoryName; + const string repositoryName = "create_branch_repo"; await _repositoryCreator.CreateRepositoryAsync(repositoryName, _giteaCollectionFixture.CancellationToken); - const string newBranchName = "feature/test_new_branch"; + const string newBranchName = "create_branch_branch"; var createBranchCommandDto = new CreateBranchCommandDto { RepositoryName = repositoryName, NewBranchName = newBranchName, - OldReferenceName = "main" + OldReferenceName = GiteaTestConstants.DefaultBranch }; // Act diff --git a/Mohaymen.GiteaClient.IntegrationTests/Gitea/Branch/GetBranchList/GetBranchListTests.cs b/Mohaymen.GiteaClient.IntegrationTests/Gitea/Branch/GetBranchList/GetBranchListTests.cs index 5c98062..d0e9b2c 100644 --- a/Mohaymen.GiteaClient.IntegrationTests/Gitea/Branch/GetBranchList/GetBranchListTests.cs +++ b/Mohaymen.GiteaClient.IntegrationTests/Gitea/Branch/GetBranchList/GetBranchListTests.cs @@ -29,10 +29,10 @@ public GetBranchListTests(GiteaCollectionFixture giteaCollectionFixture) public async Task GetBranchList_ShouldGetBranchListOfRepo_WhenInputsAreProvidedProperly() { // Arrange - const string repositoryName = GiteaTestConstants.RepositoryName; - const string branch1 = "branch1"; - const string branch2 = "branch2"; - const string branch3 = "branch3"; + const string repositoryName = "get_branch_list_repo"; + const string branch1 = "get_branch_list_repo_branch_1"; + const string branch2 = "get_branch_list_repo_branch_2"; + const string branch3 = "get_branch_list_repo_branch_3"; var cancellationToken = _giteaCollectionFixture.CancellationToken; await _repositoryCreator.CreateRepositoryAsync(repositoryName, cancellationToken); diff --git a/Mohaymen.GiteaClient.IntegrationTests/Gitea/PullRequest/CreatePullRequest/CreatePullRequestTests.cs b/Mohaymen.GiteaClient.IntegrationTests/Gitea/PullRequest/CreatePullRequest/CreatePullRequestTests.cs new file mode 100644 index 0000000..7db664e --- /dev/null +++ b/Mohaymen.GiteaClient.IntegrationTests/Gitea/PullRequest/CreatePullRequest/CreatePullRequestTests.cs @@ -0,0 +1,55 @@ +using System.Net; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Mohaymen.GiteaClient.Gitea.Client.Abstractions; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Dtos; +using Mohaymen.GiteaClient.IntegrationTests.Common.Collections.Gitea; +using Mohaymen.GiteaClient.IntegrationTests.Common.Initializers.TestData.Abstractions; +using Mohaymen.GiteaClient.IntegrationTests.Common.Models; + +namespace Mohaymen.GiteaClient.IntegrationTests.Gitea.PullRequest.CreatePullRequest; + +[Collection("GiteaIntegrationTests")] +public class CreatePullRequestTests +{ + private readonly IGiteaClient _sut; + private readonly ITestRepositoryCreator _repositoryCreator; + private readonly ITestBranchCreator _branchCreator; + private readonly GiteaCollectionFixture _giteaCollectionFixture; + + public CreatePullRequestTests(GiteaCollectionFixture giteaCollectionFixture) + { + _giteaCollectionFixture = giteaCollectionFixture ?? throw new ArgumentNullException(nameof(giteaCollectionFixture)); + _repositoryCreator = _giteaCollectionFixture.ServiceProvider.GetRequiredService(); + _branchCreator = _giteaCollectionFixture.ServiceProvider.GetRequiredService(); + _sut = giteaCollectionFixture.ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task CreatePullRequest_ShouldGetBranchListOfRepo_WhenInputsAreProvidedProperly() + { + // Arrange + const string repositoryName = "create_pull_request_repo"; + const string branchName = "create_pull_request_branch"; + var cancellationToken = _giteaCollectionFixture.CancellationToken; + + await _repositoryCreator.CreateRepositoryAsync(repositoryName, cancellationToken); + await _branchCreator.CreateBranchAsync(repositoryName, branchName, cancellationToken); + + const string title = "title"; + var createPullRequestCommandDto = new CreatePullRequestCommandDto + { + RepositoryName = repositoryName, + Title = title, + HeadBranch = branchName, + BaseBranch = GiteaTestConstants.DefaultBranch + }; + + // Act + var actual = await _sut.PullRequestClient.CreatePullRequestAsync(createPullRequestCommandDto, cancellationToken); + + // Assert + actual.StatusCode.Should().Be(HttpStatusCode.Created); + actual.Content!.Title.Should().Be(title); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/CreateRepository/CreateRepositoryTests.cs b/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/CreateRepository/CreateRepositoryTests.cs index 27f12d4..6912f7c 100644 --- a/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/CreateRepository/CreateRepositoryTests.cs +++ b/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/CreateRepository/CreateRepositoryTests.cs @@ -6,6 +6,7 @@ using Mohaymen.GiteaClient.Gitea.Repository.CreateRepository.Dtos; using Mohaymen.GiteaClient.IntegrationTests.Common.Assertions.Abstractions; using Mohaymen.GiteaClient.IntegrationTests.Common.Collections.Gitea; +using Mohaymen.GiteaClient.IntegrationTests.Common.Models; namespace Mohaymen.GiteaClient.IntegrationTests.Gitea.Repository.CreateRepository; @@ -31,7 +32,7 @@ public async Task CreateRepository_ShouldCreateRepositoryWithCreatedStatusCode_W var createRepositoryCommandDto = new CreateRepositoryCommandDto { Name = repositoryName, - DefaultBranch = "main", + DefaultBranch = GiteaTestConstants.DefaultBranch, IsPrivateBranch = true }; diff --git a/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/DeleteRepository/DeleteRepositoryTests.cs b/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/DeleteRepository/DeleteRepositoryTests.cs new file mode 100644 index 0000000..a338434 --- /dev/null +++ b/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/DeleteRepository/DeleteRepositoryTests.cs @@ -0,0 +1,62 @@ +using System.Net; +using FluentAssertions; +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; +using Mohaymen.GiteaClient.Gitea.Client.Abstractions; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Dto; +using Mohaymen.GiteaClient.IntegrationTests.Common.Assertions.Abstractions; +using Mohaymen.GiteaClient.IntegrationTests.Common.Collections.Gitea; + +namespace Mohaymen.GiteaClient.IntegrationTests.Gitea.Repository.DeleteRepository; + +[Collection("GiteaIntegrationTests")] +public class DeleteRepositoryTests : IClassFixture +{ + private readonly IGiteaClient _sut; + private readonly ITestRepositoryChecker _testRepositoryChecker; + private readonly GiteaCollectionFixture _giteaCollectionFixture; + + public DeleteRepositoryTests(GiteaCollectionFixture giteaCollectionFixture) + { + _giteaCollectionFixture = giteaCollectionFixture ?? throw new ArgumentNullException(nameof(giteaCollectionFixture)); + _testRepositoryChecker = _giteaCollectionFixture.ServiceProvider.GetRequiredService(); + _sut = _giteaCollectionFixture.ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task DeleteRepositoryAsync_ShouldReturnNotContentSuccessResponse_WhenRepositoryExistsAndDeleted() + { + // Arrange + var deletedRepositoryCommandDto = new DeleteRepositoryCommandDto + { + RepositoryName = RepositoryClassFixture.DeleteRepositoryName + }; + + // Act + var actual = await _sut.RepositoryClient.DeleteRepositoryAsync(deletedRepositoryCommandDto, _giteaCollectionFixture.CancellationToken); + + // Assert + actual.StatusCode.Should().Be(HttpStatusCode.NoContent); + var isRepositoryExists = await _testRepositoryChecker.ContainsRepositoryAsync(RepositoryClassFixture.DeleteRepositoryName, _giteaCollectionFixture.CancellationToken); + isRepositoryExists.Should().BeFalse(); + } + + [Fact] + public async Task DeleteRepositoryAsync_ShouldReturnNotFound_WhenRepositoryDoesNotExist() + { + // Arrange + const string repositoryName = "sampleFakeRepo"; + var deletedRepositoryCommandDto = new DeleteRepositoryCommandDto + { + RepositoryName = repositoryName + }; + + // Act + var actual = await _sut.RepositoryClient.DeleteRepositoryAsync(deletedRepositoryCommandDto, _giteaCollectionFixture.CancellationToken); + + // Assert + actual.StatusCode.Should().Be(HttpStatusCode.NotFound); + var isRepositoryExists = await _testRepositoryChecker.ContainsRepositoryAsync(repositoryName, _giteaCollectionFixture.CancellationToken); + isRepositoryExists.Should().BeFalse(); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/RepositoryClassFixture.cs b/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/RepositoryClassFixture.cs new file mode 100644 index 0000000..8777713 --- /dev/null +++ b/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/RepositoryClassFixture.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.DependencyInjection; +using Mohaymen.GiteaClient.IntegrationTests.Common.Collections.Gitea; +using Mohaymen.GiteaClient.IntegrationTests.Common.Initializers.TestData.Abstractions; + +namespace Mohaymen.GiteaClient.IntegrationTests.Gitea.Repository; + +internal sealed class RepositoryClassFixture : IAsyncLifetime +{ + public const string SearchRepositoryName = "SearchUseCaseRepository"; + public const string DeleteRepositoryName = "DeleteUseCaseRepository"; + + private readonly ITestRepositoryCreator _testRepositoryCreator; + private readonly GiteaCollectionFixture _giteaCollectionFixture; + + public RepositoryClassFixture(GiteaCollectionFixture giteaCollectionFixture) + { + _giteaCollectionFixture = giteaCollectionFixture ?? throw new ArgumentNullException(nameof(giteaCollectionFixture)); + _testRepositoryCreator = giteaCollectionFixture.ServiceProvider.GetRequiredService(); + } + + public async Task InitializeAsync() + { + await _testRepositoryCreator.CreateRepositoryAsync(SearchRepositoryName, _giteaCollectionFixture.CancellationToken); + await _testRepositoryCreator.CreateRepositoryAsync(DeleteRepositoryName, _giteaCollectionFixture.CancellationToken); + } + + public Task DisposeAsync() + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/SearchRepository/SearchRepositoryTests.cs b/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/SearchRepository/SearchRepositoryTests.cs new file mode 100644 index 0000000..ed36a75 --- /dev/null +++ b/Mohaymen.GiteaClient.IntegrationTests/Gitea/Repository/SearchRepository/SearchRepositoryTests.cs @@ -0,0 +1,64 @@ +using System.Net; +using FluentAssertions; +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; +using Mohaymen.GiteaClient.Gitea.Client.Abstractions; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Dtos; +using Mohaymen.GiteaClient.IntegrationTests.Common.Assertions.Abstractions; +using Mohaymen.GiteaClient.IntegrationTests.Common.Collections.Gitea; + +namespace Mohaymen.GiteaClient.IntegrationTests.Gitea.Repository.SearchRepository; + +[Collection("GiteaIntegrationTests")] +public class SearchRepositoryTests : IClassFixture +{ + private readonly IGiteaClient _sut; + private readonly GiteaCollectionFixture _giteaCollectionFixture; + + public SearchRepositoryTests(GiteaCollectionFixture giteaCollectionFixture) + { + _giteaCollectionFixture = giteaCollectionFixture ?? throw new ArgumentNullException(nameof(giteaCollectionFixture)); + _sut = giteaCollectionFixture.ServiceProvider.GetRequiredService(); + + } + + [Fact] + public async Task SearchRepositoryAsync_ShouldReturnListOfMatchedRepositories_WhenSearchQueryIsProvidedAndHasMathc() + { + // Arrange + var searchRepositoryQuery = new SearchRepositoryQueryDto + { + Query = "Repo", + Limit = 10, + Page = 1 + }; + + // Act + var actual = await _sut.RepositoryClient.SearchRepositoryAsync(searchRepositoryQuery, _giteaCollectionFixture.CancellationToken); + + // Assert + actual.StatusCode.Should().Be(HttpStatusCode.OK); + actual.Content!.SearchResult.Select(x => x.RepositoryName).Should() + .Contain(RepositoryClassFixture.SearchRepositoryName); + } + + [Fact] + public async Task SearchRepositoryAsync_ShouldReturnEmptyList_WhenSearchQueryIsProvidedAndNotMatch() + { + // Arrange + var searchRepositoryQuery = new SearchRepositoryQueryDto + { + Query = "ahmad", + Page = 1, + Limit = 5 + }; + + // Act + var actual = await _sut.RepositoryClient.SearchRepositoryAsync(searchRepositoryQuery, _giteaCollectionFixture.CancellationToken); + + // Assert + actual.StatusCode.Should().Be(HttpStatusCode.OK); + actual.Content!.SearchResult.Should().NotBeNull(); + actual.Content!.SearchResult.Should().BeEmpty(); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient.Tests/Gitea/PullRequest/Common/Facade/PullRequestFacadeTests.cs b/Mohaymen.GiteaClient.Tests/Gitea/PullRequest/Common/Facade/PullRequestFacadeTests.cs new file mode 100644 index 0000000..c8c46c9 --- /dev/null +++ b/Mohaymen.GiteaClient.Tests/Gitea/PullRequest/Common/Facade/PullRequestFacadeTests.cs @@ -0,0 +1,77 @@ +using MediatR; +using Mohaymen.GiteaClient.Gitea.PullRequest.Common.Facade; +using Mohaymen.GiteaClient.Gitea.PullRequest.Common.Facade.Abstractions; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Commands; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Dtos; +using NSubstitute; +using Xunit; + +namespace Mohaymen.GiteaClient.Tests.Gitea.PullRequest.Common.Facade; + +public class PullRequestFacadeTests +{ + private readonly IMediator _mediator; + private readonly IPullRequestFacade _sut; + + public PullRequestFacadeTests() + { + _mediator = Substitute.For(); + _sut = new PullRequestFacade(_mediator); + } + + [Fact] + public async Task CreatePullRequestAsync_ShouldCallSend_WhenAllFieldsAreProvided() + { + // Arrange + const string repositoryName = "repo"; + const string headBranch = "head"; + const string baseBranch = "base"; + const string body = "body"; + const string title = "title"; + const string assignee = "assignee"; + var assignees = new List + { + "assignee1", + "assignee2" + }; + var commandDto = new CreatePullRequestCommandDto + { + RepositoryName = repositoryName, + HeadBranch = headBranch, + BaseBranch = baseBranch, + Body = body, + Title = title, + Assignee = assignee, + Assignees = assignees + }; + + // Act + await _sut.CreatePullRequestAsync(commandDto, default); + + // Assert + await _mediator.Received(1).Send(Arg.Is(x => x.RepositoryName == repositoryName + && x.HeadBranch == headBranch + && x.BaseBranch == baseBranch + && x.Body == body + && x.Title == title + && x.Assignee == assignee + && x.Assignees!.SequenceEqual(assignees))); + } + + [Fact] + public async Task CreatePullRequestAsync_ShouldCallSend_WhenOptionalFieldsAreNotProvided() + { + // Arrange + const string repositoryName = "repo"; + var commandDto = new CreatePullRequestCommandDto + { + RepositoryName = repositoryName + }; + + // Act + await _sut.CreatePullRequestAsync(commandDto, default); + + // Assert + await _mediator.Received(1).Send(Arg.Is(x => x.RepositoryName == repositoryName)); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient.Tests/Gitea/PullRequest/CreatePullRequest/Commands/CreatePullRequestCommandHandlerTests.cs b/Mohaymen.GiteaClient.Tests/Gitea/PullRequest/CreatePullRequest/Commands/CreatePullRequestCommandHandlerTests.cs new file mode 100644 index 0000000..6f7f2b2 --- /dev/null +++ b/Mohaymen.GiteaClient.Tests/Gitea/PullRequest/CreatePullRequest/Commands/CreatePullRequestCommandHandlerTests.cs @@ -0,0 +1,96 @@ +using FluentAssertions; +using FluentValidation; +using MediatR; +using Microsoft.Extensions.Options; +using Mohaymen.GiteaClient.Core.Configs; +using Mohaymen.GiteaClient.Gitea.PullRequest.Common.ApiCall.Abstractions; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Commands; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Context; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Dtos; +using NSubstitute; +using Refit; +using Xunit; + +namespace Mohaymen.GiteaClient.Tests.Gitea.PullRequest.CreatePullRequest.Commands; + +public class CreatePullRequestCommandHandlerTests +{ + private readonly IPullRequestRestClient _pullRequestRestClient; + private readonly IOptions _options; + private readonly InlineValidator _validator; + private readonly IRequestHandler> _sut; + + public CreatePullRequestCommandHandlerTests() + { + _pullRequestRestClient = Substitute.For(); + _options = Substitute.For>(); + _validator = new InlineValidator(); + _sut = new CreatePullRequestCommandHandler(_pullRequestRestClient, _options, _validator); + } + + [Fact] + public async Task Handle_ShouldThrowsValidationException_WhenInputIsInvalid() + { + // Arrange + _validator.RuleFor(x => x).Must(_ => false); + var command = new CreatePullRequestCommand + { + RepositoryName = "repo" + }; + + // Act + var actual = async () => await _sut.Handle(command, default); + + // Assert + await actual.Should().ThrowAsync(); + } + + [Fact] + public async Task Handle_ShouldCallCreatePullRequestAsync_AndInputsAreValid() + { + // Arrange + _validator.RuleFor(x => x).Must(_ => true); + const string owner = "owner"; + const string repositoryName = "repo"; + const string headBranch = "head"; + const string baseBranch = "base"; + const string body = "body"; + const string title = "title"; + const string assignee = "assignee"; + var assignees = new List + { + "assignee1", + "assignee2" + }; + var command = new CreatePullRequestCommand + { + RepositoryName = repositoryName, + HeadBranch = headBranch, + BaseBranch = baseBranch, + Body = body, + Title = title, + Assignee = assignee, + Assignees = assignees + }; + _options.Value.Returns(new GiteaApiConfiguration + { + BaseUrl = "url", + PersonalAccessToken = "token", + RepositoriesOwner = owner + }); + + // Act + await _sut.Handle(command, default); + + // Assert + await _pullRequestRestClient.Received(1).CreatePullRequestAsync(owner, + repositoryName, + Arg.Is(x => x.HeadBranch == headBranch + && x.BaseBranch == baseBranch + && x.Body == body + && x.Title == title + && x.Assignee == assignee + && x.Assignees!.SequenceEqual(assignees)), + default); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient.Tests/Gitea/PullRequest/CreatePullRequest/Validators/CreatePullRequestCommandValidatorTests.cs b/Mohaymen.GiteaClient.Tests/Gitea/PullRequest/CreatePullRequest/Validators/CreatePullRequestCommandValidatorTests.cs new file mode 100644 index 0000000..5cf001c --- /dev/null +++ b/Mohaymen.GiteaClient.Tests/Gitea/PullRequest/CreatePullRequest/Validators/CreatePullRequestCommandValidatorTests.cs @@ -0,0 +1,52 @@ +using FluentAssertions; +using FluentValidation; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Commands; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Validators; +using Xunit; + +namespace Mohaymen.GiteaClient.Tests.Gitea.PullRequest.CreatePullRequest.Validators; + +public class CreatePullRequestCommandValidatorTests +{ + private readonly IValidator _sut; + + public CreatePullRequestCommandValidatorTests() + { + _sut = new CreatePullRequestCommandValidator(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void Validate_ShouldReturnEmptyRepositoryNameErrorCode_WhenRepositoryNameIsNullOrEmpty(string repositoryName) + { + // Arrange + var command = new CreatePullRequestCommand + { + RepositoryName = repositoryName + }; + + // Act + var actual = _sut.Validate(command); + + // Assert + actual.Errors.Select(x => x.ErrorCode) + .Should().Contain(CreatePullRequestErrorCodes.EmptyRepositoryNameErrorCode); + } + + [Fact] + public void Validate_ShouldReturnValidResult_WhenEveryThingIsProvidedProperly() + { + // Arrange + var command = new CreatePullRequestCommand + { + RepositoryName = "repo" + }; + + // Act + var actual = _sut.Validate(command); + + // Assert + actual.IsValid.Should().BeTrue(); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient.Tests/Gitea/Repository/Common/Facade/RepositoryFacadeTests.cs b/Mohaymen.GiteaClient.Tests/Gitea/Repository/Common/Facade/RepositoryFacadeTests.cs index 637a79b..ba6377d 100644 --- a/Mohaymen.GiteaClient.Tests/Gitea/Repository/Common/Facade/RepositoryFacadeTests.cs +++ b/Mohaymen.GiteaClient.Tests/Gitea/Repository/Common/Facade/RepositoryFacadeTests.cs @@ -3,6 +3,10 @@ using Mohaymen.GiteaClient.Gitea.Repository.Common.Facade.Abstractions; using Mohaymen.GiteaClient.Gitea.Repository.CreateRepository.Commands; using Mohaymen.GiteaClient.Gitea.Repository.CreateRepository.Dtos; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Commands; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Dto; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Dtos; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Queries; using NSubstitute; using Xunit; @@ -40,5 +44,38 @@ await _mediator.Received(1).Send(Arg.Is(x => x.DefaultB x.Name == repositoryName && x.IsPrivateBranch == true), default); } - + + [Fact] + public async Task SearchRepositoryAsync_ShouldCallSend_WhenEver() + { + // Arrange + const string query = "fakeQuery"; + var searchRepositoryQueryDto = new SearchRepositoryQueryDto + { + Query = query + }; + + // Act + await _sut.SearchRepositoryAsync(searchRepositoryQueryDto, default); + + // Assert + await _mediator.Received(1).Send(Arg.Is(x => x.Query == query)); + } + + [Fact] + public async Task DeleteRepositoryAsync_ShouldCallSend_WhenEver() + { + // Arrange + const string repositoryName = "fakeRepoName"; + var deleteRepositoryCommandDto = new DeleteRepositoryCommandDto + { + RepositoryName = repositoryName + }; + + // Act + await _sut.DeleteRepositoryAsync(deleteRepositoryCommandDto, default); + + // Assert + await _mediator.Received(1).Send(Arg.Is(x => x.RepositoryName == repositoryName), default); + } } \ No newline at end of file diff --git a/Mohaymen.GiteaClient.Tests/Gitea/Repository/DeleteRepository/Commands/DeleteRepositoryCommandHandlerTests.cs b/Mohaymen.GiteaClient.Tests/Gitea/Repository/DeleteRepository/Commands/DeleteRepositoryCommandHandlerTests.cs new file mode 100644 index 0000000..0c59ca7 --- /dev/null +++ b/Mohaymen.GiteaClient.Tests/Gitea/Repository/DeleteRepository/Commands/DeleteRepositoryCommandHandlerTests.cs @@ -0,0 +1,72 @@ +using FluentAssertions; +using FluentValidation; +using MediatR; +using Microsoft.Extensions.Options; +using Mohaymen.GiteaClient.Core.Configs; +using Mohaymen.GiteaClient.Gitea.Repository.Common.ApiCall.Abstractions; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Commands; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Dto; +using NSubstitute; +using Refit; +using Xunit; + +namespace Mohaymen.GiteaClient.Tests.Gitea.Repository.DeleteRepository.Commands; + +public class DeleteRepositoryCommandHandlerTests +{ + private readonly InlineValidator _validator; + private readonly IRepositoryRestClient _repositoryRestClient; + private readonly IOptions _giteaOptions; + private readonly IRequestHandler> _sut; + + public DeleteRepositoryCommandHandlerTests() + { + _validator = new InlineValidator(); + _repositoryRestClient = Substitute.For(); + _giteaOptions = Substitute.For>(); + _sut = new DeleteRepositoryCommandHandler(_validator, _repositoryRestClient, _giteaOptions); + } + + [Fact] + public async Task Handle_ShouldThrowsValidationException_WhenInputIsNotValid() + { + // Arrange + var deleteRepositoryCommand = new DeleteRepositoryCommand + { + RepositoryName = "fakeRepo" + }; + _validator.RuleFor(x => x).Must(x => false); + + // Act + var actual = async () => await _sut.Handle(deleteRepositoryCommand, default); + + // Assert + await actual.Should().ThrowAsync(); + } + + [Fact] + public async Task Handle_ShouldCallDeleteRepositoryAsync_WhenInputIsProvidedProperly() + { + // Arrange + const string repositoryName = "fakeRepo"; + const string repositoryOwner = "fakeOwner"; + var deleteRepositoryCommand = new DeleteRepositoryCommand + { + RepositoryName = repositoryName + }; + var giteaConfig = new GiteaApiConfiguration + { + BaseUrl = "fakeUrl", + PersonalAccessToken = "fakeToken", + RepositoriesOwner = repositoryOwner + }; + _giteaOptions.Value.Returns(giteaConfig); + + // Act + await _sut.Handle(deleteRepositoryCommand, default); + + // Assert + await _repositoryRestClient.Received(1).DeleteRepositoryAsync(repositoryOwner, repositoryName); + } + +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient.Tests/Gitea/Repository/DeleteRepository/Validators/DeleteRepositoryCommandValidatorTests.cs b/Mohaymen.GiteaClient.Tests/Gitea/Repository/DeleteRepository/Validators/DeleteRepositoryCommandValidatorTests.cs new file mode 100644 index 0000000..9653534 --- /dev/null +++ b/Mohaymen.GiteaClient.Tests/Gitea/Repository/DeleteRepository/Validators/DeleteRepositoryCommandValidatorTests.cs @@ -0,0 +1,52 @@ +using FluentAssertions; +using FluentValidation; +using Mohaymen.GiteaClient.Core.Validation; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Commands; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Validators; +using Xunit; + +namespace Mohaymen.GiteaClient.Tests.Gitea.Repository.DeleteRepository.Validators; + +public class DeleteRepositoryCommandValidatorTests +{ + private readonly IValidator _sut; + + public DeleteRepositoryCommandValidatorTests() + { + _sut = new DeleteRepositoryCommandValidator(); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void Validate_ShouldReturnEmptyRepositoryNameErrorCode_WhenRepositoryNameIsNullOrEmpty(string repositoryName) + { + // Arrange + var deleteRepositoryCommand = new DeleteRepositoryCommand + { + RepositoryName = repositoryName + }; + + // Act + var actual = _sut.Validate(deleteRepositoryCommand); + + // Assert + actual.Errors.Select(x => x.ErrorCode).Should().Contain(ValidationErrorCodes.EmptyRepositoryNameErrorCode); + } + + [Fact] + public void Validate_ShouldReturnValidResult_WhenRepositoryNameIsProvidedProperly() + { + // Arrange + var deleteRepositoryCommand = new DeleteRepositoryCommand + { + RepositoryName = "FakeRepositoryName" + }; + + // Act + var actual = _sut.Validate(deleteRepositoryCommand); + + // Assert + actual.IsValid.Should().BeTrue(); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient.Tests/Gitea/Repository/SearchRepository/Queries/SearchRepositoryQueryHandlerTests.cs b/Mohaymen.GiteaClient.Tests/Gitea/Repository/SearchRepository/Queries/SearchRepositoryQueryHandlerTests.cs new file mode 100644 index 0000000..b765db3 --- /dev/null +++ b/Mohaymen.GiteaClient.Tests/Gitea/Repository/SearchRepository/Queries/SearchRepositoryQueryHandlerTests.cs @@ -0,0 +1,63 @@ +using FluentAssertions; +using FluentValidation; +using MediatR; +using Mohaymen.GiteaClient.Gitea.Repository.Common.ApiCall.Abstractions; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Dtos; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Queries; +using NSubstitute; +using Refit; +using Xunit; + +namespace Mohaymen.GiteaClient.Tests.Gitea.Repository.SearchRepository.Queries; + +public class SearchRepositoryQueryHandlerTests +{ + private readonly InlineValidator _validator; + private readonly IRepositoryRestClient _repositoryRestClient; + private readonly IRequestHandler> _sut; + + public SearchRepositoryQueryHandlerTests() + { + _validator = new InlineValidator(); + _repositoryRestClient = Substitute.For(); + _sut = new SearchRepositoryQueryHandler(_validator, _repositoryRestClient); + } + + [Fact] + public async Task Handle_ShouldThrowValidationException_WhenInputIsNotValid() + { + // Arrange + var searchRepositoryQuery = new SearchRepositoryQuery + { + Query = "" + }; + _validator.RuleFor(x => x).Must(x => false); + + // Act + var actual = async () => await _sut.Handle(searchRepositoryQuery, default); + + // Assert + await actual.Should().ThrowAsync(); + } + + [Fact] + public async Task Handle_ShouldCallSearchRepositoryAsync_WhenInputIsProvidedPropery() + { + // Arrange + const string query = "fakeQuery"; + const int page = 1; + const int limit = 5; + var searchRepositoryQuery = new SearchRepositoryQuery + { + Query = query, + Page = page, + Limit = limit + }; + + // Act + await _sut.Handle(searchRepositoryQuery, default); + + // Assert + await _repositoryRestClient.Received(1).SearchRepositoryAsync(query, page, limit); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient.Tests/Gitea/Repository/SearchRepository/Validators/SearchRepositoryQueryValidatorTests.cs b/Mohaymen.GiteaClient.Tests/Gitea/Repository/SearchRepository/Validators/SearchRepositoryQueryValidatorTests.cs new file mode 100644 index 0000000..91fceb9 --- /dev/null +++ b/Mohaymen.GiteaClient.Tests/Gitea/Repository/SearchRepository/Validators/SearchRepositoryQueryValidatorTests.cs @@ -0,0 +1,91 @@ +using FluentAssertions; +using FluentValidation; +using Mohaymen.GiteaClient.Core.Validation; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Queries; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Validators; +using Xunit; + +namespace Mohaymen.GiteaClient.Tests.Gitea.Repository.SearchRepository.Validators; + +public class SearchRepositoryQueryValidatorTests +{ + private readonly IValidator _sut; + + public SearchRepositoryQueryValidatorTests() + { + _sut = new SearchRepositoryQueryValidator(); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void Validate_ShouldReturnEmptySearchQueryErrorCode_WhenQueryIsNullOrEmpty(string query) + { + // Arrange + var searchRepositoryQuery = new SearchRepositoryQuery + { + Query = query + }; + + // Act + var actual = _sut.Validate(searchRepositoryQuery); + + // Assert + actual.Errors.Select(x => x.ErrorCode).Should().Contain(ValidationErrorCodes.EmptySearchQueryErrorCode); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void Validate_ShouldReturnInvalidPageSizeErrorCode_WhenPageSizeIsLowerThanOne(int pageSize) + { + // Arrange + var searchRepositoryQuery = new SearchRepositoryQuery + { + Query = "query", + Page = pageSize + }; + + // Act + var actual = _sut.Validate(searchRepositoryQuery); + + // Assert + actual.Errors.Select(x => x.ErrorCode).Should().Contain(ValidationErrorCodes.InvalidPageSizeErrorCode); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void Validate_ShouldReturnInvalidPageSizeErrorCode_WhenLimitIsLowerThanZero(int limit) + { + // Arrange + var searchRepositoryQuery = new SearchRepositoryQuery + { + Query = "query", + Page = 1, + Limit = limit + }; + + // Act + var actual = _sut.Validate(searchRepositoryQuery); + + // Assert + actual.Errors.Select(x => x.ErrorCode).Should().Contain(ValidationErrorCodes.InvalidLimitErrorCode); + } + + [Fact] + public void Validate_ShouldReturnValidResult_WhenInputIsProvidedProperly() + { + // Arrange + var searchRepositoryQuery = new SearchRepositoryQuery + { + Query = "test" + }; + + // Act + var actual = _sut.Validate(searchRepositoryQuery); + + // Assert + actual.IsValid.Should().BeTrue(); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Core/DependencyInjection/RefitDependencyInjection.cs b/Mohaymen.GiteaClient/Core/DependencyInjection/RefitDependencyInjection.cs index 72b7ecd..cd9d2a6 100644 --- a/Mohaymen.GiteaClient/Core/DependencyInjection/RefitDependencyInjection.cs +++ b/Mohaymen.GiteaClient/Core/DependencyInjection/RefitDependencyInjection.cs @@ -41,7 +41,7 @@ private static IEnumerable GetRefitClientInterfaceTypes() return typeof(IAssemblyMarkerInterface) .Assembly .DefinedTypes - .Where(type => type.IsInterface && typeof(IRefitClientInterface).IsAssignableFrom(type)) + .Where(type => type.IsInterface && typeof(IRefitClientInterface).IsAssignableFrom(type) && type.AsType() != typeof(IRefitClientInterface)) .Select(x => x.AsType()); } } \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Core/Validation/ValidationErrorCodes.cs b/Mohaymen.GiteaClient/Core/Validation/ValidationErrorCodes.cs index 7b967b2..6b601a6 100644 --- a/Mohaymen.GiteaClient/Core/Validation/ValidationErrorCodes.cs +++ b/Mohaymen.GiteaClient/Core/Validation/ValidationErrorCodes.cs @@ -5,6 +5,7 @@ internal static class ValidationErrorCodes internal const string EmptyRepositoryNameErrorCode = "EmptyRepositoryNameErrorCode"; internal const string EmptyBranchNameErrorCode = "EmptyBranchNameErrorCode"; internal const string EmptyCommitMessageErrorCode = "EmptyCommitMessageErrorCode"; + internal const string EmptySearchQueryErrorCode = "EmptySearchQueryErrorCode"; internal const string InvalidPageSizeErrorCode = "InvalidPageSizeErrorCode"; internal const string InvalidLimitErrorCode = "InvalidLimitErrorCode"; internal const string InvalidFilePathErrorCode = "InvalidFilePathErrorCode"; diff --git a/Mohaymen.GiteaClient/Gitea/Client/Abstractions/IGiteaClient.cs b/Mohaymen.GiteaClient/Gitea/Client/Abstractions/IGiteaClient.cs index 64f45ed..bd462f7 100644 --- a/Mohaymen.GiteaClient/Gitea/Client/Abstractions/IGiteaClient.cs +++ b/Mohaymen.GiteaClient/Gitea/Client/Abstractions/IGiteaClient.cs @@ -1,5 +1,6 @@ using Mohaymen.GiteaClient.Gitea.Branch.Common.Facade.Abstractions; using Mohaymen.GiteaClient.Gitea.Commit.Common.Facades.Abstractions; +using Mohaymen.GiteaClient.Gitea.PullRequest.Common.Facade.Abstractions; using Mohaymen.GiteaClient.Gitea.Repository.Common.Facade.Abstractions; namespace Mohaymen.GiteaClient.Gitea.Client.Abstractions; @@ -9,4 +10,5 @@ public interface IGiteaClient ICommitFacade CommitClient { get; } IRepositoryFacade RepositoryClient { get; } IBranchFacade BranchClient { get; } + IPullRequestFacade PullRequestClient { get; } } \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Client/GiteaClient.cs b/Mohaymen.GiteaClient/Gitea/Client/GiteaClient.cs index f609066..1e71657 100644 --- a/Mohaymen.GiteaClient/Gitea/Client/GiteaClient.cs +++ b/Mohaymen.GiteaClient/Gitea/Client/GiteaClient.cs @@ -2,6 +2,7 @@ using Mohaymen.GiteaClient.Gitea.Branch.Common.Facade.Abstractions; using Mohaymen.GiteaClient.Gitea.Client.Abstractions; using Mohaymen.GiteaClient.Gitea.Commit.Common.Facades.Abstractions; +using Mohaymen.GiteaClient.Gitea.PullRequest.Common.Facade.Abstractions; using Mohaymen.GiteaClient.Gitea.Repository.Common.Facade.Abstractions; namespace Mohaymen.GiteaClient.Gitea.Client; @@ -11,13 +12,16 @@ internal class GiteaClient : IGiteaClient public ICommitFacade CommitClient { get; } public IRepositoryFacade RepositoryClient { get; } public IBranchFacade BranchClient { get; } + public IPullRequestFacade PullRequestClient { get; } public GiteaClient(IRepositoryFacade repositoryFacade, IBranchFacade branchClient, - ICommitFacade commitClient) + ICommitFacade commitClient, + IPullRequestFacade pullRequestClient) { RepositoryClient = repositoryFacade ?? throw new ArgumentNullException(nameof(repositoryFacade)); BranchClient = branchClient ?? throw new ArgumentNullException(nameof(branchClient)); + PullRequestClient = pullRequestClient ?? throw new ArgumentNullException(nameof(pullRequestClient)); CommitClient = commitClient ?? throw new ArgumentNullException(nameof(commitClient)); } } \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/PullRequest/Common/ApiCall/Abstractions/IPullRequestRestClient.cs b/Mohaymen.GiteaClient/Gitea/PullRequest/Common/ApiCall/Abstractions/IPullRequestRestClient.cs new file mode 100644 index 0000000..127d0ec --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/PullRequest/Common/ApiCall/Abstractions/IPullRequestRestClient.cs @@ -0,0 +1,18 @@ +using System.Threading; +using System.Threading.Tasks; +using Mohaymen.GiteaClient.Core.ApiCall.Abstractions; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Context; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Dtos; +using Refit; + +namespace Mohaymen.GiteaClient.Gitea.PullRequest.Common.ApiCall.Abstractions; + +internal interface IPullRequestRestClient : IRefitClientInterface +{ + [Post("/repos/{owner}/{repo}/pulls")] + Task> CreatePullRequestAsync( + [AliasAs("owner")] string owner, + [AliasAs("repo")] string repositoryName, + [Body] CreatePullRequestRequest createPullRequestRequest, + CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/PullRequest/Common/DependencyInstallers/PullRequestCommonDependencyInstaller.cs b/Mohaymen.GiteaClient/Gitea/PullRequest/Common/DependencyInstallers/PullRequestCommonDependencyInstaller.cs new file mode 100644 index 0000000..7b4ee80 --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/PullRequest/Common/DependencyInstallers/PullRequestCommonDependencyInstaller.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; +using Mohaymen.GiteaClient.Core.DependencyInjection.Abstractions; +using Mohaymen.GiteaClient.Gitea.PullRequest.Common.Facade; +using Mohaymen.GiteaClient.Gitea.PullRequest.Common.Facade.Abstractions; + +namespace Mohaymen.GiteaClient.Gitea.PullRequest.Common.DependencyInstallers; + +internal class PullRequestCommonDependencyInstaller : IDependencyInstaller +{ + public void Install(IServiceCollection serviceCollection) + { + serviceCollection.AddSingleton(); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/PullRequest/Common/Facade/Abstractions/IPullRequestFacade.cs b/Mohaymen.GiteaClient/Gitea/PullRequest/Common/Facade/Abstractions/IPullRequestFacade.cs new file mode 100644 index 0000000..3242fb2 --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/PullRequest/Common/Facade/Abstractions/IPullRequestFacade.cs @@ -0,0 +1,13 @@ +using System.Threading; +using System.Threading.Tasks; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Dtos; +using Refit; + +namespace Mohaymen.GiteaClient.Gitea.PullRequest.Common.Facade.Abstractions; + +public interface IPullRequestFacade +{ + Task> CreatePullRequestAsync( + CreatePullRequestCommandDto createPullRequestCommandDto, + CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/PullRequest/Common/Facade/PullRequestFacade.cs b/Mohaymen.GiteaClient/Gitea/PullRequest/Common/Facade/PullRequestFacade.cs new file mode 100644 index 0000000..1a09154 --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/PullRequest/Common/Facade/PullRequestFacade.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Mohaymen.GiteaClient.Gitea.PullRequest.Common.Facade.Abstractions; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Dtos; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Mappers; +using Refit; + +namespace Mohaymen.GiteaClient.Gitea.PullRequest.Common.Facade; + +internal class PullRequestFacade : IPullRequestFacade +{ + private readonly IMediator _mediator; + + public PullRequestFacade(IMediator mediator) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + } + + public async Task> CreatePullRequestAsync(CreatePullRequestCommandDto createPullRequestCommandDto, + CancellationToken cancellationToken) + { + var command = createPullRequestCommandDto.ToCreatePullRequestCommand(); + return await _mediator.Send(command, cancellationToken); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Commands/CreateRepositoryCommand.cs b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Commands/CreateRepositoryCommand.cs new file mode 100644 index 0000000..ff48db1 --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Commands/CreateRepositoryCommand.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using FluentValidation; +using MediatR; +using Microsoft.Extensions.Options; +using Mohaymen.GiteaClient.Core.Configs; +using Mohaymen.GiteaClient.Gitea.PullRequest.Common.ApiCall.Abstractions; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Dtos; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Mappers; +using Refit; + +namespace Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Commands; + +internal class CreatePullRequestCommand : IRequest> +{ + public required string RepositoryName { get; init; } + public string? HeadBranch { get; init; } + public string? BaseBranch { get; init; } + public string? Body { get; init; } + public string? Title { get; init; } + public string? Assignee { get; init; } + public List? Assignees { get; init; } //TODO list or array ? +} + +internal class CreatePullRequestCommandHandler : IRequestHandler> +{ + private readonly IPullRequestRestClient _pullRequestRestClient; + private readonly IOptions _options; + private readonly IValidator _validator; + + public CreatePullRequestCommandHandler(IPullRequestRestClient pullRequestRestClient, + IOptions options, + IValidator validator) + { + _pullRequestRestClient = pullRequestRestClient ?? throw new ArgumentNullException(nameof(pullRequestRestClient)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + _validator = validator ?? throw new ArgumentNullException(nameof(validator)); + } + + public async Task> Handle(CreatePullRequestCommand command, + CancellationToken cancellationToken) + { + _validator.ValidateAndThrow(command); + var createPullRequestRequest = command.ToCreatePullRequestRequest(); + var owner = _options.Value.RepositoriesOwner; + return await _pullRequestRestClient.CreatePullRequestAsync(owner, command.RepositoryName, createPullRequestRequest, cancellationToken) + .ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Context/CreatePullRequestRequest.cs b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Context/CreatePullRequestRequest.cs new file mode 100644 index 0000000..82f21a0 --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Context/CreatePullRequestRequest.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Context; + +internal class CreatePullRequestRequest +{ + [JsonProperty("head")] + public string? HeadBranch { get; init; } + + [JsonProperty("base")] + public string? BaseBranch { get; init; } + + [JsonProperty("body")] + public string? Body { get; init; } + + [JsonProperty("title")] + public string? Title { get; init; } + + [JsonProperty("assignee")] + public string? Assignee { get; init; } + + [JsonProperty("assignees")] + public List? Assignees { get; init; } //TODO list or array ? +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Dtos/CreatePullRequestCommandDto.cs b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Dtos/CreatePullRequestCommandDto.cs new file mode 100644 index 0000000..4d8495e --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Dtos/CreatePullRequestCommandDto.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Dtos; + +public class CreatePullRequestCommandDto +{ + public required string RepositoryName { get; init; } + public string? HeadBranch { get; init; } + public string? BaseBranch { get; init; } + public string? Body { get; init; } + public string? Title { get; init; } + public string? Assignee { get; init; } + public List? Assignees { get; init; } //TODO list or array ? +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Dtos/CreatePullRequestResponseDto.cs b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Dtos/CreatePullRequestResponseDto.cs new file mode 100644 index 0000000..ce89551 --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Dtos/CreatePullRequestResponseDto.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; + +namespace Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Dtos; + +public class CreatePullRequestResponseDto +{ + [JsonProperty("title")] + public string? Title { get; init; } + + //TODO which fields should be added? +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Mappers/CreatePullRequestCommandMapper.cs b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Mappers/CreatePullRequestCommandMapper.cs new file mode 100644 index 0000000..d7e7aa5 --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Mappers/CreatePullRequestCommandMapper.cs @@ -0,0 +1,24 @@ +using System; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Commands; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Dtos; + +namespace Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Mappers; + +internal static class CreatePullRequestCommandMapper +{ + internal static CreatePullRequestCommand ToCreatePullRequestCommand(this CreatePullRequestCommandDto createPullRequestCommandDto) + { + ArgumentNullException.ThrowIfNull(createPullRequestCommandDto); + + return new CreatePullRequestCommand + { + RepositoryName = createPullRequestCommandDto.RepositoryName, + HeadBranch = createPullRequestCommandDto.HeadBranch, + BaseBranch = createPullRequestCommandDto.BaseBranch, + Body = createPullRequestCommandDto.Body, + Title = createPullRequestCommandDto.Title, + Assignee = createPullRequestCommandDto.Assignee, + Assignees = createPullRequestCommandDto.Assignees + }; + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Mappers/CreatePullRequestRequestMapper.cs b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Mappers/CreatePullRequestRequestMapper.cs new file mode 100644 index 0000000..8f1daf4 --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Mappers/CreatePullRequestRequestMapper.cs @@ -0,0 +1,23 @@ +using System; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Commands; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Context; + +namespace Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Mappers; + +internal static class CreatePullRequestRequestMapper +{ + internal static CreatePullRequestRequest ToCreatePullRequestRequest(this CreatePullRequestCommand createPullRequestCommand) + { + ArgumentNullException.ThrowIfNull(createPullRequestCommand); + + return new CreatePullRequestRequest + { + HeadBranch = createPullRequestCommand.HeadBranch, + BaseBranch = createPullRequestCommand.BaseBranch, + Body = createPullRequestCommand.Body, + Title = createPullRequestCommand.Title, + Assignee = createPullRequestCommand.Assignee, + Assignees = createPullRequestCommand.Assignees + }; + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Validators/CreatePullRequestCommandValidator.cs b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Validators/CreatePullRequestCommandValidator.cs new file mode 100644 index 0000000..d4974a0 --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Validators/CreatePullRequestCommandValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; +using Mohaymen.GiteaClient.Gitea.Branch.CreateBranch.Validators; +using Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Commands; + +namespace Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Validators; + +internal sealed class CreatePullRequestCommandValidator : AbstractValidator +{ + public CreatePullRequestCommandValidator() + { + RuleFor(x => x.RepositoryName) + .NotEmpty() + .WithErrorCode(CreateBranchErrorCodes.EmptyRepositoryNameErrorCode) + .WithMessage("repository name should not be empty"); + } +} diff --git a/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Validators/CreatePullRequestErrorCodes.cs b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Validators/CreatePullRequestErrorCodes.cs new file mode 100644 index 0000000..a91c2cb --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/PullRequest/CreatePullRequest/Validators/CreatePullRequestErrorCodes.cs @@ -0,0 +1,6 @@ +namespace Mohaymen.GiteaClient.Gitea.PullRequest.CreatePullRequest.Validators; + +internal static class CreatePullRequestErrorCodes +{ + internal const string EmptyRepositoryNameErrorCode = "EmptyRepositoryNameErrorCode"; +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Repository/Common/ApiCall/Abstractions/IRepositoryRestClient.cs b/Mohaymen.GiteaClient/Gitea/Repository/Common/ApiCall/Abstractions/IRepositoryRestClient.cs index ed56cd4..f5cf639 100644 --- a/Mohaymen.GiteaClient/Gitea/Repository/Common/ApiCall/Abstractions/IRepositoryRestClient.cs +++ b/Mohaymen.GiteaClient/Gitea/Repository/Common/ApiCall/Abstractions/IRepositoryRestClient.cs @@ -2,6 +2,8 @@ using Mohaymen.GiteaClient.Core.ApiCall.Abstractions; using Mohaymen.GiteaClient.Gitea.Repository.CreateRepository.Context; using Mohaymen.GiteaClient.Gitea.Repository.CreateRepository.Dtos; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Dto; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Dtos; using Refit; namespace Mohaymen.GiteaClient.Gitea.Repository.Common.ApiCall.Abstractions; @@ -10,4 +12,10 @@ internal interface IRepositoryRestClient : IRefitClientInterface { [Post("/user/repos")] Task> CreateRepositoryAsync([Body] CreateRepositoryRequest createRepositoryRequest); + + [Get("/repos/search?q={query}&page={page}&limit={limit}")] + Task> SearchRepositoryAsync(string query, int page, int limit); + + [Delete("/repos/{owner}/{repositoryName}")] + Task> DeleteRepositoryAsync(string owner, string repositoryName); } \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Repository/Common/Facade/Abstractions/IRepositoryFacade.cs b/Mohaymen.GiteaClient/Gitea/Repository/Common/Facade/Abstractions/IRepositoryFacade.cs index 80d4176..249fa0a 100644 --- a/Mohaymen.GiteaClient/Gitea/Repository/Common/Facade/Abstractions/IRepositoryFacade.cs +++ b/Mohaymen.GiteaClient/Gitea/Repository/Common/Facade/Abstractions/IRepositoryFacade.cs @@ -1,6 +1,8 @@ using System.Threading; using System.Threading.Tasks; using Mohaymen.GiteaClient.Gitea.Repository.CreateRepository.Dtos; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Dto; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Dtos; using Refit; namespace Mohaymen.GiteaClient.Gitea.Repository.Common.Facade.Abstractions; @@ -8,4 +10,9 @@ namespace Mohaymen.GiteaClient.Gitea.Repository.Common.Facade.Abstractions; public interface IRepositoryFacade { Task> CreateRepositoryAsync(CreateRepositoryCommandDto createRepositoryCommandDto, CancellationToken cancellationToken); + + Task> SearchRepositoryAsync(SearchRepositoryQueryDto searchRepositoryQueryDto, CancellationToken cancellationToken); + + Task> DeleteRepositoryAsync(DeleteRepositoryCommandDto deleteRepositoryCommandDto, + CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Repository/Common/Facade/RepositoryFacade.cs b/Mohaymen.GiteaClient/Gitea/Repository/Common/Facade/RepositoryFacade.cs index fef8c8e..a939abe 100644 --- a/Mohaymen.GiteaClient/Gitea/Repository/Common/Facade/RepositoryFacade.cs +++ b/Mohaymen.GiteaClient/Gitea/Repository/Common/Facade/RepositoryFacade.cs @@ -5,6 +5,10 @@ using Mohaymen.GiteaClient.Gitea.Repository.Common.Facade.Abstractions; using Mohaymen.GiteaClient.Gitea.Repository.CreateRepository.Dtos; using Mohaymen.GiteaClient.Gitea.Repository.CreateRepository.Mappers; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Dto; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Mappers; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Dtos; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Mappers; using Refit; namespace Mohaymen.GiteaClient.Gitea.Repository.Common.Facade; @@ -25,4 +29,16 @@ public async Task> CreateRepositoryAsyn var command = CreateRepositoryCommandMapper.Map(createRepositoryCommandDto); return await _mediator.Send(command, cancellationToken); } + + public async Task> SearchRepositoryAsync(SearchRepositoryQueryDto searchRepositoryQueryDto, CancellationToken cancellationToken) + { + var query = SearchRepositoryQueryMapper.Map(searchRepositoryQueryDto); + return await _mediator.Send(query, cancellationToken); + } + + public async Task> DeleteRepositoryAsync(DeleteRepositoryCommandDto deleteRepositoryCommandDto, CancellationToken cancellationToken) + { + var command = DeleteRepositoryCommandMapper.Map(deleteRepositoryCommandDto); + return await _mediator.Send(command, cancellationToken); + } } \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Commands/DeleteRepositoryCommandHandler.cs b/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Commands/DeleteRepositoryCommandHandler.cs new file mode 100644 index 0000000..0bea092 --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Commands/DeleteRepositoryCommandHandler.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentValidation; +using MediatR; +using Microsoft.Extensions.Options; +using Mohaymen.GiteaClient.Core.Configs; +using Mohaymen.GiteaClient.Gitea.Repository.Common.ApiCall.Abstractions; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Dto; +using Refit; + +namespace Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Commands; + +internal class DeleteRepositoryCommand : IRequest> +{ + public required string RepositoryName { get; init; } +} + +internal class DeleteRepositoryCommandHandler : IRequestHandler> +{ + private IValidator _validator; + private IRepositoryRestClient _repositoryRestClient; + private IOptions _giteaOptions; + + public DeleteRepositoryCommandHandler(IValidator validator, + IRepositoryRestClient repositoryRestClient, + IOptions giteaOptions) + { + _validator = validator ?? throw new ArgumentNullException(nameof(validator)); + _repositoryRestClient = repositoryRestClient ?? throw new ArgumentNullException(nameof(repositoryRestClient)); + _giteaOptions = giteaOptions ?? throw new ArgumentNullException(nameof(giteaOptions)); + } + + public async Task> Handle(DeleteRepositoryCommand request, CancellationToken cancellationToken) + { + _validator.ValidateAndThrow(request); + return await _repositoryRestClient.DeleteRepositoryAsync(_giteaOptions.Value.RepositoriesOwner, request.RepositoryName); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Dto/DeleteRepositoryCommandDto.cs b/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Dto/DeleteRepositoryCommandDto.cs new file mode 100644 index 0000000..40d372d --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Dto/DeleteRepositoryCommandDto.cs @@ -0,0 +1,6 @@ +namespace Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Dto; + +public class DeleteRepositoryCommandDto +{ + public required string RepositoryName { get; init; } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Dto/DeleteRepositoryResponseDto.cs b/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Dto/DeleteRepositoryResponseDto.cs new file mode 100644 index 0000000..25f5b1d --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Dto/DeleteRepositoryResponseDto.cs @@ -0,0 +1,4 @@ +namespace Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Dto; + +public class DeleteRepositoryResponseDto +{} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Mappers/DeleteRepositoryCommandMapper.cs b/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Mappers/DeleteRepositoryCommandMapper.cs new file mode 100644 index 0000000..ca30c93 --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Mappers/DeleteRepositoryCommandMapper.cs @@ -0,0 +1,18 @@ +using System; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Commands; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Dto; + +namespace Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Mappers; + +internal static class DeleteRepositoryCommandMapper +{ + public static DeleteRepositoryCommand Map(this DeleteRepositoryCommandDto deleteRepositoryCommandDto) + { + ArgumentNullException.ThrowIfNull(deleteRepositoryCommandDto); + + return new DeleteRepositoryCommand + { + RepositoryName = deleteRepositoryCommandDto.RepositoryName + }; + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Validators/DeleteRepositoryCommandValidator.cs b/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Validators/DeleteRepositoryCommandValidator.cs new file mode 100644 index 0000000..602fdea --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/Repository/DeleteRepository/Validators/DeleteRepositoryCommandValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; +using Mohaymen.GiteaClient.Core.Validation; +using Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Commands; + +namespace Mohaymen.GiteaClient.Gitea.Repository.DeleteRepository.Validators; + +internal class DeleteRepositoryCommandValidator : AbstractValidator +{ + public DeleteRepositoryCommandValidator() + { + RuleFor(x => x.RepositoryName) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.EmptyRepositoryNameErrorCode) + .WithMessage("Repository name is empty."); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Dtos/SearchRepositoryQueryDto.cs b/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Dtos/SearchRepositoryQueryDto.cs new file mode 100644 index 0000000..182557e --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Dtos/SearchRepositoryQueryDto.cs @@ -0,0 +1,8 @@ +namespace Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Dtos; + +public sealed class SearchRepositoryQueryDto +{ + public required string Query { get; init; } + public int Page { get; init; } + public int Limit { get; init; } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Dtos/SearchRepositoryResponseDto.cs b/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Dtos/SearchRepositoryResponseDto.cs new file mode 100644 index 0000000..0be0a7c --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Dtos/SearchRepositoryResponseDto.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Dtos; + +public sealed class SearchRepositoryResponseDto +{ + [JsonProperty("data")] + public required List SearchResult { get; init; } +} + +public class RepositorySearchDto +{ + [JsonProperty("id")] + public int Id { get; init; } + + [JsonProperty("name")] + public required string RepositoryName { get; set; } +} diff --git a/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Mappers/SearchRepositoryQueryMapper.cs b/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Mappers/SearchRepositoryQueryMapper.cs new file mode 100644 index 0000000..5f26de3 --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Mappers/SearchRepositoryQueryMapper.cs @@ -0,0 +1,20 @@ +using System; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Dtos; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Queries; + +namespace Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Mappers; + +internal static class SearchRepositoryQueryMapper +{ + public static SearchRepositoryQuery Map(this SearchRepositoryQueryDto searchRepositoryQueryDto) + { + ArgumentNullException.ThrowIfNull(searchRepositoryQueryDto); + + return new SearchRepositoryQuery + { + Query = searchRepositoryQueryDto.Query, + Limit = searchRepositoryQueryDto.Limit, + Page = searchRepositoryQueryDto.Page + }; + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Queries/SearchRepositoryQueryHandler.cs b/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Queries/SearchRepositoryQueryHandler.cs new file mode 100644 index 0000000..f0a3f5e --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Queries/SearchRepositoryQueryHandler.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentValidation; +using MediatR; +using Mohaymen.GiteaClient.Gitea.Repository.Common.ApiCall.Abstractions; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Dtos; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Mappers; +using Refit; + +namespace Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Queries; + +internal sealed class SearchRepositoryQuery : IRequest> +{ + public required string Query { get; init; } + public int Page { get; init; } = 1; + public int Limit { get; init; } = 5; +} + +internal sealed class SearchRepositoryQueryHandler : IRequestHandler> +{ + private readonly IValidator _validator; + private readonly IRepositoryRestClient _repositoryRestClient; + + public SearchRepositoryQueryHandler(IValidator validator, + IRepositoryRestClient repositoryRestClient) + { + _validator = validator ?? throw new ArgumentNullException(nameof(validator)); + _repositoryRestClient = repositoryRestClient ?? throw new ArgumentNullException(nameof(repositoryRestClient)); + } + + public async Task> Handle(SearchRepositoryQuery searchRepositoryQuery, CancellationToken cancellationToken) + { + _validator.ValidateAndThrow(searchRepositoryQuery); + return await _repositoryRestClient.SearchRepositoryAsync(searchRepositoryQuery.Query, searchRepositoryQuery.Page, searchRepositoryQuery.Limit); + } +} \ No newline at end of file diff --git a/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Validators/SearchRepositoryQueryValidator.cs b/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Validators/SearchRepositoryQueryValidator.cs new file mode 100644 index 0000000..41c6bae --- /dev/null +++ b/Mohaymen.GiteaClient/Gitea/Repository/SearchRepository/Validators/SearchRepositoryQueryValidator.cs @@ -0,0 +1,26 @@ +using FluentValidation; +using Mohaymen.GiteaClient.Core.Validation; +using Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Queries; + +namespace Mohaymen.GiteaClient.Gitea.Repository.SearchRepository.Validators; + +internal sealed class SearchRepositoryQueryValidator : AbstractValidator +{ + public SearchRepositoryQueryValidator() + { + RuleFor(x => x.Query) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.EmptySearchQueryErrorCode) + .WithMessage("The search query is null or empty!"); + + RuleFor(x => x.Page) + .GreaterThanOrEqualTo(1) + .WithErrorCode(ValidationErrorCodes.InvalidPageSizeErrorCode) + .WithMessage("The page size must be greater than or equal to 1"); + + RuleFor(x => x.Limit) + .GreaterThan(0) + .WithErrorCode(ValidationErrorCodes.InvalidLimitErrorCode) + .WithMessage("The limit must be greater than 0"); + } +} \ No newline at end of file