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

feat: initial commit #22

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
25 changes: 25 additions & 0 deletions src/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
2 changes: 2 additions & 0 deletions src/.gitinore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/bin/
**/obj/
30 changes: 30 additions & 0 deletions src/Application.Tests/Application.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Bogus" Version="35.5.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="xunit" Version="2.5.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"/>
</ItemGroup>

<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Application\Application.csproj" />
</ItemGroup>

</Project>
74 changes: 74 additions & 0 deletions src/Application.Tests/Factories/CreateUserCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Application.Messages.Commands;
using Bogus;
using Domain.Enums;

namespace Application.Tests.Factories;

public class CreateUserCommandFactory
{
private readonly Faker<CreateUserCommand> _faker;
public CreateUserCommandFactory()
{
_faker = new Faker<CreateUserCommand>()
.RuleFor(u => u.Email, f => f.Internet.Email())
.RuleFor(u => u.Password, f => f.Internet.Password())
.RuleFor(u => u.Country, f => f.Address.Country())
.RuleFor(u => u.AccessType, f => f.PickRandom(new []{AccessTypeEnum.DTC, AccessTypeEnum.Employer}))
.RuleFor(u => u.FullName, f => f.Name.FullName())
.RuleFor(u => u.EmployerId, f => f.Random.Guid().ToString())
.RuleFor(u => u.BirthDate, f => f.Date.Past(30))
.RuleFor(u => u.Salary, f => f.Random.Decimal(30000, 100000));
}

public CreateUserCommandFactory WithEmail(string email)
{
_faker.RuleFor(x => x.Email, email);
return this;
}
public CreateUserCommand Create()
{
return _faker.Generate();
}

public CreateUserCommandFactory WithPassword(string password)
{
_faker.RuleFor(x => x.Password, password);
return this;
}
public CreateUserCommandFactory WithCountry(string country)
{
_faker.RuleFor(x => x.Country, country);
return this;
}

public CreateUserCommandFactory WithAccessType(string accessType)
{
_faker.RuleFor(x => x.AccessType, accessType);
return this;
}

public CreateUserCommandFactory WithFullName(string fullName)
{
_faker.RuleFor(x => x.FullName, fullName);
return this;
}

public CreateUserCommandFactory WithEmployerId(string employerId)
{
_faker.RuleFor(x => x.EmployerId, employerId);
return this;
}

public CreateUserCommandFactory WithBirthDate(DateTime? birthDate)
{
_faker.RuleFor(x => x.BirthDate, birthDate);
return this;
}

public CreateUserCommandFactory WithSalary(decimal? salary)
{
_faker.RuleFor(x => x.Salary, salary);
return this;
}

}
29 changes: 29 additions & 0 deletions src/Application.Tests/Factories/CsvContentFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Application.DTOs;
using Bogus;

namespace Application.Tests.Factories;


