diff --git a/.github/workflows/develop_autopile.yml b/.github/workflows/develop_autopile.yml index 5aa434c..b9f0ffb 100644 --- a/.github/workflows/develop_autopile.yml +++ b/.github/workflows/develop_autopile.yml @@ -1,6 +1,3 @@ -# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy -# More GitHub Actions for Azure: https://github.com/Azure/actions - name: Build and deploy ASP.Net Core app to Azure Web App - autopile on: @@ -11,7 +8,9 @@ on: jobs: build: - runs-on: windows-latest + runs-on: ubuntu-latest + permissions: + contents: read #This is required for actions/checkout steps: - uses: actions/checkout@v4 @@ -21,45 +20,15 @@ jobs: with: dotnet-version: '9.x' - - name: Build with dotnet - run: dotnet build --configuration Release - - - name: dotnet publish - run: dotnet publish -c Release -o "${{env.DOTNET_ROOT}}/myapp" + - name: Restore + run: dotnet restore ./AutoPile.sln - - name: Upload artifact for deployment job - uses: actions/upload-artifact@v4 - with: - name: .net-app - path: ${{env.DOTNET_ROOT}}/myapp + - name: Build + run: dotnet build ./AutoPile.sln --configuration Release --no-restore - deploy: - runs-on: windows-latest - needs: build - environment: - name: 'Production' - url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} - permissions: - id-token: write #This is required for requesting the JWT + - name: Test + run: dotnet test --no-build --configuration Release - steps: - - name: Download artifact from build job - uses: actions/download-artifact@v4 - with: - name: .net-app - - - name: Login to Azure - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_8D7D3ADE3F6945FDB842D69367CCC0A2 }} - tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_D560FBBA60F5467B96310A66546941E1 }} - subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_7B5512553F074DD889236ED335B0C6F2 }} - - - name: Deploy to Azure Web App - id: deploy-to-webapp - uses: azure/webapps-deploy@v3 - with: - app-name: 'autopile' - slot-name: 'Production' - package: . - \ No newline at end of file + + + diff --git a/AutoPile.API/Program.cs b/AutoPile.API/Program.cs index 329aeed..22e381e 100644 --- a/AutoPile.API/Program.cs +++ b/AutoPile.API/Program.cs @@ -110,6 +110,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); // Add this to the service configuration section in Program.cs // Register both QueueClients // Register QueueClient for Email Queue diff --git a/AutoPile.DATA/Data/AutoPileMongoDbContext.cs b/AutoPile.DATA/Data/AutoPileMongoDbContext.cs index fc4f5f9..0b84560 100644 --- a/AutoPile.DATA/Data/AutoPileMongoDbContext.cs +++ b/AutoPile.DATA/Data/AutoPileMongoDbContext.cs @@ -14,8 +14,8 @@ namespace AutoPile.DATA.Data { public class AutoPileMongoDbContext : DbContext { - public DbSet Products { get; set; } - public DbSet Reviews { get; set; } + public virtual DbSet Products { get; set; } + public virtual DbSet Reviews { get; set; } public static AutoPileMongoDbContext Create(IMongoDatabase database) => new(new DbContextOptionsBuilder() diff --git a/AutoPile.DOMAIN/Interface/IStripeService.cs b/AutoPile.DOMAIN/Interface/IStripeService.cs new file mode 100644 index 0000000..46e33e0 --- /dev/null +++ b/AutoPile.DOMAIN/Interface/IStripeService.cs @@ -0,0 +1,9 @@ +using Stripe; + +namespace AutoPile.SERVICE.Services +{ + public interface IStripeService + { + Task CreatePaymentIntentAsync(long amount, string currency); + } +} \ No newline at end of file diff --git a/AutoPile.SERVICE/Services/AuthService.cs b/AutoPile.SERVICE/Services/AuthService.cs index 052e784..fef5a90 100644 --- a/AutoPile.SERVICE/Services/AuthService.cs +++ b/AutoPile.SERVICE/Services/AuthService.cs @@ -115,20 +115,13 @@ public AuthService(UserManager userManager, ILogger u.PhoneNumber == userSignupDTO.PhoneNumber); + var existUserWithPhone = _userManager.Users.Any(u => u.PhoneNumber == userSignupDTO.PhoneNumber); if (existUserWithPhone) { throw new BadRequestException("Phone number already registered"); } - var user = new ApplicationUser - { - UserName = userSignupDTO.UserName, - Email = userSignupDTO.Email, - FirstName = userSignupDTO.FirstName, - LastName = userSignupDTO.LastName, - PhoneNumber = userSignupDTO.PhoneNumber - }; + var user = _mapper.Map(userSignupDTO); var result = await _userManager.CreateAsync(user, userSignupDTO.Password); if (!result.Succeeded) @@ -148,13 +141,8 @@ public AuthService(UserManager userManager, ILogger(user); + responseDTO.Roles = await _userManager.GetRolesAsync(user); return (responseDTO, accessToken, refreshToken); } @@ -172,8 +160,8 @@ public AuthService(UserManager userManager, ILogger(user); userResponseDTO.Roles = await _userManager.GetRolesAsync(user); - var userResponseInfoDTO = _mapper.Map(user); - userResponseInfoDTO.Roles = userResponseDTO.Roles; + //var userResponseInfoDTO = _mapper.Map(user); + //userResponseInfoDTO.Roles = userResponseDTO.Roles; //await _userInfoCache.SetUserAsync(user.Id, userResponseInfoDTO); _logger.LogInformation("Successfully cached user info for user {UserId}", user.Id); return (userResponseDTO, accessToken, refreshToken); diff --git a/AutoPile.SERVICE/Services/PaymentService.cs b/AutoPile.SERVICE/Services/PaymentService.cs index 02f851e..e93069e 100644 --- a/AutoPile.SERVICE/Services/PaymentService.cs +++ b/AutoPile.SERVICE/Services/PaymentService.cs @@ -1,27 +1,22 @@ -using AutoMapper; -using AutoPile.DATA.Data; +using AutoPile.DATA.Data; using AutoPile.DATA.Exceptions; using AutoPile.DOMAIN.DTOs.Requests; using MongoDB.Bson; using Stripe; -using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; namespace AutoPile.SERVICE.Services { public class PaymentService : IPaymentService { - private readonly AutoPileManagementDbContext _autoPileManagementDbContext; private readonly AutoPileMongoDbContext _autoPileMongoDbContext; - private readonly IMapper _mapper; + private readonly IStripeService _stripeService; - public PaymentService(AutoPileManagementDbContext autoPileManagementDbContext, AutoPileMongoDbContext autoPileMongoDbContext, IMapper mapper) + public PaymentService(AutoPileMongoDbContext autoPileMongoDbContext, IStripeService stripeService) { - _autoPileManagementDbContext = autoPileManagementDbContext; _autoPileMongoDbContext = autoPileMongoDbContext; - _mapper = mapper; + _stripeService = stripeService; } public async Task PaymentIntentCreateAsync(PaymentIntentCreate paymentIntentCreate) @@ -32,19 +27,7 @@ public async Task PaymentIntentCreateAsync(PaymentIntentCreate pa } var totalAmount = await CalculateTotalAmountAsync(paymentIntentCreate.Items); - - var options = new PaymentIntentCreateOptions - { - Amount = (long)(totalAmount * 100), - Currency = "aud", - AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions - { - Enabled = true, - }, - }; - - var service = new PaymentIntentService(); - return await service.CreateAsync(options); + return await _stripeService.CreatePaymentIntentAsync((long)(totalAmount * 100), "aud"); } private async Task CalculateTotalAmountAsync(Item[] items) diff --git a/AutoPile.SERVICE/Services/StripeService.cs b/AutoPile.SERVICE/Services/StripeService.cs new file mode 100644 index 0000000..ec852d3 --- /dev/null +++ b/AutoPile.SERVICE/Services/StripeService.cs @@ -0,0 +1,27 @@ +using Stripe; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoPile.SERVICE.Services +{ + public class StripeService : IStripeService + { + public async Task CreatePaymentIntentAsync(long amount, string currency) + { + var options = new PaymentIntentCreateOptions + { + Amount = amount, + Currency = currency, + AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions + { + Enabled = true, + }, + }; + var service = new PaymentIntentService(); + return await service.CreateAsync(options); + } + } +} \ No newline at end of file diff --git a/AutoPile.UnitTests/AuthServiceTests.cs b/AutoPile.UnitTests/AuthServiceTests.cs new file mode 100644 index 0000000..c481c99 --- /dev/null +++ b/AutoPile.UnitTests/AuthServiceTests.cs @@ -0,0 +1,236 @@ +using AutoMapper; +using AutoPile.DATA.Cache; +using AutoPile.DATA.Exceptions; +using AutoPile.DOMAIN.DTOs.Requests; +using AutoPile.DOMAIN.DTOs.Responses; +using AutoPile.DOMAIN.Models.Entities; +using AutoPile.SERVICE.Services; +using AutoPile.SERVICE.Utilities; +using FluentAssertions; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Moq; +using Resend; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoPile.UnitTests +{ + public class AuthServiceTests + { + private readonly Mock> _userManagerMock; + private readonly Mock _mapperMock; + private readonly Mock _jwtTokenGeneratorMock; + private readonly Mock _resendMock; + private readonly Mock _configurationMock; + private readonly Mock _emailQueueServiceMock; + private readonly Mock _userInfoCacheMock; + private readonly Mock> _loggerMock; + private readonly AuthService _authServiceMock; + + public AuthServiceTests() + { + var storeMock = new Mock>(); + _userManagerMock = new Mock>( + storeMock.Object, + null, // IOptions + null, // IPasswordHasher + null, // IEnumerable> + null, // IEnumerable> + null, // ILookupNormalizer + null, // IdentityErrorDescriber + null, // IServiceProvider + null // ILogger> + ); + _mapperMock = new Mock(); + _userInfoCacheMock = new Mock(); + _jwtTokenGeneratorMock = new Mock(); + _configurationMock = new Mock(); + _resendMock = new Mock(); + _emailQueueServiceMock = new Mock(); + _loggerMock = new Mock>(); + _authServiceMock = new AuthService(_userManagerMock.Object, _loggerMock.Object, _emailQueueServiceMock.Object, + _configurationMock.Object, _mapperMock.Object, _userInfoCacheMock.Object, _jwtTokenGeneratorMock.Object, _resendMock.Object); + } + + [Fact] + public async Task SignUpUser_ValidInput_ReturnUserResponseDTO() + { + // Arrange + var password = "TestPassword"; + var userSignUpDTO = new UserSignupDTO() + { + FirstName = "firstName", + LastName = "lastName", + UserName = "TestUserName", + PhoneNumber = "TestPhoneNumber", + Email = "TestEmail", + Password = password + }; + + _mapperMock.Setup(m => m.Map(It.IsAny())) + .Returns(new ApplicationUser + { + UserName = userSignUpDTO.UserName, + Email = userSignUpDTO.Email, + FirstName = userSignUpDTO.FirstName, + LastName = userSignUpDTO.LastName, + PhoneNumber = userSignUpDTO.PhoneNumber, + Id = "TestId" + }); + + _mapperMock.Setup(m => m.Map(It.IsAny())) + .Returns(new UserResponseDTO + { + UserName = userSignUpDTO.UserName, + Email = userSignUpDTO.Email, + Id = "TestId" + }); + + _userManagerMock.Setup(u => u.FindByEmailAsync(userSignUpDTO.Email)) + .ReturnsAsync(null as ApplicationUser); + + _userManagerMock.Setup(u => u.Users) + .Returns(new List().AsQueryable()); + + _userManagerMock.Setup(u => u.CreateAsync(It.IsAny(), password)) + .ReturnsAsync(IdentityResult.Success); + + _userManagerMock.Setup(u => u.AddToRoleAsync(It.IsAny(), "User")) + .ReturnsAsync(IdentityResult.Success); + + _userManagerMock.Setup(u => u.GetRolesAsync(It.IsAny())) + .ReturnsAsync(["User"]); + + _jwtTokenGeneratorMock.Setup(t => t.GenerateJwtToken(It.IsAny())) + .Returns("test_access_token"); + + // Act + var result = await _authServiceMock.SignupUserAsync(userSignUpDTO); + + // Assert + result.Should().NotBeNull(); + var (userResponse, accessToken, refreshToken) = result; + + userResponse.Should().NotBeNull(); + userResponse.Should().BeOfType(); + userResponse.UserName.Should().Be(userSignUpDTO.UserName); + userResponse.Email.Should().Be(userSignUpDTO.Email); + userResponse.Roles.Should().Contain("User"); + + accessToken.Should().Be("test_access_token"); + refreshToken.Should().NotBeNull(); + + _userManagerMock.Verify(u => u.CreateAsync(It.IsAny(), password), Times.Once); + _userManagerMock.Verify(u => u.AddToRoleAsync(It.IsAny(), "User"), Times.Once); + _jwtTokenGeneratorMock.Verify(t => t.GenerateJwtToken(It.IsAny()), Times.Once); + } + + [Fact] + public async Task SignInUser_ValidInput_ReturnUserResponseDTO() + { + //Arrange + var password = "TestPassword"; + var userSignUpDTO = new UserSignupDTO() + { + FirstName = "firstName", + LastName = "lastName", + UserName = "TestUserName", + PhoneNumber = "TestPhoneNumber", + Email = "TestEmail", + Password = password + }; + + var userSigninDTO = new UserSigninDTO() + { + Email = "TestEmail", + Password = password + }; + + var user = new ApplicationUser + { + UserName = userSignUpDTO.UserName, + Email = userSignUpDTO.Email, + FirstName = userSignUpDTO.FirstName, + LastName = userSignUpDTO.LastName, + PhoneNumber = userSignUpDTO.PhoneNumber, + Id = "TestId" + }; + _userManagerMock.Setup(u => u.FindByEmailAsync(userSignUpDTO.Email)).ReturnsAsync(user); + _userManagerMock.Setup(u => u.CheckPasswordAsync(It.IsAny(), password)).ReturnsAsync(true); + _jwtTokenGeneratorMock.Setup(t => t.GenerateJwtToken(It.IsAny())).Returns("test_access_token"); + + _mapperMock.Setup(m => m.Map(user)).Returns(new UserResponseDTO + { + UserName = userSignUpDTO.UserName, + Email = userSignUpDTO.Email, + Id = "TestId", + }); + + _userManagerMock.Setup(u => u.GetRolesAsync(It.IsAny())) + .ReturnsAsync(["User"]); + + //Act + var result = await _authServiceMock.SigninAsync(userSigninDTO); + + //Assert + result.Should().NotBeNull(); + var (userResponse, accessToken, refreshToken) = result; + + userResponse.Should().NotBeNull(); + userResponse.Should().BeOfType(); + userResponse.UserName.Should().Be(userSignUpDTO.UserName); + userResponse.Email.Should().Be(userSignUpDTO.Email); + userResponse.Roles.Should().Contain("User"); + + accessToken.Should().Be("test_access_token"); + refreshToken.Should().NotBeNull(); + + _userManagerMock.Verify(u => u.CheckPasswordAsync(It.IsAny(), password), Times.Once); + _jwtTokenGeneratorMock.Verify(t => t.GenerateJwtToken(It.IsAny()), Times.Once); + _userManagerMock.Verify(u => u.GetRolesAsync(It.IsAny()), Times.Once); + } + + [Fact] + public async Task SigninUser_WrongCredentialInput_ReturnNoFoundException() + { + //Arrange + var password = "TestPassword"; + var userSignUpDTO = new UserSignupDTO() + { + FirstName = "firstName", + LastName = "lastName", + UserName = "TestUserName", + PhoneNumber = "TestPhoneNumber", + Email = "TestEmail", + Password = password + }; + var userSignInDTO = new UserSigninDTO() + { + Email = "TestEmail", + Password = password + }; + var user = new ApplicationUser + { + UserName = userSignUpDTO.UserName, + Email = userSignUpDTO.Email, + FirstName = userSignUpDTO.FirstName, + LastName = userSignUpDTO.LastName, + PhoneNumber = userSignUpDTO.PhoneNumber, + Id = "TestId" + }; + _userManagerMock.Setup(u => u.FindByEmailAsync(userSignUpDTO.Email)).ReturnsAsync(user); + _userManagerMock.Setup(u => u.CheckPasswordAsync(user, password)).ReturnsAsync(false); + + //Act & Assert + await _authServiceMock.Invoking(x => x.SigninAsync(userSignInDTO)) + .Should().ThrowAsync() + .WithMessage("Email does not exist or incorrect password"); + } + } +} \ No newline at end of file diff --git a/AutoPile.UnitTests/AutoPile.UnitTests.csproj b/AutoPile.UnitTests/AutoPile.UnitTests.csproj new file mode 100644 index 0000000..d0b088d --- /dev/null +++ b/AutoPile.UnitTests/AutoPile.UnitTests.csproj @@ -0,0 +1,27 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + diff --git a/AutoPile.UnitTests/PaymentServiceTests.cs b/AutoPile.UnitTests/PaymentServiceTests.cs new file mode 100644 index 0000000..7efa1e9 --- /dev/null +++ b/AutoPile.UnitTests/PaymentServiceTests.cs @@ -0,0 +1,55 @@ +using AutoPile.DATA.Data; +using AutoPile.DOMAIN.DTOs.Requests; +using AutoPile.DOMAIN.Models.Entities; +using AutoPile.SERVICE.Services; +using Microsoft.EntityFrameworkCore; +using MongoDB.Bson; +using Moq; +using Stripe; +using FluentAssertions; +using Xunit; + +namespace AutoPile.UnitTests +{ + public class PaymentServiceTests + { + private readonly Mock _mongoDbContextMock; + private readonly Mock _stripeServiceMock; + private readonly PaymentService _paymentService; + + public PaymentServiceTests() + { + _mongoDbContextMock = new Mock(new DbContextOptionsBuilder().Options); + _stripeServiceMock = new Mock(); + _paymentService = new PaymentService(_mongoDbContextMock.Object, _stripeServiceMock.Object); + } + + [Fact] + public async Task CreatePaymentIntent_ValidItemListInput_ReturnSecretObject() + { + // Arrange + var productId = ObjectId.GenerateNewId(); + var product = new DOMAIN.Models.Entities.Product { Id = productId, Price = 100 }; + var item = new Item { ProductId = productId.ToString(), Quantity = 2 }; + var itemList = new PaymentIntentCreate { Items = [item] }; + + _mongoDbContextMock.Setup(x => x.Products.FindAsync(productId)) + .ReturnsAsync(product); + + var mockPaymentIntent = new PaymentIntent + { + ClientSecret = "mock_client_secret_test" + }; + + _stripeServiceMock.Setup(x => x.CreatePaymentIntentAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(mockPaymentIntent); + + // Act + var result = await _paymentService.PaymentIntentCreateAsync(itemList); + + // Assert + Assert.NotNull(result); + result.ClientSecret.Should().Be("mock_client_secret_test"); + } + } +} \ No newline at end of file diff --git a/AutoPile.UnitTests/ReviewServiceTests.cs b/AutoPile.UnitTests/ReviewServiceTests.cs new file mode 100644 index 0000000..dee53ac --- /dev/null +++ b/AutoPile.UnitTests/ReviewServiceTests.cs @@ -0,0 +1,201 @@ +using AutoMapper; +using AutoPile.DATA.Cache; +using AutoPile.DATA.Data; +using AutoPile.DOMAIN.DTOs.Requests; +using AutoPile.DOMAIN.DTOs.Responses; +using AutoPile.DOMAIN.Interface; +using AutoPile.DOMAIN.Models.Entities; +using AutoPile.SERVICE.Services; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using MongoDB.Bson; +using MongoDB.Driver; +using Moq; +using System.Linq.Expressions; +using Xunit; + +namespace AutoPile.UnitTests +{ + public class ReviewServiceTests + { + private readonly Mock _mapperMock; + private readonly Mock _blobServiceMock; + private readonly Mock _mongoDbContextMock; + private readonly Mock _managementDbContextMock; + private readonly Mock _reviewsCacheMock; + private readonly ReviewService _reviewService; + private readonly Mock> _reviewsDbSetMock; + private readonly Mock> _productsDbSetMock; + private readonly Mock> _usersDbSetMock; + + public ReviewServiceTests() + { + _mapperMock = new Mock(); + _blobServiceMock = new Mock(); + _mongoDbContextMock = new Mock(new DbContextOptionsBuilder().Options); + _managementDbContextMock = new Mock(new DbContextOptions()); + _reviewsCacheMock = new Mock(); + _reviewsDbSetMock = new Mock>(); + _productsDbSetMock = new Mock>(); + _usersDbSetMock = new Mock>(); + + _mongoDbContextMock.Setup(x => x.Reviews).Returns(_reviewsDbSetMock.Object); + _mongoDbContextMock.Setup(x => x.Products).Returns(_productsDbSetMock.Object); + _managementDbContextMock.Setup(x => x.Users).Returns(_usersDbSetMock.Object); + + _reviewService = new ReviewService( + _mapperMock.Object, + _blobServiceMock.Object, + _mongoDbContextMock.Object, + _managementDbContextMock.Object, + _reviewsCacheMock.Object + ); + var userId = "testUserId"; + var user = new ApplicationUser { Id = userId }; + _managementDbContextMock.Setup(x => x.Users.FindAsync(userId)) + .ReturnsAsync(user); + } + + [Fact] + public async Task CreateReviewAsync_ValidInput_ReturnsReviewResponseDTO() + { + // Arrange + + var productId = ObjectId.GenerateNewId().ToString(); + + var reviewCreateDto = new ReviewCreateDTO + { + ProductId = productId, + Title = "Test Review", + Content = "Test Content", + Subtitle = "Test Subtitle", + Rating = 5 + }; + + var product = new Product { Id = ObjectId.Parse(productId) }; + + var review = new Review + { + Id = ObjectId.GenerateNewId(), + Title = reviewCreateDto.Title, + Content = reviewCreateDto.Content, + Subtitle = reviewCreateDto.Subtitle, + Rating = reviewCreateDto.Rating, + UserId = "testUserId", + ProductId = ObjectId.Parse(productId) + }; + + var reviewResponseDto = new ReviewResponseDTO + { + Id = review.Id.ToString(), + Title = review.Title, + Content = review.Content, + Subtitle = review.Subtitle, + Rating = review.Rating, + UserId = review.UserId, + ProductId = review.ProductId.ToString(), + CreatedAt = review.CreatedAt, + UpdatedAt = review.CreatedAt + }; + + _mongoDbContextMock.Setup(x => x.Products.FindAsync(ObjectId.Parse(productId))) + .ReturnsAsync(product); + + _mapperMock.Setup(x => x.Map(reviewCreateDto)) + .Returns(review); + + _mapperMock.Setup(x => x.Map(review)) + .Returns(reviewResponseDto); + + // Act + var result = await _reviewService.CreateReviewAsync(reviewCreateDto, "testUserId"); + + // Assert + Assert.NotNull(result); + result.Should().BeEquivalentTo(reviewResponseDto); + + _mongoDbContextMock.Verify(x => x.Reviews.AddAsync(review, default), Times.Once); + _mongoDbContextMock.Verify(x => x.SaveChangesAsync(default), Times.Once); + } + + [Fact] + public async Task GetReview_ValidInput_ReturnResponseDTO() + { + //Arrange + var productId = ObjectId.GenerateNewId(); + var review = new Review + { + Id = ObjectId.GenerateNewId(), + ProductId = productId, + Title = "Test Review", + Content = "Test Content", + Subtitle = "Test Subtitle", + UserId = "testUserId", + Rating = 5 + }; + + var reviewResponseDto = new ReviewResponseDTO + { + Id = review.Id.ToString(), + Title = review.Title, + Content = review.Content, + Subtitle = review.Subtitle, + Rating = review.Rating, + UserId = review.UserId, + ProductId = review.ProductId.ToString(), + CreatedAt = review.CreatedAt, + UpdatedAt = review.CreatedAt + }; + _mongoDbContextMock.Setup(x => x.Reviews.FindAsync(review.Id)).ReturnsAsync(review); + _mapperMock.Setup(x => x.Map(review)) + .Returns(reviewResponseDto); + + //Act + var result = await _reviewService.GetReviewByIdAsync(review.Id.ToString()); + + //Assert + result.Should().NotBeNull(); + result.Should().BeEquivalentTo(reviewResponseDto); + } + + [Fact] + public async Task DeleteReview_ValidInput_NoReturn() + { + //Arrange + var productId = ObjectId.GenerateNewId(); + var review = new Review + { + Id = ObjectId.GenerateNewId(), + ProductId = productId, + Title = "Test Review", + Content = "Test Content", + Subtitle = "Test Subtitle", + UserId = "testUserId", + Rating = 5, + ImageUrl = "test-image.jpg" + }; + + var reviewResponseDto = new ReviewResponseDTO + { + Id = review.Id.ToString(), + Title = review.Title, + Content = review.Content, + Subtitle = review.Subtitle, + Rating = review.Rating, + UserId = review.UserId, + ProductId = review.ProductId.ToString(), + CreatedAt = review.CreatedAt, + UpdatedAt = review.CreatedAt + }; + _mongoDbContextMock.Setup(x => x.Reviews.FindAsync(review.Id)).ReturnsAsync(review); + _mapperMock.Setup(x => x.Map(review)) + .Returns(reviewResponseDto); + //Act + await _reviewService.DeleteReviewAsync(review.Id.ToString(), "testUserId"); + + //Assert + _mongoDbContextMock.Verify(x => x.Remove(review), Times.Once); + _blobServiceMock.Verify(x => x.DeleteImageAsync(review.ImageUrl), Times.Once); + } + } +} \ No newline at end of file diff --git a/AutoPile.sln b/AutoPile.sln index a963626..c5c5660 100644 --- a/AutoPile.sln +++ b/AutoPile.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoPile.SERVICE", "AutoPil EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoPile.API", "AutoPile.API\AutoPile.API.csproj", "{3DDF33AA-990E-4BA9-AC47-29E2EB706D0C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoPile.UnitTests", "AutoPile.UnitTests\AutoPile.UnitTests.csproj", "{D8051ACC-12FD-4ED0-BE59-5D937D0A1A84}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {3DDF33AA-990E-4BA9-AC47-29E2EB706D0C}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DDF33AA-990E-4BA9-AC47-29E2EB706D0C}.Release|Any CPU.ActiveCfg = Release|Any CPU {3DDF33AA-990E-4BA9-AC47-29E2EB706D0C}.Release|Any CPU.Build.0 = Release|Any CPU + {D8051ACC-12FD-4ED0-BE59-5D937D0A1A84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8051ACC-12FD-4ED0-BE59-5D937D0A1A84}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8051ACC-12FD-4ED0-BE59-5D937D0A1A84}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8051ACC-12FD-4ED0-BE59-5D937D0A1A84}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE