Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,4 @@ _Pvt_Extensions

# FAKE - F# Make
.fake/
.angular/
30 changes: 30 additions & 0 deletions AJGRE.Application.Tests/AJGRE.Application.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="8.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

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

</Project>
73 changes: 73 additions & 0 deletions AJGRE.Application.Tests/Services/SanctionedEntityServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using AJGRE.Application.DTOs;
using AJGRE.Application.Services;
using ajgre_technical_interview.Models;
using AJRE.Domain.Interfaces;
using FluentAssertions;
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace AJGRE.Application.Tests.Services
{
public class SanctionedEntityServiceTests
{
[Fact]
public async Task ListAllAsync_Maps_Entities_To_EntityDto()
{
var entities = new List<SanctionedEntity>
{
new SanctionedEntity {Name = "A", Domicile = "X", Accepted = true },
new SanctionedEntity { Name = "B", Domicile = "Y", Accepted = false }
};
var mockRepo = new Mock<ISanctionedEntityRepository>();
mockRepo.Setup(r => r.GetSanctionedEntitiesAsync()).ReturnsAsync(entities);
var svc = new SanctionedEntityService(mockRepo.Object);

var dtos = (await svc.ListAllAsync()).ToList();

dtos.Should().HaveCount(2);
dtos[0].Id.Should().Be(entities[0].Id);
dtos[0].Name.Should().Be("A");
}

[Fact]
public async Task AddAsync_Throws_When_Name_Or_Domicile_Empty()
{
var mockRepo = new Mock<ISanctionedEntityRepository>();
var svc = new SanctionedEntityService(mockRepo.Object);

Func<Task> act = () => svc.AddAsync(new EntityDto(Guid.Empty, "", "", true));
await act.Should().ThrowAsync<ArgumentException>();
}

[Fact]
public async Task AddAsync_Throws_When_Duplicate()
{
var dto = new EntityDto(Guid.Empty, "Name", "Dom", true);
var mockRepo = new Mock<ISanctionedEntityRepository>();
mockRepo.Setup(r => r.ExistsAsync(dto.Name, dto.Domicile)).ReturnsAsync(true);
var svc = new SanctionedEntityService(mockRepo.Object);

Func<Task> act = () => svc.AddAsync(dto);
await act.Should().ThrowAsync<InvalidOperationException>();
}

[Fact]
public async Task AddAsync_Creates_Entity_When_Valid()
{
var dto = new EntityDto(Guid.Empty, "Name", "Dom", true);
var mockRepo = new Mock<ISanctionedEntityRepository>();
mockRepo.Setup(r => r.ExistsAsync(dto.Name, dto.Domicile)).ReturnsAsync(false);
mockRepo.Setup(r => r.CreateSanctionedEntityAsync(It.IsAny<SanctionedEntity>()));
var svc = new SanctionedEntityService(mockRepo.Object);

await svc.AddAsync(dto);

mockRepo.Verify(r => r.CreateSanctionedEntityAsync(
It.Is<SanctionedEntity>(e => e.Name == dto.Name && e.Domicile == dto.Domicile)), Times.Once);
}
}
}
1 change: 1 addition & 0 deletions AJGRE.Application.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
13 changes: 13 additions & 0 deletions AJGRE.Application/AJGRE.Application.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<ItemGroup>
<ProjectReference Include="..\AJRE.Domain\AJGRE.Domain.csproj" />
</ItemGroup>

</Project>
15 changes: 15 additions & 0 deletions AJGRE.Application/DTOs/EntityDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AJGRE.Application.DTOs
{
public record EntityDto(
Guid Id,
string Name,
string Domicile,
bool Accepted
);
}
16 changes: 16 additions & 0 deletions AJGRE.Application/Interfaces/ISanctionedEntityService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using AJGRE.Application.DTOs;
using ajgre_technical_interview.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AJGRE.Application.Interfaces
{
public interface ISanctionedEntityService
{
Task<IEnumerable<EntityDto>> ListAllAsync();
Task<SanctionedEntity> AddAsync(EntityDto dto);
}
}
39 changes: 39 additions & 0 deletions AJGRE.Application/Services/SanctionedEntityService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using AJGRE.Application.DTOs;
using AJGRE.Application.Interfaces;
using ajgre_technical_interview.Models;
using AJRE.Domain.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AJGRE.Application.Services
{
public class SanctionedEntityService : ISanctionedEntityService
{
private readonly ISanctionedEntityRepository _repo;
public SanctionedEntityService(ISanctionedEntityRepository repo) => _repo = repo;

public async Task<IEnumerable<EntityDto>> ListAllAsync()
=> (await _repo.GetSanctionedEntitiesAsync())
.Select(e => new EntityDto(e.Id, e.Name, e.Domicile, e.Accepted));

public async Task<SanctionedEntity> AddAsync(EntityDto dto)
{
if (string.IsNullOrWhiteSpace(dto.Name) || string.IsNullOrWhiteSpace(dto.Domicile))
throw new ArgumentException("Name and Domicile are required.");

if (await _repo.ExistsAsync(dto.Name, dto.Domicile))
throw new InvalidOperationException("Duplicate entity.");

var entity = new SanctionedEntity
{
Name = dto.Name,
Domicile = dto.Domicile,
Accepted = dto.Accepted
};
return await _repo.CreateSanctionedEntityAsync(entity);
}
}
}
17 changes: 17 additions & 0 deletions AJGRE.Infrastructure/AJGRE.Infrastructure.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.5" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\AJRE.Domain\AJGRE.Domain.csproj" />
</ItemGroup>

