From 64f3f062375c313e27a40dc1bd00d253c22b4dcc Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Sun, 11 May 2025 14:49:44 +0200 Subject: [PATCH 1/4] Add endpoint for editing events --- .../DTOs/Request/CreateAddressDto.cs | 8 +- .../DTOs/Request/EditEventCategoryDto.cs | 5 ++ .../Events/Abstractions/IEventRepository.cs | 5 +- .../Events/Abstractions/IEventService.cs | 1 + .../Events/Controllers/EventsController.cs | 27 +++++++ .../Events/DTOs/Request/EditEventDto.cs | 16 ++++ .../DTOs/Response/EditEventResponseDto.cs | 3 + .../Events/Repositories/EventRepository.cs | 21 +++++ .../TickAPI/Events/Services/EventService.cs | 76 ++++++++++++++++--- 9 files changed, 146 insertions(+), 16 deletions(-) create mode 100644 TickAPI/TickAPI/Categories/DTOs/Request/EditEventCategoryDto.cs create mode 100644 TickAPI/TickAPI/Events/DTOs/Request/EditEventDto.cs create mode 100644 TickAPI/TickAPI/Events/DTOs/Response/EditEventResponseDto.cs diff --git a/TickAPI/TickAPI/Addresses/DTOs/Request/CreateAddressDto.cs b/TickAPI/TickAPI/Addresses/DTOs/Request/CreateAddressDto.cs index 8aa9f4c..c79aecd 100644 --- a/TickAPI/TickAPI/Addresses/DTOs/Request/CreateAddressDto.cs +++ b/TickAPI/TickAPI/Addresses/DTOs/Request/CreateAddressDto.cs @@ -1,12 +1,10 @@ -using TickAPI.Events.Models; - -namespace TickAPI.Addresses.DTOs.Request; +namespace TickAPI.Addresses.DTOs.Request; public record CreateAddressDto( - string Country, string City, string? Street, uint? HouseNumber, uint? FlatNumber, - string PostalCode); \ No newline at end of file + string PostalCode +); \ No newline at end of file diff --git a/TickAPI/TickAPI/Categories/DTOs/Request/EditEventCategoryDto.cs b/TickAPI/TickAPI/Categories/DTOs/Request/EditEventCategoryDto.cs new file mode 100644 index 0000000..ae2d123 --- /dev/null +++ b/TickAPI/TickAPI/Categories/DTOs/Request/EditEventCategoryDto.cs @@ -0,0 +1,5 @@ +namespace TickAPI.Categories.DTOs.Request; + +public record EditEventCategoryDto( + string CategoryName +); diff --git a/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs b/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs index 8f4c560..a7e4cb1 100644 --- a/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs +++ b/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs @@ -1,4 +1,5 @@ -using TickAPI.Common.Results.Generic; +using TickAPI.Common.Results; +using TickAPI.Common.Results.Generic; using TickAPI.Events.Models; using TickAPI.Organizers.Models; @@ -10,4 +11,6 @@ public interface IEventRepository public IQueryable GetEvents(); public IQueryable GetEventsByOranizer(Organizer organizer); public Task> GetEventByIdAsync(Guid eventId); + public Task SaveEventAsync(Event ev); + public Task> GetEventByIdAndOrganizerAsync(Guid eventId, Organizer organizer); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Abstractions/IEventService.cs b/TickAPI/TickAPI/Events/Abstractions/IEventService.cs index 3b4f020..b029b39 100644 --- a/TickAPI/TickAPI/Events/Abstractions/IEventService.cs +++ b/TickAPI/TickAPI/Events/Abstractions/IEventService.cs @@ -20,4 +20,5 @@ public Task> CreateNewEventAsync(string name, string description, public Task>> GetEventsAsync(int page, int pageSize, EventFiltersDto? eventFilters = null); public Task> GetEventsPaginationDetailsAsync(int pageSize); public Task> GetEventDetailsAsync(Guid eventId); + public Task> EditEventAsync(Organizer organizer, Guid eventId, string name, string description, DateTime startDate, DateTime endDate, uint? minimumAge, CreateAddressDto editAddress, List categories, EventStatus eventStatus); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Controllers/EventsController.cs b/TickAPI/TickAPI/Events/Controllers/EventsController.cs index c1ff0e1..95cbf68 100644 --- a/TickAPI/TickAPI/Events/Controllers/EventsController.cs +++ b/TickAPI/TickAPI/Events/Controllers/EventsController.cs @@ -114,4 +114,31 @@ public async Task> GetEventDetails([Fro var eventDetailsResult = await _eventService.GetEventDetailsAsync(id); return eventDetailsResult.ToObjectResult(); } + + [AuthorizeWithPolicy(AuthPolicies.VerifiedOrganizerPolicy)] + [HttpPatch("{id:guid}")] + public async Task> EditEvent([FromRoute] Guid id, [FromBody] EditEventDto request) + { + var emailResult = _claimsService.GetEmailFromClaims(User.Claims); + if (emailResult.IsError) + { + return emailResult.ToObjectResult(); + } + var email = emailResult.Value!; + + var organizerResult = await _organizerService.GetOrganizerByEmailAsync(email); + if (organizerResult.IsError) + { + return organizerResult.ToObjectResult(); + } + var organizer = organizerResult.Value!; + + var editedEventResult = await _eventService.EditEventAsync(organizer, id, request.Name, request.Description, request.StartDate, request.EndDate, request.MinimumAge, + request.EditAddress, request.Categories, request.EventStatus); + + if (editedEventResult.IsError) + return editedEventResult.ToObjectResult(); + + return Ok("Event edited succesfully"); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/DTOs/Request/EditEventDto.cs b/TickAPI/TickAPI/Events/DTOs/Request/EditEventDto.cs new file mode 100644 index 0000000..e441955 --- /dev/null +++ b/TickAPI/TickAPI/Events/DTOs/Request/EditEventDto.cs @@ -0,0 +1,16 @@ +using TickAPI.Addresses.DTOs.Request; +using TickAPI.Categories.DTOs.Request; +using TickAPI.Events.Models; + +namespace TickAPI.Events.DTOs.Request; + +public record EditEventDto( + string Name, + string Description, + DateTime StartDate, + DateTime EndDate, + uint? MinimumAge, + List Categories, + EventStatus EventStatus, + CreateAddressDto EditAddress +); diff --git a/TickAPI/TickAPI/Events/DTOs/Response/EditEventResponseDto.cs b/TickAPI/TickAPI/Events/DTOs/Response/EditEventResponseDto.cs new file mode 100644 index 0000000..455ecdc --- /dev/null +++ b/TickAPI/TickAPI/Events/DTOs/Response/EditEventResponseDto.cs @@ -0,0 +1,3 @@ +namespace TickAPI.Events.DTOs.Response; + +public record EditEventResponseDto(); diff --git a/TickAPI/TickAPI/Events/Repositories/EventRepository.cs b/TickAPI/TickAPI/Events/Repositories/EventRepository.cs index bdc59ee..bfd667e 100644 --- a/TickAPI/TickAPI/Events/Repositories/EventRepository.cs +++ b/TickAPI/TickAPI/Events/Repositories/EventRepository.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using TickAPI.Common.Results; using TickAPI.Common.Results.Generic; using TickAPI.Common.TickApiDbContext; using TickAPI.Events.Abstractions; @@ -54,4 +55,24 @@ public async Task> GetEventByIdAsync(Guid eventId) return Result.Success(@event); } + + public async Task SaveEventAsync(Event ev) + { + var fromDb = await GetEventByIdAsync(ev.Id); + if (fromDb.IsError) + return Result.PropagateError(fromDb); + await _tickApiDbContext.SaveChangesAsync(); + return Result.Success(); + } + + public async Task> GetEventByIdAndOrganizerAsync(Guid eventId, Organizer organizer) + { + var organizerEvents = GetEventsByOranizer(organizer); + var ev = await organizerEvents.Where(e => e.Id == eventId).FirstAsync(); + if (ev is null) + { + return Result.Failure(StatusCodes.Status404NotFound, $"Event with id {eventId} not found for organizer with id {organizer.Id}"); + } + return Result.Success(ev); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Services/EventService.cs b/TickAPI/TickAPI/Events/Services/EventService.cs index 8177bd7..dbd3d0c 100644 --- a/TickAPI/TickAPI/Events/Services/EventService.cs +++ b/TickAPI/TickAPI/Events/Services/EventService.cs @@ -4,6 +4,7 @@ using TickAPI.Common.Pagination.Responses; using TickAPI.Categories.Abstractions; using TickAPI.Categories.DTOs.Request; +using TickAPI.Common.Results; using TickAPI.Common.Time.Abstractions; using TickAPI.Events.Abstractions; using TickAPI.Events.Models; @@ -40,32 +41,31 @@ public EventService(IEventRepository eventRepository, IOrganizerService organize _ticketService = ticketService; } - public async Task> CreateNewEventAsync(string name, string description, DateTime startDate, DateTime endDate, + public async Task> CreateNewEventAsync(string name, string description, DateTime startDate, DateTime endDate, uint? minimumAge, CreateAddressDto createAddress, List categories, List ticketTypes, EventStatus eventStatus, string organizerEmail) { var organizerResult = await _organizerService.GetOrganizerByEmailAsync(organizerEmail); if (!organizerResult.IsSuccess) return Result.PropagateError(organizerResult); - - if (endDate < startDate) - return Result.Failure(StatusCodes.Status400BadRequest, "End date should be after start date"); + var datesCheck = CheckEventDates(startDate, endDate); + if (datesCheck.IsError) + return Result.PropagateError(datesCheck); - if (startDate < _dateTimeService.GetCurrentDateTime()) - return Result.Failure(StatusCodes.Status400BadRequest, "Start date is in the past"); - if (ticketTypes.Any(t => t.AvailableFrom > endDate)) { return Result.Failure(StatusCodes.Status400BadRequest, "Tickets can't be available after the event is over"); } var address = await _addressService.GetOrCreateAddressAsync(createAddress); - + if (address.IsError) + { + return Result.PropagateError(address); + } + var categoryNames = categories.Select(c => c.CategoryName).ToList(); - var categoriesByNameResult = _categoryService.GetCategoriesByNames(categoryNames); - if (categoriesByNameResult.IsError) { return Result.PropagateError(categoriesByNameResult); @@ -163,6 +163,51 @@ public async Task> GetEventDetailsAsync(Guid return Result.Success(details); } + public async Task> EditEventAsync(Organizer organizer, Guid eventId, string name, string description, DateTime startDate, DateTime endDate, uint? minimumAge, CreateAddressDto editAddress, List categories, + EventStatus eventStatus) + { + var existingEventResult = await _eventRepository.GetEventByIdAndOrganizerAsync(eventId, organizer); + if (existingEventResult.IsError) + { + return existingEventResult; + } + var existingEvent = existingEventResult.Value!; + + var datesCheck = CheckEventDates(startDate, endDate); + if (datesCheck.IsError) + return Result.PropagateError(datesCheck); + + var address = await _addressService.GetOrCreateAddressAsync(editAddress); + if (address.IsError) + { + return Result.PropagateError(address); + } + + var categoryNames = categories.Select(c => c.CategoryName).ToList(); + var categoriesByNameResult = _categoryService.GetCategoriesByNames(categoryNames); + if (categoriesByNameResult.IsError) + { + return Result.PropagateError(categoriesByNameResult); + } + + existingEvent.Name = name; + existingEvent.Description = description; + existingEvent.StartDate = startDate; + existingEvent.EndDate = endDate; + existingEvent.MinimumAge = minimumAge; + existingEvent.Address = address.Value!; + existingEvent.Categories = categoriesByNameResult.Value!; + existingEvent.EventStatus = eventStatus; + + var saveResult = await _eventRepository.SaveEventAsync(existingEvent); + if (saveResult.IsError) + { + return Result.PropagateError(saveResult); + } + + return Result.Success(existingEvent); + } + private async Task>> GetPaginatedEventsAsync(IQueryable events, int page, int pageSize) { var paginatedEventsResult = await _paginationService.PaginateAsync(events, pageSize, page); @@ -203,4 +248,15 @@ private static GetEventResponseDto MapEventToGetEventResponseDto(Event ev) return new GetEventResponseDto(ev.Id, ev.Name, ev.Description, ev.StartDate, ev.EndDate, ev.MinimumAge, minimumPrice, maximumPrice, categories, ev.EventStatus, address); } + + private Result CheckEventDates(DateTime startDate, DateTime endDate) + { + if (endDate < startDate) + return Result.Failure(StatusCodes.Status400BadRequest, "End date should be after start date"); + + if (startDate < _dateTimeService.GetCurrentDateTime()) + return Result.Failure(StatusCodes.Status400BadRequest, "Start date is in the past"); + + return Result.Success(); + } } \ No newline at end of file From a0b879a46ba08bdda0b10f107057b0ed87ea015f Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Sun, 11 May 2025 15:02:08 +0200 Subject: [PATCH 2/4] Add tests for `EventService:EditEvenAsync` --- .../Events/Services/EventServiceTests.cs | 622 ++++++++++++++++++ 1 file changed, 622 insertions(+) diff --git a/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs b/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs index cda1b68..5f87144 100644 --- a/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs +++ b/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs @@ -9,6 +9,7 @@ using TickAPI.Categories.Abstractions; using TickAPI.Categories.DTOs.Request; using TickAPI.Categories.Models; +using TickAPI.Common.Results; using TickAPI.Events.Models; using TickAPI.Organizers.Abstractions; using TickAPI.Organizers.Models; @@ -683,4 +684,625 @@ public async Task GetEventDetailsAsync_WhenFails_ShouldReturnEventError() Assert.Equal(StatusCodes.Status404NotFound, result.StatusCode); Assert.Equal($"event with id {@event.Id} not found", result.ErrorMsg); } + + [Fact] + public async Task EditEventAsync_WhenDataValid_ShouldUpdateEvent() + { + // Arrange + var organizer = new Organizer + { + Email = "organizer@example.com", + IsVerified = true + }; + var eventId = Guid.NewGuid(); + string name = "Updated Concert"; + string description = "Updated description of a concert"; + DateTime startDate = new DateTime(2025, 6, 1); + DateTime endDate = new DateTime(2025, 7, 1); + uint? minimumAge = 21; + EventStatus eventStatus = EventStatus.SoldOut; + + var existingEvent = new Event + { + Id = eventId, + Name = "Original Concert", + Description = "Original description", + StartDate = new DateTime(2025, 5, 1), + EndDate = new DateTime(2025, 5, 15), + MinimumAge = 18, + EventStatus = EventStatus.TicketsAvailable, + Organizer = organizer, + Categories = new List + { + new Category { Name = "rock" } + }, + Address = new Address + { + Country = "United States", + City = "Chicago", + Street = "State st", + HouseNumber = 100, + FlatNumber = null, + PostalCode = "60001" + } + }; + + List categories = + [ + new EditEventCategoryDto("jazz"), + new EditEventCategoryDto("classical") + ]; + + List expectedCategories = + [ + new Category { Name = "jazz" }, + new Category { Name = "classical" } + ]; + + CreateAddressDto editAddress = new CreateAddressDto("United States", "New York", "Broadway", 42, null, "10001"); + var updatedAddress = new Address + { + Country = "United States", + City = "New York", + Street = "Broadway", + HouseNumber = 42, + FlatNumber = null, + PostalCode = "10001" + }; + + var eventRepositoryMock = new Mock(); + eventRepositoryMock + .Setup(e => e.GetEventByIdAndOrganizerAsync(eventId, organizer)) + .ReturnsAsync(Result.Success(existingEvent)); + eventRepositoryMock + .Setup(e => e.SaveEventAsync(It.IsAny())) + .ReturnsAsync(Result.Success()); + + var organizerServiceMock = new Mock(); + + var addressServiceMock = new Mock(); + addressServiceMock + .Setup(m => m.GetOrCreateAddressAsync(editAddress)) + .ReturnsAsync(Result
.Success(updatedAddress)); + + var dateTimeServiceMock = new Mock(); + dateTimeServiceMock + .Setup(m => m.GetCurrentDateTime()) + .Returns(new DateTime(2025, 4, 15)); + + var categoryServiceMock = new Mock(); + categoryServiceMock + .Setup(c => c.GetCategoriesByNames(It.IsAny>())) + .Returns(Result>.Success(expectedCategories)); + + var paginationServiceMock = new Mock(); + var ticketServiceMock = new Mock(); + + var sut = new EventService( + eventRepositoryMock.Object, + organizerServiceMock.Object, + addressServiceMock.Object, + dateTimeServiceMock.Object, + paginationServiceMock.Object, + categoryServiceMock.Object, + ticketServiceMock.Object); + + // Act + var result = await sut.EditEventAsync( + organizer, + eventId, + name, + description, + startDate, + endDate, + minimumAge, + editAddress, + categories, + eventStatus); + + // Assert + Assert.True(result.IsSuccess); + Assert.Equal(name, result.Value!.Name); + Assert.Equal(description, result.Value!.Description); + Assert.Equal(startDate, result.Value!.StartDate); + Assert.Equal(endDate, result.Value!.EndDate); + Assert.Equal(minimumAge, result.Value!.MinimumAge); + Assert.Equal(eventStatus, result.Value!.EventStatus); + Assert.Equal(expectedCategories.Count, result.Value!.Categories.Count); + Assert.Equal(updatedAddress, result.Value!.Address); + + // Verify repository was called to save the updated event + eventRepositoryMock.Verify(e => e.SaveEventAsync(It.IsAny()), Times.Once); + } + + [Fact] + public async Task EditEventAsync_WhenEventNotFound_ShouldReturnError() + { + // Arrange + var organizer = new Organizer + { + Email = "organizer@example.com", + IsVerified = true + }; + var eventId = Guid.NewGuid(); + string name = "Updated Concert"; + string description = "Updated description"; + DateTime startDate = new DateTime(2025, 6, 1); + DateTime endDate = new DateTime(2025, 7, 1); + uint? minimumAge = 21; + EventStatus eventStatus = EventStatus.SoldOut; + + List categories = + [ + new EditEventCategoryDto("jazz"), + new EditEventCategoryDto("classical") + ]; + + CreateAddressDto editAddress = new CreateAddressDto("United States", "New York", "Broadway", 42, null, "10001"); + + var eventRepositoryMock = new Mock(); + eventRepositoryMock + .Setup(e => e.GetEventByIdAndOrganizerAsync(eventId, organizer)) + .ReturnsAsync(Result.Failure(StatusCodes.Status404NotFound, "Event not found or not owned by organizer")); + + var organizerServiceMock = new Mock(); + var addressServiceMock = new Mock(); + var dateTimeServiceMock = new Mock(); + var categoryServiceMock = new Mock(); + var paginationServiceMock = new Mock(); + var ticketServiceMock = new Mock(); + + var sut = new EventService( + eventRepositoryMock.Object, + organizerServiceMock.Object, + addressServiceMock.Object, + dateTimeServiceMock.Object, + paginationServiceMock.Object, + categoryServiceMock.Object, + ticketServiceMock.Object); + + // Act + var result = await sut.EditEventAsync( + organizer, + eventId, + name, + description, + startDate, + endDate, + minimumAge, + editAddress, + categories, + eventStatus); + + // Assert + Assert.False(result.IsSuccess); + Assert.Equal(StatusCodes.Status404NotFound, result.StatusCode); + Assert.Equal("Event not found or not owned by organizer", result.ErrorMsg); + } + + [Fact] + public async Task EditEventAsync_WhenEndDateBeforeStartDate_ShouldReturnBadRequest() + { + // Arrange + var organizer = new Organizer + { + Email = "organizer@example.com", + IsVerified = true + }; + var eventId = Guid.NewGuid(); + string name = "Updated Concert"; + string description = "Updated description"; + DateTime startDate = new DateTime(2025, 7, 1); + DateTime endDate = new DateTime(2025, 6, 1); // End date before start date + uint? minimumAge = 21; + EventStatus eventStatus = EventStatus.SoldOut; + + var existingEvent = new Event + { + Id = eventId, + Name = "Original Concert", + Organizer = organizer + }; + + List categories = + [ + new EditEventCategoryDto("jazz") + ]; + + CreateAddressDto editAddress = new CreateAddressDto("United States", "New York", "Broadway", 42, null, "10001"); + + var eventRepositoryMock = new Mock(); + eventRepositoryMock + .Setup(e => e.GetEventByIdAndOrganizerAsync(eventId, organizer)) + .ReturnsAsync(Result.Success(existingEvent)); + + var organizerServiceMock = new Mock(); + var addressServiceMock = new Mock(); + + var dateTimeServiceMock = new Mock(); + dateTimeServiceMock + .Setup(m => m.GetCurrentDateTime()) + .Returns(new DateTime(2025, 4, 15)); + + var categoryServiceMock = new Mock(); + var paginationServiceMock = new Mock(); + var ticketServiceMock = new Mock(); + + var sut = new EventService( + eventRepositoryMock.Object, + organizerServiceMock.Object, + addressServiceMock.Object, + dateTimeServiceMock.Object, + paginationServiceMock.Object, + categoryServiceMock.Object, + ticketServiceMock.Object); + + // Act + var result = await sut.EditEventAsync( + organizer, + eventId, + name, + description, + startDate, + endDate, + minimumAge, + editAddress, + categories, + eventStatus); + + // Assert + Assert.False(result.IsSuccess); + Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode); + Assert.Equal("End date should be after start date", result.ErrorMsg); + } + + [Fact] + public async Task EditEventAsync_WhenStartDateInPast_ShouldReturnBadRequest() + { + // Arrange + var organizer = new Organizer + { + Email = "organizer@example.com", + IsVerified = true + }; + var eventId = Guid.NewGuid(); + string name = "Updated Concert"; + string description = "Updated description"; + DateTime startDate = new DateTime(2025, 4, 1); // Start date in the past compared to current date + DateTime endDate = new DateTime(2025, 5, 1); + uint? minimumAge = 21; + EventStatus eventStatus = EventStatus.SoldOut; + + var existingEvent = new Event + { + Id = eventId, + Name = "Original Concert", + Organizer = organizer + }; + + List categories = + [ + new EditEventCategoryDto("jazz") + ]; + + CreateAddressDto editAddress = new CreateAddressDto("United States", "New York", "Broadway", 42, null, "10001"); + + var eventRepositoryMock = new Mock(); + eventRepositoryMock + .Setup(e => e.GetEventByIdAndOrganizerAsync(eventId, organizer)) + .ReturnsAsync(Result.Success(existingEvent)); + + var organizerServiceMock = new Mock(); + var addressServiceMock = new Mock(); + + var dateTimeServiceMock = new Mock(); + dateTimeServiceMock + .Setup(m => m.GetCurrentDateTime()) + .Returns(new DateTime(2025, 4, 15)); // Current date is after start date + + var categoryServiceMock = new Mock(); + var paginationServiceMock = new Mock(); + var ticketServiceMock = new Mock(); + + var sut = new EventService( + eventRepositoryMock.Object, + organizerServiceMock.Object, + addressServiceMock.Object, + dateTimeServiceMock.Object, + paginationServiceMock.Object, + categoryServiceMock.Object, + ticketServiceMock.Object); + + // Act + var result = await sut.EditEventAsync( + organizer, + eventId, + name, + description, + startDate, + endDate, + minimumAge, + editAddress, + categories, + eventStatus); + + // Assert + Assert.False(result.IsSuccess); + Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode); + Assert.Equal("Start date is in the past", result.ErrorMsg); + } + + [Fact] + public async Task EditEventAsync_WhenAddressServiceFails_ShouldPropagateError() + { + // Arrange + var organizer = new Organizer + { + Email = "organizer@example.com", + IsVerified = true + }; + var eventId = Guid.NewGuid(); + string name = "Updated Concert"; + string description = "Updated description"; + DateTime startDate = new DateTime(2025, 6, 1); + DateTime endDate = new DateTime(2025, 7, 1); + uint? minimumAge = 21; + EventStatus eventStatus = EventStatus.SoldOut; + + var existingEvent = new Event + { + Id = eventId, + Name = "Original Concert", + Organizer = organizer + }; + + List categories = + [ + new EditEventCategoryDto("jazz") + ]; + + CreateAddressDto editAddress = new CreateAddressDto("", "", "", 0, null, ""); // Invalid address + + var eventRepositoryMock = new Mock(); + eventRepositoryMock + .Setup(e => e.GetEventByIdAndOrganizerAsync(eventId, organizer)) + .ReturnsAsync(Result.Success(existingEvent)); + + var organizerServiceMock = new Mock(); + + var addressServiceMock = new Mock(); + addressServiceMock + .Setup(m => m.GetOrCreateAddressAsync(editAddress)) + .ReturnsAsync(Result
.Failure(StatusCodes.Status400BadRequest, "Invalid address data")); + + var dateTimeServiceMock = new Mock(); + dateTimeServiceMock + .Setup(m => m.GetCurrentDateTime()) + .Returns(new DateTime(2025, 4, 15)); + + var categoryServiceMock = new Mock(); + var paginationServiceMock = new Mock(); + var ticketServiceMock = new Mock(); + + var sut = new EventService( + eventRepositoryMock.Object, + organizerServiceMock.Object, + addressServiceMock.Object, + dateTimeServiceMock.Object, + paginationServiceMock.Object, + categoryServiceMock.Object, + ticketServiceMock.Object); + + // Act + var result = await sut.EditEventAsync( + organizer, + eventId, + name, + description, + startDate, + endDate, + minimumAge, + editAddress, + categories, + eventStatus); + + // Assert + Assert.False(result.IsSuccess); + Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode); + Assert.Equal("Invalid address data", result.ErrorMsg); + } + + [Fact] + public async Task EditEventAsync_WhenCategoryServiceFails_ShouldPropagateError() + { + // Arrange + var organizer = new Organizer + { + Email = "organizer@example.com", + IsVerified = true + }; + var eventId = Guid.NewGuid(); + string name = "Updated Concert"; + string description = "Updated description"; + DateTime startDate = new DateTime(2025, 6, 1); + DateTime endDate = new DateTime(2025, 7, 1); + uint? minimumAge = 21; + EventStatus eventStatus = EventStatus.SoldOut; + + var existingEvent = new Event + { + Id = eventId, + Name = "Original Concert", + Organizer = organizer + }; + + List categories = + [ + new EditEventCategoryDto("non-existent-category") + ]; + + CreateAddressDto editAddress = new CreateAddressDto("United States", "New York", "Broadway", 42, null, "10001"); + var updatedAddress = new Address + { + Country = "United States", + City = "New York", + Street = "Broadway", + HouseNumber = 42, + FlatNumber = null, + PostalCode = "10001" + }; + + var eventRepositoryMock = new Mock(); + eventRepositoryMock + .Setup(e => e.GetEventByIdAndOrganizerAsync(eventId, organizer)) + .ReturnsAsync(Result.Success(existingEvent)); + + var organizerServiceMock = new Mock(); + + var addressServiceMock = new Mock(); + addressServiceMock + .Setup(m => m.GetOrCreateAddressAsync(editAddress)) + .ReturnsAsync(Result
.Success(updatedAddress)); + + var dateTimeServiceMock = new Mock(); + dateTimeServiceMock + .Setup(m => m.GetCurrentDateTime()) + .Returns(new DateTime(2025, 4, 15)); + + var categoryServiceMock = new Mock(); + categoryServiceMock + .Setup(c => c.GetCategoriesByNames(It.IsAny>())) + .Returns(Result>.Failure(StatusCodes.Status404NotFound, "One or more categories not found")); + + var paginationServiceMock = new Mock(); + var ticketServiceMock = new Mock(); + + var sut = new EventService( + eventRepositoryMock.Object, + organizerServiceMock.Object, + addressServiceMock.Object, + dateTimeServiceMock.Object, + paginationServiceMock.Object, + categoryServiceMock.Object, + ticketServiceMock.Object); + + // Act + var result = await sut.EditEventAsync( + organizer, + eventId, + name, + description, + startDate, + endDate, + minimumAge, + editAddress, + categories, + eventStatus); + + // Assert + Assert.False(result.IsSuccess); + Assert.Equal(StatusCodes.Status404NotFound, result.StatusCode); + Assert.Equal("One or more categories not found", result.ErrorMsg); + } + + [Fact] + public async Task EditEventAsync_WhenSaveEventFails_ShouldPropagateError() + { + // Arrange + var organizer = new Organizer + { + Email = "organizer@example.com", + IsVerified = true + }; + var eventId = Guid.NewGuid(); + string name = "Updated Concert"; + string description = "Updated description"; + DateTime startDate = new DateTime(2025, 6, 1); + DateTime endDate = new DateTime(2025, 7, 1); + uint? minimumAge = 21; + EventStatus eventStatus = EventStatus.SoldOut; + + var existingEvent = new Event + { + Id = eventId, + Name = "Original Concert", + Organizer = organizer + }; + + List categories = + [ + new EditEventCategoryDto("jazz") + ]; + + List expectedCategories = + [ + new Category { Name = "jazz" } + ]; + + CreateAddressDto editAddress = new CreateAddressDto("United States", "New York", "Broadway", 42, null, "10001"); + var updatedAddress = new Address + { + Country = "United States", + City = "New York", + Street = "Broadway", + HouseNumber = 42, + FlatNumber = null, + PostalCode = "10001" + }; + + var eventRepositoryMock = new Mock(); + eventRepositoryMock + .Setup(e => e.GetEventByIdAndOrganizerAsync(eventId, organizer)) + .ReturnsAsync(Result.Success(existingEvent)); + eventRepositoryMock + .Setup(e => e.SaveEventAsync(It.IsAny())) + .ReturnsAsync(Result.Failure(StatusCodes.Status500InternalServerError, "Database error occurred")); + + var organizerServiceMock = new Mock(); + + var addressServiceMock = new Mock(); + addressServiceMock + .Setup(m => m.GetOrCreateAddressAsync(editAddress)) + .ReturnsAsync(Result
.Success(updatedAddress)); + + var dateTimeServiceMock = new Mock(); + dateTimeServiceMock + .Setup(m => m.GetCurrentDateTime()) + .Returns(new DateTime(2025, 4, 15)); + + var categoryServiceMock = new Mock(); + categoryServiceMock + .Setup(c => c.GetCategoriesByNames(It.IsAny>())) + .Returns(Result>.Success(expectedCategories)); + + var paginationServiceMock = new Mock(); + var ticketServiceMock = new Mock(); + + var sut = new EventService( + eventRepositoryMock.Object, + organizerServiceMock.Object, + addressServiceMock.Object, + dateTimeServiceMock.Object, + paginationServiceMock.Object, + categoryServiceMock.Object, + ticketServiceMock.Object); + + // Act + var result = await sut.EditEventAsync( + organizer, + eventId, + name, + description, + startDate, + endDate, + minimumAge, + editAddress, + categories, + eventStatus); + + // Assert + Assert.False(result.IsSuccess); + Assert.Equal(StatusCodes.Status500InternalServerError, result.StatusCode); + Assert.Equal("Database error occurred", result.ErrorMsg); + } } \ No newline at end of file From 1554e2d03bce9e54ea6930cf30a9034808ccfc40 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Mon, 12 May 2025 10:04:16 +0200 Subject: [PATCH 3/4] fix \n --- TickAPI/TickAPI/Events/Services/EventService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/TickAPI/TickAPI/Events/Services/EventService.cs b/TickAPI/TickAPI/Events/Services/EventService.cs index dbd3d0c..e232b20 100644 --- a/TickAPI/TickAPI/Events/Services/EventService.cs +++ b/TickAPI/TickAPI/Events/Services/EventService.cs @@ -63,7 +63,6 @@ public async Task> CreateNewEventAsync(string name, string descri { return Result.PropagateError(address); } - var categoryNames = categories.Select(c => c.CategoryName).ToList(); var categoriesByNameResult = _categoryService.GetCategoriesByNames(categoryNames); if (categoriesByNameResult.IsError) From 08f2bbb607afa10ee6b70bef5fc08a36e86566b3 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Mon, 12 May 2025 10:27:25 +0200 Subject: [PATCH 4/4] Update logic behind checking if dates are correct in edited event --- .../Events/Services/EventServiceTests.cs | 234 +++++++++++++++++- .../TickAPI/Events/Services/EventService.cs | 40 +-- 2 files changed, 247 insertions(+), 27 deletions(-) diff --git a/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs b/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs index 5f87144..34b23ca 100644 --- a/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs +++ b/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs @@ -724,7 +724,8 @@ public async Task EditEventAsync_WhenDataValid_ShouldUpdateEvent() HouseNumber = 100, FlatNumber = null, PostalCode = "60001" - } + }, + TicketTypes = [], }; List categories = @@ -901,7 +902,8 @@ public async Task EditEventAsync_WhenEndDateBeforeStartDate_ShouldReturnBadReque { Id = eventId, Name = "Original Concert", - Organizer = organizer + Organizer = organizer, + TicketTypes = [], }; List categories = @@ -957,7 +959,7 @@ public async Task EditEventAsync_WhenEndDateBeforeStartDate_ShouldReturnBadReque } [Fact] - public async Task EditEventAsync_WhenStartDateInPast_ShouldReturnBadRequest() + public async Task EditEventAsync_WhenStartDateChangedAndInPast_ShouldReturnBadRequest() { // Arrange var organizer = new Organizer @@ -977,7 +979,9 @@ public async Task EditEventAsync_WhenStartDateInPast_ShouldReturnBadRequest() { Id = eventId, Name = "Original Concert", - Organizer = organizer + Organizer = organizer, + TicketTypes = [], + StartDate = new DateTime(2025, 3, 1), }; List categories = @@ -1032,6 +1036,219 @@ public async Task EditEventAsync_WhenStartDateInPast_ShouldReturnBadRequest() Assert.Equal("Start date is in the past", result.ErrorMsg); } + [Fact] + public async Task EditEventAsync_StartDateNotChangedAndInPast_ShouldUpdateEvent() + { + // Arrange + var organizer = new Organizer + { + Email = "organizer@example.com", + IsVerified = true + }; + var eventId = Guid.NewGuid(); + string name = "Updated Concert"; + string description = "Updated description of a concert"; + DateTime startDate = new DateTime(2020, 1, 1); + DateTime endDate = new DateTime(2025, 7, 1); + uint? minimumAge = 21; + EventStatus eventStatus = EventStatus.SoldOut; + + var existingEvent = new Event + { + Id = eventId, + Name = "Original Concert", + Description = "Original description", + StartDate = new DateTime(2020, 1, 1), + EndDate = new DateTime(2025, 5, 15), + MinimumAge = 18, + EventStatus = EventStatus.TicketsAvailable, + Organizer = organizer, + Categories = new List + { + new Category { Name = "rock" } + }, + Address = new Address + { + Country = "United States", + City = "Chicago", + Street = "State st", + HouseNumber = 100, + FlatNumber = null, + PostalCode = "60001" + }, + TicketTypes = [], + }; + + List categories = + [ + new EditEventCategoryDto("jazz"), + new EditEventCategoryDto("classical") + ]; + + List expectedCategories = + [ + new Category { Name = "jazz" }, + new Category { Name = "classical" } + ]; + + CreateAddressDto editAddress = new CreateAddressDto("United States", "New York", "Broadway", 42, null, "10001"); + var updatedAddress = new Address + { + Country = "United States", + City = "New York", + Street = "Broadway", + HouseNumber = 42, + FlatNumber = null, + PostalCode = "10001" + }; + + var eventRepositoryMock = new Mock(); + eventRepositoryMock + .Setup(e => e.GetEventByIdAndOrganizerAsync(eventId, organizer)) + .ReturnsAsync(Result.Success(existingEvent)); + eventRepositoryMock + .Setup(e => e.SaveEventAsync(It.IsAny())) + .ReturnsAsync(Result.Success()); + + var organizerServiceMock = new Mock(); + + var addressServiceMock = new Mock(); + addressServiceMock + .Setup(m => m.GetOrCreateAddressAsync(editAddress)) + .ReturnsAsync(Result
.Success(updatedAddress)); + + var dateTimeServiceMock = new Mock(); + dateTimeServiceMock + .Setup(m => m.GetCurrentDateTime()) + .Returns(new DateTime(2025, 4, 15)); + + var categoryServiceMock = new Mock(); + categoryServiceMock + .Setup(c => c.GetCategoriesByNames(It.IsAny>())) + .Returns(Result>.Success(expectedCategories)); + + var paginationServiceMock = new Mock(); + var ticketServiceMock = new Mock(); + + var sut = new EventService( + eventRepositoryMock.Object, + organizerServiceMock.Object, + addressServiceMock.Object, + dateTimeServiceMock.Object, + paginationServiceMock.Object, + categoryServiceMock.Object, + ticketServiceMock.Object); + + // Act + var result = await sut.EditEventAsync( + organizer, + eventId, + name, + description, + startDate, + endDate, + minimumAge, + editAddress, + categories, + eventStatus); + + // Assert + Assert.True(result.IsSuccess); + Assert.Equal(name, result.Value!.Name); + Assert.Equal(description, result.Value!.Description); + Assert.Equal(startDate, result.Value!.StartDate); + Assert.Equal(endDate, result.Value!.EndDate); + Assert.Equal(minimumAge, result.Value!.MinimumAge); + Assert.Equal(eventStatus, result.Value!.EventStatus); + Assert.Equal(expectedCategories.Count, result.Value!.Categories.Count); + Assert.Equal(updatedAddress, result.Value!.Address); + + // Verify repository was called to save the updated event + eventRepositoryMock.Verify(e => e.SaveEventAsync(It.IsAny()), Times.Once); + } + + [Fact] + public async Task EditEventAsync_WhenTicketTypeAvailableFromAfterEndDate_ShouldReturnBadRequest() + { + // Arrange + var organizer = new Organizer + { + Email = "organizer@example.com", + IsVerified = true + }; + var eventId = Guid.NewGuid(); + string name = "Updated Concert"; + string description = "Updated description"; + DateTime startDate = new DateTime(2025, 4, 1); + DateTime endDate = new DateTime(2025, 5, 1); + uint? minimumAge = 21; + EventStatus eventStatus = EventStatus.SoldOut; + + var existingEvent = new Event + { + Id = eventId, + Name = "Original Concert", + Organizer = organizer, + TicketTypes = [ + new TicketType + { + AvailableFrom = new DateTime(3000, 12, 12), + } + ], + }; + + List categories = + [ + new EditEventCategoryDto("jazz") + ]; + + CreateAddressDto editAddress = new CreateAddressDto("United States", "New York", "Broadway", 42, null, "10001"); + + var eventRepositoryMock = new Mock(); + eventRepositoryMock + .Setup(e => e.GetEventByIdAndOrganizerAsync(eventId, organizer)) + .ReturnsAsync(Result.Success(existingEvent)); + + var organizerServiceMock = new Mock(); + var addressServiceMock = new Mock(); + + var dateTimeServiceMock = new Mock(); + dateTimeServiceMock + .Setup(m => m.GetCurrentDateTime()) + .Returns(new DateTime(2000, 4, 15)); + + var categoryServiceMock = new Mock(); + var paginationServiceMock = new Mock(); + var ticketServiceMock = new Mock(); + + var sut = new EventService( + eventRepositoryMock.Object, + organizerServiceMock.Object, + addressServiceMock.Object, + dateTimeServiceMock.Object, + paginationServiceMock.Object, + categoryServiceMock.Object, + ticketServiceMock.Object); + + // Act + var result = await sut.EditEventAsync( + organizer, + eventId, + name, + description, + startDate, + endDate, + minimumAge, + editAddress, + categories, + eventStatus); + + // Assert + Assert.False(result.IsSuccess); + Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode); + Assert.Equal("Tickets can't be available after the event is over", result.ErrorMsg); + } + [Fact] public async Task EditEventAsync_WhenAddressServiceFails_ShouldPropagateError() { @@ -1053,7 +1270,8 @@ public async Task EditEventAsync_WhenAddressServiceFails_ShouldPropagateError() { Id = eventId, Name = "Original Concert", - Organizer = organizer + Organizer = organizer, + TicketTypes = [], }; List categories = @@ -1133,7 +1351,8 @@ public async Task EditEventAsync_WhenCategoryServiceFails_ShouldPropagateError() { Id = eventId, Name = "Original Concert", - Organizer = organizer + Organizer = organizer, + TicketTypes = [], }; List categories = @@ -1226,7 +1445,8 @@ public async Task EditEventAsync_WhenSaveEventFails_ShouldPropagateError() { Id = eventId, Name = "Original Concert", - Organizer = organizer + Organizer = organizer, + TicketTypes = [], }; List categories = diff --git a/TickAPI/TickAPI/Events/Services/EventService.cs b/TickAPI/TickAPI/Events/Services/EventService.cs index e232b20..39f7807 100644 --- a/TickAPI/TickAPI/Events/Services/EventService.cs +++ b/TickAPI/TickAPI/Events/Services/EventService.cs @@ -49,15 +49,20 @@ public async Task> CreateNewEventAsync(string name, string descri if (!organizerResult.IsSuccess) return Result.PropagateError(organizerResult); - var datesCheck = CheckEventDates(startDate, endDate); + var ticketTypesConverted = ticketTypes.Select(t => new TicketType + { + Description = t.Description, + AvailableFrom = t.AvailableFrom, + Currency = t.Currency, + MaxCount = t.MaxCount, + Price = t.Price, + }) + .ToList(); + + var datesCheck = CheckEventDates(startDate, endDate, ticketTypesConverted); if (datesCheck.IsError) return Result.PropagateError(datesCheck); - if (ticketTypes.Any(t => t.AvailableFrom > endDate)) - { - return Result.Failure(StatusCodes.Status400BadRequest, "Tickets can't be available after the event is over"); - } - var address = await _addressService.GetOrCreateAddressAsync(createAddress); if (address.IsError) { @@ -69,17 +74,7 @@ public async Task> CreateNewEventAsync(string name, string descri { return Result.PropagateError(categoriesByNameResult); } - - var ticketTypesConverted = ticketTypes.Select(t => new TicketType - { - Description = t.Description, - AvailableFrom = t.AvailableFrom, - Currency = t.Currency, - MaxCount = t.MaxCount, - Price = t.Price, - }) - .ToList(); - + var @event = new Event { Name = name, @@ -172,7 +167,7 @@ public async Task> EditEventAsync(Organizer organizer, Guid eventI } var existingEvent = existingEventResult.Value!; - var datesCheck = CheckEventDates(startDate, endDate); + var datesCheck = CheckEventDates(startDate, endDate, existingEvent.TicketTypes, existingEvent.StartDate == startDate); if (datesCheck.IsError) return Result.PropagateError(datesCheck); @@ -248,13 +243,18 @@ private static GetEventResponseDto MapEventToGetEventResponseDto(Event ev) minimumPrice, maximumPrice, categories, ev.EventStatus, address); } - private Result CheckEventDates(DateTime startDate, DateTime endDate) + private Result CheckEventDates(DateTime startDate, DateTime endDate, IEnumerable ticketTypes, bool skipStartDateEvaluation = false) { if (endDate < startDate) return Result.Failure(StatusCodes.Status400BadRequest, "End date should be after start date"); - if (startDate < _dateTimeService.GetCurrentDateTime()) + if (!skipStartDateEvaluation && startDate < _dateTimeService.GetCurrentDateTime()) return Result.Failure(StatusCodes.Status400BadRequest, "Start date is in the past"); + + if (ticketTypes.Any(t => t.AvailableFrom > endDate)) + { + return Result.Failure(StatusCodes.Status400BadRequest, "Tickets can't be available after the event is over"); + } return Result.Success(); }