diff --git a/Retroactiune.Core/Entities/FeedbackReceiver.cs b/Retroactiune.Core/Entities/FeedbackReceiver.cs index 5c98beb..6980ea3 100644 --- a/Retroactiune.Core/Entities/FeedbackReceiver.cs +++ b/Retroactiune.Core/Entities/FeedbackReceiver.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Text.Json.Serialization; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; @@ -33,7 +34,7 @@ namespace Retroactiune.Core.Entities public override int GetHashCode() { - return base.GetHashCode(); + return RuntimeHelpers.GetHashCode(this); } } } \ No newline at end of file diff --git a/Retroactiune.Core/Entities/Token.cs b/Retroactiune.Core/Entities/Token.cs index 1058ded..90f75ae 100644 --- a/Retroactiune.Core/Entities/Token.cs +++ b/Retroactiune.Core/Entities/Token.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Text.Json.Serialization; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; @@ -30,14 +31,15 @@ namespace Retroactiune.Core.Entities { return false; } - - return string.Equals(Id, convertedObj.Id) && string.Equals(FeedbackReceiverId, convertedObj.FeedbackReceiverId) && - TimeUsed == convertedObj.TimeUsed && ExpiryTime == convertedObj.ExpiryTime; + + return string.Equals(Id, convertedObj.Id) && + string.Equals(FeedbackReceiverId, convertedObj.FeedbackReceiverId) && + (CreatedAt - convertedObj.CreatedAt).Milliseconds == 0; } public override int GetHashCode() { - return base.GetHashCode(); + return RuntimeHelpers.GetHashCode(this); } } } \ No newline at end of file diff --git a/Retroactiune.Core/Services/TokenService.cs b/Retroactiune.Core/Services/TokenService.cs index 47cffc9..b01b3fe 100644 --- a/Retroactiune.Core/Services/TokenService.cs +++ b/Retroactiune.Core/Services/TokenService.cs @@ -56,13 +56,12 @@ namespace Retroactiune.Core.Services public async Task> ListTokens(TokenListFilters filters) { - // TODO Write unit tests. var filterBuilder = new FilterDefinitionBuilder(); var activeFilters = new List>(); var tokensListFilter = FilterDefinition.Empty; // Filter by token ids. - if (filters.Ids.Any()) + if (filters.Ids != null && filters.Ids.Any()) { activeFilters.Add(filterBuilder.In(i => i.Id, filters.Ids)); } diff --git a/Retroactiune.IntegrationTests/Retroactiune.WebAPI/Controllers/TestFeedbackReceiver.cs b/Retroactiune.IntegrationTests/Retroactiune.WebAPI/Controllers/TestFeedbackReceiversController.cs similarity index 98% rename from Retroactiune.IntegrationTests/Retroactiune.WebAPI/Controllers/TestFeedbackReceiver.cs rename to Retroactiune.IntegrationTests/Retroactiune.WebAPI/Controllers/TestFeedbackReceiversController.cs index 4f22ac9..55f07f4 100644 --- a/Retroactiune.IntegrationTests/Retroactiune.WebAPI/Controllers/TestFeedbackReceiver.cs +++ b/Retroactiune.IntegrationTests/Retroactiune.WebAPI/Controllers/TestFeedbackReceiversController.cs @@ -24,12 +24,12 @@ using JsonSerializer = System.Text.Json.JsonSerializer; namespace Retroactiune.IntegrationTests.Retroactiune.WebAPI.Controllers { [Collection("IntegrationTests")] - public class TestFeedbackReceiver : IClassFixture + public class TestFeedbackReceiversController : IClassFixture { private readonly MongoDbFixture _mongoDb; private readonly HttpClient _client; - public TestFeedbackReceiver(WebApiTestingFactory factory) + public TestFeedbackReceiversController(WebApiTestingFactory factory) { _client = factory.CreateClient(); var dbSettings = factory.Services.GetService>(); diff --git a/Retroactiune.IntegrationTests/Retroactiune.WebAPI/Controllers/TestTokens.cs b/Retroactiune.IntegrationTests/Retroactiune.WebAPI/Controllers/TestTokensController.cs similarity index 54% rename from Retroactiune.IntegrationTests/Retroactiune.WebAPI/Controllers/TestTokens.cs rename to Retroactiune.IntegrationTests/Retroactiune.WebAPI/Controllers/TestTokensController.cs index e62ff70..f81662b 100644 --- a/Retroactiune.IntegrationTests/Retroactiune.WebAPI/Controllers/TestTokens.cs +++ b/Retroactiune.IntegrationTests/Retroactiune.WebAPI/Controllers/TestTokensController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; @@ -7,6 +8,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using AutoFixture.Xunit2; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using MongoDB.Bson; @@ -16,16 +18,17 @@ using Retroactiune.Core.Entities; using Retroactiune.Infrastructure; using Retroactiune.IntegrationTests.Retroactiune.WebAPI.Fixtures; using Xunit; +using JsonSerializer = System.Text.Json.JsonSerializer; namespace Retroactiune.IntegrationTests.Retroactiune.WebAPI.Controllers { [Collection("IntegrationTests")] - public class TestTokens : IClassFixture + public class TestTokensController : IClassFixture { private readonly MongoDbFixture _mongoDb; private readonly HttpClient _client; - public TestTokens(WebApiTestingFactory factory) + public TestTokensController(WebApiTestingFactory factory) { _client = factory.CreateClient(); var dbSettings = factory.Services.GetService>(); @@ -207,5 +210,168 @@ namespace Retroactiune.IntegrationTests.Retroactiune.WebAPI.Controllers // Assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } + + [Fact] + public async Task Test_ListTokens_NoFilter_Empty() + { + // Setup + await _mongoDb.DropAsync(); + + // Test + var response = await _client.GetAsync("api/v1/Tokens"); + var items = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync()); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Empty(items); + } + + [Fact] + public async Task Test_ListTokens_NoFilter() + { + // Setup + await _mongoDb.DropAsync(); + var timeNow = DateTime.UtcNow; + var tokens = TokensFixture.Generate(10, timeNow); + await _mongoDb.TokensCollection.InsertManyAsync(tokens); + + // Test + var response = await _client.GetAsync("api/v1/Tokens"); + var items = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync()); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(tokens.Count, items.Count); + for (var i = 0; i < tokens.Count; i++) + { + Assert.Equal(tokens[i], items[i]); + } + } + + [Fact] + public async Task Test_ListTokens_Filter_FeedbackReceiverId() + { + // Setup + await _mongoDb.DropAsync(); + + var timeNow = DateTime.UtcNow; + var tokens = TokensFixture.Generate(13, timeNow); + var expectedTokens = TokensFixture.Generate(1, timeNow); + + var qb = new QueryBuilder + { + {"FeedbackReceiverId", expectedTokens[0].FeedbackReceiverId}, + }; + + await _mongoDb.TokensCollection.InsertManyAsync(tokens); + await _mongoDb.TokensCollection.InsertManyAsync(expectedTokens); + + // Test + var response = await _client.GetAsync($"api/v1/Tokens{qb}"); + var items = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync()); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Single(items); + Assert.Equal(expectedTokens[0], items[0]); + } + + [Fact] + public async Task Test_ListTokens_Filter_Ids() + { + // Setup + await _mongoDb.DropAsync(); + + var timeNow = DateTime.UtcNow; + var tokens = TokensFixture.Generate(13, timeNow); + var expectedTokens = TokensFixture.Generate(1, timeNow); + + var qb = new QueryBuilder + { + {"Ids", expectedTokens[0].Id}, + }; + + await _mongoDb.TokensCollection.InsertManyAsync(tokens); + await _mongoDb.TokensCollection.InsertManyAsync(expectedTokens); + + // Test + var response = await _client.GetAsync($"api/v1/Tokens{qb}"); + var items = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync()); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Single(items); + Assert.Equal(expectedTokens[0], items[0]); + } + + [Fact] + public async Task Test_ListTokens_Filter_CreatedRange() + { + // Setup + await _mongoDb.DropAsync(); + + var timeNow = DateTime.UtcNow; + + var oldTokens = TokensFixture.Generate(13, timeNow.AddDays(-10)); + var futureTokens = TokensFixture.Generate(13, timeNow.AddDays(10)); + var expectedTokens = TokensFixture.Generate(5, timeNow); + + var qb = new QueryBuilder + { + {"CreatedAfter", timeNow.AddDays(-3).ToString(CultureInfo.InvariantCulture)}, + {"CreatedBefore", timeNow.AddDays(3).ToString(CultureInfo.InvariantCulture)}, + }; + + await _mongoDb.TokensCollection.InsertManyAsync(oldTokens); + await _mongoDb.TokensCollection.InsertManyAsync(expectedTokens); + await _mongoDb.TokensCollection.InsertManyAsync(futureTokens); + + // Test + var response = await _client.GetAsync($"api/v1/Tokens{qb}"); + var items = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync()); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(expectedTokens.Count, items.Count); + for (var i = 0; i < items.Count; i++) + { + Assert.Equal(expectedTokens[i], items[i]); + } + } + + [Fact] + public async Task Test_ListTokens_Filter_UsedRange() + { + // Setup + await _mongoDb.DropAsync(); + + var timeNow = DateTime.UtcNow; + + var oldTokens = TokensFixture.Generate(13, timeNow.AddDays(-10)); + var futureTokens = TokensFixture.Generate(13, timeNow.AddDays(10)); + var expectedTokens = TokensFixture.Generate(5, timeNow, null, null, timeNow); + + var qb = new QueryBuilder + { + {"UsedAfter", timeNow.AddDays(-3).ToString(CultureInfo.InvariantCulture)}, + {"UsedBefore", timeNow.AddDays(3).ToString(CultureInfo.InvariantCulture)}, + }; + + await _mongoDb.TokensCollection.InsertManyAsync(oldTokens); + await _mongoDb.TokensCollection.InsertManyAsync(expectedTokens); + await _mongoDb.TokensCollection.InsertManyAsync(futureTokens); + + // Test + var response = await _client.GetAsync($"api/v1/Tokens{qb}"); + var items = JsonSerializer.Deserialize>(await response.Content.ReadAsStringAsync()); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(expectedTokens.Count, items.Count); + for (var i = 0; i < items.Count; i++) + { + Assert.Equal(expectedTokens[i], items[i]); + } + } } } \ No newline at end of file diff --git a/Retroactiune.IntegrationTests/Retroactiune.WebAPI/Fixtures/TokensFixture.cs b/Retroactiune.IntegrationTests/Retroactiune.WebAPI/Fixtures/TokensFixture.cs new file mode 100644 index 0000000..fa53d6c --- /dev/null +++ b/Retroactiune.IntegrationTests/Retroactiune.WebAPI/Fixtures/TokensFixture.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using MongoDB.Bson; +using Retroactiune.Core.Entities; + +namespace Retroactiune.IntegrationTests.Retroactiune.WebAPI.Fixtures +{ + public static class TokensFixture + { + public static List Generate(int number, DateTime createdAt, ObjectId? feedbackReceiverId = null, + DateTime? expiryTime = null, DateTime? timeUsed = null) + { + var list = new List(); + for (var i = 0; i < number; i++) + { + var finalFeedbackReceiverId = ObjectId.GenerateNewId().ToString(); + if (feedbackReceiverId != null) + { + finalFeedbackReceiverId = feedbackReceiverId.ToString(); + } + + + list.Add(new Token + { + Id = ObjectId.GenerateNewId().ToString(), + FeedbackReceiverId = finalFeedbackReceiverId, + CreatedAt = createdAt, + TimeUsed = timeUsed, + ExpiryTime = expiryTime + }); + } + + return list; + } + } +} \ No newline at end of file diff --git a/Retroactiune.UnitTests/Retroactiune.Core/Services/TestTokensService.cs b/Retroactiune.UnitTests/Retroactiune.Core/Services/TestTokensService.cs index 8ea2952..9bdbd81 100644 --- a/Retroactiune.UnitTests/Retroactiune.Core/Services/TestTokensService.cs +++ b/Retroactiune.UnitTests/Retroactiune.Core/Services/TestTokensService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; @@ -114,5 +115,91 @@ namespace Retroactiune.Tests.Retroactiune.Core.Services It.IsAny>(), It.IsAny()), Times.Once); } + + [Fact] + public async Task Test_ListTokens_NoFilters_Ok() + { + // Setup + var mongoDatabaseMock = new Mock(); + var mongoClientMock = new Mock(); + var mongoSettingsMock = new Mock(); + var mongoCollectionMock = new Mock>(); + var mongoCursorMock = new Mock>(); + + mongoSettingsMock.SetupGet(i => i.DatabaseName).Returns("MyDB"); + mongoSettingsMock.SetupGet(i => i.TokensCollectionName).Returns("tokens"); + + mongoClientMock + .Setup(stub => stub.GetDatabase(It.IsAny(), + It.IsAny())) + .Returns(mongoDatabaseMock.Object); + + mongoDatabaseMock + .Setup(i => i.GetCollection(It.IsAny(), + It.IsAny())) + .Returns(mongoCollectionMock.Object); + + mongoCollectionMock.Setup(i => i.FindAsync(It.IsAny>(), + It.IsAny>(), It.IsAny())).ReturnsAsync(mongoCursorMock.Object); + + // Test + var service = new TokenService(mongoClientMock.Object, mongoSettingsMock.Object); + var result = await service.ListTokens(new TokenListFilters()); + + // Assert + Assert.IsType>(result); + mongoCollectionMock.Verify( + i + => i.FindAsync(It.IsAny>(), + It.IsAny>(), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task Test_ListTokens_Filters_Ok() + { + // Setup + var mongoDatabaseMock = new Mock(); + var mongoClientMock = new Mock(); + var mongoSettingsMock = new Mock(); + var mongoCollectionMock = new Mock>(); + var mongoCursorMock = new Mock>(); + + mongoSettingsMock.SetupGet(i => i.DatabaseName).Returns("MyDB"); + mongoSettingsMock.SetupGet(i => i.TokensCollectionName).Returns("tokens"); + + mongoClientMock + .Setup(stub => stub.GetDatabase(It.IsAny(), + It.IsAny())) + .Returns(mongoDatabaseMock.Object); + + mongoDatabaseMock + .Setup(i => i.GetCollection(It.IsAny(), + It.IsAny())) + .Returns(mongoCollectionMock.Object); + + mongoCollectionMock.Setup(i => i.FindAsync(It.IsAny>(), + It.IsAny>(), It.IsAny())).ReturnsAsync(mongoCursorMock.Object); + + // Test + var service = new TokenService(mongoClientMock.Object, mongoSettingsMock.Object); + var result = await service.ListTokens(new TokenListFilters + { + Ids = new []{"a", "b"}, + FeedbackReceiverId = "abc", + CreatedAfter = DateTime.UtcNow, + CreatedBefore = DateTime.UtcNow, + UsedAfter = DateTime.UtcNow, + UsedBefore = DateTime.UtcNow + }); + + // Assert + Assert.IsType>(result); + mongoCollectionMock.Verify( + i + => i.FindAsync(It.IsAny>(), + It.IsAny>(), + It.IsAny()), Times.Once); + } } } \ 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 1ad19ae..a51954f 100644 --- a/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestFeedbackReceiverController.cs +++ b/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestFeedbackReceiverController.cs @@ -8,6 +8,7 @@ using Moq; using Retroactiune.Controllers; using Retroactiune.Core.Entities; using Retroactiune.Core.Interfaces; +using Retroactiune.Core.Services; using Retroactiune.DataTransferObjects; using Xunit; @@ -86,6 +87,27 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers Times.Once); } + [Fact] + public async Task DeleteMany_BadRequest() + { + // Arrange + var mapper = TestUtils.GetMapper(); + var mockService = new Mock(); + var logger = new Mock>(); + mockService.Setup(i => i.DeleteManyAsync(It.IsAny>())) + .ThrowsAsync(new GenericServiceException("op failed")); + + // Test + 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); + + // Assert + Assert.IsType(result); + mockService.Verify(s => s.DeleteManyAsync(items), + Times.Once); + } + [Fact] public async Task Get_Successful() { diff --git a/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestTokensController.cs b/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestTokensController.cs index 1fcaf2a..38fdec2 100644 --- a/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestTokensController.cs +++ b/Retroactiune.UnitTests/Retroactiune.WebAPI/Controllers/TestTokensController.cs @@ -134,5 +134,43 @@ namespace Retroactiune.Tests.Retroactiune.WebAPI.Controllers Assert.IsType(result); tokens.Verify(i => i.DeleteTokens(new[] {"my_guid", "b"}), Times.Once); } + + [Fact] + public async Task Test_ListTokens_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, logger.Object, mapper); + var result = await controller.ListTokens(null); + + // Assert + Assert.IsType(result); + tokens.Verify(i => i.ListTokens(It.IsAny()), Times.Once); + } + + [Fact] + public async Task Test_ListTokens_BadRequest() + { + // Arrange + var mapper = TestUtils.GetMapper(); + var feedbackService = new Mock(); + var tokens = new Mock(); + var logger = new Mock>(); + tokens.Setup(i => i.ListTokens(It.IsAny())) + .Throws(new GenericServiceException("op fail")); + + // Test + var controller = new TokensController(feedbackService.Object, tokens.Object, logger.Object, mapper); + var result = await controller.ListTokens(null); + + // Assert + Assert.IsType(result); + tokens.Verify(i => i.ListTokens(It.IsAny()), Times.Once); + } } } \ No newline at end of file diff --git a/Retroactiune.WebAPI/Controllers/TokensController.cs b/Retroactiune.WebAPI/Controllers/TokensController.cs index ab27a91..3d2823e 100644 --- a/Retroactiune.WebAPI/Controllers/TokensController.cs +++ b/Retroactiune.WebAPI/Controllers/TokensController.cs @@ -42,7 +42,6 @@ namespace Retroactiune.Controllers [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task ListTokens([FromQuery] ListTokensFiltersDto filtersDto) { - // TODO: Write unit & integration tests. try { var tokenFilters = _mapper.Map(filtersDto);