diff --git a/Retroactiune.Core/Interfaces/ITokensService.cs b/Retroactiune.Core/Interfaces/ITokensService.cs index b6cba63..6062263 100644 --- a/Retroactiune.Core/Interfaces/ITokensService.cs +++ b/Retroactiune.Core/Interfaces/ITokensService.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Retroactiune.Core.Entities; +using Retroactiune.Core.Services; namespace Retroactiune.Core.Interfaces { @@ -22,5 +24,12 @@ namespace Retroactiune.Core.Interfaces /// A list of tokens to delete. /// The result of the delete operation. public Task DeleteTokens(IEnumerable tokenIds); + + /// + /// List and filters tokens. + /// + /// Filters object for filtering results. + /// A list of tokens matching the filters. + public Task> ListTokens(TokenListFilters filters); } } \ No newline at end of file diff --git a/Retroactiune.Core/Services/TokenListFilters.cs b/Retroactiune.Core/Services/TokenListFilters.cs new file mode 100644 index 0000000..cc2e9a4 --- /dev/null +++ b/Retroactiune.Core/Services/TokenListFilters.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; + +namespace Retroactiune.Core.Services +{ + /// + /// TokenListFilters is a data class representing the filters used to list tokens. + /// + public class TokenListFilters + { + /// + /// Id filters tokens by their ids. + /// + public IEnumerable Id { get; set; } + + /// + /// FeedbackReceiverId filters tokens by their assigned FeedbackReceiverId. + /// + public string FeedbackReceiverId { get; set; } + + /// + /// CreatedAfter filters token that have been created after the given date. + /// + public DateTime? CreatedAfter { get; set; } + + /// + /// CreatedBefore filters token that have been created before the given date. + /// + public DateTime? CreatedBefore { get; set; } + + /// + /// UsedAfter filters token that have been used after the given date. + /// + public DateTime? UsedAfter { get; set; } + + /// + /// UsedBefore filters token that have been used before the given date. + /// + public DateTime? UsedBefore { get; set; } + } +} \ No newline at end of file diff --git a/Retroactiune.Core/Services/TokensService.cs b/Retroactiune.Core/Services/TokensService.cs index 8f4a9db..95076b0 100644 --- a/Retroactiune.Core/Services/TokensService.cs +++ b/Retroactiune.Core/Services/TokensService.cs @@ -52,5 +52,12 @@ namespace Retroactiune.Core.Services throw new GenericServiceException($"Operation failed: {e.Message} {e.StackTrace}"); } } + + public async Task> ListTokens(TokenListFilters filters) + { + // TODO Write unit tests. + // TODO: Implement + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestFeedbackReceiverController.cs b/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestFeedbackReceiverController.cs index e7a61b6..1ad19ae 100644 --- a/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestFeedbackReceiverController.cs +++ b/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestFeedbackReceiverController.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using AutoFixture.Xunit2; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Moq; using Retroactiune.Controllers; using Retroactiune.Core.Entities; @@ -20,9 +21,10 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers // Arrange var mapper = TestUtils.GetMapper(); var mockService = new Mock(); + var logger = new Mock>(); // Test - var controller = new FeedbackReceiversController(mockService.Object, mapper, null); + var controller = new FeedbackReceiversController(mockService.Object, mapper, null, logger.Object); var result = await controller.Post(new List()); // Assert, null because we don't have the ApiBehaviourOptions set, which would generate the IActionResult for the invalid input. @@ -36,9 +38,10 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers // Arrange var mapper = TestUtils.GetMapper(); var mockService = new Mock(); + var logger = new Mock>(); // Test - var controller = new FeedbackReceiversController(mockService.Object, mapper, null); + var controller = new FeedbackReceiversController(mockService.Object, mapper, null, logger.Object); var result = await controller.Post(items); // Assert @@ -52,9 +55,10 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers // Arrange var mapper = TestUtils.GetMapper(); var mockService = new Mock(); + var logger = new Mock>(); // Test - var controller = new FeedbackReceiversController(mockService.Object, mapper, null); + var controller = new FeedbackReceiversController(mockService.Object, mapper, null, logger.Object); var result = await controller.Delete("bad_guid_but_unit_test_works_cause_validation_doesnt"); // Assert @@ -62,16 +66,17 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers mockService.Verify(s => s.DeleteManyAsync(new[] {"bad_guid_but_unit_test_works_cause_validation_doesnt"}), Times.Once); } - + [Fact] public async Task DeleteMany_Successful() { // Arrange var mapper = TestUtils.GetMapper(); var mockService = new Mock(); + var logger = new Mock>(); // Test - var controller = new FeedbackReceiversController(mockService.Object, mapper, null); + var controller = new FeedbackReceiversController(mockService.Object, mapper, null, logger.Object); var items = new[] {"bad_guid_but_unit_test_works_cause_validation_doesnt", "2", "3"}; var result = await controller.DeleteMany(items); @@ -87,11 +92,12 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers // Arrange var mapper = TestUtils.GetMapper(); var mockService = new Mock(); + var logger = new Mock>(); mockService.Setup(i => i.FindAsync(It.IsAny>(), null, null)) .ReturnsAsync(new[] {new FeedbackReceiver()}); // Test - var controller = new FeedbackReceiversController(mockService.Object, mapper, null); + var controller = new FeedbackReceiversController(mockService.Object, mapper, null, logger.Object); var result = await controller.Get("bad_guid_but_unit_test_works_cause_validation_doesnt"); // Assert @@ -107,9 +113,10 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers // Arrange var mapper = TestUtils.GetMapper(); var mockService = new Mock(); + var logger = new Mock>(); // Test - var controller = new FeedbackReceiversController(mockService.Object, mapper, null); + var controller = new FeedbackReceiversController(mockService.Object, mapper, null, logger.Object); var result = await controller.Get("bad_guid_but_unit_test_works_cause_validation_doesnt"); // Assert @@ -125,10 +132,11 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers // Arrange var mapper = TestUtils.GetMapper(); var mockService = new Mock(); + var logger = new Mock>(); var filterArr = filter as string[] ?? filter.ToArray(); // Test - var controller = new FeedbackReceiversController(mockService.Object, mapper, null); + var controller = new FeedbackReceiversController(mockService.Object, mapper, null, logger.Object); var result = await controller.List(filterArr, offset, limit); Assert.IsType(result); diff --git a/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestTokensController.cs b/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestTokensController.cs index be51e64..1fcaf2a 100644 --- a/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestTokensController.cs +++ b/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestTokensController.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using AutoFixture.Xunit2; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Moq; using Retroactiune.Controllers; using Retroactiune.Core.Entities; @@ -19,10 +20,12 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers { // Arrange var feedbackService = new Mock(); + var mapper = TestUtils.GetMapper(); var tokens = new Mock(); + var logger = new Mock>(); // Test - var controller = new TokensController(feedbackService.Object, tokens.Object); + var controller = new TokensController(feedbackService.Object, tokens.Object, logger.Object, mapper); var result = await controller.GenerateTokens(new GenerateTokensDto()); // Assert @@ -33,8 +36,10 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers public async Task Test_GenerateTokens_Success(FeedbackReceiver randFedFeedbackReceiver) { // Arrange + var mapper = TestUtils.GetMapper(); var feedbackService = new Mock(); var tokens = new Mock(); + var logger = new Mock>(); feedbackService.Setup(i => i.FindAsync(It.IsAny>(), It.IsAny(), @@ -42,7 +47,7 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers .ReturnsAsync(new[] {randFedFeedbackReceiver}); // Test - var controller = new TokensController(feedbackService.Object, tokens.Object); + var controller = new TokensController(feedbackService.Object, tokens.Object, logger.Object, mapper); var result = await controller.GenerateTokens(new GenerateTokensDto { NumberOfTokens = 2, @@ -58,11 +63,13 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers public async Task Test_Delete_Ok() { // Arrange + var mapper = TestUtils.GetMapper(); var feedbackService = new Mock(); var tokens = new Mock(); + var logger = new Mock>(); // Test - var controller = new TokensController(feedbackService.Object, tokens.Object); + var controller = new TokensController(feedbackService.Object, tokens.Object, logger.Object, mapper); var result = await controller.DeleteToken("my_guid"); // Assert @@ -74,13 +81,15 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers public async Task Test_Delete_BadRequest() { // Arrange + var mapper = TestUtils.GetMapper(); var feedbackService = new Mock(); var tokens = new Mock(); + var logger = new Mock>(); tokens.Setup(i => i.DeleteTokens(It.IsAny>())) .Throws(new GenericServiceException("op fail")); // Test - var controller = new TokensController(feedbackService.Object, tokens.Object); + var controller = new TokensController(feedbackService.Object, tokens.Object, logger.Object, mapper); var result = await controller.DeleteToken("my_guid"); // Assert @@ -92,11 +101,13 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers public async Task Test_DeleteMany_Ok() { // Arrange + var mapper = TestUtils.GetMapper(); var feedbackService = new Mock(); var tokens = new Mock(); + var logger = new Mock>(); // Test - var controller = new TokensController(feedbackService.Object, tokens.Object); + var controller = new TokensController(feedbackService.Object, tokens.Object, logger.Object, mapper); var result = await controller.DeleteTokens(new[] {"my_guid", "b"}); // Assert @@ -108,13 +119,15 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers public async Task Test_DeleteMany_BadRequest() { // Arrange + var mapper = TestUtils.GetMapper(); var feedbackService = new Mock(); var tokens = new Mock(); + var logger = new Mock>(); tokens.Setup(i => i.DeleteTokens(It.IsAny>())) .Throws(new GenericServiceException("op fail")); // Test - var controller = new TokensController(feedbackService.Object, tokens.Object); + var controller = new TokensController(feedbackService.Object, tokens.Object, logger.Object, mapper); var result = await controller.DeleteTokens(new[] {"my_guid", "b"}); // Assert diff --git a/Retroactiune.WebAPI/Controllers/FeedbackReceiversController.cs b/Retroactiune.WebAPI/Controllers/FeedbackReceiversController.cs index 9def126..16fd295 100644 --- a/Retroactiune.WebAPI/Controllers/FeedbackReceiversController.cs +++ b/Retroactiune.WebAPI/Controllers/FeedbackReceiversController.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using AutoMapper; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Retroactiune.Core.Entities; using Retroactiune.Core.Interfaces; @@ -21,13 +22,15 @@ namespace Retroactiune.Controllers private readonly IOptions _apiBehaviorOptions; private readonly IFeedbackReceiverService _service; private readonly IMapper _mapper; + private readonly ILogger _logger; public FeedbackReceiversController(IFeedbackReceiverService service, IMapper mapper, - IOptions apiBehaviorOptions) + IOptions apiBehaviorOptions, ILogger logger) { _service = service; _mapper = mapper; _apiBehaviorOptions = apiBehaviorOptions; + _logger = logger; } @@ -80,7 +83,7 @@ namespace Retroactiune.Controllers [StringLength(24, ErrorMessage = "invalid guid, must be 24 characters", MinimumLength = 24)] string guid) { - await _service.DeleteManyAsync(new [] {guid}); + await _service.DeleteManyAsync(new[] {guid}); return NoContent(); } @@ -133,7 +136,7 @@ namespace Retroactiune.Controllers { return Ok(await _service.FindAsync(filter, offset, limit)); } - + /// /// Deletes FeedbackReceiver identified by ids. /// @@ -143,7 +146,7 @@ namespace Retroactiune.Controllers /// [HttpDelete] [ProducesResponseType(typeof(NoContentResult), StatusCodes.Status204NoContent)] - [ProducesResponseType(typeof(BasicResponse),StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(BasicResponse), StatusCodes.Status400BadRequest)] public async Task DeleteMany([Required] IEnumerable ids) { try @@ -153,6 +156,7 @@ namespace Retroactiune.Controllers } catch (GenericServiceException e) { + _logger.LogError("{Message}", e.Message); return BadRequest(new BasicResponse { Message = e.Message diff --git a/Retroactiune.WebAPI/Controllers/TokensController.cs b/Retroactiune.WebAPI/Controllers/TokensController.cs index 711c9a2..ab27a91 100644 --- a/Retroactiune.WebAPI/Controllers/TokensController.cs +++ b/Retroactiune.WebAPI/Controllers/TokensController.cs @@ -2,8 +2,11 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; +using AutoMapper; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Retroactiune.Core.Entities; using Retroactiune.Core.Interfaces; using Retroactiune.Core.Services; using Retroactiune.DataTransferObjects; @@ -14,18 +17,46 @@ namespace Retroactiune.Controllers [Route("api/v1/[controller]")] public class TokensController : ControllerBase { - // TODO: Implement ListTokens. - // Filters for: FeedbackReceiver IDS - // for: start < CreatedTime < end - // for start < TimeUsed end - private readonly IFeedbackReceiverService _feedbackReceiverService; private readonly ITokensService _tokensService; + private readonly IMapper _mapper; + private readonly ILogger _logger; - public TokensController(IFeedbackReceiverService feedbackReceiverService, ITokensService tokensService) + public TokensController(IFeedbackReceiverService feedbackReceiverService, ITokensService tokensService, + ILogger logger, IMapper mapper) { _feedbackReceiverService = feedbackReceiverService; _tokensService = tokensService; + _mapper = mapper; + _logger = logger; + } + + /// + /// The list tokens controller retrieves a list of tokens. + /// + /// Object that holds filters for listing tokens. + /// A list of tokens. + /// The request is invalid. + [HttpGet] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task ListTokens([FromQuery] ListTokensFiltersDto filtersDto) + { + // TODO: Write unit & integration tests. + try + { + var tokenFilters = _mapper.Map(filtersDto); + var response = await _tokensService.ListTokens(tokenFilters); + return Ok(response); + } + catch (GenericServiceException e) + { + _logger.LogError("{Message}", e.Message); + return BadRequest(new BasicResponse() + { + Message = e.Message + }); + } } /// @@ -69,7 +100,7 @@ namespace Retroactiune.Controllers /// [HttpDelete] [ProducesResponseType(typeof(NoContentResult), StatusCodes.Status204NoContent)] - [ProducesResponseType(typeof(BasicResponse),StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(BasicResponse), StatusCodes.Status400BadRequest)] public async Task DeleteTokens([Required] IEnumerable tokenIds) { try @@ -79,13 +110,14 @@ namespace Retroactiune.Controllers } catch (GenericServiceException e) { + _logger.LogError("{Message}", e.Message); return BadRequest(new BasicResponse { Message = e.Message }); } } - + /// /// Deletes a Token given it's guid. /// @@ -97,15 +129,17 @@ namespace Retroactiune.Controllers [ProducesResponseType(typeof(NoContentResult), StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task DeleteToken( - [StringLength(24, ErrorMessage = "invalid guid, must be 24 characters", MinimumLength = 24)] string guid) + [StringLength(24, ErrorMessage = "invalid guid, must be 24 characters", MinimumLength = 24)] + string guid) { try { - await _tokensService.DeleteTokens(new []{ guid }); + await _tokensService.DeleteTokens(new[] {guid}); return NoContent(); } catch (GenericServiceException e) { + _logger.LogError("{Message}", e.Message); return BadRequest(new BasicResponse { Message = e.Message diff --git a/Retroactiune.WebAPI/DataTransferObjects/ListTokensFiltersDto.cs b/Retroactiune.WebAPI/DataTransferObjects/ListTokensFiltersDto.cs new file mode 100644 index 0000000..369674f --- /dev/null +++ b/Retroactiune.WebAPI/DataTransferObjects/ListTokensFiltersDto.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Retroactiune.Core.Entities; + +namespace Retroactiune.DataTransferObjects +{ + /// + /// DTO with filters for listing tokens. + /// + public class ListTokensFiltersDto + { + public IEnumerable Id { get; set; } + [StringLength(24, ErrorMessage = "invalid guid, must be 24 characters", MinimumLength = 24)] + public string FeedbackReceiverId { get; set; } + public DateTime? CreatedAfter { get; set; } + public DateTime? CreatedBefore { get; set; } + public DateTime? UsedAfter { get; set; } + public DateTime? UsedBefore { get; set; } + } +} \ No newline at end of file diff --git a/Retroactiune.WebAPI/MappingProfile.cs b/Retroactiune.WebAPI/MappingProfile.cs index 60d38e5..c3e5a07 100644 --- a/Retroactiune.WebAPI/MappingProfile.cs +++ b/Retroactiune.WebAPI/MappingProfile.cs @@ -1,5 +1,6 @@ using AutoMapper; using Retroactiune.Core.Entities; +using Retroactiune.Core.Services; using Retroactiune.DataTransferObjects; namespace Retroactiune @@ -8,6 +9,7 @@ namespace Retroactiune { public MappingProfile() { + CreateMap(); CreateMap().ReverseMap(); CreateMap(); }