Skip to content
This repository was archived by the owner on Mar 17, 2026. It is now read-only.
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
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Threading;
using System.Threading.Tasks;
using FakeItEasy;
using Microsoft.AspNetCore.Mvc;
using TodoList.Api.Controllers;
using TodoList.Services.Modules.TodoListModule;
using TodoList.Services.Modules.TodoListModule.Models;
using TodoList.Services.Modules.TodoListModule.ViewModels;
using TodoList.Services.Shared;
using Xunit;

namespace TodoList.Api.UnitTests.ControllerTests.TodoItemsControllerTests
{
public class PostTodoItemTests
{
[Fact]
public async Task ShouldReturnCreatedItemWhenValidInputPassed()
{
// Arrange
var cancellationToken = CancellationToken.None;
var description = "Test";
var todoItemModel = new CreateTodoItemModel { Description = description };
var itemCreated = new GetTodoItemViewModel { Description = description };
var serviceResult = new ServiceResult<GetTodoItemViewModel>();
serviceResult.ToResourceCreatedResult(itemCreated);

var fakeService = A.Fake<ITodoListService>();
A.CallTo(() => fakeService.CreateTodoItem(todoItemModel, cancellationToken))
.Returns(Task.FromResult(serviceResult));

var controller = new TodoItemsController(fakeService);

// Act
var actionResult = await controller.PostTodoItem(todoItemModel, cancellationToken);

// Assert
A.CallTo(() => fakeService.CreateTodoItem(todoItemModel, cancellationToken)).MustHaveHappenedOnceExactly();
var createdAtActionResult = Assert.IsType<CreatedAtActionResult>(actionResult);
Assert.Equal(nameof(controller.GetTodoItem), createdAtActionResult.ActionName);
Assert.Equal(itemCreated, createdAtActionResult.Value);
}

[Fact]
public async Task ShoudlReturnBadRequestWhenInvalidInputPassed()
{
// Arrange
var cancellationToken = CancellationToken.None;
CreateTodoItemModel todoItemModel = null;

var serviceResult = new ServiceResult<GetTodoItemViewModel>();
serviceResult.BadRequestWithMessage("Description is required");

var fakeService = A.Fake<ITodoListService>();
A.CallTo(() => fakeService.CreateTodoItem(todoItemModel, cancellationToken))
.Returns(Task.FromResult(serviceResult));

var controller = new TodoItemsController(fakeService);

// Act
var actionResult = await controller.PostTodoItem(todoItemModel, cancellationToken);

// Assert
var badRequestResult = Assert.IsType<BadRequestObjectResult>(actionResult);
Assert.Equal("Description is required", badRequestResult.Value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FakeItEasy" Version="7.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
Expand Down
85 changes: 85 additions & 0 deletions Backend/TodoList.Api.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33927.249
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoList.Api", "TodoList.Api\TodoList.Api.csproj", "{065CE954-2618-4A24-A613-9C336C2154C6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoList.Api.UnitTests", "TodoList.Api.UnitTests\TodoList.Api.UnitTests.csproj", "{2BBBFEED-85B6-4361-85B2-F9E08C86C55B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoList.Domain", "TodoList.Domain\TodoList.Domain.csproj", "{693E7B0C-DA2B-446E-B729-03FE4E1B184C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{81EDD5C8-EAE7-4964-80B5-6B90241C7028}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{739BBC78-14C8-425F-9627-8BB80757A021}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Presentations", "Presentations", "{54522759-2130-4896-822B-E4780B726E31}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{E05B50FF-13BB-495F-9B73-65A5683AE3C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoList.Infrastructure.Persistence.EFCore", "TodoList.Infrastructure.Persistence.EFCore\TodoList.Infrastructure.Persistence.EFCore.csproj", "{9A26C735-BF26-4E9C-BA1E-6BE5BDCF6A4F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoList.Services", "TodoList.Services\TodoList.Services.csproj", "{25041BFD-A612-4DDC-BAE9-217ACD3E8188}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoList.Infrastructure.Application.Services", "TodoList.Infrastructure.Application.Services\TodoList.Infrastructure.Application.Services.csproj", "{D259C88D-A212-4496-AF88-0CDF210C055C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoList.Infrastructure.Persistence.EFCore.UnitTests", "TodoList.Infrastructure.Persistence.EFCore.UnitTests\TodoList.Infrastructure.Persistence.EFCore.UnitTests.csproj", "{4E87ADC6-EE79-4793-84AF-A5F4530C73D1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoList.Infrastructure.Application.Services.UnitTests", "TodoList.Infrastructure.Application.Services.UnitTests\TodoList.Infrastructure.Application.Services.UnitTests.csproj", "{164F6FFB-637D-4359-93E4-0D5E84A526BF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{065CE954-2618-4A24-A613-9C336C2154C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{065CE954-2618-4A24-A613-9C336C2154C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{065CE954-2618-4A24-A613-9C336C2154C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{065CE954-2618-4A24-A613-9C336C2154C6}.Release|Any CPU.Build.0 = Release|Any CPU
{2BBBFEED-85B6-4361-85B2-F9E08C86C55B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BBBFEED-85B6-4361-85B2-F9E08C86C55B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BBBFEED-85B6-4361-85B2-F9E08C86C55B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BBBFEED-85B6-4361-85B2-F9E08C86C55B}.Release|Any CPU.Build.0 = Release|Any CPU
{693E7B0C-DA2B-446E-B729-03FE4E1B184C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{693E7B0C-DA2B-446E-B729-03FE4E1B184C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{693E7B0C-DA2B-446E-B729-03FE4E1B184C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{693E7B0C-DA2B-446E-B729-03FE4E1B184C}.Release|Any CPU.Build.0 = Release|Any CPU
{9A26C735-BF26-4E9C-BA1E-6BE5BDCF6A4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A26C735-BF26-4E9C-BA1E-6BE5BDCF6A4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A26C735-BF26-4E9C-BA1E-6BE5BDCF6A4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A26C735-BF26-4E9C-BA1E-6BE5BDCF6A4F}.Release|Any CPU.Build.0 = Release|Any CPU
{25041BFD-A612-4DDC-BAE9-217ACD3E8188}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{25041BFD-A612-4DDC-BAE9-217ACD3E8188}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25041BFD-A612-4DDC-BAE9-217ACD3E8188}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25041BFD-A612-4DDC-BAE9-217ACD3E8188}.Release|Any CPU.Build.0 = Release|Any CPU
{D259C88D-A212-4496-AF88-0CDF210C055C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D259C88D-A212-4496-AF88-0CDF210C055C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D259C88D-A212-4496-AF88-0CDF210C055C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D259C88D-A212-4496-AF88-0CDF210C055C}.Release|Any CPU.Build.0 = Release|Any CPU
{4E87ADC6-EE79-4793-84AF-A5F4530C73D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E87ADC6-EE79-4793-84AF-A5F4530C73D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E87ADC6-EE79-4793-84AF-A5F4530C73D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E87ADC6-EE79-4793-84AF-A5F4530C73D1}.Release|Any CPU.Build.0 = Release|Any CPU
{164F6FFB-637D-4359-93E4-0D5E84A526BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{164F6FFB-637D-4359-93E4-0D5E84A526BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{164F6FFB-637D-4359-93E4-0D5E84A526BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{164F6FFB-637D-4359-93E4-0D5E84A526BF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{065CE954-2618-4A24-A613-9C336C2154C6} = {54522759-2130-4896-822B-E4780B726E31}
{2BBBFEED-85B6-4361-85B2-F9E08C86C55B} = {81EDD5C8-EAE7-4964-80B5-6B90241C7028}
{693E7B0C-DA2B-446E-B729-03FE4E1B184C} = {E05B50FF-13BB-495F-9B73-65A5683AE3C2}
{9A26C735-BF26-4E9C-BA1E-6BE5BDCF6A4F} = {739BBC78-14C8-425F-9627-8BB80757A021}
{25041BFD-A612-4DDC-BAE9-217ACD3E8188} = {E05B50FF-13BB-495F-9B73-65A5683AE3C2}
{D259C88D-A212-4496-AF88-0CDF210C055C} = {739BBC78-14C8-425F-9627-8BB80757A021}
{4E87ADC6-EE79-4793-84AF-A5F4530C73D1} = {81EDD5C8-EAE7-4964-80B5-6B90241C7028}
{164F6FFB-637D-4359-93E4-0D5E84A526BF} = {81EDD5C8-EAE7-4964-80B5-6B90241C7028}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {061F7879-5D14-4C5E-A4A9-782C6FEB1132}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

namespace TodoList.Api.Configurations
{
public static class ApiVersioningAndSwaggerConfiguration
{
public static void AddApiVersioningAndSwagger(this IServiceCollection services)
{
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
});

services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TodoList.Api" });
});

services.AddSwaggerGen();

}

public static IApplicationBuilder UseSwaggerEndpoint(this IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoList.Api v1"));
return app;
}
}
}
30 changes: 30 additions & 0 deletions Backend/TodoList.Api/Configurations/CorsConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace TodoList.Api.Configurations
{
public static class CorsConfiguration
{
public static IServiceCollection AddCorsPolicies(this IServiceCollection services, string policyName)
{
services.AddCors(options =>
{
options.AddPolicy(policyName,
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});

return services;
}

public static IApplicationBuilder UseCorsPolicy(this IApplicationBuilder applicationBuilder, string poliocyName)
{
applicationBuilder.UseCors(poliocyName);
return applicationBuilder;
}
}
}
18 changes: 18 additions & 0 deletions Backend/TodoList.Api/Configurations/InfrastructureConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Microsoft.Extensions.DependencyInjection;
using TodoList.Infrastructure.Application.Services.Extensions;
using TodoList.Infrastructure.Persistence.EFCore.Extensions;

namespace TodoList.Api.Configurations
{
public static class InfrastructureConfiguration
{
public static void AddApplicationServices(this IServiceCollection services)
{
services.AddAppServices();
}
public static void AddEfCoreAsPersistence(this IServiceCollection services)
{
services.AddInMemoryPersistence();
}
}
}
8 changes: 8 additions & 0 deletions Backend/TodoList.Api/Controllers/ApiControllerBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Microsoft.AspNetCore.Mvc;

namespace TodoList.Api.Controllers
{
public abstract class ApiControllerBase: ControllerBase
{
}
}
74 changes: 74 additions & 0 deletions Backend/TodoList.Api/Controllers/TodoItemsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using TodoList.Services.Modules.TodoListModule;
using TodoList.Services.Modules.TodoListModule.Models;
using TodoList.Services.Shared;

namespace TodoList.Api.Controllers
{

[ApiVersion("1")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class TodoItemsController : ApiControllerBase
{
private readonly ITodoListService todoListService;

public TodoItemsController(ITodoListService todoListService)
{
this.todoListService = todoListService;
}

// GET: api/TodoItems/...
[HttpGet]
public async Task<IActionResult> GetTodoItem(CancellationToken token)
{
var result = await this.todoListService.GetItems(token).ConfigureAwait(false);
return Ok(result);
}

[HttpGet("{id}")]
public async Task<IActionResult> GetTodoItem(Guid id, CancellationToken token)
{
var result = await this.todoListService.GetItem(id, token).ConfigureAwait(false);

if (result.ResultCode == ResultCode.NotFound)
{
return NotFound();
}

return Ok(result);
}

// POST: api/TodoItems
[HttpPost]
public async Task<IActionResult> PostTodoItem(CreateTodoItemModel todoItem, CancellationToken token)
{
var result = await this.todoListService.CreateTodoItem(todoItem, token).ConfigureAwait(false);
if (result.ResultCode == ResultCode.Created)
{
return CreatedAtAction(nameof(GetTodoItem), new { id = result.Result.Id }, result.Result);
}

return BadRequest(result.Message);

}

// PUT: api/TodoItems/...
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(Guid id, UpdateTodoItemModel todoItem, CancellationToken token)
{
var result = await this.todoListService.UpdateTodoItemStatus(id, todoItem, token).ConfigureAwait(false);

if (result.ResultCode == ResultCode.NotFound)
{
return NotFound();
}

return NoContent();
}
}
}
14 changes: 14 additions & 0 deletions Backend/TodoList.Api/Models/ApiErrorResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;

namespace TodoList.Api.Models
{
public class ApiErrorResponse
{
public string Type { get; set; }
public string Title { get; set; }
public string Detail { get; set; }
public int Status { get; set; }
public string Instance { get; set; }
public IEnumerable<Dictionary<string, string[]>> Errors { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
//"launchUrl": "swagger",
"launchUrl": "api/todoitems",
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
Expand All @@ -22,8 +21,7 @@
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
//"launchUrl": "swagger",
"launchUrl": "api/todoitems",
"launchUrl": "swagger",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
Expand Down
Loading