</Project>
22 changes: 22 additions & 0 deletions AJGRE.Infrastructure/Configuration/DependencyInjection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using AJGRE.Infrastructure.Repository;
using AJRE.Domain.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AJGRE.Infrastructure.Configuration
{
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
{
services.AddSingleton<ISanctionedEntityRepository, SanctionedEntityRepository>();


return services;
}
}
}
51 changes: 51 additions & 0 deletions AJGRE.Infrastructure/Repository/SanctionedEntityRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using ajgre_technical_interview.Models;
using AJRE.Domain.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AJGRE.Infrastructure.Repository
{
public class SanctionedEntityRepository : ISanctionedEntityRepository
{
private static readonly IList<SanctionedEntity> SanctionedEntities = new List<SanctionedEntity>
{
new SanctionedEntity { Name = "Forbidden Company", Domicile = "Mars", Accepted = false },
new SanctionedEntity { Name = "Allowed Company", Domicile = "Venus", Accepted = true },
new SanctionedEntity { Name = "Good Ltd", Domicile = "Saturn", Accepted = true },
new SanctionedEntity { Name = "Evil Plc", Domicile = "Venus", Accepted = false }
};

public async Task<IList<SanctionedEntity>> GetSanctionedEntitiesAsync()
{
var entities = SanctionedEntities
.OrderBy(e => e.Name)
.ThenBy(e => e.Domicile)
.ToList();

return await Task.FromResult(entities);
}

public async Task<SanctionedEntity> GetSanctionedEntityByIdAsync(Guid id)
{
return await Task.FromResult(
SanctionedEntities.First(e => e.Id.Equals(id)));
}

public async Task<SanctionedEntity> CreateSanctionedEntityAsync(SanctionedEntity sanctionedEntity)
{
SanctionedEntities.Add(sanctionedEntity);
return await Task.FromResult(sanctionedEntity);
}

public async Task<bool> ExistsAsync(string name, string domicile)
{
var exists = SanctionedEntities.Any(e =>
e.Name.Equals(name, StringComparison.OrdinalIgnoreCase) &&
e.Domicile.Equals(domicile, StringComparison.OrdinalIgnoreCase));
return await Task.FromResult(exists);
}
}
}
32 changes: 32 additions & 0 deletions AJGRE.WebApi.Tests/AJGRE.WebApi.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="8.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.7" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ajgre-technical-interview.csproj" />
<ProjectReference Include="..\AJGRE.Application\AJGRE.Application.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using AJGRE.Application.DTOs;
using AJGRE.Application.Interfaces;
using AJGRE.Application.Services;
using ajgre_technical_interview.Controllers;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;

namespace AJGRE.WebApi.Tests.Controllers
{
public class SanctionedEntitiesControllerTests
{
[Fact]
public async Task ListAll_Returns_Ok()
{
var dtos = new List<EntityDto> { new EntityDto(System.Guid.NewGuid(), "X", "Y", true) };
var mockSvc = new Mock<ISanctionedEntityService>(MockBehavior.Strict);
mockSvc.Setup(s => s.ListAllAsync()).ReturnsAsync(dtos);
var ctrl = new SanctionedEntitiesController(mockSvc.Object);

var result = await ctrl.ListAll();

result.Result.Should().BeOfType<OkObjectResult>();
(result.Result as OkObjectResult).Value.Should().BeEquivalentTo(dtos);
}
}
}
1 change: 1 addition & 0 deletions AJGRE.WebApi.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
9 changes: 9 additions & 0 deletions AJRE.Domain/AJGRE.Domain.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

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

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
{
public class SanctionedEntity
{
public Guid Id => Guid.NewGuid();
public Guid Id { get; } = Guid.NewGuid();
public string Name { get; set; } = string.Empty;
public string Domicile { get; set; } = string.Empty;
public bool Accepted { get; set; }


}
}
Loading