Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update orderdetail use case #154

Merged
merged 2 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ jobs:
name: "Create terraform destroy plan"
needs: [create-app]
runs-on: ubuntu-latest
environment: dev
steps:
- name: Create plan
uses: hashicorp/tfc-workflows-github/actions/create-run@v1.3.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@

namespace FIAP.TechChallenge.ByteMeBurger.Application.UseCases.Orders;

public class GetOrderDetailsUseCase(IOrderRepository repository) : IGetOrderDetailsUseCase
public class GetOrderDetailsUseCase(IOrderRepository repository, ICustomerRepository customerRepository)
: IGetOrderDetailsUseCase
{
public async Task<Order?> Execute(Guid id)
{
return id == Guid.Empty ? null : await repository.GetAsync(id);
var order = await GetOrder(id);
if (order is null) return null;

if (order.Customer is not null)
order.SetCustomer(await GetCustomer(order.Customer.Id)!);

return order;
}

private async Task<Order?> GetOrder(Guid id) => id == Guid.Empty ? null : await repository.GetAsync(id);

private Task<Customer?> GetCustomer(Guid id) => customerRepository.FindByIdAsync(id);
}
Original file line number Diff line number Diff line change
@@ -1,51 +1,49 @@
using System.Security.Cryptography;
using FIAP.TechChallenge.ByteMeBurger.Domain.Entities;
using FIAP.TechChallenge.ByteMeBurger.Domain.Entities;
using FIAP.TechChallenge.ByteMeBurger.Domain.Interfaces;
using Amazon.CognitoIdentityProvider;
using Amazon.CognitoIdentityProvider.Model;
using FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Factory;
using FIAP.TechChallenge.ByteMeBurger.Domain.Base;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway;

public class CognitoUserManager : ICustomerRepository
public class CognitoUserManager(
ICognitoClientFactory cognitoClientFactory,
ILogger<CognitoUserManager> logger,
IOptions<CognitoSettings> settings)
: ICustomerRepository
{
private readonly IAmazonCognitoIdentityProvider _cognitoClient;
private readonly string _userPoolId;
private readonly string _clientId;

public CognitoUserManager(ICognitoClientFactory cognitoClientFactory, IOptions<CognitoSettings> settings)
{
_cognitoClient = cognitoClientFactory.CreateClient();
_userPoolId = settings.Value.UserPoolId;
_clientId = settings.Value.UserPoolClientId;
}
private readonly IAmazonCognitoIdentityProvider _cognitoClient = cognitoClientFactory.CreateClient();
private readonly string _userPoolId = settings.Value.UserPoolId;

public async Task<Customer?> FindByCpfAsync(string cpf)
{
try
{
logger.LogInformation("Fetching user with CPF {cpf}", cpf);
var response = await _cognitoClient.AdminGetUserAsync(new AdminGetUserRequest
{
UserPoolId = _userPoolId,
Username = cpf
Username = cpf,
});

var email = response.UserAttributes.First(attr => attr.Name == "email").Value;
var name = response.UserAttributes.First(attr => attr.Name == "name").Value;
var sub = response.UserAttributes.First(attr => attr.Name == "sub").Value;
var customer = new Customer(Guid.Parse(sub), cpf, name, email);
var attributes = response.UserAttributes;

return customer;
var email = attributes.First(attr => attr.Name == "email").Value;
var name = attributes.First(attr => attr.Name == "name").Value;
var sub = attributes.First(attr => attr.Name == "sub").Value;
return new Customer(Guid.Parse(sub), cpf, name, email);
}
catch (UserNotFoundException)
{
logger.LogWarning("Customer not found CPF {cpf}", cpf);
return null;
}
catch (Exception ex)
{
Console.WriteLine($"Error fetching user: {ex.Message}");
logger.LogError(ex, "Error fetching user");
throw;
}
}
Expand All @@ -54,6 +52,7 @@ public async Task<Customer> CreateAsync(Customer customer)
{
try
{
logger.LogInformation("Trying to create new customer");
var signUpResponse = await _cognitoClient.AdminCreateUserAsync(new AdminCreateUserRequest()
{
Username = customer.Cpf,
Expand All @@ -65,58 +64,55 @@ public async Task<Customer> CreateAsync(Customer customer)
}
});

customer.Id = Guid.Parse(signUpResponse.User.Attributes.First(a=>a.Name is "sub").Value);
logger.LogInformation("Customer successfully created.");
customer.Id = Guid.Parse(signUpResponse.User.Attributes.First(a => a.Name is "sub").Value);
return customer;
}
catch (UsernameExistsException ex)
{
Console.WriteLine($"Error registering user: {ex.Message}");
logger.LogWarning(ex, "There's already a customer using the provided CPF value");
throw new DomainException("There's already a customer using the provided CPF value.");
}
catch (Exception ex)
{
Console.WriteLine($"Error registering user: {ex.Message}");
logger.LogError(ex, "Error registering user");
throw;
}
}

