diff --git a/Orso.Arpa.Api/Controllers/AppointmentsController.cs b/Orso.Arpa.Api/Controllers/AppointmentsController.cs
index e10b14b23..d2f9d08ef 100644
--- a/Orso.Arpa.Api/Controllers/AppointmentsController.cs
+++ b/Orso.Arpa.Api/Controllers/AppointmentsController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -10,301 +11,347 @@
using Orso.Arpa.Domain.AppointmentDomain.Enums;
using Orso.Arpa.Domain.UserDomain.Enums;
-namespace Orso.Arpa.Api.Controllers
+namespace Orso.Arpa.Api.Controllers;
+
+public class AppointmentsController : BaseController
{
- public class AppointmentsController : BaseController
+ private readonly IAppointmentService _appointmentService;
+
+ public AppointmentsController(IAppointmentService appointmentService)
{
- private readonly IAppointmentService _appointmentService;
+ _appointmentService = appointmentService;
+ }
- public AppointmentsController(IAppointmentService appointmentService)
- {
- _appointmentService = appointmentService;
- }
+ ///
+ /// Queries a list of appointments dependent on the given date and date range
+ ///
+ ///
+ ///
+ /// A list of appointments
+ ///
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpGet]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task>> Get([FromQuery] DateTime? date,
+ [FromQuery] DateRange range)
+ {
+ return Ok(await _appointmentService.GetAsync(date, range));
+ }
- ///
- /// Queries a list of appointments dependent on the given date and date range
- ///
- ///
- ///
- /// A list of appointments
- ///
- [Authorize(Roles = RoleNames.Staff)]
- [HttpGet]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public async Task>> Get([FromQuery] DateTime? date, [FromQuery] DateRange range)
- {
- return Ok(await _appointmentService.GetAsync(date, range));
- }
+ ///
+ /// Gets an appointment by id
+ ///
+ ///
+ /// Default: true. If true, request will be very slow!
+ /// The queried appointment
+ ///
+ /// If entity could not be found
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpGet("{id}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ public async Task> GetById([FromRoute] Guid id,
+ [FromQuery] bool includeParticipations = true)
+ {
+ return Ok(await _appointmentService.GetByIdAsync(id, includeParticipations));
+ }
- ///
- /// Gets an appointment by id
- ///
- ///
- /// Default: true. If true, request will be very slow!
- /// The queried appointment
- ///
- /// If entity could not be found
- [Authorize(Roles = RoleNames.Staff)]
- [HttpGet("{id}")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- public async Task> GetById([FromRoute] Guid id, [FromQuery] bool includeParticipations = true)
- {
- return Ok(await _appointmentService.GetByIdAsync(id, includeParticipations));
- }
+ ///
+ /// Creates a new appointment
+ ///
+ ///
+ /// The created appointment
+ /// Returns the created appointment
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpPost]
+ [ProducesResponseType(StatusCodes.Status201Created)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task> Post(
+ [FromBody] AppointmentCreateDto appointmentCreateDto)
+ {
+ AppointmentDto createdAppointment =
+ await _appointmentService.CreateAsync(appointmentCreateDto);
+ return CreatedAtAction(nameof(GetById), new { id = createdAppointment.Id },
+ createdAppointment);
+ }
- ///
- /// Creates a new appointment
- ///
- ///
- /// The created appointment
- /// Returns the created appointment
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpPost]
- [ProducesResponseType(StatusCodes.Status201Created)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task> Post([FromBody] AppointmentCreateDto appointmentCreateDto)
- {
- AppointmentDto createdAppointment = await _appointmentService.CreateAsync(appointmentCreateDto);
- return CreatedAtAction(nameof(GetById), new { id = createdAppointment.Id }, createdAppointment);
- }
+ ///
+ /// Adds a room to an existing appointment
+ ///
+ ///
+ ///
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpPost("{id}/rooms/{roomId}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task AddRoom([FromRoute] AppointmentAddRoomDto addRoomDto)
+ {
+ await _appointmentService.AddRoomAsync(addRoomDto);
+ return NoContent();
+ }
- ///
- /// Adds a room to an existing appointment
- ///
- ///
- ///
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpPost("{id}/rooms/{roomId}")]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task AddRoom([FromRoute] AppointmentAddRoomDto addRoomDto)
- {
- await _appointmentService.AddRoomAsync(addRoomDto);
- return NoContent();
- }
+ ///
+ /// Adds a project to an existing appointment
+ ///
+ ///
+ ///
+ ///
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpPost("{id}/projects/{projectId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task> AddProject(
+ [FromRoute] AppointmentAddProjectDto addProjectDto,
+ [FromQuery] bool includeParticipations = true)
+ {
+ return await _appointmentService.AddProjectAsync(addProjectDto, includeParticipations);
+ }
- ///
- /// Adds a project to an existing appointment
- ///
- ///
- ///
- ///
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpPost("{id}/projects/{projectId}")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task> AddProject([FromRoute] AppointmentAddProjectDto addProjectDto, [FromQuery] bool includeParticipations = true)
- {
- return await _appointmentService.AddProjectAsync(addProjectDto, includeParticipations);
- }
+ ///
+ /// Adds a section to an existing appointment
+ ///
+ ///
+ ///
+ ///
+ /// If domain validation fails
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpPost("{id}/sections/{sectionId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task> AddSection(
+ [FromRoute] AppointmentAddSectionDto addSectionDto,
+ [FromQuery] bool includeParticipations = true)
+ {
+ return await _appointmentService.AddSectionAsync(addSectionDto, includeParticipations);
+ }
- ///
- /// Adds a section to an existing appointment
- ///
- ///
- ///
- ///
- /// If domain validation fails
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpPost("{id}/sections/{sectionId}")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task> AddSection([FromRoute] AppointmentAddSectionDto addSectionDto, [FromQuery] bool includeParticipations = true)
- {
- return await _appointmentService.AddSectionAsync(addSectionDto, includeParticipations);
- }
+ ///
+ /// Sends an appointment changed notification to all participants of the appointment
+ ///
+ ///
+ ///
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpPost("{id}/notification")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task> SendAppointmentChangedNotification(
+ SendAppointmentChangedNotificationDto sendAppointmentChangedNotificationDto)
+ {
+ await _appointmentService.SendAppointmentChangedNotificationAsync(
+ sendAppointmentChangedNotificationDto);
+ return NoContent();
+ }
- ///
- /// Sends an appointment changed notification to all participants of the appointment
- ///
- ///
- ///
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpPost("{id}/notification")]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task> SendAppointmentChangedNotification(SendAppointmentChangedNotificationDto sendAppointmentChangedNotificationDto)
- {
- await _appointmentService.SendAppointmentChangedNotificationAsync(sendAppointmentChangedNotificationDto);
- return NoContent();
- }
+ ///
+ /// Sets the venue of an existing appointment
+ ///
+ ///
+ ///
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpPut("{id}/venue/set/{venueId}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task SetVenue([FromRoute] AppointmentSetVenueDto setVenueDto)
+ {
+ await _appointmentService.SetVenueAsync(setVenueDto);
+ return NoContent();
+ }
- ///
- /// Sets the venue of an existing appointment
- ///
- ///
- ///
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpPut("{id}/venue/set/{venueId}")]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task SetVenue([FromRoute] AppointmentSetVenueDto setVenueDto)
- {
- await _appointmentService.SetVenueAsync(setVenueDto);
- return NoContent();
- }
+ ///
+ /// Modifies existing appointment
+ ///
+ ///
+ ///
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpPut("{id}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task Put(AppointmentModifyDto appointmentModifyDto)
+ {
+ await _appointmentService.ModifyAsync(appointmentModifyDto);
- ///
- /// Modifies existing appointment
- ///
- ///
- ///
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpPut("{id}")]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task Put(AppointmentModifyDto appointmentModifyDto)
- {
- await _appointmentService.ModifyAsync(appointmentModifyDto);
+ return NoContent();
+ }
- return NoContent();
- }
+ ///
+ /// Removes room from existing appointment
+ ///
+ ///
+ ///
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpDelete("{id}/rooms/{roomId}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task RemoveRoom([FromRoute] AppointmentRemoveRoomDto removeRoomDto)
+ {
+ await _appointmentService.RemoveRoomAsync(removeRoomDto);
+ return NoContent();
+ }
- ///
- /// Removes room from existing appointment
- ///
- ///
- ///
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpDelete("{id}/rooms/{roomId}")]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task RemoveRoom([FromRoute] AppointmentRemoveRoomDto removeRoomDto)
- {
- await _appointmentService.RemoveRoomAsync(removeRoomDto);
- return NoContent();
- }
+ ///
+ /// Removes section from existing appointment
+ ///
+ ///
+ ///
+ ///
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpDelete("{id}/sections/{sectionId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task> RemoveSection(
+ [FromRoute] AppointmentRemoveSectionDto removeSectionDto,
+ [FromQuery] bool includeParticipations = true)
+ {
+ return await _appointmentService.RemoveSectionAsync(removeSectionDto,
+ includeParticipations);
+ }
- ///
- /// Removes section from existing appointment
- ///
- ///
- ///
- ///
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpDelete("{id}/sections/{sectionId}")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task> RemoveSection([FromRoute] AppointmentRemoveSectionDto removeSectionDto, [FromQuery] bool includeParticipations = true)
- {
- return await _appointmentService.RemoveSectionAsync(removeSectionDto, includeParticipations);
- }
+ ///
+ /// Removes project from existing appointment
+ ///
+ ///
+ ///
+ ///
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpDelete("{id}/projects/{projectId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task> RemoveProject(
+ [FromRoute] AppointmentRemoveProjectDto removeProjectDto,
+ [FromQuery] bool includeParticipations = true)
+ {
+ return await _appointmentService.RemoveProjectAsync(removeProjectDto,
+ includeParticipations);
+ }
- ///
- /// Removes project from existing appointment
- ///
- ///
- ///
- ///
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpDelete("{id}/projects/{projectId}")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task> RemoveProject([FromRoute] AppointmentRemoveProjectDto removeProjectDto, [FromQuery] bool includeParticipations = true)
- {
- return await _appointmentService.RemoveProjectAsync(removeProjectDto, includeParticipations);
- }
+ ///
+ /// Deletes existing appointment by id
+ ///
+ ///
+ ///
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Admin)]
+ [HttpDelete("{id}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task Delete([FromRoute] Guid id)
+ {
+ await _appointmentService.DeleteAsync(id);
+ return NoContent();
+ }
- ///
- /// Deletes existing appointment by id
- ///
- ///
- ///
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Admin)]
- [HttpDelete("{id}")]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task Delete([FromRoute] Guid id)
- {
- await _appointmentService.DeleteAsync(id);
- return NoContent();
- }
+ ///
+ /// Sets start and end time of an existing appointment
+ ///
+ ///
+ ///
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpPut("{id}/dates/set")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task> SetDates(AppointmentSetDatesDto setDatesDto)
+ {
+ return await _appointmentService.SetDatesAsync(setDatesDto);
+ }
- ///
- /// Sets start and end time of an existing appointment
- ///
- ///
- ///
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpPut("{id}/dates/set")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task> SetDates(AppointmentSetDatesDto setDatesDto)
- {
- return await _appointmentService.SetDatesAsync(setDatesDto);
- }
+ ///
+ /// Sets the result of an appointment participation
+ ///
+ ///
+ ///
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpPut("{id}/participations/{personId}/result")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task SetParticipationResult(
+ AppointmentParticipationSetResultDto setParticipationResult)
+ {
+ await _appointmentService.SetParticipationResultAsync(setParticipationResult);
+ return NoContent();
+ }
- ///
- /// Sets the result of an appointment participation
- ///
- ///
- ///
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpPut("{id}/participations/{personId}/result")]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task SetParticipationResult(AppointmentParticipationSetResultDto setParticipationResult)
- {
- await _appointmentService.SetParticipationResultAsync(setParticipationResult);
- return NoContent();
- }
+ ///
+ /// Sets the prediction of an appointment participation
+ ///
+ ///
+ ///
+ /// If entity could not be found
+ /// If validation fails
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpPut("{id}/participations/{personId}/prediction")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ValidationProblemDetails),
+ StatusCodes.Status422UnprocessableEntity)]
+ public async Task SetParticipationPrediction(
+ AppointmentParticipationSetPredictionDto setParticipationPrediction)
+ {
+ await _appointmentService.SetParticipationPredictionAsync(setParticipationPrediction);
+ return NoContent();
+ }
- ///
- /// Sets the prediction of an appointment participation
- ///
- ///
- ///
- /// If entity could not be found
- /// If validation fails
- [Authorize(Roles = RoleNames.Staff)]
- [HttpPut("{id}/participations/{personId}/prediction")]
- [ProducesResponseType(StatusCodes.Status204NoContent)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status404NotFound)]
- [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status422UnprocessableEntity)]
- public async Task SetParticipationPrediction(AppointmentParticipationSetPredictionDto setParticipationPrediction)
- {
- await _appointmentService.SetParticipationPredictionAsync(setParticipationPrediction);
- return NoContent();
- }
+ ///
+ /// Exports all appointments to ics file
+ ///
+ ///
+ [Authorize(Roles = RoleNames.Staff)]
+ [HttpGet("export")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task ExportToIcs()
+ {
+ string serializedCalendar = await _appointmentService.ExportAppointmentsToIcsAsync();
+ return File(Encoding.UTF8.GetBytes(serializedCalendar), "text/calendar",
+ "appointments.ics");
}
}
diff --git a/Orso.Arpa.Api/Orso.Arpa.Api.csproj b/Orso.Arpa.Api/Orso.Arpa.Api.csproj
index 4cc6402ef..920a9d969 100644
--- a/Orso.Arpa.Api/Orso.Arpa.Api.csproj
+++ b/Orso.Arpa.Api/Orso.Arpa.Api.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/Orso.Arpa.Api/appsettings.Development.json b/Orso.Arpa.Api/appsettings.Development.json
index 28a7faa22..0b54b6739 100644
--- a/Orso.Arpa.Api/appsettings.Development.json
+++ b/Orso.Arpa.Api/appsettings.Development.json
@@ -18,8 +18,12 @@
"internalLogLevel": "Info",
"internalLogFile": "${basedir}/internal-nlog.txt",
"extensions": [
- { "assembly": "NLog.Extensions.Logging" },
- { "assembly": "NLog.Web.AspNetCore" }
+ {
+ "assembly": "NLog.Extensions.Logging"
+ },
+ {
+ "assembly": "NLog.Web.AspNetCore"
+ }
],
"default-wrapper": {
"type": "AsyncWrapper",
@@ -47,7 +51,7 @@
"EmailConfiguration": {
"From": "dev@arpa.orso.co",
"SmtpServer": "localhost",
- "Port": 25,
+ "Port": 8025,
"Username": "",
"Password": "",
"DefaultSubject": "Message from ARPA"
@@ -100,7 +104,10 @@
"IpRateLimiting": {
"EnableEndpointRateLimiting": true,
"StackBlockedRequests": true,
- "EndpointWhitelist": [ "options:*", "post:/graphql" ],
+ "EndpointWhitelist": [
+ "options:*",
+ "post:/graphql"
+ ],
"HttpStatusCode": 429,
"GeneralRules": [
{
diff --git a/Orso.Arpa.Application/AppointmentApplication/Interfaces/IAppointmentService.cs b/Orso.Arpa.Application/AppointmentApplication/Interfaces/IAppointmentService.cs
index b605ee3da..e0abf35cd 100644
--- a/Orso.Arpa.Application/AppointmentApplication/Interfaces/IAppointmentService.cs
+++ b/Orso.Arpa.Application/AppointmentApplication/Interfaces/IAppointmentService.cs
@@ -5,38 +5,47 @@
using Orso.Arpa.Application.AppointmentParticipationApplication.Model;
using Orso.Arpa.Domain.AppointmentDomain.Enums;
-namespace Orso.Arpa.Application.AppointmentApplication.Interfaces
+namespace Orso.Arpa.Application.AppointmentApplication.Interfaces;
+
+public interface IAppointmentService
{
- public interface IAppointmentService
- {
- Task> GetAsync(DateTime? date, DateRange range);
+ Task> GetAsync(DateTime? date, DateRange? range);
+
+ Task GetByIdAsync(Guid id, bool includeParticipations);
+
+ Task CreateAsync(AppointmentCreateDto appointmentCreateDto);
+
+ Task ModifyAsync(AppointmentModifyDto appointmentModifyDto);
- Task GetByIdAsync(Guid id, bool includeParticipations);
+ Task RemoveRoomAsync(AppointmentRemoveRoomDto removeRoomDto);
- Task CreateAsync(AppointmentCreateDto appointmentCreateDto);
+ Task AddRoomAsync(AppointmentAddRoomDto addRoomDto);
- Task ModifyAsync(AppointmentModifyDto appointmentModifyDto);
+ Task AddProjectAsync(AppointmentAddProjectDto addProjectDto,
+ bool includeParticipations);
- Task RemoveRoomAsync(AppointmentRemoveRoomDto removeRoomDto);
+ Task AddSectionAsync(AppointmentAddSectionDto addSectionDto,
+ bool includeParticipations);
- Task AddRoomAsync(AppointmentAddRoomDto addRoomDto);
+ Task RemoveSectionAsync(AppointmentRemoveSectionDto removeSectionDto,
+ bool includeParticipations);
- Task AddProjectAsync(AppointmentAddProjectDto addProjectDto, bool includeParticipations);
+ Task RemoveProjectAsync(AppointmentRemoveProjectDto removeProjectDto,
+ bool includeParticipations);
- Task AddSectionAsync(AppointmentAddSectionDto addSectionDto, bool includeParticipations);
+ Task SetVenueAsync(AppointmentSetVenueDto setVenueDto);
- Task RemoveSectionAsync(AppointmentRemoveSectionDto removeSectionDto, bool includeParticipations);
+ Task SetDatesAsync(AppointmentSetDatesDto setDatesDto);
- Task RemoveProjectAsync(AppointmentRemoveProjectDto removeProjectDto, bool includeParticipations);
+ Task DeleteAsync(Guid id);
- Task SetVenueAsync(AppointmentSetVenueDto setVenueDto);
+ Task SetParticipationResultAsync(AppointmentParticipationSetResultDto setParticipationResult);
- Task SetDatesAsync(AppointmentSetDatesDto setDatesDto);
+ Task SetParticipationPredictionAsync(
+ AppointmentParticipationSetPredictionDto setParticipationPrediction);
- Task DeleteAsync(Guid id);
+ Task SendAppointmentChangedNotificationAsync(
+ SendAppointmentChangedNotificationDto sendAppointmentChangedNotificationDto);
- Task SetParticipationResultAsync(AppointmentParticipationSetResultDto setParticipationResult);
- Task SetParticipationPredictionAsync(AppointmentParticipationSetPredictionDto setParticipationPrediction);
- Task SendAppointmentChangedNotificationAsync(SendAppointmentChangedNotificationDto sendAppointmentChangedNotificationDto);
- }
+ Task ExportAppointmentsToIcsAsync();
}
diff --git a/Orso.Arpa.Application/AppointmentApplication/Services/AppointmentService.cs b/Orso.Arpa.Application/AppointmentApplication/Services/AppointmentService.cs
index 1752c08c5..d78733545 100644
--- a/Orso.Arpa.Application/AppointmentApplication/Services/AppointmentService.cs
+++ b/Orso.Arpa.Application/AppointmentApplication/Services/AppointmentService.cs
@@ -11,6 +11,7 @@
using Orso.Arpa.Domain.AppointmentDomain.Commands;
using Orso.Arpa.Domain.AppointmentDomain.Enums;
using Orso.Arpa.Domain.AppointmentDomain.Model;
+using Orso.Arpa.Domain.AppointmentDomain.Queries;
using Orso.Arpa.Domain.AppointmentDomain.Util;
using Orso.Arpa.Domain.General.Extensions;
using Orso.Arpa.Domain.General.GenericHandlers;
@@ -18,139 +19,171 @@
using Orso.Arpa.Domain.SectionDomain.Model;
using Orso.Arpa.Domain.SectionDomain.Queries;
-namespace Orso.Arpa.Application.AppointmentApplication.Services
+namespace Orso.Arpa.Application.AppointmentApplication.Services;
+
+public class AppointmentService : BaseService<
+ AppointmentDto,
+ Appointment,
+ AppointmentCreateDto,
+ CreateAppointment.Command,
+ AppointmentModifyDto,
+ AppointmentModifyBodyDto,
+ ModifyAppointment.Command>, IAppointmentService
{
- public class AppointmentService : BaseService<
- AppointmentDto,
- Appointment,
- AppointmentCreateDto,
- CreateAppointment.Command,
- AppointmentModifyDto,
- AppointmentModifyBodyDto,
- ModifyAppointment.Command>, IAppointmentService
- {
- public AppointmentService(IMediator mediator, IMapper mapper) : base(mediator, mapper)
- {
- }
+ public AppointmentService(IMediator mediator, IMapper mapper) : base(mediator, mapper)
+ {
+ }
- public async Task AddProjectAsync(AppointmentAddProjectDto addProjectDto, bool includeParticipations)
- {
- AddProjectToAppointment.Command command = _mapper.Map(addProjectDto);
- await _mediator.Send(command);
- return await GetByIdAsync(addProjectDto.Id, includeParticipations);
- }
+ public async Task AddProjectAsync(AppointmentAddProjectDto addProjectDto,
+ bool includeParticipations)
+ {
+ AddProjectToAppointment.Command command =
+ _mapper.Map(addProjectDto);
+ await _mediator.Send(command);
+ return await GetByIdAsync(addProjectDto.Id, includeParticipations);
+ }
- public async Task AddSectionAsync(AppointmentAddSectionDto addSectionDto, bool includeParticipations)
- {
- AddSectionToAppointment.Command command = _mapper.Map(addSectionDto);
- await _mediator.Send(command);
- return await GetByIdAsync(addSectionDto.Id, includeParticipations);
- }
+ public async Task AddSectionAsync(AppointmentAddSectionDto addSectionDto,
+ bool includeParticipations)
+ {
+ AddSectionToAppointment.Command command =
+ _mapper.Map(addSectionDto);
+ await _mediator.Send(command);
+ return await GetByIdAsync(addSectionDto.Id, includeParticipations);
+ }
- public async Task AddRoomAsync(AppointmentAddRoomDto addRoomDto)
- {
- AddRoomToAppointment.Command command = _mapper.Map(addRoomDto);
- await _mediator.Send(command);
- }
+ public async Task AddRoomAsync(AppointmentAddRoomDto addRoomDto)
+ {
+ AddRoomToAppointment.Command
+ command = _mapper.Map(addRoomDto);
+ await _mediator.Send(command);
+ }
- public async Task> GetAsync(DateTime? date, DateRange range)
+ public async Task> GetAsync(DateTime? date, DateRange? range)
+ {
+ if (range.HasValue)
{
date ??= DateTime.Today;
+ DateTime rangeStartTime = DateHelper.GetStartTime(date.Value, range.Value);
+ DateTime rangeEndTime = DateHelper.GetEndTime(date.Value, range.Value);
- DateTime rangeStartTime = DateHelper.GetStartTime(date.Value, range);
- DateTime rangeEndTime = DateHelper.GetEndTime(date.Value, range);
+ IQueryable entities = await _mediator.Send(new List.Query(
+ a => (a.EndTime <= rangeEndTime && a.EndTime >= rangeStartTime) ||
+ (a.EndTime > rangeEndTime && a.StartTime <= rangeEndTime),
+ asSplitQuery: true));
+ return _mapper.ProjectTo(entities);
+ }
+ else
+ {
IQueryable entities = await _mediator.Send(new List.Query(
- predicate: a =>
- (a.EndTime <= rangeEndTime && a.EndTime >= rangeStartTime)
- || (a.EndTime > rangeEndTime && a.StartTime <= rangeEndTime),
asSplitQuery: true));
- return [.. _mapper.ProjectTo(entities)];
+ return _mapper.ProjectTo(entities);
}
+ }
- public async Task GetByIdAsync(Guid id, bool includeParticipations)
+ public async Task GetByIdAsync(Guid id, bool includeParticipations)
+ {
+ Appointment appointment = await _mediator.Send(new Details.Query(id));
+ AppointmentDto dto = _mapper.Map(appointment);
+ if (includeParticipations)
{
- Appointment appointment = await _mediator.Send(new Details.Query(id));
- AppointmentDto dto = _mapper.Map(appointment);
- if (includeParticipations)
- {
- var treeQuery = new ListFlattenedSectionTree.Query();
- IEnumerable> flattenedTree = await _mediator.Send(treeQuery);
- await AddParticipationsAsync(dto, appointment, flattenedTree);
- }
- return dto;
+ var treeQuery = new ListFlattenedSectionTree.Query();
+ IEnumerable> flattenedTree = await _mediator.Send(treeQuery);
+ await AddParticipationsAsync(dto, appointment, flattenedTree);
}
- private async Task AddParticipationsAsync(
- AppointmentDto dto,
- Appointment appointment,
- IEnumerable> flattenedTree)
- {
- var query = new ListParticipationsForAppointment.Query
- {
- Appointment = appointment,
- SectionTree = flattenedTree,
- };
+ return dto;
+ }
- IEnumerable personGrouping = await _mediator.Send(query);
+ public async Task RemoveProjectAsync(
+ AppointmentRemoveProjectDto removeProjectDto, bool includeParticipations)
+ {
+ RemoveProjectFromAppointment.Command command =
+ _mapper.Map(removeProjectDto);
+ await _mediator.Send(command);
+ return await GetByIdAsync(removeProjectDto.Id, includeParticipations);
+ }
- dto.Participations = _mapper.Map>(personGrouping);
- }
+ public async Task RemoveSectionAsync(
+ AppointmentRemoveSectionDto removeSectionDto, bool includeParticipations)
+ {
+ RemoveSectionFromAppointment.Command command =
+ _mapper.Map(removeSectionDto);
+ await _mediator.Send(command);
+ return await GetByIdAsync(removeSectionDto.Id, includeParticipations);
+ }
- public async Task RemoveProjectAsync(AppointmentRemoveProjectDto removeProjectDto, bool includeParticipations)
- {
- RemoveProjectFromAppointment.Command command = _mapper.Map(removeProjectDto);
- await _mediator.Send(command);
- return await GetByIdAsync(removeProjectDto.Id, includeParticipations);
- }
+ public async Task RemoveRoomAsync(AppointmentRemoveRoomDto removeRoomDto)
+ {
+ RemoveRoomFromAppointment.Command command =
+ _mapper.Map(removeRoomDto);
+ await _mediator.Send(command);
+ }
- public async Task RemoveSectionAsync(AppointmentRemoveSectionDto removeSectionDto, bool includeParticipations)
- {
- RemoveSectionFromAppointment.Command command = _mapper.Map(removeSectionDto);
- await _mediator.Send(command);
- return await GetByIdAsync(removeSectionDto.Id, includeParticipations);
- }
+ public async Task SetDatesAsync(AppointmentSetDatesDto setDatesDto)
+ {
+ SetDates.Command command = _mapper.Map(setDatesDto);
+ Appointment appointment = await _mediator.Send(command);
+ AppointmentDto dto = _mapper.Map(appointment);
+ var treeQuery = new ListFlattenedSectionTree.Query();
+ IEnumerable> flattenedTree = await _mediator.Send(treeQuery);
+ await AddParticipationsAsync(dto, appointment, flattenedTree);
+ return dto;
+ }
- public async Task RemoveRoomAsync(AppointmentRemoveRoomDto removeRoomDto)
- {
- RemoveRoomFromAppointment.Command command = _mapper.Map(removeRoomDto);
- await _mediator.Send(command);
- }
+ public async Task SetVenueAsync(AppointmentSetVenueDto setVenueDto)
+ {
+ SetVenue.Command command = _mapper.Map(setVenueDto);
+ await _mediator.Send(command);
+ }
- public async Task SetDatesAsync(AppointmentSetDatesDto setDatesDto)
- {
- SetDates.Command command = _mapper.Map(setDatesDto);
- Appointment appointment = await _mediator.Send(command);
- AppointmentDto dto = _mapper.Map(appointment);
- var treeQuery = new ListFlattenedSectionTree.Query();
- IEnumerable> flattenedTree = await _mediator.Send(treeQuery);
- await AddParticipationsAsync(dto, appointment, flattenedTree);
- return dto;
- }
+ public async Task SetParticipationResultAsync(
+ AppointmentParticipationSetResultDto setParticipationResult)
+ {
+ SetAppointmentParticipationResult.Command command =
+ _mapper.Map(setParticipationResult);
+ await _mediator.Send(command);
+ }
- public async Task SetVenueAsync(AppointmentSetVenueDto setVenueDto)
- {
- SetVenue.Command command = _mapper.Map(setVenueDto);
- await _mediator.Send(command);
- }
+ public async Task SetParticipationPredictionAsync(
+ AppointmentParticipationSetPredictionDto setParticipationPrediction)
+ {
+ SetAppointmentParticipationPrediction.Command command =
+ _mapper.Map(setParticipationPrediction);
+ await _mediator.Send(command);
+ }
- public async Task SetParticipationResultAsync(AppointmentParticipationSetResultDto setParticipationResult)
- {
- SetAppointmentParticipationResult.Command command = _mapper.Map(setParticipationResult);
- await _mediator.Send(command);
- }
+ public async Task SendAppointmentChangedNotificationAsync(
+ SendAppointmentChangedNotificationDto sendAppointmentChangedNotificationDto)
+ {
+ SendAppointmentChangedNotification.Command command =
+ _mapper.Map(
+ sendAppointmentChangedNotificationDto);
+ await _mediator.Send(command);
+ }
- public async Task SetParticipationPredictionAsync(AppointmentParticipationSetPredictionDto setParticipationPrediction)
+ private async Task AddParticipationsAsync(
+ AppointmentDto dto,
+ Appointment appointment,
+ IEnumerable> flattenedTree)
+ {
+ var query = new ListParticipationsForAppointment.Query
{
- SetAppointmentParticipationPrediction.Command command = _mapper.Map(setParticipationPrediction);
- await _mediator.Send(command);
- }
+ Appointment = appointment, SectionTree = flattenedTree
+ };
- public async Task SendAppointmentChangedNotificationAsync(SendAppointmentChangedNotificationDto sendAppointmentChangedNotificationDto)
- {
- SendAppointmentChangedNotification.Command command = _mapper.Map(sendAppointmentChangedNotificationDto);
- await _mediator.Send(command);
- }
+ IEnumerable personGrouping =
+ await _mediator.Send(query);
+
+ dto.Participations =
+ _mapper.Map>(personGrouping);
+ }
+
+ public async Task ExportAppointmentsToIcsAsync()
+ {
+ var query = new ExportAppointmentsToIcs.Query();
+ return await _mediator.Send(query);
}
}
diff --git a/Orso.Arpa.Application/Orso.Arpa.Application.csproj b/Orso.Arpa.Application/Orso.Arpa.Application.csproj
index e1a6aea97..bcb0b2c5a 100644
--- a/Orso.Arpa.Application/Orso.Arpa.Application.csproj
+++ b/Orso.Arpa.Application/Orso.Arpa.Application.csproj
@@ -9,4 +9,4 @@
-
\ No newline at end of file
+
diff --git a/Orso.Arpa.Domain/AppointmentDomain/Queries/ExportAppointmentsToIcs.cs b/Orso.Arpa.Domain/AppointmentDomain/Queries/ExportAppointmentsToIcs.cs
new file mode 100644
index 000000000..1e21fec0b
--- /dev/null
+++ b/Orso.Arpa.Domain/AppointmentDomain/Queries/ExportAppointmentsToIcs.cs
@@ -0,0 +1,57 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Ical.Net;
+using Ical.Net.CalendarComponents;
+using Ical.Net.DataTypes;
+using Ical.Net.Serialization;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+using Orso.Arpa.Domain.General.Interfaces;
+using Orso.Arpa.Misc.Extensions;
+
+namespace Orso.Arpa.Domain.AppointmentDomain.Queries;
+
+public class ExportAppointmentsToIcs
+{
+ public class Query : IRequest
+ {
+ }
+
+ public class Handler : IRequestHandler
+ {
+ private readonly IArpaContext _arpaContext;
+
+ public Handler(IArpaContext context)
+ {
+ _arpaContext = context;
+ }
+
+ public async Task Handle(Query request, CancellationToken cancellationToken)
+ {
+ List events = await _arpaContext.Appointments.Select(a => new CalendarEvent
+ {
+ Summary = a.Name,
+ Description = (a.Category != null ? a.Category.SelectValue.Name : "-") + " | " +
+ (string.IsNullOrEmpty(a.PublicDetails) ? "-" : a.PublicDetails) +
+ " | " +
+ (string.IsNullOrEmpty(a.InternalDetails) ? "-" : a.InternalDetails),
+ Location = a.Venue != null ? a.Venue.Address.City : "-",
+ Start = new CalDateTime(DateTimeExtensions.ConvertToLocalTimeBerlin(a.StartTime)),
+ End = new CalDateTime(DateTimeExtensions.ConvertToLocalTimeBerlin(a.EndTime))
+ }).ToListAsync(cancellationToken);
+
+ var calendar = new Calendar();
+
+ string timeZoneId = "Europe/Berlin";
+ var timeZone = new VTimeZone(timeZoneId);
+ calendar.AddTimeZone(timeZone);
+
+ calendar.Events.AddRange(events);
+
+ var serializer = new CalendarSerializer();
+ return serializer.SerializeToString(calendar);
+ }
+ }
+}
diff --git a/Orso.Arpa.Domain/Orso.Arpa.Domain.csproj b/Orso.Arpa.Domain/Orso.Arpa.Domain.csproj
index a68d4d8e4..4dbdee6a8 100644
--- a/Orso.Arpa.Domain/Orso.Arpa.Domain.csproj
+++ b/Orso.Arpa.Domain/Orso.Arpa.Domain.csproj
@@ -15,6 +15,7 @@
+
diff --git a/Orso.Arpa.Domain/UserDomain/Commands/RegisterUser.cs b/Orso.Arpa.Domain/UserDomain/Commands/RegisterUser.cs
index 06d8275c7..268f0e5a7 100644
--- a/Orso.Arpa.Domain/UserDomain/Commands/RegisterUser.cs
+++ b/Orso.Arpa.Domain/UserDomain/Commands/RegisterUser.cs
@@ -101,7 +101,7 @@ public async Task Handle(Command request, CancellationToken cancellationTo
break;
default:
throw new ValidationException(
- [new(nameof(Command.Email), "Multiple persons found with this email address. Registration aborted. Please contact your system admin.")]);
+ [new ValidationFailure(nameof(Command.Email), "Multiple persons found with this email address. Registration aborted. Please contact your system admin.")]);
}
var user = new User
diff --git a/Orso.Arpa.Infrastructure/Orso.Arpa.Infrastructure.csproj b/Orso.Arpa.Infrastructure/Orso.Arpa.Infrastructure.csproj
index 942a3ac17..ce74fa42f 100644
--- a/Orso.Arpa.Infrastructure/Orso.Arpa.Infrastructure.csproj
+++ b/Orso.Arpa.Infrastructure/Orso.Arpa.Infrastructure.csproj
@@ -22,4 +22,4 @@
-
\ No newline at end of file
+
diff --git a/Orso.Arpa.Mail/Orso.Arpa.Mail.csproj b/Orso.Arpa.Mail/Orso.Arpa.Mail.csproj
index d92e25de2..b0f21ccb1 100644
--- a/Orso.Arpa.Mail/Orso.Arpa.Mail.csproj
+++ b/Orso.Arpa.Mail/Orso.Arpa.Mail.csproj
@@ -37,4 +37,4 @@
PreserveNewest
-
\ No newline at end of file
+
diff --git a/Orso.Arpa.Misc/Extensions/DateTimeExtensions.cs b/Orso.Arpa.Misc/Extensions/DateTimeExtensions.cs
index 3e1ff1069..5e5e13301 100644
--- a/Orso.Arpa.Misc/Extensions/DateTimeExtensions.cs
+++ b/Orso.Arpa.Misc/Extensions/DateTimeExtensions.cs
@@ -16,7 +16,7 @@ public static string ToGermanDateTimeString(this DateTime dateTime)
return berlinDateTime.ToString("dddd, dd.MM.yyyy HH:mm", new CultureInfo("en-GB"));
}
- private static DateTime ConvertToLocalTimeBerlin(DateTime dateTime)
+ public static DateTime ConvertToLocalTimeBerlin(DateTime dateTime)
{
var berlinTimeZone = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
DateTime berlinDateTime = TimeZoneInfo.ConvertTimeFromUtc(dateTime, berlinTimeZone);
diff --git a/Orso.Arpa.Misc/Orso.Arpa.Misc.csproj b/Orso.Arpa.Misc/Orso.Arpa.Misc.csproj
index 153982783..35ab18fbd 100644
--- a/Orso.Arpa.Misc/Orso.Arpa.Misc.csproj
+++ b/Orso.Arpa.Misc/Orso.Arpa.Misc.csproj
@@ -8,4 +8,4 @@
-
\ No newline at end of file
+
diff --git a/Orso.Arpa.Persistence/Orso.Arpa.Persistence.csproj b/Orso.Arpa.Persistence/Orso.Arpa.Persistence.csproj
index 531c23b02..cdfd5a3e6 100644
--- a/Orso.Arpa.Persistence/Orso.Arpa.Persistence.csproj
+++ b/Orso.Arpa.Persistence/Orso.Arpa.Persistence.csproj
@@ -79,4 +79,4 @@
PreserveNewest
-
\ No newline at end of file
+
diff --git a/Tests/Orso.Arpa.Api.Tests/IntegrationTests/AppointmentsControllerTests.cs b/Tests/Orso.Arpa.Api.Tests/IntegrationTests/AppointmentsControllerTests.cs
index a36e14fdf..e2160b6b5 100644
--- a/Tests/Orso.Arpa.Api.Tests/IntegrationTests/AppointmentsControllerTests.cs
+++ b/Tests/Orso.Arpa.Api.Tests/IntegrationTests/AppointmentsControllerTests.cs
@@ -5,6 +5,8 @@
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
+using Ical.Net;
+using Ical.Net.DataTypes;
using Microsoft.AspNetCore.Mvc;
using netDumbster.smtp;
using NUnit.Framework;
@@ -12,556 +14,660 @@
using Orso.Arpa.Application.AppointmentApplication.Model;
using Orso.Arpa.Application.AppointmentParticipationApplication.Model;
using Orso.Arpa.Application.MusicianProfileApplication.Model;
+using Orso.Arpa.Domain.AddressDomain.Model;
using Orso.Arpa.Domain.AppointmentDomain.Enums;
using Orso.Arpa.Domain.AppointmentDomain.Model;
using Orso.Arpa.Domain.PersonDomain.Model;
+using Orso.Arpa.Domain.SelectValueDomain.Model;
+using Orso.Arpa.Domain.VenueDomain.Model;
using Orso.Arpa.Persistence.Seed;
using Orso.Arpa.Tests.Shared.DtoTestData;
using Orso.Arpa.Tests.Shared.FakeData;
using Orso.Arpa.Tests.Shared.TestSeedData;
-namespace Orso.Arpa.Api.Tests.IntegrationTests
+namespace Orso.Arpa.Api.Tests.IntegrationTests;
+
+[TestFixture]
+public class AppointmentsControllerTests : IntegrationTestBase
{
- [TestFixture]
- public class AppointmentsControllerTests : IntegrationTestBase
+ private static IEnumerable s_appointmentQueryTestData
{
- private static IEnumerable s_appointmentQueryTestData
+ get
{
- get
- {
- yield return new TestCaseData(DateRange.Day, new DateTime(2019, 12, 21), new List {
+ yield return new TestCaseData(DateRange.Day, new DateTime(2019, 12, 21),
+ new List
+ {
AppointmentListDtoData.RockingXMasRehearsal,
AppointmentListDtoData.RehearsalWeekend
});
- // 16.-22.12.2019
- yield return new TestCaseData(DateRange.Week, new DateTime(2019, 12, 21), new List {
+ // 16.-22.12.2019
+ yield return new TestCaseData(DateRange.Week, new DateTime(2019, 12, 21),
+ new List
+ {
AppointmentListDtoData.RockingXMasRehearsal,
AppointmentListDtoData.AppointmentWithoutProject,
AppointmentListDtoData.RehearsalWeekend
});
- yield return new TestCaseData(DateRange.Month, new DateTime(2020, 12, 21), new List {
+ yield return new TestCaseData(DateRange.Month, new DateTime(2020, 12, 21),
+ new List
+ {
AppointmentListDtoData.AuditionDays,
AppointmentListDtoData.PhotoSession,
AppointmentListDtoData.StaffMeeting
});
- }
}
+ }
- [TestCaseSource(nameof(s_appointmentQueryTestData))]
- [Test, Order(1)]
- public async Task Should_Get_Appointments(
- DateRange dateRange,
- DateTime date,
- IList expectedDtos)
- {
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .GetAsync(ApiEndpoints.AppointmentsController.Get(date, dateRange));
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
- IEnumerable result = await DeserializeResponseMessageAsync>(responseMessage);
-
- _ = result.Should().BeEquivalentTo(expectedDtos);
- }
+ [TestCaseSource(nameof(s_appointmentQueryTestData))]
+ [Test]
+ [Order(1)]
+ public async Task Should_Get_Appointments(
+ DateRange dateRange,
+ DateTime date,
+ IList expectedDtos)
+ {
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .GetAsync(ApiEndpoints.AppointmentsController.Get(date, dateRange));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
+ IEnumerable result =
+ await DeserializeResponseMessageAsync>(responseMessage);
+
+ _ = result.Should().BeEquivalentTo(expectedDtos);
+ }
- private static IEnumerable s_appointmentByIdQueryTestData
+ private static IEnumerable s_appointmentByIdQueryTestData
+ {
+ get
{
- get
- {
- yield return new TestCaseData(AppointmentDtoData.RockingXMasRehearsal);
- yield return new TestCaseData(AppointmentDtoData.RockingXMasConcert);
- yield return new TestCaseData(AppointmentDtoData.AfterShowParty);
- yield return new TestCaseData(AppointmentDtoData.StaffMeeting);
- yield return new TestCaseData(AppointmentDtoData.PhotoSession);
- yield return new TestCaseData(AppointmentDtoData.RehearsalWeekend);
- yield return new TestCaseData(AppointmentDtoData.AuditionDays);
- yield return new TestCaseData(AppointmentDtoData.AltoRehearsal);
- yield return new TestCaseData(AppointmentDtoData.SopranoRehearsal);
- }
+ yield return new TestCaseData(AppointmentDtoData.RockingXMasRehearsal);
+ yield return new TestCaseData(AppointmentDtoData.RockingXMasConcert);
+ yield return new TestCaseData(AppointmentDtoData.AfterShowParty);
+ yield return new TestCaseData(AppointmentDtoData.StaffMeeting);
+ yield return new TestCaseData(AppointmentDtoData.PhotoSession);
+ yield return new TestCaseData(AppointmentDtoData.RehearsalWeekend);
+ yield return new TestCaseData(AppointmentDtoData.AuditionDays);
+ yield return new TestCaseData(AppointmentDtoData.AltoRehearsal);
+ yield return new TestCaseData(AppointmentDtoData.SopranoRehearsal);
}
+ }
- [Test, Order(2)]
- [TestCaseSource(nameof(s_appointmentByIdQueryTestData))]
- public async Task Should_Get_By_Id_With_Participations(AppointmentDto expectedDto)
- {
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(FakeUsers.Staff)
- .GetAsync(ApiEndpoints.AppointmentsController.Get(expectedDto.Id, true));
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
- AppointmentDto result = await DeserializeResponseMessageAsync(responseMessage);
- _ = result.Should().BeEquivalentTo(expectedDto);
- }
+ [Test]
+ [Order(2)]
+ [TestCaseSource(nameof(s_appointmentByIdQueryTestData))]
+ public async Task Should_Get_By_Id_With_Participations(AppointmentDto expectedDto)
+ {
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(FakeUsers.Staff)
+ .GetAsync(ApiEndpoints.AppointmentsController.Get(expectedDto.Id, true));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
+ AppointmentDto result =
+ await DeserializeResponseMessageAsync(responseMessage);
+ _ = result.Should().BeEquivalentTo(expectedDto);
+ }
- [Test, Order(3)]
- [TestCaseSource(nameof(s_appointmentByIdQueryTestData))]
- public async Task Should_Get_By_Id_Without_Participations(AppointmentDto expectedDto)
- {
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(FakeUsers.Staff)
- .GetAsync(ApiEndpoints.AppointmentsController.Get(expectedDto.Id, false));
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
- AppointmentDto result = await DeserializeResponseMessageAsync(responseMessage);
- _ = result.Should().BeEquivalentTo(expectedDto, opt => opt.Excluding(dto => dto.Participations));
- _ = result.Participations.Should().BeEmpty();
- }
+ [Test]
+ [Order(3)]
+ [TestCaseSource(nameof(s_appointmentByIdQueryTestData))]
+ public async Task Should_Get_By_Id_Without_Participations(AppointmentDto expectedDto)
+ {
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(FakeUsers.Staff)
+ .GetAsync(ApiEndpoints.AppointmentsController.Get(expectedDto.Id, false));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
+ AppointmentDto result =
+ await DeserializeResponseMessageAsync(responseMessage);
+ _ = result.Should()
+ .BeEquivalentTo(expectedDto, opt => opt.Excluding(dto => dto.Participations));
+ _ = result.Participations.Should().BeEmpty();
+ }
- [Test, Order(4)]
- public async Task Should_Send_Appointment_Changed_Notification() {
- // Arrange
- _fakeSmtpServer.ClearReceivedEmail();
- IEnumerable expectedToAddresses = [
- "arpa@test.smtp"
- ];
- IEnumerable expectedBccAddresses = [
- UserSeedData.Admin.Email,
- UserTestSeedData.UserWithoutRole.Email,
- UserTestSeedData.Staff.Email,
- UserTestSeedData.Performer.Email
- ];
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PostAsync(ApiEndpoints.AppointmentsController.SendAppointmentChangedNotification(
- AppointmentSeedData.PhotoSession.Id,
- true), null);
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
- _fakeSmtpServer.ReceivedEmailCount.Should().Be(1);
- SmtpMessage sentEmail = _fakeSmtpServer.ReceivedEmail[0];
- sentEmail.ToAddresses.Select(a => a.Address).Should().BeEquivalentTo(expectedToAddresses, opt => opt.WithoutStrictOrdering());
- sentEmail.BccAddresses.Select(a => a.Address).Should().BeEquivalentTo(expectedBccAddresses, opt => opt.WithoutStrictOrdering());
- sentEmail.Subject.Should().Be("Ein Termin in ARPA wurde aktualisiert!");
- }
+ [Test]
+ [Order(4)]
+ public async Task Should_Export_Appointments_To_Ics()
+ {
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .GetAsync(ApiEndpoints.AppointmentsController.ExportToIcs());
+
+ // Assert
+ responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
+ responseMessage.Content.Headers.ContentType.ToString().Should().Be("text/calendar");
+
+ string icsContent = await responseMessage.Content.ReadAsStringAsync();
+ icsContent.Should().NotBeNullOrEmpty();
+
+
+ icsContent.Should().Contain("BEGIN:VEVENT");
+ icsContent.Should().Contain("END:VEVENT");
+
+ icsContent.Should().Contain("SUMMARY:");
+ icsContent.Should().Contain("DTSTART:");
+ icsContent.Should().Contain("DTEND:");
+ icsContent.Should().Contain("DESCRIPTION:");
+
+ icsContent.Should().Contain("BEGIN:VTIMEZONE");
+ icsContent.Should().Contain("TZID:Europe/Berlin");
+
+ var calendar = Calendar.Load(icsContent);
+ calendar.Events.Should().NotBeEmpty();
+ var firstEvent = calendar.Events.First();
+ firstEvent.Summary.Should().NotBeNullOrEmpty();
+ firstEvent.Start.Should().BeOfType();
+ firstEvent.End.Should().BeOfType();
+ ((CalDateTime)firstEvent.Start).Value.Should().BeAfter(DateTime.MinValue);
+ ((CalDateTime)firstEvent.End).Value.Should().BeAfter(((CalDateTime)firstEvent.Start).Value);
+ }
- [Test, Order(1000)]
- public async Task Should_Create()
- {
- // Arrange
- var createDto = new AppointmentCreateDto
- {
- Name = "New Appointment",
- InternalDetails = "Internal Details",
- PublicDetails = "Public Details",
- EndTime = new DateTime(2021, 3, 5, 14, 15, 20),
- StartTime = new DateTime(2021, 3, 5, 9, 15, 20),
- SalaryId = Guid.Parse("88da1c17-9efc-4f69-ba0f-39c76592845b"),
- Status = AppointmentStatus.Scheduled
- };
-
- var expectedDto = new AppointmentDto
- {
- Name = createDto.Name,
- CreatedBy = _staff.DisplayName,
- CreatedAt = FakeDateTime.UtcNow,
- ModifiedAt = null,
- ModifiedBy = null,
- InternalDetails = createDto.InternalDetails,
- PublicDetails = createDto.PublicDetails,
- EndTime = createDto.EndTime,
- StartTime = createDto.StartTime,
- Status = createDto.Status,
- SalaryId = createDto.SalaryId
- };
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PostAsync(ApiEndpoints.AppointmentsController.Post(), BuildStringContent(createDto));
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.Created);
-
- AppointmentDto result = await DeserializeResponseMessageAsync(responseMessage);
-
- _ = result.Should().BeEquivalentTo(expectedDto, opt => opt.Excluding(r => r.Id));
- _ = result.Id.Should().NotBeEmpty();
- _ = responseMessage.Headers.Location.AbsolutePath.Should().Be($"/{ApiEndpoints.AppointmentsController.Get(result.Id)}");
- }
+ [Test]
+ [Order(5)]
+ public async Task Should_Send_Appointment_Changed_Notification()
+ {
+ // Arrange
+ _fakeSmtpServer.ClearReceivedEmail();
+ IEnumerable expectedToAddresses =
+ [
+ "arpa@test.smtp"
+ ];
+ IEnumerable expectedBccAddresses =
+ [
+ UserSeedData.Admin.Email,
+ UserTestSeedData.UserWithoutRole.Email,
+ UserTestSeedData.Staff.Email,
+ UserTestSeedData.Performer.Email
+ ];
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PostAsync(ApiEndpoints.AppointmentsController.SendAppointmentChangedNotification(
+ AppointmentSeedData.PhotoSession.Id,
+ true), null);
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
+ _fakeSmtpServer.ReceivedEmailCount.Should().Be(1);
+ SmtpMessage sentEmail = _fakeSmtpServer.ReceivedEmail[0];
+ sentEmail.ToAddresses.Select(a => a.Address).Should()
+ .BeEquivalentTo(expectedToAddresses, opt => opt.WithoutStrictOrdering());
+ sentEmail.BccAddresses.Select(a => a.Address).Should().BeEquivalentTo(expectedBccAddresses,
+ opt => opt.WithoutStrictOrdering());
+ sentEmail.Subject.Should().Be("Ein Termin in ARPA wurde aktualisiert!");
+ }
- [Test, Order(104)]
- public async Task Should_Add_Room()
+ [Test]
+ [Order(1000)]
+ public async Task Should_Create()
+ {
+ // Arrange
+ var createDto = new AppointmentCreateDto
{
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PostAsync(ApiEndpoints.AppointmentsController.Room(
- AppointmentSeedData.RockingXMasRehearsal.Id,
- RoomSeedData.AulaWeiherhofSchule.Id), null);
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
- }
-
- [Test, Order(100)]
- public async Task Should_Add_Section()
+ Name = "New Appointment",
+ InternalDetails = "Internal Details",
+ PublicDetails = "Public Details",
+ EndTime = new DateTime(2021, 3, 5, 14, 15, 20),
+ StartTime = new DateTime(2021, 3, 5, 9, 15, 20),
+ SalaryId = Guid.Parse("88da1c17-9efc-4f69-ba0f-39c76592845b"),
+ Status = AppointmentStatus.Scheduled
+ };
+
+ var expectedDto = new AppointmentDto
{
- AppointmentDto expectedDto = AppointmentDtoData.RockingXMasRehearsal;
- expectedDto.Participations.RemoveAt(1);
- expectedDto.Participations.RemoveAt(1); // the second item has already been removed so the third item is on index pos. 1 now
- expectedDto.Sections.Add(SectionDtoData.Alto);
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PostAsync(ApiEndpoints.AppointmentsController.Section(
- AppointmentSeedData.RockingXMasRehearsal.Id,
- SectionSeedData.Alto.Id), null);
+ Name = createDto.Name,
+ CreatedBy = _staff.DisplayName,
+ CreatedAt = FakeDateTime.UtcNow,
+ ModifiedAt = null,
+ ModifiedBy = null,
+ InternalDetails = createDto.InternalDetails,
+ PublicDetails = createDto.PublicDetails,
+ EndTime = createDto.EndTime,
+ StartTime = createDto.StartTime,
+ Status = createDto.Status,
+ SalaryId = createDto.SalaryId
+ };
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PostAsync(ApiEndpoints.AppointmentsController.Post(), BuildStringContent(createDto));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.Created);
+
+ AppointmentDto result =
+ await DeserializeResponseMessageAsync(responseMessage);
+
+ _ = result.Should().BeEquivalentTo(expectedDto, opt => opt.Excluding(r => r.Id));
+ _ = result.Id.Should().NotBeEmpty();
+ _ = responseMessage.Headers.Location.AbsolutePath.Should()
+ .Be($"/{ApiEndpoints.AppointmentsController.Get(result.Id)}");
+ }
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
- AppointmentDto result = await DeserializeResponseMessageAsync(responseMessage);
- _ = result.Should().BeEquivalentTo(expectedDto);
- }
+ [Test]
+ [Order(104)]
+ public async Task Should_Add_Room()
+ {
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PostAsync(ApiEndpoints.AppointmentsController.Room(
+ AppointmentSeedData.RockingXMasRehearsal.Id,
+ RoomSeedData.AulaWeiherhofSchule.Id), null);
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
+ }
- [Test, Order(101)]
- public async Task Should_Add_Project()
- {
- // Arrange
- AppointmentDto expectedDto = AppointmentDtoData.RockingXMasConcert;
- expectedDto.Participations.Clear();
- expectedDto.Participations.Add(AppointmentDtoData.PerformerParticipation);
- expectedDto.Projects.Add(ProjectDtoData.HoorayForHollywood);
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PostAsync(ApiEndpoints.AppointmentsController.Project(
- AppointmentSeedData.AppointmentWithoutProject.Id,
- ProjectSeedData.HoorayForHollywood.Id), null);
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
- AppointmentDto result = await DeserializeResponseMessageAsync(responseMessage);
- _ = result.Should().BeEquivalentTo(expectedDto);
- }
+ [Test]
+ [Order(100)]
+ public async Task Should_Add_Section()
+ {
+ AppointmentDto expectedDto = AppointmentDtoData.RockingXMasRehearsal;
+ expectedDto.Participations.RemoveAt(1);
+ expectedDto.Participations
+ .RemoveAt(
+ 1); // the second item has already been removed so the third item is on index pos. 1 now
+ expectedDto.Sections.Add(SectionDtoData.Alto);
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PostAsync(ApiEndpoints.AppointmentsController.Section(
+ AppointmentSeedData.RockingXMasRehearsal.Id,
+ SectionSeedData.Alto.Id), null);
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
+ AppointmentDto result =
+ await DeserializeResponseMessageAsync(responseMessage);
+ _ = result.Should().BeEquivalentTo(expectedDto);
+ }
- private static IEnumerable PersonTestData
+ [Test]
+ [Order(101)]
+ public async Task Should_Add_Project()
+ {
+ // Arrange
+ AppointmentDto expectedDto = AppointmentDtoData.RockingXMasConcert;
+ expectedDto.Participations.Clear();
+ expectedDto.Participations.Add(AppointmentDtoData.PerformerParticipation);
+ expectedDto.Projects.Add(ProjectDtoData.HoorayForHollywood);
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PostAsync(ApiEndpoints.AppointmentsController.Project(
+ AppointmentSeedData.AppointmentWithoutProject.Id,
+ ProjectSeedData.HoorayForHollywood.Id), null);
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
+ AppointmentDto result =
+ await DeserializeResponseMessageAsync(responseMessage);
+ _ = result.Should().BeEquivalentTo(expectedDto);
+ }
+
+ private static IEnumerable PersonTestData
+ {
+ get
{
- get
- {
- yield return new TestCaseData(PersonTestSeedData.Performer, HttpStatusCode.NoContent);
- yield return new TestCaseData(PersonTestSeedData.LockedOutUser, HttpStatusCode.Forbidden);
- }
+ yield return new TestCaseData(PersonTestSeedData.Performer, HttpStatusCode.NoContent);
+ yield return new TestCaseData(PersonTestSeedData.LockedOutUser,
+ HttpStatusCode.Forbidden);
}
+ }
- private static readonly string[] s_appointmentNotFoundMessage = ["Appointment could not be found."];
+ private static readonly string[] s_appointmentNotFoundMessage =
+ ["Appointment could not be found."];
- [Test, Order(105)]
- [TestCaseSource(nameof(PersonTestData))]
- public async Task Should_Set_Participation_Result(Person person, HttpStatusCode expectedStatusCode)
- {
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PutAsync(ApiEndpoints.AppointmentsController.SetParticipationResult(
+ [Test]
+ [Order(105)]
+ [TestCaseSource(nameof(PersonTestData))]
+ public async Task Should_Set_Participation_Result(Person person,
+ HttpStatusCode expectedStatusCode)
+ {
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PutAsync(ApiEndpoints.AppointmentsController.SetParticipationResult(
AppointmentSeedData.RockingXMasRehearsal.Id,
- person.Id), BuildStringContent(new AppointmentParticipationSetResultBodyDto { Result = AppointmentParticipationResult.AwaitingScan }));
+ person.Id),
+ BuildStringContent(new AppointmentParticipationSetResultBodyDto
+ {
+ Result = AppointmentParticipationResult.AwaitingScan
+ }));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(expectedStatusCode);
+ }
- // Assert
- _ = responseMessage.StatusCode.Should().Be(expectedStatusCode);
- }
+ [Test]
+ [Order(106)]
+ public async Task Should_Set_Venue()
+ {
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PutAsync(ApiEndpoints.AppointmentsController.SetVenue(
+ AppointmentSeedData.AppointmentWithoutProject.Id,
+ VenueSeedData.WeiherhofSchule.Id), null);
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
+ }
- [Test, Order(106)]
- public async Task Should_Set_Venue()
- {
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PutAsync(ApiEndpoints.AppointmentsController.SetVenue(
- AppointmentSeedData.AppointmentWithoutProject.Id,
- VenueSeedData.WeiherhofSchule.Id), null);
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
- }
+ [Test]
+ [Order(107)]
+ public async Task Should_Modify()
+ {
+ // Arrange
+ Appointment appointmentToModify = AppointmentSeedData.AppointmentWithoutProject;
- [Test, Order(107)]
- public async Task Should_Modify()
+ var modifyDto = new AppointmentModifyBodyDto
{
- // Arrange
- Appointment appointmentToModify = AppointmentSeedData.AppointmentWithoutProject;
+ Name = "New Appointment",
+ InternalDetails = "Internal Details",
+ PublicDetails = "Public Details",
+ CategoryId = SelectValueMappingSeedData.AppointmentCategoryMappings[0].Id,
+ SalaryId = SelectValueMappingSeedData.AppointmentSalaryMappings[0].Id,
+ SalaryPatternId = SelectValueMappingSeedData.AppointmentSalaryPatternMappings[0].Id,
+ EndTime = FakeDateTime.UtcNow.AddHours(5),
+ StartTime = FakeDateTime.UtcNow,
+ Status = AppointmentStatus.Confirmed
+ };
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PutAsync(ApiEndpoints.AppointmentsController.Put(appointmentToModify.Id),
+ BuildStringContent(modifyDto));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
+ }
- var modifyDto = new AppointmentModifyBodyDto
- {
- Name = "New Appointment",
- InternalDetails = "Internal Details",
- PublicDetails = "Public Details",
- CategoryId = SelectValueMappingSeedData.AppointmentCategoryMappings[0].Id,
- SalaryId = SelectValueMappingSeedData.AppointmentSalaryMappings[0].Id,
- SalaryPatternId = SelectValueMappingSeedData.AppointmentSalaryPatternMappings[0].Id,
- EndTime = FakeDateTime.UtcNow.AddHours(5),
- StartTime = FakeDateTime.UtcNow,
- Status = AppointmentStatus.Confirmed
- };
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PutAsync(ApiEndpoints.AppointmentsController.Put(appointmentToModify.Id), BuildStringContent(modifyDto));
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
- }
+ [Test]
+ [Order(108)]
+ public async Task Should_Modify_With_Only_Mandatory_Fields_Specified()
+ {
+ // Arrange
+ Appointment appointmentToModify = AppointmentSeedData.AppointmentWithoutProject;
- [Test, Order(108)]
- public async Task Should_Modify_With_Only_Mandatory_Fields_Specified()
+ var modifyDto = new AppointmentModifyBodyDto
{
- // Arrange
- Appointment appointmentToModify = AppointmentSeedData.AppointmentWithoutProject;
-
- var modifyDto = new AppointmentModifyBodyDto
- {
- Name = "New Appointment",
- InternalDetails = "Internal Details",
- PublicDetails = "Public Details",
- EndTime = FakeDateTime.UtcNow.AddHours(5),
- StartTime = FakeDateTime.UtcNow,
- };
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PutAsync(ApiEndpoints.AppointmentsController.Put(appointmentToModify.Id), BuildStringContent(modifyDto));
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
- }
+ Name = "New Appointment",
+ InternalDetails = "Internal Details",
+ PublicDetails = "Public Details",
+ EndTime = FakeDateTime.UtcNow.AddHours(5),
+ StartTime = FakeDateTime.UtcNow
+ };
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PutAsync(ApiEndpoints.AppointmentsController.Put(appointmentToModify.Id),
+ BuildStringContent(modifyDto));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
+ }
- [Test, Order(109)]
- public async Task Should_Not_Modify_If_Not_Existing_Id_Is_Supplied()
+ [Test]
+ [Order(109)]
+ public async Task Should_Not_Modify_If_Not_Existing_Id_Is_Supplied()
+ {
+ // Arrange
+ var modifyDto = new AppointmentModifyBodyDto
{
- // Arrange
- var modifyDto = new AppointmentModifyBodyDto
- {
- Name = "New Appointment",
- InternalDetails = "Internal Details",
- PublicDetails = "Public Details",
- EndTime = FakeDateTime.UtcNow.AddHours(5),
- StartTime = FakeDateTime.UtcNow,
- };
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PutAsync(ApiEndpoints.AppointmentsController.Put(Guid.NewGuid()), BuildStringContent(modifyDto));
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NotFound);
- ValidationProblemDetails errorMessage = await DeserializeResponseMessageAsync(responseMessage);
- _ = errorMessage.Title.Should().Be("Resource not found.");
- _ = errorMessage.Status.Should().Be(404);
- _ = errorMessage.Errors.Should().BeEquivalentTo(new Dictionary() { { "Id", s_appointmentNotFoundMessage } });
- }
+ Name = "New Appointment",
+ InternalDetails = "Internal Details",
+ PublicDetails = "Public Details",
+ EndTime = FakeDateTime.UtcNow.AddHours(5),
+ StartTime = FakeDateTime.UtcNow
+ };
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PutAsync(ApiEndpoints.AppointmentsController.Put(Guid.NewGuid()),
+ BuildStringContent(modifyDto));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NotFound);
+ ValidationProblemDetails errorMessage =
+ await DeserializeResponseMessageAsync(responseMessage);
+ _ = errorMessage.Title.Should().Be("Resource not found.");
+ _ = errorMessage.Status.Should().Be(404);
+ _ = errorMessage.Errors.Should()
+ .BeEquivalentTo(
+ new Dictionary { { "Id", s_appointmentNotFoundMessage } });
+ }
- [Test, Order(108)]
- public async Task Should_Set_Dates()
+ [Test]
+ [Order(108)]
+ public async Task Should_Set_Dates()
+ {
+ // Arrange
+ Appointment appointmentToModify = AppointmentSeedData.PhotoSession;
+ var setDatesDto = new AppointmentSetDatesBodyDto
{
- // Arrange
- Appointment appointmentToModify = AppointmentSeedData.PhotoSession;
- var setDatesDto = new AppointmentSetDatesBodyDto
- {
- StartTime = FakeDateTime.UtcNow,
- EndTime = FakeDateTime.UtcNow.AddHours(5)
- };
- AppointmentDto expectedDto = AppointmentDtoData.PhotoSession;
- expectedDto.EndTime = setDatesDto.EndTime.Value;
- expectedDto.StartTime = setDatesDto.StartTime.Value;
- expectedDto.ModifiedBy = _staff.DisplayName;
- expectedDto.ModifiedAt = FakeDateTime.UtcNow;
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PutAsync(ApiEndpoints.AppointmentsController.SetDates(appointmentToModify.Id), BuildStringContent(setDatesDto));
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
- AppointmentDto result = await DeserializeResponseMessageAsync(responseMessage);
- _ = result.Should().BeEquivalentTo(expectedDto);
- }
+ StartTime = FakeDateTime.UtcNow, EndTime = FakeDateTime.UtcNow.AddHours(5)
+ };
+ AppointmentDto expectedDto = AppointmentDtoData.PhotoSession;
+ expectedDto.EndTime = setDatesDto.EndTime.Value;
+ expectedDto.StartTime = setDatesDto.StartTime.Value;
+ expectedDto.ModifiedBy = _staff.DisplayName;
+ expectedDto.ModifiedAt = FakeDateTime.UtcNow;
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PutAsync(ApiEndpoints.AppointmentsController.SetDates(appointmentToModify.Id),
+ BuildStringContent(setDatesDto));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
+ AppointmentDto result =
+ await DeserializeResponseMessageAsync(responseMessage);
+ _ = result.Should().BeEquivalentTo(expectedDto);
+ }
- [Test, Order(109)]
- public async Task Should_Remove_Room()
- {
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .DeleteAsync(ApiEndpoints.AppointmentsController.Room(
- AppointmentSeedData.AfterShowParty.Id,
- RoomSeedData.AulaWeiherhofSchule.Id));
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
- }
+ [Test]
+ [Order(109)]
+ public async Task Should_Remove_Room()
+ {
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .DeleteAsync(ApiEndpoints.AppointmentsController.Room(
+ AppointmentSeedData.AfterShowParty.Id,
+ RoomSeedData.AulaWeiherhofSchule.Id));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
+ }
- [Test, Order(102)]
- public async Task Should_Remove_Section()
+ [Test]
+ [Order(102)]
+ public async Task Should_Remove_Section()
+ {
+ // Arrange
+ AppointmentDto expectedDto = AppointmentDtoData.AfterShowParty;
+ expectedDto.Sections.Clear();
+ expectedDto.Participations.Add(new AppointmentParticipationListItemDto
{
- // Arrange
- AppointmentDto expectedDto = AppointmentDtoData.AfterShowParty;
- expectedDto.Sections.Clear();
- expectedDto.Participations.Add(new AppointmentParticipationListItemDto
+ Person = ReducedPersonDtoData.Staff,
+ MusicianProfiles = new List
{
- Person = ReducedPersonDtoData.Staff,
- MusicianProfiles = new List
- {
- ReducedMusicianProfileDtoData.StaffProfile1,
- ReducedMusicianProfileDtoData.StaffProfile2
- }
- });
- expectedDto.Participations.Add(new AppointmentParticipationListItemDto
+ ReducedMusicianProfileDtoData.StaffProfile1,
+ ReducedMusicianProfileDtoData.StaffProfile2
+ }
+ });
+ expectedDto.Participations.Add(new AppointmentParticipationListItemDto
+ {
+ Person = ReducedPersonDtoData.Admin,
+ MusicianProfiles = new List
{
- Person = ReducedPersonDtoData.Admin,
- MusicianProfiles = new List
- {
- ReducedMusicianProfileDtoData.AdminProfile1
- }
- });
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .DeleteAsync(ApiEndpoints.AppointmentsController.Section(
- AppointmentSeedData.AfterShowParty.Id,
- SectionSeedData.Alto.Id));
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
- AppointmentDto result = await DeserializeResponseMessageAsync(responseMessage);
- _ = result.Should().BeEquivalentTo(expectedDto);
- }
+ ReducedMusicianProfileDtoData.AdminProfile1
+ }
+ });
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .DeleteAsync(ApiEndpoints.AppointmentsController.Section(
+ AppointmentSeedData.AfterShowParty.Id,
+ SectionSeedData.Alto.Id));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
+ AppointmentDto result =
+ await DeserializeResponseMessageAsync(responseMessage);
+ _ = result.Should().BeEquivalentTo(expectedDto);
+ }
- [Test, Order(103)]
- public async Task Should_Remove_Project()
- {
- // Arrange
- AppointmentDto expectedDto = AppointmentDtoData.StaffMeeting;
- expectedDto.Projects.Clear();
- expectedDto.Participations.Clear();
-
- AppointmentParticipationListItemDto performerParticipation = AppointmentDtoData.PerformerParticipationRockingXMasRehearsal;
- performerParticipation.MusicianProfiles.Add(ReducedMusicianProfileDtoData.PerformerHornProfile);
- performerParticipation.MusicianProfiles.Add(ReducedMusicianProfileDtoData.PerformerDeactivatedTubaProfile);
- performerParticipation.Participation = null;
- expectedDto.Participations.Add(performerParticipation);
-
- AppointmentParticipationListItemDto staffParticipation = AppointmentDtoData.StaffParticipation;
- staffParticipation.Participation = null;
- expectedDto.Participations.Add(staffParticipation);
-
- AppointmentParticipationListItemDto adminParticipation = AppointmentDtoData.AdminParticipation;
- adminParticipation.MusicianProfiles.Add(ReducedMusicianProfileDtoData.AdminProfile2);
- expectedDto.Participations.Add(adminParticipation);
-
- expectedDto.Participations.Add(AppointmentDtoData.WithoutRoleParticipation);
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .DeleteAsync(ApiEndpoints.AppointmentsController.Project(
- AppointmentSeedData.StaffMeeting.Id,
- ProjectSeedData.HoorayForHollywood.Id));
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
- AppointmentDto result = await DeserializeResponseMessageAsync(responseMessage);
- _ = result.Should().BeEquivalentTo(expectedDto);
- }
+ [Test]
+ [Order(103)]
+ public async Task Should_Remove_Project()
+ {
+ // Arrange
+ AppointmentDto expectedDto = AppointmentDtoData.StaffMeeting;
+ expectedDto.Projects.Clear();
+ expectedDto.Participations.Clear();
+
+ AppointmentParticipationListItemDto performerParticipation =
+ AppointmentDtoData.PerformerParticipationRockingXMasRehearsal;
+ performerParticipation.MusicianProfiles.Add(ReducedMusicianProfileDtoData
+ .PerformerHornProfile);
+ performerParticipation.MusicianProfiles.Add(ReducedMusicianProfileDtoData
+ .PerformerDeactivatedTubaProfile);
+ performerParticipation.Participation = null;
+ expectedDto.Participations.Add(performerParticipation);
+
+ AppointmentParticipationListItemDto staffParticipation =
+ AppointmentDtoData.StaffParticipation;
+ staffParticipation.Participation = null;
+ expectedDto.Participations.Add(staffParticipation);
+
+ AppointmentParticipationListItemDto adminParticipation =
+ AppointmentDtoData.AdminParticipation;
+ adminParticipation.MusicianProfiles.Add(ReducedMusicianProfileDtoData.AdminProfile2);
+ expectedDto.Participations.Add(adminParticipation);
+
+ expectedDto.Participations.Add(AppointmentDtoData.WithoutRoleParticipation);
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .DeleteAsync(ApiEndpoints.AppointmentsController.Project(
+ AppointmentSeedData.StaffMeeting.Id,
+ ProjectSeedData.HoorayForHollywood.Id));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.OK);
+ AppointmentDto result =
+ await DeserializeResponseMessageAsync(responseMessage);
+ _ = result.Should().BeEquivalentTo(expectedDto);
+ }
- [Test, Order(118)]
- public async Task Should_Set_New_Participation_Prediction()
+ [Test]
+ [Order(118)]
+ public async Task Should_Set_New_Participation_Prediction()
+ {
+ // Arrange
+ var dto = new AppointmentParticipationSetPredictionBodyDto
{
- // Arrange
- var dto = new AppointmentParticipationSetPredictionBodyDto
- {
- CommentByPerformerInner = "CommentByPerformerInner",
- Prediction = AppointmentParticipationPrediction.Partly
- };
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PutAsync(ApiEndpoints.AppointmentsController.SetParticipationPrediction(
+ CommentByPerformerInner = "CommentByPerformerInner",
+ Prediction = AppointmentParticipationPrediction.Partly
+ };
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PutAsync(ApiEndpoints.AppointmentsController.SetParticipationPrediction(
AppointmentSeedData.PhotoSession.Id,
PersonSeedData.AdminPersonId),
- BuildStringContent(dto));
+ BuildStringContent(dto));
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
- }
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
+ }
- [Test, Order(119)]
- public async Task Should_Set_Existing_Participation_Prediction()
+ [Test]
+ [Order(119)]
+ public async Task Should_Set_Existing_Participation_Prediction()
+ {
+ // Arrange
+ var dto = new AppointmentParticipationSetPredictionBodyDto
{
- // Arrange
- var dto = new AppointmentParticipationSetPredictionBodyDto
- {
- CommentByPerformerInner = "CommentByPerformerInner",
- Prediction = AppointmentParticipationPrediction.Yes
- };
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .PutAsync(ApiEndpoints.AppointmentsController.SetParticipationPrediction(
+ CommentByPerformerInner = "CommentByPerformerInner",
+ Prediction = AppointmentParticipationPrediction.Yes
+ };
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .PutAsync(ApiEndpoints.AppointmentsController.SetParticipationPrediction(
AppointmentSeedData.RockingXMasRehearsal.Id,
_performer.PersonId),
- BuildStringContent(dto));
+ BuildStringContent(dto));
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
- }
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
+ }
- [Test, Order(10004)]
- public async Task Should_Delete()
- {
- // Arrange
- Appointment appointmentToDelete = AppointmentSeedData.StaffMeeting;
-
- // Act
- HttpResponseMessage responseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_admin)
- .DeleteAsync(ApiEndpoints.AppointmentsController.Delete(appointmentToDelete.Id));
-
- // Assert
- _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
-
- HttpResponseMessage getResponseMessage = await _authenticatedServer
- .CreateClient()
- .AuthenticateWith(_staff)
- .GetAsync(ApiEndpoints.AppointmentsController.Get(appointmentToDelete.Id));
- _ = getResponseMessage.StatusCode.Should().Be(HttpStatusCode.NotFound);
- }
+
+ [Test]
+ [Order(10004)]
+ public async Task Should_Delete()
+ {
+ // Arrange
+ Appointment appointmentToDelete = AppointmentSeedData.StaffMeeting;
+
+ // Act
+ HttpResponseMessage responseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_admin)
+ .DeleteAsync(ApiEndpoints.AppointmentsController.Delete(appointmentToDelete.Id));
+
+ // Assert
+ _ = responseMessage.StatusCode.Should().Be(HttpStatusCode.NoContent);
+
+ HttpResponseMessage getResponseMessage = await _authenticatedServer
+ .CreateClient()
+ .AuthenticateWith(_staff)
+ .GetAsync(ApiEndpoints.AppointmentsController.Get(appointmentToDelete.Id));
+ _ = getResponseMessage.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
}
diff --git a/Tests/Orso.Arpa.Api.Tests/IntegrationTests/Shared/ApiEndpoints.cs b/Tests/Orso.Arpa.Api.Tests/IntegrationTests/Shared/ApiEndpoints.cs
index f225cb8f8..75fa42eaf 100644
--- a/Tests/Orso.Arpa.Api.Tests/IntegrationTests/Shared/ApiEndpoints.cs
+++ b/Tests/Orso.Arpa.Api.Tests/IntegrationTests/Shared/ApiEndpoints.cs
@@ -41,7 +41,7 @@ public static class ClubController {
private static string Club => $"{Base}/club";
public static string Get() => Club;
}
-
+
public static class UsersController
{
private static string Users => $"{Base}/users";
@@ -267,6 +267,8 @@ public static string SetParticipationPrediction(Guid id, Guid personId) =>
public static string SendAppointmentChangedNotification(Guid id, bool forceSending) =>
$"{Appointments}/{id}/notification?forceSending={forceSending}";
+
+ public static string ExportToIcs() => $"{Appointments}/export";
}
public static class AuditLogsController
diff --git a/Tests/Orso.Arpa.Api.Tests/Orso.Arpa.Api.Tests.csproj b/Tests/Orso.Arpa.Api.Tests/Orso.Arpa.Api.Tests.csproj
index 18730814a..c6efbf0cb 100644
--- a/Tests/Orso.Arpa.Api.Tests/Orso.Arpa.Api.Tests.csproj
+++ b/Tests/Orso.Arpa.Api.Tests/Orso.Arpa.Api.Tests.csproj
@@ -28,4 +28,4 @@
..\Orso.Arpa.Tests.Libs\netDumbster.dll
-
\ No newline at end of file
+
diff --git a/Tests/Orso.Arpa.Application.Tests/Orso.Arpa.Application.Tests.csproj b/Tests/Orso.Arpa.Application.Tests/Orso.Arpa.Application.Tests.csproj
index 1cbec3ae2..6064670f3 100644
--- a/Tests/Orso.Arpa.Application.Tests/Orso.Arpa.Application.Tests.csproj
+++ b/Tests/Orso.Arpa.Application.Tests/Orso.Arpa.Application.Tests.csproj
@@ -17,4 +17,4 @@
-
\ No newline at end of file
+
diff --git a/Tests/Orso.Arpa.Domain.Tests/AppointmentTests/QueryHandlerTests/ExportAppointmentsToIcsHandlerTests.cs b/Tests/Orso.Arpa.Domain.Tests/AppointmentTests/QueryHandlerTests/ExportAppointmentsToIcsHandlerTests.cs
new file mode 100644
index 000000000..ee4501b00
--- /dev/null
+++ b/Tests/Orso.Arpa.Domain.Tests/AppointmentTests/QueryHandlerTests/ExportAppointmentsToIcsHandlerTests.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using FluentAssertions;
+using Microsoft.EntityFrameworkCore;
+using MockQueryable.NSubstitute;
+using NSubstitute;
+using NUnit.Framework;
+using Orso.Arpa.Domain.AppointmentDomain.Model;
+using Orso.Arpa.Domain.AppointmentDomain.Queries;
+using Orso.Arpa.Domain.General.Interfaces;
+using Orso.Arpa.Tests.Shared.FakeData;
+using Orso.Arpa.Tests.Shared.TestSeedData;
+
+namespace Orso.Arpa.Domain.Tests.AppointmentTests.QueryHandlerTests;
+
+[TestFixture]
+public class ExportAppointmentsToIcsHandlerTests
+{
+ private IArpaContext _arpaContext;
+ private ExportAppointmentsToIcs.Handler _handler;
+ private static readonly string[] separator = ["\r\n", "\r", "\n", Environment.NewLine];
+
+ [SetUp]
+ public void Setup()
+ {
+ _arpaContext = Substitute.For();
+ _handler = new ExportAppointmentsToIcs.Handler(_arpaContext);
+ }
+
+ [Test]
+ public async Task Should_Export_Appointments_To_Ics()
+ {
+ // Arrange
+ DbSet appointmentMock =
+ new List
+ {
+ FakeAppointments.RockingXMasRehearsal, AppointmentSeedData.RehearsalWeekend
+ }.AsQueryable().BuildMockDbSet();
+ _arpaContext.Appointments.Returns(appointmentMock);
+ string[] expectedResultWithoutDynamicValues = [
+ "BEGIN:VCALENDAR",
+ "PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 4.0//EN",
+ "VERSION:2.0",
+ "BEGIN:VTIMEZONE",
+ "TZID:Europe/Berlin",
+ "X-LIC-LOCATION:Europe/Berlin",
+ "END:VTIMEZONE",
+ "BEGIN:VEVENT",
+ "DESCRIPTION:Rehearsal | Let's rock | I need more coffee",
+ "DTEND:20191221T193000",
+ "DTSTART:20191221T110000",
+ "LOCATION:Freiburg",
+ "SEQUENCE:0",
+ "SUMMARY:Rocking X-mas Dress Rehearsal",
+ "END:VEVENT",
+ "BEGIN:VEVENT",
+ "DESCRIPTION:- | Accordion rehearsal weekend | -",
+ "DTEND:20191224T170000",
+ "DTSTART:20191220T160000",
+ "LOCATION:-",
+ "SEQUENCE:0",
+ "SUMMARY:Rehearsal weekend",
+ "END:VEVENT",
+ "END:VCALENDAR"];
+
+ // Act
+ string result = await _handler.Handle(new ExportAppointmentsToIcs.Query(), new CancellationToken());
+ string[] normalizedResult = RemoveDtstampAndUid(result);
+
+ // Assert
+ normalizedResult.Should().BeEquivalentTo(expectedResultWithoutDynamicValues, opt => opt.WithStrictOrdering());
+ }
+
+ private static string[] RemoveDtstampAndUid(string icsContent)
+ {
+ var lines = icsContent.Split(separator, StringSplitOptions.None);
+ return lines
+ .Where(line =>
+ !line.StartsWith("DTSTAMP")
+ && !line.StartsWith("UID")
+ && !string.IsNullOrEmpty(line))
+ .ToArray();
+ }
+}
diff --git a/Tests/Orso.Arpa.Domain.Tests/Orso.Arpa.Domain.Tests.csproj b/Tests/Orso.Arpa.Domain.Tests/Orso.Arpa.Domain.Tests.csproj
index 895538eb9..f8d9134ca 100644
--- a/Tests/Orso.Arpa.Domain.Tests/Orso.Arpa.Domain.Tests.csproj
+++ b/Tests/Orso.Arpa.Domain.Tests/Orso.Arpa.Domain.Tests.csproj
@@ -17,6 +17,7 @@
+
@@ -40,6 +41,7 @@
+
diff --git a/Tests/Orso.Arpa.Infrastructure.Tests/Orso.Arpa.Infrastructure.Tests.csproj b/Tests/Orso.Arpa.Infrastructure.Tests/Orso.Arpa.Infrastructure.Tests.csproj
index b89b01671..0f581e519 100644
--- a/Tests/Orso.Arpa.Infrastructure.Tests/Orso.Arpa.Infrastructure.Tests.csproj
+++ b/Tests/Orso.Arpa.Infrastructure.Tests/Orso.Arpa.Infrastructure.Tests.csproj
@@ -17,4 +17,4 @@
-
\ No newline at end of file
+
diff --git a/Tests/Orso.Arpa.Mail.Tests/Orso.Arpa.Mail.Tests.csproj b/Tests/Orso.Arpa.Mail.Tests/Orso.Arpa.Mail.Tests.csproj
index f457f36a1..75e9d0886 100644
--- a/Tests/Orso.Arpa.Mail.Tests/Orso.Arpa.Mail.Tests.csproj
+++ b/Tests/Orso.Arpa.Mail.Tests/Orso.Arpa.Mail.Tests.csproj
@@ -20,4 +20,4 @@
..\Orso.Arpa.Tests.Libs\netDumbster.dll
-
\ No newline at end of file
+
diff --git a/Tests/Orso.Arpa.Persistence.Tests/Orso.Arpa.Persistence.Tests.csproj b/Tests/Orso.Arpa.Persistence.Tests/Orso.Arpa.Persistence.Tests.csproj
index 2476cc382..4481d5316 100644
--- a/Tests/Orso.Arpa.Persistence.Tests/Orso.Arpa.Persistence.Tests.csproj
+++ b/Tests/Orso.Arpa.Persistence.Tests/Orso.Arpa.Persistence.Tests.csproj
@@ -15,4 +15,4 @@
-
\ No newline at end of file
+
diff --git a/Tests/Orso.Arpa.Tests.Shared/Orso.Arpa.Tests.Shared.csproj b/Tests/Orso.Arpa.Tests.Shared/Orso.Arpa.Tests.Shared.csproj
index 6066b4878..bf4a30a83 100644
--- a/Tests/Orso.Arpa.Tests.Shared/Orso.Arpa.Tests.Shared.csproj
+++ b/Tests/Orso.Arpa.Tests.Shared/Orso.Arpa.Tests.Shared.csproj
@@ -21,4 +21,4 @@
PreserveNewest
-
\ No newline at end of file
+