Skip to content

Commit

Permalink
feat: adding unit and integration tests for filter recipe
Browse files Browse the repository at this point in the history
  • Loading branch information
raffreitas committed Dec 27, 2024
1 parent cf97bb5 commit 4f89ede
Show file tree
Hide file tree
Showing 8 changed files with 364 additions and 0 deletions.
52 changes: 52 additions & 0 deletions tests/CommonTestsUtilities/Entities/RecipeBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Bogus;
using MyRecipes.Domain.Entities;
using MyRecipes.Domain.Enums;

namespace CommonTestsUtilities.Entities;
public class RecipeBuilder
{
public static IList<Recipe> Collecion(User user, uint count = 2)
{
var list = new List<Recipe>();

if (count == 0)
count = 1;

var recipeId = 1;

for (int i = 0; i < count; i++)
{
var fakeRecipe = Build(user);
fakeRecipe.Id = recipeId++;
list.Add(fakeRecipe);
}

return list;
}

public static Recipe Build(User user)
{
return new Faker<Recipe>()
.RuleFor(r => r.Id, () => 1)
.RuleFor(r => r.Title, (f) => f.Lorem.Word())
.RuleFor(r => r.CookingTime, (f) => f.PickRandom<CookingTime>())
.RuleFor(r => r.Difficulty, (f) => f.PickRandom<Difficulty>())
.RuleFor(r => r.Ingredients, (f) => f.Make(1, () => new Ingredient
{
Id = 1,
Item = f.Commerce.ProductName()
}))
.RuleFor(r => r.Instructions, (f) => f.Make(1, () => new Instruction
{
Id = 1,
Step = 1,
Text = f.Lorem.Paragraph()
}))
.RuleFor(r => r.DishTypes, (f) => f.Make(1, () => new MyRecipes.Domain.Entities.DishType
{
Id = 1,
Type = f.PickRandom<MyRecipes.Domain.Enums.DishType>()
}))
.RuleFor(r => r.UserId, () => user.Id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Moq;
using MyRecipes.Domain.Dtos;
using MyRecipes.Domain.Entities;
using MyRecipes.Domain.Repositories.Recipe;

namespace CommonTestsUtilities.Repositories;
public class RecipeReadOnlyRepositoryBuilder
{
private readonly Mock<IRecipeReadOnlyRepository> _repository;

public RecipeReadOnlyRepositoryBuilder(Mock<IRecipeReadOnlyRepository> repository) => _repository = repository;

public RecipeReadOnlyRepositoryBuilder Filter(User user, IList<Recipe> recipes)
{
_repository.Setup(r => r.Filter(user, It.IsAny<FilterRecipesDto>())).ReturnsAsync(recipes);
return this;
}

public RecipeReadOnlyRepositoryBuilder() => _repository = new Mock<IRecipeReadOnlyRepository>();

public IRecipeReadOnlyRepository Build() => _repository.Object;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Bogus;
using MyRecipes.Communication.Enums;
using MyRecipes.Communication.Requests;

namespace CommonTestsUtilities.Requests;
public class RequestFilterRecipeJsonBuilder
{
public static RequestFilterRecipeJson Build()
{
return new Faker<RequestFilterRecipeJson>()
.RuleFor(r => r.CookingTimes, f => f.Make(1, () => f.PickRandom<CookingTime>()))
.RuleFor(r => r.Difficulties, f => f.Make(1, () => f.PickRandom<Difficulty>()))
.RuleFor(r => r.DishTypes, f => f.Make(1, () => f.PickRandom<DishType>()))
.RuleFor(r => r.RecipeTitle_Ingredient, f => f.Lorem.Word());
}
}
61 changes: 61 additions & 0 deletions tests/UseCases.Tests/Recipe/Filter/FilterRecipeUseCaseTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using CommonTestsUtilities.Entities;
using CommonTestsUtilities.LoggedUser;
using CommonTestsUtilities.Mapper;
using CommonTestsUtilities.Repositories;
using CommonTestsUtilities.Requests;
using FluentAssertions;
using MyRecipes.Application.UseCases.Recipe.Filter;
using MyRecipes.Communication.Enums;
using MyRecipes.Exceptions;
using MyRecipes.Exceptions.ExceptionsBase;

namespace UseCases.Tests.Recipe.Filter;
public class FilterRecipeUseCaseTest
{
[Fact]
public async Task Success()
{
var (user, _) = UserBuilder.Build();

var request = RequestFilterRecipeJsonBuilder.Build();
var recipes = RecipeBuilder.Collecion(user);

var useCase = CreateUseCase(user, recipes);

var result = await useCase.Execute(request);

result.Should().NotBeNull();
result.Recipes.Should().NotBeNullOrEmpty();
result.Recipes.Should().HaveCount(recipes.Count);
}

[Fact]
public async Task Error_CookintTime_Invalid()
{
var (user, _) = UserBuilder.Build();

var recipes = RecipeBuilder.Collecion(user);

var request = RequestFilterRecipeJsonBuilder.Build();
request.CookingTimes.Add((CookingTime)99999);


var useCase = CreateUseCase(user, recipes);

Func<Task> act = async () => await useCase.Execute(request);

(await act.Should().ThrowAsync<ErrorOnValidationException>())
.Where(e => e.ErrorMessages.Count == 1 &&
e.ErrorMessages.Contains(ResourceMessagesExceptions.COOKING_TIME_NOT_SUPPORTED));
}

private static FilterRecipeUseCase CreateUseCase(MyRecipes.Domain.Entities.User user,
IList<MyRecipes.Domain.Entities.Recipe> recipes)
{
var mapper = MapperBuilder.Build();
var loggedUser = LoggedUserBuilder.Build(user);
var repository = new RecipeReadOnlyRepositoryBuilder().Filter(user, recipes).Build();

return new FilterRecipeUseCase(loggedUser, mapper, repository);
}
}
66 changes: 66 additions & 0 deletions tests/Validators.Tests/Recipe/Filter/FilterRecipeValidatorTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using CommonTestsUtilities.Requests;
using FluentAssertions;
using MyRecipes.Application.UseCases.Recipe.Filter;
using MyRecipes.Communication.Enums;
using MyRecipes.Exceptions;

namespace Validators.Tests.Recipe.Filter;
public class FilterRecipeValidatorTest
{
[Fact]
public void Success()
{
var validator = new FilterRecipeValidator();

var request = RequestFilterRecipeJsonBuilder.Build();

var result = validator.Validate(request);

result.IsValid.Should().BeTrue();
}

[Fact]
public void Error_Invalid_Cooking_Time()
{
var validator = new FilterRecipeValidator();

var request = RequestFilterRecipeJsonBuilder.Build();
request.CookingTimes.Add((CookingTime)1000);

var result = validator.Validate(request);

result.IsValid.Should().BeFalse();
result.Errors.Should().ContainSingle()
.And.Contain(e => e.ErrorMessage.Equals(ResourceMessagesExceptions.COOKING_TIME_NOT_SUPPORTED));
}

[Fact]
public void Error_Invalid_Difficulty()
{
var validator = new FilterRecipeValidator();

var request = RequestFilterRecipeJsonBuilder.Build();
request.Difficulties.Add((Difficulty)1000);

var result = validator.Validate(request);

result.IsValid.Should().BeFalse();
result.Errors.Should().ContainSingle()
.And.Contain(e => e.ErrorMessage.Equals(ResourceMessagesExceptions.DIFFICULTY_LEVEL_NOT_SUPPORTED));
}

[Fact]
public void Error_Invalid_DishType()
{
var validator = new FilterRecipeValidator();

var request = RequestFilterRecipeJsonBuilder.Build();
request.DishTypes.Add((DishType)1000);

var result = validator.Validate(request);

result.IsValid.Should().BeFalse();
result.Errors.Should().ContainSingle()
.And.Contain(e => e.ErrorMessage.Equals(ResourceMessagesExceptions.DISH_TYPE_NOT_SUPPORTED));
}
}
12 changes: 12 additions & 0 deletions tests/WebApi.Tests/CustomWebApplicationFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MyRecipes.Domain.Enums;
using MyRecipes.Infrastructure.DataAccess;

namespace WebApi.Tests;

public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
private MyRecipes.Domain.Entities.Recipe _recipe = default!;
private MyRecipes.Domain.Entities.User _user = default!;
private string _password = string.Empty;

Expand Down Expand Up @@ -43,10 +45,20 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
public string GetPassword() => _password;
public Guid GetUserIdentifier() => _user.UserIdentifier;

public string GetRecipeTitle() => _recipe.Title;
public Difficulty GetRecipeDifficulty() => _recipe.Difficulty!.Value;
public CookingTime GetRecipeCookingTime() => _recipe.CookingTime!.Value;
public IList<DishType> GetDishTypes() => [.. _recipe.DishTypes.Select(c => c.Type)];

private void StartDatabase(MyRecipesDbContext dbContext)
{
(_user, _password) = UserBuilder.Build();

_recipe = RecipeBuilder.Build(_user);

dbContext.Users.Add(_user);
dbContext.Recipes.Add(_recipe);

dbContext.SaveChanges();
}
}
44 changes: 44 additions & 0 deletions tests/WebApi.Tests/Recipe/Filter/FilterRecipeInvalidTokenTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using CommonTestsUtilities.Requests;
using CommonTestsUtilities.Tokens;
using FluentAssertions;
using System.Net;

namespace WebApi.Tests.Recipe.Filter;
public class FilterRecipeInvalidTokenTest : MyRecipesClassFixture
{
private const string METHOD = "recipes/filter";
public FilterRecipeInvalidTokenTest(CustomWebApplicationFactory factory) : base(factory)
{
}

[Fact]
public async Task Error_Token_Invalid()
{
var request = RequestFilterRecipeJsonBuilder.Build();

var response = await DoPost(method: METHOD, request: request, token: "invalidToken");

response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}

[Fact]
public async Task Error_Without_Token()
{
var request = RequestFilterRecipeJsonBuilder.Build();

var response = await DoPost(method: METHOD, request: request, token: string.Empty);

response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}

[Fact]
public async Task Error_Token_With_User_NotFound()
{
var request = RequestFilterRecipeJsonBuilder.Build();
var token = JwtTokenGeneratorBuilder.Build().Generate(Guid.NewGuid());

var response = await DoPost(method: METHOD, request: request, token: token);

response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
}
91 changes: 91 additions & 0 deletions tests/WebApi.Tests/Recipe/Filter/FilterRecipeTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using CommonTestsUtilities.Requests;
using CommonTestsUtilities.Tokens;
using FluentAssertions;
using MyRecipes.Communication.Requests;
using MyRecipes.Exceptions;
using System.Globalization;
using System.Net;
using System.Text.Json;
using WebApi.Tests.InlineData;

namespace WebApi.Tests.Recipe.Filter;
public class FilterRecipeTest : MyRecipesClassFixture
{
private const string METHOD = "recipes/filter";

private readonly Guid _userIdentifier;
private readonly string _recipeTitle;
private readonly MyRecipes.Domain.Enums.CookingTime _recipeCookingTime;
private readonly MyRecipes.Domain.Enums.Difficulty _recipeDifficultyLevel;
private readonly IList<MyRecipes.Domain.Enums.DishType> _recipeDishTypes;


public FilterRecipeTest(CustomWebApplicationFactory factory) : base(factory)
{
_userIdentifier = factory.GetUserIdentifier();

_recipeTitle = factory.GetRecipeTitle();
_recipeCookingTime = factory.GetRecipeCookingTime();
_recipeDifficultyLevel = factory.GetRecipeDifficulty();
_recipeDishTypes = factory.GetDishTypes();
}

[Fact]
public async Task Success()
{
var request = new RequestFilterRecipeJson
{
CookingTimes = [(MyRecipes.Communication.Enums.CookingTime)_recipeCookingTime],
Difficulties = [(MyRecipes.Communication.Enums.Difficulty)_recipeDifficultyLevel],
DishTypes = [.. _recipeDishTypes.Select(dishType => (MyRecipes.Communication.Enums.DishType)dishType)],
RecipeTitle_Ingredient = _recipeTitle,
};

var token = JwtTokenGeneratorBuilder.Build().Generate(_userIdentifier);

var response = await DoPost(method: METHOD, request: request, token: token);

response.StatusCode.Should().Be(HttpStatusCode.OK);

using var resopnseBody = await response.Content.ReadAsStreamAsync();
var responseData = await JsonDocument.ParseAsync(resopnseBody);

responseData.RootElement.GetProperty("recipes").EnumerateArray().Should().NotBeNullOrEmpty();
}

[Fact]
public async Task Success_NoContent()
{
var request = RequestFilterRecipeJsonBuilder.Build();
request.RecipeTitle_Ingredient = "recipe-not-found";

var token = JwtTokenGeneratorBuilder.Build().Generate(_userIdentifier);

var response = await DoPost(method: METHOD, request: request, token: token);

response.StatusCode.Should().Be(HttpStatusCode.NoContent);
}

[Theory]
[ClassData(typeof(CultureInlineDataTest))]
public async Task Error_CookingTime_Invalid(string culture)
{
var request = RequestFilterRecipeJsonBuilder.Build();
request.CookingTimes.Add((MyRecipes.Communication.Enums.CookingTime)999);
var token = JwtTokenGeneratorBuilder.Build().Generate(_userIdentifier);

var result = await DoPost(method: METHOD, request: request, token: token, culture: culture);

result.StatusCode.Should().Be(HttpStatusCode.BadRequest);

await using var responseBody = await result.Content.ReadAsStreamAsync();

var responseData = await JsonDocument.ParseAsync(responseBody);

var errors = responseData.RootElement.GetProperty("errors").EnumerateArray();

var expectedMessage = ResourceMessagesExceptions.ResourceManager.GetString("COOKING_TIME_NOT_SUPPORTED", new CultureInfo(culture));

errors.Should().HaveCount(1).And.Contain(c => c.GetString()!.Equals(expectedMessage));
}
}

0 comments on commit 4f89ede

Please sign in to comment.