From d29b1c2a8c753afa24e90687947b8af5fde4ca60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=86=D0=BD=D0=BD=D0=B0=20=D0=A8=D0=BF=D0=BE=D0=BD=D1=8C?= =?UTF-8?q?=D0=BA=D0=B0?= Date: Fri, 8 May 2026 12:42:06 +0300 Subject: [PATCH 1/4] fix/SSAD-0032: use request id in timeline item query --- .../Timeline/TimelineItem/GetById/GetTimelineItemByIdHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streetcode/Streetcode.BLL/MediatR/Timeline/TimelineItem/GetById/GetTimelineItemByIdHandler.cs b/Streetcode/Streetcode.BLL/MediatR/Timeline/TimelineItem/GetById/GetTimelineItemByIdHandler.cs index 470b9e1..e30df2e 100644 --- a/Streetcode/Streetcode.BLL/MediatR/Timeline/TimelineItem/GetById/GetTimelineItemByIdHandler.cs +++ b/Streetcode/Streetcode.BLL/MediatR/Timeline/TimelineItem/GetById/GetTimelineItemByIdHandler.cs @@ -25,7 +25,7 @@ public async Task> Handle(GetTimelineItemByIdQuery reque { var timelineItem = await _repositoryWrapper.TimelineRepository .GetFirstOrDefaultAsync( - predicate: ti => true, + predicate: ti => ti.Id == request.Id, include: ti => ti .Include(til => til.HistoricalContextTimelines) .ThenInclude(x => x.HistoricalContext)!); From aceadb0ca8f0cca158ab3f9c8f9c645557b9728e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=86=D0=BD=D0=BD=D0=B0=20=D0=A8=D0=BF=D0=BE=D0=BD=D1=8C?= =?UTF-8?q?=D0=BA=D0=B0?= Date: Fri, 8 May 2026 14:35:47 +0300 Subject: [PATCH 2/4] test/#003: add GetById handler tests for TimelineItem --- .../GetTimelineItemByIdHandlerTests.cs | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetById/GetTimelineItemByIdHandlerTests.cs diff --git a/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetById/GetTimelineItemByIdHandlerTests.cs b/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetById/GetTimelineItemByIdHandlerTests.cs new file mode 100644 index 0000000..76f7927 --- /dev/null +++ b/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetById/GetTimelineItemByIdHandlerTests.cs @@ -0,0 +1,207 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// +namespace Streetcode.XUnitTest.BLL.MediatR.Timeline.TimelineItem.GetById +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Threading.Tasks; + using AutoMapper; + using Microsoft.EntityFrameworkCore.Query; + using Moq; + using Streetcode.BLL.DTO.Timeline; + using Streetcode.BLL.Interfaces.Logging; + using Streetcode.BLL.MediatR.Timeline.TimelineItem.GetById; + using Streetcode.DAL.Entities.Timeline; + using Streetcode.DAL.Enums; + using Streetcode.DAL.Repositories.Interfaces.Base; + using Streetcode.DAL.Repositories.Interfaces.Timeline; + using Xunit; + + /// + /// Unit tests for GetTimelineItemByIdHandler. + /// + public class GetTimelineItemByIdHandlerTests + { + private readonly Mock repoWrapperMock; + private readonly Mock timelineRepoMock; + private readonly Mock mapperMock; + private readonly Mock loggerMock; + + private readonly GetTimelineItemByIdHandler handler; + + /// + /// Initializes a new instance of the class. + /// + public GetTimelineItemByIdHandlerTests() + { + this.repoWrapperMock = new Mock(); + this.timelineRepoMock = new Mock(); + this.mapperMock = new Mock(); + this.loggerMock = new Mock(); + + this.repoWrapperMock + .Setup(x => x.TimelineRepository) + .Returns(this.timelineRepoMock.Object); + + this.handler = new GetTimelineItemByIdHandler( + this.repoWrapperMock.Object, + this.mapperMock.Object, + this.loggerMock.Object); + } + + /// + /// Should return TimelineItemDTO when item exists. + /// + /// A task representing the asynchronous operation. + [Fact] + public async Task Handle_ShouldReturnTimelineItemDTO_WhenItemExists() + { + var query = new GetTimelineItemByIdQuery(1); + + var timelineItem = new DAL.Entities.Timeline.TimelineItem + { + Id = 1, + Title = "Test Title", + Description = "Description", + Date = new DateTime(2020, 01, 01), + DateViewPattern = DateViewPattern.Year, + HistoricalContextTimelines = new List + { + new HistoricalContextTimeline + { + HistoricalContext = new HistoricalContext + { + Id = 1, + Title = "Historical Context 1", + }, + }, + }, + }; + + var expectedDto = new TimelineItemDTO + { + Id = 1, + Title = "Test Title", + Description = "Description", + Date = new DateTime(2020, 01, 01), + DateViewPattern = DateViewPattern.Year, + HistoricalContexts = new List + { + new HistoricalContextDTO + { + Id = 1, + Title = "Historical Context 1", + }, + }, + }; + + this.timelineRepoMock + .Setup(r => r.GetFirstOrDefaultAsync( + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>())) + .ReturnsAsync(timelineItem); + + this.mapperMock + .Setup(m => m.Map(timelineItem)) + .Returns(expectedDto); + + var result = await this.handler.Handle(query, CancellationToken.None); + + Assert.True(result.IsSuccess); + Assert.NotNull(result.Value); + + Assert.Equal(expectedDto.Id, result.Value.Id); + Assert.Equal(expectedDto.Title, result.Value.Title); + Assert.Equal(expectedDto.Description, result.Value.Description); + Assert.Equal(expectedDto.Date, result.Value.Date); + Assert.Equal(expectedDto.DateViewPattern, result.Value.DateViewPattern); + + Assert.NotNull(result.Value.HistoricalContexts); + Assert.Single(result.Value.HistoricalContexts); + + Assert.Equal( + expectedDto.HistoricalContexts.First().Title, + result.Value.HistoricalContexts.First().Title); + + this.mapperMock.Verify( + m => m.Map(timelineItem), + Times.Once); + } + + /// + /// Should return failure when item not found. + /// + /// A task representing the asynchronous operation. + [Fact] + public async Task Handle_ShouldReturnFail_WhenItemNotFound() + { + var query = new GetTimelineItemByIdQuery(1); + + var expectedErrorMessage = + $"Cannot find a timeline item with corresponding id: {query.Id}"; + + this.timelineRepoMock + .Setup(r => r.GetFirstOrDefaultAsync( + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>())) + .ReturnsAsync((DAL.Entities.Timeline.TimelineItem?)null); + + var result = await this.handler.Handle(query, CancellationToken.None); + + Assert.True(result.IsFailed); + + Assert.Equal( + expectedErrorMessage, + result.Errors.First().Message); + + this.loggerMock.Verify( + l => l.LogError( + It.Is(q => q.Id == query.Id), + expectedErrorMessage), + Times.Once); + + this.mapperMock.Verify( + m => m.Map( + It.IsAny()), + Times.Never); + } + + /// + /// Should throw exception when repository fails. + /// + /// A task representing the asynchronous operation. + [Fact] + public async Task Handle_ShouldThrowException_WhenRepositoryFails() + { + var query = new GetTimelineItemByIdQuery(1); + + this.timelineRepoMock + .Setup(r => r.GetFirstOrDefaultAsync( + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>())) + .ThrowsAsync(new Exception("Database failure")); + + var exception = await Assert.ThrowsAsync(() => + this.handler.Handle(query, CancellationToken.None)); + + Assert.Equal("Database failure", exception.Message); + + this.mapperMock.Verify( + m => m.Map( + It.IsAny()), + Times.Never); + + this.loggerMock.Verify( + l => l.LogError( + It.IsAny(), + It.IsAny()), + Times.Never); + } + } +} From 0a893230ee81a5b7da1a58ebcf48855f53644a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=86=D0=BD=D0=BD=D0=B0=20=D0=A8=D0=BF=D0=BE=D0=BD=D1=8C?= =?UTF-8?q?=D0=BA=D0=B0?= Date: Sun, 10 May 2026 07:33:10 +0300 Subject: [PATCH 3/4] test/#3: updateGetById handler tests for TimelineItem, add GetAll handler tests for TimelineItem --- .../GetAll/GetAllTimelineItemHandlerTests.cs | 214 ++++++++++++++++++ .../GetTimelineItemByIdHandlerTests.cs | 56 +++-- 2 files changed, 253 insertions(+), 17 deletions(-) create mode 100644 Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetAll/GetAllTimelineItemHandlerTests.cs diff --git a/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetAll/GetAllTimelineItemHandlerTests.cs b/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetAll/GetAllTimelineItemHandlerTests.cs new file mode 100644 index 0000000..fc39c6e --- /dev/null +++ b/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetAll/GetAllTimelineItemHandlerTests.cs @@ -0,0 +1,214 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// +namespace Streetcode.XUnitTest.BLL.MediatR.Timeline.TimelineItem.GetAll +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AutoMapper; + using Microsoft.EntityFrameworkCore.Query; + using Moq; + using Streetcode.BLL.DTO.Timeline; + using Streetcode.BLL.Interfaces.Logging; + using Streetcode.BLL.MediatR.Timeline.TimelineItem.GetAll; + using Streetcode.DAL.Entities.Timeline; + using Streetcode.DAL.Enums; + using Streetcode.DAL.Repositories.Interfaces.Base; + using Streetcode.DAL.Repositories.Interfaces.Timeline; + using Xunit; + + /// + /// Unit tests for GetAllTimelineItemsHandler. + /// + public class GetAllTimelineItemsHandlerTests + { + private readonly Mock repoWrapperMock; + private readonly Mock timelineRepoMock; + private readonly Mock mapperMock; + private readonly Mock loggerMock; + + private readonly GetAllTimelineItemsHandler handler; + + /// + /// Initializes a new instance of the class. + /// + public GetAllTimelineItemsHandlerTests() + { + this.repoWrapperMock = new Mock(); + this.timelineRepoMock = new Mock(); + this.mapperMock = new Mock(); + this.loggerMock = new Mock(); + + this.repoWrapperMock + .Setup(x => x.TimelineRepository) + .Returns(this.timelineRepoMock.Object); + + this.handler = new GetAllTimelineItemsHandler( + this.repoWrapperMock.Object, + this.mapperMock.Object, + this.loggerMock.Object); + } + + /// + /// Should return timeline item DTO collection when items exist. + /// + /// A task representing the asynchronous operation. + [Fact] + public async Task Handle_ShouldReturnTimelineItemDtos_WhenItemsExist() + { + var query = new GetAllTimelineItemsQuery(); + + var timelineItems = new List + { + new TimelineItem + { + Id = 1, + Title = "Test Title", + Description = "Description", + Date = new DateTime(2020, 01, 01), + DateViewPattern = DateViewPattern.Year, + HistoricalContextTimelines = new List + { + new HistoricalContextTimeline + { + HistoricalContext = new HistoricalContext + { + Id = 1, + Title = "Historical Context 1", + }, + }, + }, + }, + }; + + var expectedDtos = new List + { + new TimelineItemDTO + { + Id = 1, + Title = "Test Title", + Description = "Description", + Date = new DateTime(2020, 01, 01), + DateViewPattern = DateViewPattern.Year, + HistoricalContexts = new List + { + new HistoricalContextDTO + { + Id = 1, + Title = "Historical Context 1", + }, + }, + }, + }; + + this.timelineRepoMock + .Setup(r => r.GetAllAsync( + null, + It.IsAny, IIncludableQueryable>>())) + .ReturnsAsync(timelineItems); + + this.mapperMock + .Setup(m => m.Map>(It.IsAny>())) + .Returns(expectedDtos); + + var result = await this.handler.Handle(query, CancellationToken.None); + + Assert.True(result.IsSuccess); + Assert.NotNull(result.Value); + Assert.Single(result.Value); + + var item = result.Value.First(); + + Assert.Equal("Test Title", item.Title); + Assert.Equal(1, item.Id); + Assert.Equal("Description", item.Description); + + this.mapperMock.Verify( + m => m.Map>( + It.IsAny>()), + Times.Once); + + this.timelineRepoMock.Verify( + r => r.GetAllAsync( + null, + It.IsAny, + IIncludableQueryable>>()), + Times.Once); + } + + /// + /// Should throw exception when repository fails. + /// + /// A task representing the asynchronous operation. + [Fact] + public async Task Handle_ShouldPropagateException_WhenRepositoryThrows() + { + var query = new GetAllTimelineItemsQuery(); + + this.timelineRepoMock + .Setup(r => r.GetAllAsync( + null, + It.IsAny, + IIncludableQueryable>>())) + .ThrowsAsync(new Exception("Database failure")); + + var exception = await Assert.ThrowsAsync(() => + this.handler.Handle(query, CancellationToken.None)); + + Assert.Equal("Database failure", exception.Message); + + this.mapperMock.Verify( + m => m.Map>( + It.IsAny>()), + Times.Never); + + this.loggerMock.Verify( + l => l.LogError( + It.IsAny(), + It.IsAny()), + Times.Never); + } + + /// + /// Should return empty collection when no timeline items exist. + /// + /// A task representing the asynchronous operation. + [Fact] + public async Task Handle_ShouldReturnEmptyCollection_WhenNoItemsExist() + { + var query = new GetAllTimelineItemsQuery(); + + this.timelineRepoMock + .Setup(r => r.GetAllAsync( + null, + It.IsAny, + IIncludableQueryable>>())) + .ReturnsAsync(new List()); + + this.mapperMock + .Setup(m => m.Map>( + It.IsAny>())) + .Returns(new List()); + + var result = await this.handler.Handle(query, CancellationToken.None); + + Assert.True(result.IsSuccess); + Assert.NotNull(result.Value); + Assert.Empty(result.Value); + + this.mapperMock.Verify( + m => m.Map>( + It.IsAny>()), + Times.Once); + + this.timelineRepoMock.Verify( + r => r.GetAllAsync( + null, + It.IsAny, + IIncludableQueryable>>()), + Times.Once); + } + } +} diff --git a/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetById/GetTimelineItemByIdHandlerTests.cs b/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetById/GetTimelineItemByIdHandlerTests.cs index 76f7927..705cefe 100644 --- a/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetById/GetTimelineItemByIdHandlerTests.cs +++ b/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetById/GetTimelineItemByIdHandlerTests.cs @@ -61,7 +61,7 @@ public async Task Handle_ShouldReturnTimelineItemDTO_WhenItemExists() { var query = new GetTimelineItemByIdQuery(1); - var timelineItem = new DAL.Entities.Timeline.TimelineItem + var timelineItem = new TimelineItem { Id = 1, Title = "Test Title", @@ -100,13 +100,14 @@ public async Task Handle_ShouldReturnTimelineItemDTO_WhenItemExists() this.timelineRepoMock .Setup(r => r.GetFirstOrDefaultAsync( - It.IsAny>>(), - It.IsAny, - IIncludableQueryable>>())) + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>())) .ReturnsAsync(timelineItem); this.mapperMock - .Setup(m => m.Map(timelineItem)) + .Setup(m => m.Map( + It.IsAny())) .Returns(expectedDto); var result = await this.handler.Handle(query, CancellationToken.None); @@ -120,7 +121,6 @@ public async Task Handle_ShouldReturnTimelineItemDTO_WhenItemExists() Assert.Equal(expectedDto.Date, result.Value.Date); Assert.Equal(expectedDto.DateViewPattern, result.Value.DateViewPattern); - Assert.NotNull(result.Value.HistoricalContexts); Assert.Single(result.Value.HistoricalContexts); Assert.Equal( @@ -128,7 +128,15 @@ public async Task Handle_ShouldReturnTimelineItemDTO_WhenItemExists() result.Value.HistoricalContexts.First().Title); this.mapperMock.Verify( - m => m.Map(timelineItem), + m => m.Map( + It.IsAny()), + Times.Once); + + this.timelineRepoMock.Verify( + r => r.GetFirstOrDefaultAsync( + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>()), Times.Once); } @@ -146,10 +154,10 @@ public async Task Handle_ShouldReturnFail_WhenItemNotFound() this.timelineRepoMock .Setup(r => r.GetFirstOrDefaultAsync( - It.IsAny>>(), - It.IsAny, - IIncludableQueryable>>())) - .ReturnsAsync((DAL.Entities.Timeline.TimelineItem?)null); + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>())) + .ReturnsAsync((TimelineItem?)null); var result = await this.handler.Handle(query, CancellationToken.None); @@ -167,8 +175,15 @@ public async Task Handle_ShouldReturnFail_WhenItemNotFound() this.mapperMock.Verify( m => m.Map( - It.IsAny()), + It.IsAny()), Times.Never); + + this.timelineRepoMock.Verify( + r => r.GetFirstOrDefaultAsync( + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>()), + Times.Once); } /// @@ -176,15 +191,15 @@ public async Task Handle_ShouldReturnFail_WhenItemNotFound() /// /// A task representing the asynchronous operation. [Fact] - public async Task Handle_ShouldThrowException_WhenRepositoryFails() + public async Task Handle_ShouldPropagateException_WhenRepositoryThrows() { var query = new GetTimelineItemByIdQuery(1); this.timelineRepoMock .Setup(r => r.GetFirstOrDefaultAsync( - It.IsAny>>(), - It.IsAny, - IIncludableQueryable>>())) + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>())) .ThrowsAsync(new Exception("Database failure")); var exception = await Assert.ThrowsAsync(() => @@ -194,7 +209,7 @@ public async Task Handle_ShouldThrowException_WhenRepositoryFails() this.mapperMock.Verify( m => m.Map( - It.IsAny()), + It.IsAny()), Times.Never); this.loggerMock.Verify( @@ -202,6 +217,13 @@ public async Task Handle_ShouldThrowException_WhenRepositoryFails() It.IsAny(), It.IsAny()), Times.Never); + + this.timelineRepoMock.Verify( + r => r.GetFirstOrDefaultAsync( + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>()), + Times.Once); } } } From 5c27c03cad3abdf6d55726695254b40712465758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=86=D0=BD=D0=BD=D0=B0=20=D0=A8=D0=BF=D0=BE=D0=BD=D1=8C?= =?UTF-8?q?=D0=BA=D0=B0?= Date: Sun, 10 May 2026 08:12:10 +0300 Subject: [PATCH 4/4] test/#3: add GetStreetcodeId handler tests for TimelineItem --- ...tTimelineItemByStreetcodeIdHandlerTests.cs | 232 ++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetByStreetcodeId/GetTimelineItemByStreetcodeIdHandlerTests.cs diff --git a/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetByStreetcodeId/GetTimelineItemByStreetcodeIdHandlerTests.cs b/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetByStreetcodeId/GetTimelineItemByStreetcodeIdHandlerTests.cs new file mode 100644 index 0000000..4c5317e --- /dev/null +++ b/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Timeline/TimelineItem/GetByStreetcodeId/GetTimelineItemByStreetcodeIdHandlerTests.cs @@ -0,0 +1,232 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace Streetcode.XUnitTest.BLL.MediatR.Timeline.TimelineItem.GetByStreetcodeId +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Threading.Tasks; + using AutoMapper; + using Microsoft.EntityFrameworkCore.Query; + using Moq; + using Streetcode.BLL.DTO.Timeline; + using Streetcode.BLL.Interfaces.Logging; + using Streetcode.BLL.MediatR.Timeline.TimelineItem.GetByStreetcodeId; + using Streetcode.DAL.Entities.Timeline; + using Streetcode.DAL.Enums; + using Streetcode.DAL.Repositories.Interfaces.Base; + using Streetcode.DAL.Repositories.Interfaces.Timeline; + using Xunit; + + /// + /// Unit tests for GetTimelineItemsByStreetcodeIdHandler. + /// + public class GetTimelineItemByStreetcodeIdHandlerTests + { + private readonly Mock repoWrapperMock; + private readonly Mock timelineRepoMock; + private readonly Mock mapperMock; + private readonly Mock loggerMock; + + private readonly GetTimelineItemsByStreetcodeIdHandler handler; + + /// + /// Initializes a new instance of the class. + /// + public GetTimelineItemByStreetcodeIdHandlerTests() + { + this.repoWrapperMock = new Mock(); + this.timelineRepoMock = new Mock(); + this.mapperMock = new Mock(); + this.loggerMock = new Mock(); + + this.repoWrapperMock + .Setup(x => x.TimelineRepository) + .Returns(this.timelineRepoMock.Object); + + this.handler = new GetTimelineItemsByStreetcodeIdHandler( + this.repoWrapperMock.Object, + this.mapperMock.Object, + this.loggerMock.Object); + } + + /// + /// Should return TimelineItemDTO when item exists. + /// + /// A task representing the asynchronous operation. + [Fact] + public async Task Handle_ShouldReturnTimelineItemDTO_WhenItemExists() + { + var query = new GetTimelineItemsByStreetcodeIdQuery(1); + + var timelineItems = new List + { + new TimelineItem + { + Id = 1, + Title = "Test Title", + Description = "Description", + Date = new DateTime(2020, 01, 01), + DateViewPattern = DateViewPattern.Year, + StreetcodeId = 1, + HistoricalContextTimelines = new List + { + new HistoricalContextTimeline + { + HistoricalContext = new HistoricalContext + { + Id = 1, + Title = "Historical Context 1", + }, + }, + }, + }, + }; + + var expectedDtos = new List + { + new TimelineItemDTO + { + Id = 1, + Title = "Test Title", + Description = "Description", + Date = new DateTime(2020, 01, 01), + DateViewPattern = DateViewPattern.Year, + HistoricalContexts = new List + { + new HistoricalContextDTO + { + Id = 1, + Title = "Historical Context 1", + }, + }, + }, + }; + + this.timelineRepoMock + .Setup(r => r.GetAllAsync( + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>())) + .ReturnsAsync(timelineItems); + + this.mapperMock + .Setup(m => m.Map>(It.IsAny>())) + .Returns(expectedDtos); + + var result = await this.handler.Handle(query, CancellationToken.None); + + Assert.True(result.IsSuccess); + Assert.NotNull(result.Value); + + var item = result.Value.First(); + + Assert.Equal(1, item.Id); + Assert.Equal(expectedDtos.First().Title, item.Title); + Assert.Equal(expectedDtos.First().Description, item.Description); + Assert.Equal(expectedDtos.First().Date, item.Date); + Assert.Equal(expectedDtos.First().DateViewPattern, item.DateViewPattern); + + Assert.Single(item.HistoricalContexts); + + Assert.Equal( + expectedDtos.First().HistoricalContexts.First().Title, + item.HistoricalContexts.First().Title); + + this.mapperMock.Verify( + m => m.Map>( + It.IsAny>()), + Times.Once); + + this.timelineRepoMock.Verify( + r => r.GetAllAsync( + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>()), + Times.Once); + } + + /// + /// Should throw exception when repository fails. + /// + /// A task representing the asynchronous operation. + [Fact] + public async Task Handle_ShouldPropagateException_WhenRepositoryThrows() + { + var query = new GetTimelineItemsByStreetcodeIdQuery(1); + + this.timelineRepoMock + .Setup(r => r.GetAllAsync( + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>())) + .ThrowsAsync(new Exception("Database failure")); + + var exception = await Assert.ThrowsAsync(() => + this.handler.Handle(query, CancellationToken.None)); + + Assert.Equal("Database failure", exception.Message); + + this.mapperMock.Verify( + m => m.Map>( + It.IsAny>()), + Times.Never); + + this.loggerMock.Verify( + l => l.LogError( + It.IsAny(), + It.IsAny()), + Times.Never); + + this.timelineRepoMock.Verify( + r => r.GetAllAsync( + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>()), + Times.Once); + } + + /// + /// Should return empty collection when no timeline items exist. + /// + /// A task representing the asynchronous operation. + [Fact] + public async Task Handle_ShouldReturnEmptyCollection_WhenNoItemsExist() + { + var query = new GetTimelineItemsByStreetcodeIdQuery(1); + + this.timelineRepoMock + .Setup(r => r.GetAllAsync( + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>())) + .ReturnsAsync(new List()); + + this.mapperMock + .Setup(m => m.Map>( + It.IsAny>())) + .Returns(new List()); + + var result = await this.handler.Handle(query, CancellationToken.None); + + Assert.True(result.IsSuccess); + Assert.NotNull(result.Value); + Assert.Empty(result.Value); + + this.mapperMock.Verify( + m => m.Map>( + It.IsAny>()), + Times.Once); + + this.timelineRepoMock.Verify( + r => r.GetAllAsync( + It.IsAny>>(), + It.IsAny, + IIncludableQueryable>>()), + Times.Once); + } + } +}