Skip to content
This repository has been archived by the owner on Jul 9, 2024. It is now read-only.

Commit

Permalink
Implemented project reading.
Browse files Browse the repository at this point in the history
  • Loading branch information
Utar94 committed Apr 26, 2024
1 parent 7dbec26 commit 354a02d
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 0 deletions.
12 changes: 12 additions & 0 deletions backend/src/Logitar.Master.Application/BadRequestException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Logitar.Master.Contracts.Errors;

namespace Logitar.Master.Application;

public abstract class BadRequestException : Exception
{
public abstract Error Error { get; }

protected BadRequestException(string? message = null, Exception? innerException = null) : base(message, innerException)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Logitar.Master.Contracts.Projects;
using MediatR;

namespace Logitar.Master.Application.Projects.Queries;

public record ReadProjectQuery(Guid? Id, string? UniqueKey) : IRequest<Project?>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Logitar.Master.Contracts.Projects;
using MediatR;

namespace Logitar.Master.Application.Projects.Queries;

internal class ReadProjectQueryHandler : IRequestHandler<ReadProjectQuery, Project?>
{
private readonly IProjectQuerier _projectQuerier;

public ReadProjectQueryHandler(IProjectQuerier projectQuerier)
{
_projectQuerier = projectQuerier;
}

public async Task<Project?> Handle(ReadProjectQuery query, CancellationToken cancellationToken)
{
Dictionary<Guid, Project> projects = new(capacity: 2);

if (query.Id.HasValue)
{
Project? project = await _projectQuerier.ReadAsync(query.Id.Value, cancellationToken);
if (project != null)
{
projects[project.Id] = project;
}
}

if (!string.IsNullOrWhiteSpace(query.UniqueKey))
{
Project? project = await _projectQuerier.ReadAsync(query.UniqueKey, cancellationToken);
if (project != null)
{
projects[project.Id] = project;
}
}

if (projects.Count > 1)
{
throw TooManyResultsException<Project>.ExpectedSingle(projects.Count);
}

return projects.Values.SingleOrDefault();
}
}
57 changes: 57 additions & 0 deletions backend/src/Logitar.Master.Application/TooManyResultsException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Logitar.Master.Contracts.Errors;

namespace Logitar.Master.Application;

public class TooManyResultsException : BadRequestException
{
private const string ErrorMessage = "There are too many results.";

public string TypeName
{
get => (string)Data[nameof(TypeName)]!;
private set => Data[nameof(TypeName)] = value;
}
public int ExpectedCount
{
get => (int)Data[nameof(ExpectedCount)]!;
private set => Data[nameof(ExpectedCount)] = value;
}
public int ActualCount
{
get => (int)Data[nameof(ActualCount)]!;
private set => Data[nameof(ActualCount)] = value;
}

public override Error Error
{
get
{
Error error = new(this.GetErrorCode(), ErrorMessage);
error.Add(nameof(ExpectedCount), ExpectedCount.ToString());
error.Add(nameof(ActualCount), ActualCount.ToString());
return error;
}
}

public TooManyResultsException(Type type, int expectedCount, int actualCount) : base(BuildMessage(type, expectedCount, actualCount))
{
TypeName = type.GetNamespaceQualifiedName();
ExpectedCount = expectedCount;
ActualCount = actualCount;
}

private static string BuildMessage(Type type, int expectedCount, int actualCount) => new ErrorMessageBuilder(ErrorMessage)
.AddData(nameof(TypeName), type.GetNamespaceQualifiedName())
.AddData(nameof(ExpectedCount), expectedCount)
.AddData(nameof(ActualCount), actualCount)
.Build();
}

public class TooManyResultsException<T> : TooManyResultsException
{
public TooManyResultsException(int expectedCount, int actualCount) : base(typeof(T), expectedCount, actualCount)
{
}

public static TooManyResultsException<T> ExpectedSingle(int actualCount) => new(expectedCount: 1, actualCount);
}
15 changes: 15 additions & 0 deletions backend/src/Logitar.Master/Controllers/ProjectController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Logitar.Master.Application.Projects.Commands;
using Logitar.Master.Application.Projects.Queries;
using Logitar.Master.Contracts.Projects;
using Logitar.Master.Extensions;
using MediatR;
Expand All @@ -24,5 +25,19 @@ public async Task<ActionResult<Project>> CreateAsync([FromBody] CreateProjectPay
return Created(BuildLocation(project), project);
}

[HttpGet("{id}")]
public async Task<ActionResult<Project>> ReadAsync(Guid id, CancellationToken cancellationToken)
{
Project? project = await _sender.Send(new ReadProjectQuery(id, UniqueKey: null), cancellationToken);
return project == null ? NotFound() : Ok(project);
}

[HttpGet("key:{uniqueKey}")]
public async Task<ActionResult<Project>> ReadAsync(string uniqueKey, CancellationToken cancellationToken)
{
Project? project = await _sender.Send(new ReadProjectQuery(Id: null, uniqueKey), cancellationToken);
return project == null ? NotFound() : Ok(project);
}

private Uri BuildLocation(Project project) => HttpContext.BuildLocation("projects/{id}", new Dictionary<string, string> { ["id"] = project.Id.ToString() });
}
5 changes: 5 additions & 0 deletions backend/src/Logitar.Master/Filters/ExceptionHandling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public override void OnException(ExceptionContext context)
context.Result = new BadRequestObjectResult(BuildError(validation));
context.ExceptionHandled = true;
}
else if (context.Exception is BadRequestException badRequest)
{
context.Result = new BadRequestObjectResult(badRequest.Error);
context.ExceptionHandled = true;
}
else if (context.Exception is ConflictException conflict)
{
context.Result = new ConflictObjectResult(conflict.Error);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Logitar.Master.Contracts.Projects;
using Logitar.Master.Domain.Projects;
using Microsoft.Extensions.DependencyInjection;

namespace Logitar.Master.Application.Projects.Queries;

[Trait(Traits.Category, Categories.Integration)]
public class ReadProjectQueryTests : IntegrationTests
{
private readonly IProjectRepository _projectRepository;

private readonly ProjectAggregate _master;
private readonly ProjectAggregate _portal;

public ReadProjectQueryTests() : base()
{
_projectRepository = ServiceProvider.GetRequiredService<IProjectRepository>();

_master = new(new UniqueKeyUnit("MASTER"));
_portal = new(new UniqueKeyUnit("PORTAL"));
}

public override async Task InitializeAsync()
{
await base.InitializeAsync();

await _projectRepository.SaveAsync([_master, _portal]);
}

[Fact(DisplayName = "It should return null when no project was found.")]
public async Task It_should_return_null_when_no_project_was_found()
{
ReadProjectQuery query = new(Id: Guid.Empty, UniqueKey: "test");
Project? project = await Mediator.Send(query);
Assert.Null(project);
}

[Fact(DisplayName = "It should return the project found by ID.")]
public async Task It_should_return_the_project_found_by_Id()
{
ReadProjectQuery query = new(Id: _master.Id.ToGuid(), UniqueKey: null);
Project? project = await Mediator.Send(query);
Assert.NotNull(project);
Assert.Equal(_master.Id.ToGuid(), project.Id);
}

[Fact(DisplayName = "It should return the project found by unique key.")]
public async Task It_should_return_the_project_found_by_unique_key()
{
ReadProjectQuery query = new(Id: null, UniqueKey: " portal ");
Project? project = await Mediator.Send(query);
Assert.NotNull(project);
Assert.Equal(_portal.Id.ToGuid(), project.Id);
}

[Fact(DisplayName = "It should throw TooManyResultsException when many projects were found.")]
public async Task It_should_throw_TooManyResultsException_when_many_projects_were_found()
{
ReadProjectQuery query = new(_master.Id.ToGuid(), UniqueKey: " portal ");
var exception = await Assert.ThrowsAsync<TooManyResultsException<Project>>(async () => await Mediator.Send(query));
Assert.Equal(1, exception.ExpectedCount);
Assert.Equal(2, exception.ActualCount);
}
}

0 comments on commit 354a02d

Please sign in to comment.