public static class CsvContentFactory
{
public static (string, List<CsvLineModel>) GenerateCsvContent(int numberOfLines)
{
var faker = new Faker<CsvLineModel>()
.RuleFor(u => u.Email, f => f.Internet.Email())
.RuleFor(u => u.FullName, f => f.Name.FullName())
.RuleFor(u => u.Country, f => f.Address.Country())
.RuleFor(u => u.BirthDate, f => f.Date.Past(30).ToString("MM/dd/yyyy"))
.RuleFor(u => u.Salary, f => f.Random.Decimal(30000, 100000));

var csvLines = new List<string> { "Email,FullName,Country,BirthDate,Salary" };
var models = new List<CsvLineModel>();
for (int i = 0; i < numberOfLines; i++)
{
var line = faker.Generate();
models.Add(line);
csvLines.Add($"{line.Email},{line.FullName},{line.Country},{line.BirthDate},{line.Salary}");
}

return (string.Join("\n", csvLines), models);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Application.Messages.Commands;
using Application.Messages.Handlers.Commands;
using Infrastructure.Records;
using Infrastructure.Services.Interfaces;
using Moq;

namespace Application.Tests.Messages.Handlers.Commands;


[Trait("Category", "Unit")]
public class CreateUserCommandHandlerTests
{
[Fact]
public async Task Handle_GivenValidUser_CreatesUserSuccessfully()
{
// Arrange
var userServiceClientMock = new Mock<IUserServiceClient>();
var handler = new CreateUserCommandHandler(userServiceClientMock.Object);
var command = new CreateUserCommand(email: "[email protected]",
fullName: "FullName",
password: "Password",
country: "Country",
accessType: "AccessType",
employerId: "EmployerId",
birthDate: DateTime.Now,
salary: 50000);

userServiceClientMock.Setup(client => client.CreateUserAsync(It.IsAny<CreateUserDto>(), It.IsAny<CancellationToken>())).ReturnsAsync(true);

// Act
var result = await handler.Handle(command, CancellationToken.None);

// Assert
Assert.True(result);
userServiceClientMock.Verify(client => client.CreateUserAsync(It.IsAny<CreateUserDto>(), It.IsAny<CancellationToken>()), Times.Once);
}

[Fact]
public async Task Handle_GivenInvalidUser_ReturnsFailure()
{
// Arrange
var userServiceClientMock = new Mock<IUserServiceClient>();
var handler = new CreateUserCommandHandler(userServiceClientMock.Object);
var command = new CreateUserCommand(email: "[email protected]",
fullName: "FullName",
password: "Password",
country: "Country",
accessType: "AccessType",
employerId: "EmployerId",
birthDate: DateTime.Now,
salary: 50000);

userServiceClientMock.Setup(client => client.CreateUserAsync(It.IsAny<CreateUserDto>(), It.IsAny<CancellationToken>())).ReturnsAsync(false);

// Act
var result = await handler.Handle(command, CancellationToken.None);

// Assert
Assert.False(result);
userServiceClientMock.Verify(client => client.CreateUserAsync(It.IsAny<CreateUserDto>(), It.IsAny<CancellationToken>()), Times.Once);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System.Net;
using System.Text;
using Application.Messages.Commands;
using Application.Messages.Handlers.Commands;
using Application.Messages.Queries;
using Application.Tests.Factories;
using Domain.Enums;
using Infrastructure.Records;
using MediatR;
using Microsoft.Extensions.Logging;
using Moq;
using Moq.Protected;

namespace Application.Tests.Messages.Handlers.Commands;

[Trait("Category", "Unit")]
public class ProcessEligibilityFileCommandHandlerTests
{
[Fact]
public async Task Handle_EmptyFile_ReturnsEmptyResult()
{
// Arrange
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var httpMessageHandlerMock = new Mock<HttpMessageHandler>();
var httpClient = new HttpClient(httpMessageHandlerMock.Object);
httpClientFactoryMock.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);

var emptyStream = new MemoryStream(Encoding.UTF8.GetBytes("")); // Empty content
httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StreamContent(emptyStream)
});

var loggerMock = new Mock<ILogger<ProcessEligibilityFileCommandHandler>>();
var mediatorMock = new Mock<IMediator>();

var handler = new ProcessEligibilityFileCommandHandler(httpClientFactoryMock.Object, loggerMock.Object, mediatorMock.Object);

// Act
var result = await handler.Handle(new ProcessEligibilityFileCommand("http://example.com/empty.csv", "employerName"), CancellationToken.None);

// Assert
Assert.Empty(result.ProcessedLines);
Assert.Empty(result.NonProcessedLines);
Assert.Empty(result.Errors);
}


[Fact]
public async Task Handle_DownloadFails_ThrowsException()
{
// Arrange
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var httpMessageHandlerMock = new Mock<HttpMessageHandler>();
var httpClient = new HttpClient(httpMessageHandlerMock.Object);
httpClientFactoryMock.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);

httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.NotFound // Simulate failure
});

var loggerMock = new Mock<ILogger<ProcessEligibilityFileCommandHandler>>();
var mediatorMock = new Mock<IMediator>();

var handler = new ProcessEligibilityFileCommandHandler(httpClientFactoryMock.Object, loggerMock.Object, mediatorMock.Object);

// Act & Assert
await Assert.ThrowsAsync<HttpRequestException>(() => handler.Handle(new ProcessEligibilityFileCommand("http://example.com/nonexistent.csv", "employerName"), CancellationToken.None));
}

[Theory]
[InlineData(5)]
[InlineData(15)]
[InlineData(54)]
[InlineData(13)]
public async Task Handle_NonEmptyFile_ProcessesDataCorrectly(int countOfRecords)
{
// Arrange
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var httpMessageHandlerMock = new Mock<HttpMessageHandler>();
var loggerMock = new Mock<ILogger<ProcessEligibilityFileCommandHandler>>();
var mediatorMock = new Mock<IMediator>();
var httpClient = new HttpClient(httpMessageHandlerMock.Object);
httpClientFactoryMock.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);

var (csvContent, lineModels) = CsvContentFactory.GenerateCsvContent(countOfRecords);
foreach (var lineModel in lineModels)
{
mediatorMock.Setup(x => x.Send(It.Is<GetUserByEmailQuery>(q => q.Email == lineModel.Email), It.IsAny<CancellationToken>()))
.ReturnsAsync(new UserDto()
{
Email = lineModel.Email,
Country = lineModel.Country,
Salary = lineModel.Salary,
AccessType = AccessTypeEnum.Employer,
Id = Guid.NewGuid().ToString(),
BirthDate = DateTime.Now.AddYears(-30),
FullName = lineModel.FullName,
EmployerId = Guid.NewGuid().ToString()
});
}
var csvStream = new MemoryStream(Encoding.UTF8.GetBytes(csvContent));
httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StreamContent(csvStream)
});


var handler = new ProcessEligibilityFileCommandHandler(httpClientFactoryMock.Object, loggerMock.Object, mediatorMock.Object);

// Act
var result = await handler.Handle(new ProcessEligibilityFileCommand("http://example.com/nonempty.csv", "employerName"), CancellationToken.None);

// Assert
Assert.NotEmpty(result.ProcessedLines);
Assert.Empty(result.NonProcessedLines);
Assert.Empty(result.Errors);
mediatorMock.Verify(x => x.Send(
It.IsAny<TerminateUnlistedUsersCommand>(),
It.IsAny<CancellationToken>()),
Times.Once);

}
}
Loading