private static string GenerateRandomPassword(int length)
public async Task<Customer?> FindByIdAsync(Guid id)
{
using var rng = RandomNumberGenerator.Create();
var characterSets = new[]
try
{
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"1234567890",
"!@#$%^&*()"
};
var allChars = string.Concat(characterSets);
var passwordChars = new char[length];
// Ensure the password contains at least one character from each character set
for (int i = 0; i < characterSets.Length && i < length; i++)
logger.LogInformation("Fetching user with Id {CustomerId}", id);
var response = await _cognitoClient.ListUsersAsync(new ListUsersRequest()
{
Filter = "sub=\"" + id + "\"",
UserPoolId = _userPoolId,
});

if (response.Users.Count > 0)
{
var attributes = response.Users[0].Attributes;

var email = attributes.First(attr => attr.Name == "email").Value;
var name = attributes.First(attr => attr.Name == "name").Value;
var sub = attributes.First(attr => attr.Name == "sub").Value;
var cpf = response.Users[0].Username;
return new Customer(Guid.Parse(sub), cpf, name, email);
}

return default;
}
catch (UserNotFoundException)
{
passwordChars[i] = GetRandomChar(characterSets[i], rng);
logger.LogWarning("User not found.");
return null;
}
// Fill the rest of the password with random characters
for (int i = characterSets.Length; i < length; i++)
catch (Exception ex)
{
passwordChars[i] = GetRandomChar(allChars, rng);
logger.LogError(ex, "Error fetching user");
throw;
}
// Shuffle the password to ensure randomness
passwordChars = passwordChars.OrderBy(_ => GetRandomInt(rng, int.MaxValue)).ToArray();
return new string(passwordChars);
}
private static char GetRandomChar(string chars, RandomNumberGenerator rng)
{
var bytes = new byte[4];
rng.GetBytes(bytes);
var index = BitConverter.ToUInt32(bytes, 0) % chars.Length;
return chars[(int)index];
}
private static int GetRandomInt(RandomNumberGenerator rng, int max)
{
var bytes = new byte[4];
rng.GetBytes(bytes);
return (int)(BitConverter.ToUInt32(bytes, 0) % max);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
</ItemGroup>

Expand Down
5 changes: 5 additions & 0 deletions src/FIAP.TechChallenge.ByteMeBurger.Domain/Entities/Order.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

public PaymentId? PaymentId { get; set; }

public Order()

Check warning on line 22 in src/FIAP.TechChallenge.ByteMeBurger.Domain/Entities/Order.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'TrackingCode' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
: base(Guid.NewGuid())
{
Created = DateTime.UtcNow;
Expand All @@ -31,14 +31,14 @@
Created = DateTime.UtcNow;
}

internal Order(Customer customer)

Check warning on line 34 in src/FIAP.TechChallenge.ByteMeBurger.Domain/Entities/Order.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'TrackingCode' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
: base(Guid.NewGuid())
{
Customer = customer;
Created = DateTime.UtcNow;
}

internal Order(Guid id, Customer customer)

Check warning on line 41 in src/FIAP.TechChallenge.ByteMeBurger.Domain/Entities/Order.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'TrackingCode' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
: base(id)
{
Customer = customer;
Expand Down Expand Up @@ -99,6 +99,11 @@
Update();
}

public void SetCustomer(Customer customer)
{
Customer = customer;
}

public void ConfirmPayment()
{
if (Status != OrderStatus.PaymentPending)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ public interface ICustomerRepository
Task<Customer?> FindByCpfAsync(string cpf);

Task<Customer> CreateAsync(Customer customer);

Task<Customer?> FindByIdAsync(Guid id);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Data;
using System.Diagnostics.CodeAnalysis;
using Dapper;
using FIAP.TechChallenge.ByteMeBurger.Domain.Entities;
using FIAP.TechChallenge.ByteMeBurger.Domain.Interfaces;
Expand Down Expand Up @@ -44,4 +45,11 @@ public async Task<Customer> CreateAsync(Customer customer)

return customer;
}

[ExcludeFromCodeCoverage]
[Obsolete("will be removed soon")]
public Task<Customer?> FindByIdAsync(Guid id)
{
throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,9 @@ public Task<Customer> CreateAsync(Customer customer)
_customers.Add(customer);
return Task.FromResult(customer);
}

public Task<Customer?> FindByIdAsync(Guid id)
{
throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public async Task<ReadOnlyCollection<Order>> GetAllAsync()
}
else
{
order = new Order(orderListDto.Id, customerDto.FromDtoToEntity(), (OrderStatus)orderListDto.Status,
order = new Order(orderListDto.Id, new Customer(customerDto.Id), (OrderStatus)orderListDto.Status,
new OrderTrackingCode(orderListDto.TrackingCode), orderListDto.Created,
orderListDto.Updated);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using FIAP.TechChallenge.ByteMeBurger.Application.UseCases.Orders;
using FIAP.TechChallenge.ByteMeBurger.Domain.Interfaces;

namespace FIAP.TechChallenge.ByteMeBurger.Application.Test.UseCases.Orders;

[TestSubject(typeof(GetOrderDetailsUseCase))]
public class GetOrderDetailsUseCaseTest
{
private readonly Mock<IOrderRepository> _orderRepository;
private readonly Mock<ICustomerRepository> _customerRepository;
private readonly IGetOrderDetailsUseCase _useCase;

public GetOrderDetailsUseCaseTest()
{
_orderRepository = new Mock<IOrderRepository>();
_customerRepository = new Mock<ICustomerRepository>();
_useCase = new GetOrderDetailsUseCase(_orderRepository.Object, _customerRepository.Object);
}

[Fact]
public async Task Execute_ShouldReturnOrder_WhenOrderExists()
{
// Arrange
var orderId = Guid.NewGuid();
var order = new Order(orderId, null);
_orderRepository.Setup(r => r.GetAsync(orderId)).ReturnsAsync(order);

// Act
var result = await _useCase.Execute(orderId);

// Assert
using var scope = new AssertionScope();
result.Should().NotBeNull();
result.Id.Should().Be(orderId);
_customerRepository.Verify(c => c.FindByIdAsync(It.IsAny<Guid>()), Times.Never());
}

[Fact]
public async Task Execute_ShouldReturnNull_WhenOrderDoesNotExist()
{
// Arrange
var orderId = Guid.NewGuid();
_orderRepository.Setup(r => r.GetAsync(orderId)).ReturnsAsync((Order?)null);

// Act
var result = await _useCase.Execute(orderId);

// Assert
result.Should().BeNull();
_customerRepository.Verify(c => c.FindByIdAsync(It.IsAny<Guid>()), Times.Never());
}

[Fact]
public async Task Execute_ShouldSetCustomer_WhenCustomerExists()
{
// Arrange
var orderId = Guid.NewGuid();
var customerId = Guid.NewGuid();
var order = new Order(orderId, new Customer(customerId));
var customer = new Customer(customerId, "82227621095", "John Doe", "email@gmail.com");

_orderRepository.Setup(r => r.GetAsync(orderId)).ReturnsAsync(order);
_customerRepository.Setup(c => c.FindByIdAsync(customerId)).ReturnsAsync(customer);

// Act
var result = await _useCase.Execute(orderId);

// Assert
result.Should().NotBeNull();
result.Customer.Should().NotBeNull();
result.Customer.Should().Be(customer);
}

[Fact]
public async Task Execute_ShouldReturnNull_WhenOrderIdIsEmpty()
{
// Act
var result = await _useCase.Execute(Guid.Empty);

// Assert
result.Should().BeNull();
}
}
Loading
Loading