diff --git a/db/query_statistics.sql b/db/query_statistics.sql new file mode 100644 index 0000000..bc1d94c --- /dev/null +++ b/db/query_statistics.sql @@ -0,0 +1,11 @@ +SELECT customer.customername AS customer, + TO_CHAR(appointment."yearMonth", 'Month YYYY') AS month, + appointment.count AS appointments, + participant.count AS participants, + voting.count AS votings +FROM "appointmentStatistic" appointment + LEFT JOIN "participantStatistic" participant ON appointment."customerId" = participant."customerId" + LEFT JOIN "votingStatistic" voting ON appointment."customerId" = voting."customerId" + LEFT JOIN customer ON appointment."customerId" = customer.customerid +WHERE appointment."customerId" = '11111111-1111-1111-1111-111111111111' +ORDER BY appointment."yearMonth" DESC; \ No newline at end of file diff --git a/src/Dataport.Terminfinder.BusinessLayer.Tests/AppointmentBusinessLayerTests.cs b/src/Dataport.Terminfinder.BusinessLayer.Tests/AppointmentBusinessLayerTests.cs index b9734b0..990f013 100644 --- a/src/Dataport.Terminfinder.BusinessLayer.Tests/AppointmentBusinessLayerTests.cs +++ b/src/Dataport.Terminfinder.BusinessLayer.Tests/AppointmentBusinessLayerTests.cs @@ -1,4 +1,4 @@ -using Dataport.Terminfinder.BusinessLayer.Security; +using Dataport.Terminfinder.BusinessLayer.Security; using Dataport.Terminfinder.BusinessObject; using Dataport.Terminfinder.BusinessObject.Enum; using Dataport.Terminfinder.Repository; @@ -16,6 +16,7 @@ public class AppointmentBusinessLayerTests private static readonly Guid ExpectedCustomerId = Guid.Parse("BE1D657A-4D06-40DB-8443-D67BBB950EE7"); private static readonly Guid ExpectedParticipantId1 = Guid.Parse("09F659EF-CBBE-4B8F-BF0F-73BC3C942C0A"); private static readonly Guid ExpectedParticipantId2 = Guid.Parse("D1C2DB5B-3FD8-4D8B-9A64-0C90298897D0"); + private static readonly string ExpectedParticipantName = "Max"; private static readonly Guid ExpectedVotingId = Guid.Parse("B198FD02-2C48-4932-AC34-A6878C65DC36"); private static readonly VotingStatusType ExpectedVotingStatus = VotingStatusType.Accepted; private static readonly string ExpectedPassword = "P4$$w0rd"; @@ -31,11 +32,10 @@ public void CheckMaxTotalCountOfParticipants_Add_Okay() var fakeCountOfParticipantsInDatabase = 10; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfParticipants(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfParticipantsInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMaxTotalCountOfParticipants(ExpectedCustomerId, ExpectedAppointmentId, participants); Assert.IsTrue(result); @@ -48,11 +48,10 @@ public void CheckMaxTotalCountOfParticipants_AddAndModify_Okay() var fakeCountOfParticipantsInDatabase = 10; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfParticipants(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfParticipantsInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMaxTotalCountOfParticipants(ExpectedCustomerId, ExpectedAppointmentId, participants); Assert.IsTrue(result); @@ -65,11 +64,10 @@ public void CheckMaxTotalCountOfParticipants_MaxElementsAddReached_Okay() var fakeCountOfParticipantsInDatabase = 4900; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfParticipants(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfParticipantsInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMaxTotalCountOfParticipants(ExpectedCustomerId, ExpectedAppointmentId, participants); Assert.IsTrue(result); @@ -82,11 +80,10 @@ public void CheckMaxTotalCountOfParticipants_MaxElementsAddModifyReached_Okay() var fakeCountOfParticipantsInDatabase = 4900; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfParticipants(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfParticipantsInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMaxTotalCountOfParticipants(ExpectedCustomerId, ExpectedAppointmentId, participants); Assert.IsTrue(result); @@ -99,11 +96,10 @@ public void CheckMaxTotalCountOfParticipants_Add_False() var fakeCountOfParticipantsInDatabase = 4900; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfParticipants(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfParticipantsInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMaxTotalCountOfParticipants(ExpectedCustomerId, ExpectedAppointmentId, participants); Assert.IsFalse(result); @@ -116,11 +112,10 @@ public void CheckMaxTotalCountOfParticipants_AddModifyAndDelete_False() var fakeCountOfParticipantsInDatabase = 4900; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfParticipants(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfParticipantsInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMaxTotalCountOfParticipants(ExpectedCustomerId, ExpectedAppointmentId, participants); Assert.IsFalse(result); @@ -137,11 +132,10 @@ public void CheckMaxTotalCountOfSuggestedDates_Add_Okay() var fakeCountOfSuggestedDatesInDatabase = 10; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfSuggestedDatesInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMaxTotalCountOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId, suggestedDates); Assert.IsTrue(result); @@ -154,11 +148,10 @@ public void CheckMaxTotalCountOfSuggestedDates_AddAndModify_Okay() var fakeCountOfSuggestedDatesInDatabase = 10; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfSuggestedDatesInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMaxTotalCountOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId, suggestedDates); Assert.IsTrue(result); @@ -171,11 +164,10 @@ public void CheckMaxTotalCountOfSuggestedDatesMaxElements_AddReached_Okay() var fakeCountOfSuggestedDatesInDatabase = 90; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfSuggestedDatesInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMaxTotalCountOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId, suggestedDates); Assert.IsTrue(result); @@ -188,11 +180,10 @@ public void CheckMaxTotalCountOfSuggestedDates_MaxElements_AddAndModifyReached_O var fakeCountOfSuggestedDatesInDatabase = 90; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfSuggestedDatesInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMaxTotalCountOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId, suggestedDates); Assert.IsTrue(result); @@ -205,11 +196,10 @@ public void CheckMaxTotalCountOfSuggestedDates_Add_False() var fakeCountOfSuggestedDatesInDatabase = 90; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfSuggestedDatesInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMaxTotalCountOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId, suggestedDates); Assert.IsFalse(result); @@ -222,11 +212,10 @@ public void CheckMaxTotalCountOfSuggestedDates_AddAndModify_False() var fakeCountOfSuggestedDatesInDatabase = 90; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfSuggestedDatesInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMaxTotalCountOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId, suggestedDates); Assert.IsFalse(result); @@ -243,11 +232,10 @@ public void CheckMinTotalCountOfSuggestedDates_Add_Okay() var fakeCountOfSuggestedDatesInDatabase = 10; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfSuggestedDatesInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMinTotalCountOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId, suggestedDates); Assert.IsTrue(result); @@ -260,11 +248,10 @@ public void CheckMinTotalCountOfSuggestedDates_MinElementsAddNotReached_Okay() var fakeCountOfSuggestedDatesInDatabase = 0; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfSuggestedDatesInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMinTotalCountOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId, suggestedDates); Assert.IsFalse(result); @@ -295,11 +282,10 @@ public void CheckMinTotalCountOfSuggestedDatesWithToDeletedDates_False() var fakeCountOfSuggestedDatesInDatabase = 5; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfSuggestedDatesInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMinTotalCountOfSuggestedDatesWithToDeletedDates(ExpectedCustomerId, ExpectedAppointmentId, suggestedDates); @@ -327,11 +313,10 @@ public void CheckMinTotalCountOfSuggestedDatesWithToDeletedDates_True() var fakeCountOfSuggestedDatesInDatabase = 5; var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); mockAppointmentRepo.Setup(bl => bl.GetNumberOfSuggestedDates(ExpectedCustomerId, ExpectedAppointmentId)) .Returns(fakeCountOfSuggestedDatesInDatabase); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.CheckMinTotalCountOfSuggestedDatesWithToDeletedDates(ExpectedCustomerId, ExpectedAppointmentId, suggestedDates); @@ -364,10 +349,7 @@ public void SetAppointmentForeignKeys_Okay() fakeAppointment.SuggestedDates.Add(fakeSuggestedDate2); fakeSuggestedDate1.Votings = new List { new() }; - - var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(); sut.SetAppointmentForeignKeys(fakeAppointment, ExpectedCustomerId); @@ -410,10 +392,7 @@ public void SetParticipantsForeignKeys_Okay() } } ]; - - var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(); sut.SetParticipantsForeignKeys(fakeParticipants, ExpectedCustomerId, ExpectedAppointmentId); @@ -438,6 +417,53 @@ public void SetParticipantsForeignKeys_Okay() #endregion + #region AddAndUpdateParticipants + + [TestMethod] + public void AddAndUpdateParticipants_IncrementParticipantStatisticCount_CallsExpectedMethod() + { + var mockStatisticRepo = new Mock(); + var sut = CreateSut(statisticRepository: mockStatisticRepo.Object); + + var participants = new List + { + new() { ParticipantId = Guid.Empty, Name = ExpectedParticipantName } + }; + + sut.AddAndUpdateParticipants(ExpectedCustomerId, ExpectedAppointmentId, participants); + + mockStatisticRepo.Verify(r => r.IncrementParticipantStatisticCount(ExpectedCustomerId), Times.Once()); + mockStatisticRepo.Verify(r => r.IncrementVotingStatisticCount(It.IsAny(), It.IsAny()), Times.Never()); + } + + [TestMethod] + public void AddAndUpdateParticipants_IncrementVotingStatisticCount_CallsExpectedMethod() + { + var mockStatisticRepo = new Mock(); + var sut = CreateSut(statisticRepository: mockStatisticRepo.Object); + + var participants = new List + { + new() + { + ParticipantId = Guid.Empty, + Name = ExpectedParticipantName, + Votings = new List + { + new() { VotingId = Guid.Empty }, + new() { VotingId = Guid.Empty } + } + } + }; + + sut.AddAndUpdateParticipants(ExpectedCustomerId, ExpectedAppointmentId, participants); + + mockStatisticRepo.Verify(r => r.IncrementParticipantStatisticCount(ExpectedCustomerId), Times.Once()); + mockStatisticRepo.Verify(r => r.IncrementVotingStatisticCount(ExpectedCustomerId, 2), Times.Once()); + } + + #endregion + #region GetAppointment [TestMethod] @@ -449,7 +475,7 @@ public void GetAppointment_getExistingAppointment_appointmentPasswordNotSet() ); var mockCustomerRepo = new Mock(); mockCustomerRepo.Setup(r => r.ExistsCustomer(It.IsAny())).Returns(true); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object, customerRepository: mockCustomerRepo.Object); var result = sut.GetAppointment(ExpectedCustomerId, ExpectedAppointmentId); Assert.IsNotNull(result); @@ -465,7 +491,7 @@ public void GetAppointment_NotExistingCustomerId_Null() ); var mockCustomerRepo = new Mock(); mockCustomerRepo.Setup(r => r.ExistsCustomer(It.IsAny())).Returns(false); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object, customerRepository: mockCustomerRepo.Object); var result = sut.GetAppointment(ExpectedCustomerId, ExpectedAppointmentId); Assert.IsNull(result); @@ -480,8 +506,7 @@ public void IsAppointmentPasswordProtected_appointmentExistsAndHasAnyPassword_tr { var mockAppointmentRepo = new Mock(); mockAppointmentRepo.Setup(r => r.GetAppointmentPassword(It.IsAny(), It.IsAny())).Returns("Dummy"); - var mockCustomerRepo = new Mock(); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.IsAppointmentPasswordProtected(ExpectedCustomerId, ExpectedAppointmentId); Assert.IsTrue(result); @@ -500,7 +525,7 @@ public void GetAppointmentByAdminId_getExistingAppointment_appointmentPasswordNo ); var mockCustomerRepo = new Mock(); mockCustomerRepo.Setup(r => r.ExistsCustomer(It.IsAny())).Returns(true); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object, customerRepository: mockCustomerRepo.Object); var result = sut.GetAppointmentByAdminId(ExpectedCustomerId, ExpectedAdminId); Assert.IsNotNull(result); @@ -516,7 +541,7 @@ public void GetAppointmentByAdminId_NotExistingCustomerId_Null() ); var mockCustomerRepo = new Mock(); mockCustomerRepo.Setup(r => r.ExistsCustomer(It.IsAny())).Returns(false); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object, customerRepository: mockCustomerRepo.Object); var result = sut.GetAppointmentByAdminId(ExpectedCustomerId, ExpectedAdminId); Assert.IsNull(result); @@ -532,8 +557,7 @@ public void IsAppointmentPasswordProtectedByAdminId_appointmentExistsAndHasAnyPa var mockAppointmentRepo = new Mock(); mockAppointmentRepo.Setup(r => r.GetAppointmentPasswordByAdmin(It.IsAny(), It.IsAny())) .Returns("Dummy"); - var mockCustomerRepo = new Mock(); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.IsAppointmentPasswordProtectedByAdminId(ExpectedCustomerId, ExpectedAdminId); Assert.IsTrue(result); @@ -555,7 +579,7 @@ public void AddAppointment_createAppointment_appointmentPasswordOfResultNotSet() ); var mockCustomerRepo = new Mock(); mockCustomerRepo.Setup(r => r.ExistsCustomer(It.IsAny())).Returns(true); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object, customerRepository: mockCustomerRepo.Object); var result = sut.AddAppointment(new Appointment { @@ -582,7 +606,7 @@ public void AddAppointment_createAppointment_hashPasswordInAppointmentAndStoreIt var mockCustomerRepo = new Mock(); mockCustomerRepo.Setup(r => r.ExistsCustomer(It.IsAny())).Returns(true); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object, customerRepository: mockCustomerRepo.Object); var result = sut.AddAppointment(new Appointment { @@ -592,6 +616,26 @@ public void AddAppointment_createAppointment_hashPasswordInAppointmentAndStoreIt mockAppointmentRepo.Verify(r => r.AddAndUpdateAppointment(It.IsAny()), Times.Once()); } + [TestMethod] + public void AddAppointment_IncrementAppointmentStatisticCount_CallsExpectedMethod() + { + var mockAppointmentRepo = new Mock(); + var mockCustomerRepo = new Mock(); + mockCustomerRepo.Setup(r => r.ExistsCustomer(It.IsAny())).Returns(true); + + var mockStatisticRepo = new Mock(); + var sut = CreateSut(mockAppointmentRepo.Object, mockStatisticRepo.Object, mockCustomerRepo.Object); + + sut.AddAppointment( + new Appointment + { + AppointmentId = ExpectedAppointmentId, CustomerId = ExpectedCustomerId, Password = ExpectedPassword + } + ); + + mockStatisticRepo.Verify(r => r.IncrementAppointmentStatisticCount(ExpectedCustomerId), Times.Once()); + } + #endregion #region UpdateAppointment @@ -611,7 +655,7 @@ public void UpdateAppointment_updateAppointment_appointmentPasswordOfResultNotSe ); var mockCustomerRepo = new Mock(); mockCustomerRepo.Setup(r => r.ExistsCustomer(It.IsAny())).Returns(true); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object, customerRepository: mockCustomerRepo.Object); var result = sut.UpdateAppointment(new Appointment { @@ -638,7 +682,7 @@ public void UpdateAppointment_updateAppointment_hashPasswordInAppointmentAndStor var mockCustomerRepo = new Mock(); mockCustomerRepo.Setup(r => r.ExistsCustomer(It.IsAny())).Returns(true); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object, customerRepository: mockCustomerRepo.Object); var result = sut.UpdateAppointment(new Appointment { @@ -666,11 +710,10 @@ public void VerifyAppointmentPassword_submittedPasswordCouldBeVerified_true() var mockAppointmentRepo = new Mock(); mockAppointmentRepo.Setup(r => r.GetAppointmentPassword(It.IsAny(), It.IsAny())) .Returns(ExpectedHashPassword); - var mockCustomerRepo = new Mock(); var mockBcryptWrapper = new Mock(); mockBcryptWrapper.Setup(w => w.Verify(ExpectedPassword, ExpectedHashPassword)).Returns(true); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object, mockBcryptWrapper.Object); + var sut = CreateSut(mockAppointmentRepo.Object, bcryptWrapper: mockBcryptWrapper.Object); var result = sut.VerifyAppointmentPassword(ExpectedCustomerId, ExpectedAppointmentId, ExpectedPassword); Assert.IsTrue(result); @@ -704,11 +747,10 @@ public void VerifyAppointmentPasswordByAdminId_submittedPasswordCouldBeVerified_ mockAppointmentRepo.Setup(r => r.GetAppointmentPasswordByAdmin(It.IsAny(), It.IsAny())) .Returns(ExpectedHashPassword); - var mockCustomerRepo = new Mock(); var mockBcryptWrapper = new Mock(); mockBcryptWrapper.Setup(w => w.Verify(ExpectedPassword, ExpectedHashPassword)).Returns(true); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object, mockBcryptWrapper.Object); + var sut = CreateSut(mockAppointmentRepo.Object, bcryptWrapper: mockBcryptWrapper.Object); var result = sut.VerifyAppointmentPasswordByAdminId(ExpectedCustomerId, ExpectedAdminId, ExpectedPassword); Assert.IsTrue(result); @@ -740,9 +782,8 @@ public void ExistsAppointment_AppointmentExists_true() { var mockAppointmentRepo = new Mock(); mockAppointmentRepo.Setup(r => r.ExistsAppointment(It.IsAny(), It.IsAny())).Returns(true); - var mockCustomerRepo = new Mock(); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.ExistsAppointment(ExpectedCustomerId, ExpectedAppointmentId); Assert.IsTrue(result); @@ -775,9 +816,8 @@ public void ExistsAppointmentIsStarted_appointmentIsStarted_false() { var mockAppointmentRepo = new Mock(); mockAppointmentRepo.Setup(r => r.ExistsAppointmentIsStarted(It.IsAny(), It.IsAny())).Returns(false); - var mockCustomerRepo = new Mock(); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.ExistsAppointmentIsStarted(ExpectedCustomerId, ExpectedAppointmentId); Assert.IsFalse(result); @@ -801,9 +841,7 @@ public void ExistsAppointmentByAdminId_appointmentExists_true() { var mockAppointmentRepo = new Mock(); mockAppointmentRepo.Setup(r => r.ExistsAppointmentByAdminId(It.IsAny(), It.IsAny())).Returns(true); - var mockCustomerRepo = new Mock(); - - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(mockAppointmentRepo.Object); var result = sut.ExistsAppointmentByAdminId(ExpectedCustomerId, ExpectedAdminId); Assert.IsTrue(result); @@ -843,10 +881,7 @@ public void ParticipantToDeleteAreValid_participantGuidsAreValid_true() CustomerId = ExpectedCustomerId, AppointmentId = ExpectedAppointmentId }; - - var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(); var result = sut.ParticipantToDeleteAreValid(fakeParticipant); Assert.IsTrue(result); @@ -856,10 +891,7 @@ public void ParticipantToDeleteAreValid_participantGuidsAreValid_true() public void ParticipantToDeleteAreValid_participantGuidsAreNotValid_false() { var fakeParticipant = new Participant { CustomerId = ExpectedCustomerId, AppointmentId = ExpectedAppointmentId }; - - var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(); var result = sut.ParticipantToDeleteAreValid(fakeParticipant); Assert.IsFalse(result); @@ -878,10 +910,7 @@ public void ParticipantsAreValid_participantsAreValid_true() CustomerId = ExpectedCustomerId, AppointmentId = ExpectedAppointmentId }; - - var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(); var result = sut.ParticipantsAreValid(new List { fakeParticipant }); Assert.IsTrue(result); @@ -890,9 +919,7 @@ public void ParticipantsAreValid_participantsAreValid_true() [TestMethod] public void ParticipantsAreValid_participantsAreNull_false() { - var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(); var result = sut.ParticipantsAreValid(null); Assert.IsFalse(result); @@ -918,10 +945,7 @@ public void ParticipantsAreValid_VotingsAreValid_true() } } }; - - var mockAppointmentRepo = new Mock(); - var mockCustomerRepo = new Mock(); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object); + var sut = CreateSut(); var result = sut.ParticipantsAreValid(new List { fakeParticipant }); Assert.IsTrue(result); @@ -938,7 +962,6 @@ public void SetAppointmentStatusType_ChangeStatusType_StartedToPaused_Okay() var oldStatusType = AppointmentStatusType.Started; var newStatusType = AppointmentStatusType.Paused; - var mockCustomerRepo = new Mock(); var mockAppointmentRepo = new Mock(); mockAppointmentRepo.Setup(r => r.ExistsAppointmentByAdminId(It.IsAny(), It.IsAny())).Returns(true); mockAppointmentRepo.Setup(r => r.GetAppointmentStatusTypeByAdmin(It.IsAny(), It.IsAny())) @@ -956,7 +979,7 @@ public void SetAppointmentStatusType_ChangeStatusType_StartedToPaused_Okay() mockAppointmentBusinessLayer .Setup(r => r.IsAppointmentPasswordProtectedByAdminId(It.IsAny(), It.IsAny())).Returns(true); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object, mockBcryptWrapper.Object); + var sut = CreateSut(mockAppointmentRepo.Object, bcryptWrapper: mockBcryptWrapper.Object); var appointment = sut.SetAppointmentStatusType(ExpectedCustomerId, ExpectedAdminId, newStatusType); Assert.IsFalse(appointment == null); @@ -970,7 +993,6 @@ public void SetAppointmentStatusType_ChangeStatusType_PausedToStarted_Okay() var oldStatusType = AppointmentStatusType.Paused; var newStatusType = AppointmentStatusType.Started; - var mockCustomerRepo = new Mock(); var mockAppointmentRepo = new Mock(); mockAppointmentRepo.Setup(r => r.ExistsAppointmentByAdminId(It.IsAny(), It.IsAny())).Returns(true); mockAppointmentRepo.Setup(r => r.GetAppointmentStatusTypeByAdmin(It.IsAny(), It.IsAny())) @@ -988,7 +1010,7 @@ public void SetAppointmentStatusType_ChangeStatusType_PausedToStarted_Okay() mockAppointmentBusinessLayer .Setup(r => r.IsAppointmentPasswordProtectedByAdminId(It.IsAny(), It.IsAny())).Returns(true); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object, mockBcryptWrapper.Object); + var sut = CreateSut(mockAppointmentRepo.Object, bcryptWrapper: mockBcryptWrapper.Object); var appointment = sut.SetAppointmentStatusType(ExpectedCustomerId, ExpectedAdminId, newStatusType); Assert.IsFalse(appointment == null); @@ -1002,7 +1024,6 @@ public void SetAppointmentStatusType_ChangeStatusType_DeletedToStarted_Exception var oldStatusType = AppointmentStatusType.Deleted; var newStatusType = AppointmentStatusType.Started; - var mockCustomerRepo = new Mock(); var mockAppointmentRepo = new Mock(); mockAppointmentRepo.Setup(r => r.ExistsAppointmentByAdminId(It.IsAny(), It.IsAny())).Returns(true); mockAppointmentRepo.Setup(r => r.GetAppointmentStatusTypeByAdmin(It.IsAny(), It.IsAny())) @@ -1020,7 +1041,7 @@ public void SetAppointmentStatusType_ChangeStatusType_DeletedToStarted_Exception mockAppointmentBusinessLayer .Setup(r => r.IsAppointmentPasswordProtectedByAdminId(It.IsAny(), It.IsAny())).Returns(true); - var sut = CreateSut(mockAppointmentRepo.Object, mockCustomerRepo.Object, mockBcryptWrapper.Object); + var sut = CreateSut(mockAppointmentRepo.Object, bcryptWrapper: mockBcryptWrapper.Object); var appointmentObject = sut.SetAppointmentStatusType(ExpectedCustomerId, ExpectedAdminId, newStatusType); Assert.IsNull(appointmentObject); @@ -1062,16 +1083,19 @@ public void SaveAppointment_CallExpectedMethods() private static AppointmentBusinessLayer CreateSut( [CanBeNull] IAppointmentRepository appointmentRepository = null, + [CanBeNull] IStatisticRepository statisticRepository = null, [CanBeNull] ICustomerRepository customerRepository = null, [CanBeNull] IBcryptWrapper bcryptWrapper = null) { var appointmentRepositoryToUse = appointmentRepository ?? new Mock().Object; + var statisticRepositoryToUse = statisticRepository ?? new Mock().Object; var customerRepositoryToUse = customerRepository ?? new Mock().Object; var mockBcryptWrapper = bcryptWrapper ?? CreateDefaultBcryptWrapper(); var logger = new Mock>(); return new AppointmentBusinessLayer( appointmentRepositoryToUse, + statisticRepositoryToUse, customerRepositoryToUse, mockBcryptWrapper, logger.Object); diff --git a/src/Dataport.Terminfinder.BusinessLayer/AppointmentBusinessLayer.cs b/src/Dataport.Terminfinder.BusinessLayer/AppointmentBusinessLayer.cs index 8775f38..d72918f 100644 --- a/src/Dataport.Terminfinder.BusinessLayer/AppointmentBusinessLayer.cs +++ b/src/Dataport.Terminfinder.BusinessLayer/AppointmentBusinessLayer.cs @@ -10,6 +10,7 @@ namespace Dataport.Terminfinder.BusinessLayer; public class AppointmentBusinessLayer : BusinessLayerBase, IAppointmentBusinessLayer { private readonly IAppointmentRepository _appointmentRepo; + private readonly IStatisticRepository _statisticRepo; private readonly IBcryptWrapper _bcryptWrapper; private static readonly int MaxCountOfElementsOfParticipants = 5000; private static readonly int MaxCountOfElementsOfSuggestedDates = 100; @@ -19,10 +20,12 @@ public class AppointmentBusinessLayer : BusinessLayerBase, IAppointmentBusinessL /// constructor /// /// Appointment repository + /// Statistic repository /// Customer repository /// BCryptWrapper /// Logger public AppointmentBusinessLayer(IAppointmentRepository appointmentRepo, + IStatisticRepository statisticRepo, ICustomerRepository customerRepo, IBcryptWrapper bcryptWrapper, ILogger logger) @@ -31,6 +34,7 @@ public AppointmentBusinessLayer(IAppointmentRepository appointmentRepo, Logger.LogDebug($"Enter {nameof(AppointmentBusinessLayer)}"); _appointmentRepo = appointmentRepo; + _statisticRepo = statisticRepo; _bcryptWrapper = bcryptWrapper ?? throw new ArgumentNullException(nameof(bcryptWrapper)); } @@ -185,6 +189,7 @@ public Appointment AddAppointment(Appointment appointment) if (appointment != null && ExistsCustomer(appointment.CustomerId)) { HashPasswordInAppointment(appointment); + _statisticRepo.IncrementAppointmentStatisticCount(appointment.CustomerId); _appointmentRepo.AddAndUpdateAppointment(appointment); appointment = _appointmentRepo.GetAppointment(appointment.CustomerId, appointment.AppointmentId); } @@ -290,6 +295,17 @@ public ICollection AddAndUpdateParticipants(Guid customerId, Guid a return (participants); } + if (participants.Any(participant => participant.ParticipantId == Guid.Empty)) + { + _statisticRepo.IncrementParticipantStatisticCount(customerId); + } + + var newVotingCount = participants.Sum(p => p.Votings?.Count(v => v.VotingId == Guid.Empty) ?? 0); + if (newVotingCount > 0) + { + _statisticRepo.IncrementVotingStatisticCount(customerId, newVotingCount); + } + _appointmentRepo.AddAndUpdateParticipants(participants); participants = _appointmentRepo.GetParticipants(customerId, appointmentId); return (participants); diff --git a/src/Dataport.Terminfinder.BusinessObject/AppointmentStatistic.cs b/src/Dataport.Terminfinder.BusinessObject/AppointmentStatistic.cs new file mode 100644 index 0000000..3121995 --- /dev/null +++ b/src/Dataport.Terminfinder.BusinessObject/AppointmentStatistic.cs @@ -0,0 +1,35 @@ +namespace Dataport.Terminfinder.BusinessObject; + +/// +/// Representation for an appointment statistic +/// +[ExcludeFromCodeCoverage] +[Table("appointmentStatistic", Schema = "public")] +public class AppointmentStatistic +{ + /// + /// Identifier of the appointment statistic + /// + [Column("appointmentStatisticId")] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid AppointmentStatisticId { get; set; } + + /// + /// Identifier of the customer + /// + /// 00000000-0000-0000-0000-000000000000 + [Column("customerId")] + public Guid CustomerId { get; set; } + + /// + /// The year and month for which the statistic is recorded + /// + [Column("yearMonth")] + public DateOnly YearMonth { get; set; } + + /// + /// The count for the recorded statistic + /// + [Column("count")] + public int Count { get; set; } = 1; +} \ No newline at end of file diff --git a/src/Dataport.Terminfinder.BusinessObject/ParticipantStatistic.cs b/src/Dataport.Terminfinder.BusinessObject/ParticipantStatistic.cs new file mode 100644 index 0000000..e11e5e0 --- /dev/null +++ b/src/Dataport.Terminfinder.BusinessObject/ParticipantStatistic.cs @@ -0,0 +1,35 @@ +namespace Dataport.Terminfinder.BusinessObject; + +/// +/// Representation for a Participant statistic +/// +[ExcludeFromCodeCoverage] +[Table("participantStatistic", Schema = "public")] +public class ParticipantStatistic +{ + /// + /// Identifier of the participant statistic + /// + [Column("participantStatisticId")] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid ParticipantStatisticId { get; set; } + + /// + /// Identifier of the customer + /// + /// 00000000-0000-0000-0000-000000000000 + [Column("customerId")] + public Guid CustomerId { get; set; } + + /// + /// The year and month for which the statistic is recorded + /// + [Column("yearMonth")] + public DateOnly YearMonth { get; set; } + + /// + /// The count for the recorded statistic + /// + [Column("count")] + public int Count { get; set; } = 1; +} \ No newline at end of file diff --git a/src/Dataport.Terminfinder.BusinessObject/VotingStatistic.cs b/src/Dataport.Terminfinder.BusinessObject/VotingStatistic.cs new file mode 100644 index 0000000..fc34351 --- /dev/null +++ b/src/Dataport.Terminfinder.BusinessObject/VotingStatistic.cs @@ -0,0 +1,35 @@ +namespace Dataport.Terminfinder.BusinessObject; + +/// +/// Representation for a Voting statistic +/// +[ExcludeFromCodeCoverage] +[Table("votingStatistic", Schema = "public")] +public class VotingStatistic +{ + /// + /// Identifier of the voting statistic + /// + [Column("votingStatisticId")] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid VotingStatisticId { get; set; } + + /// + /// Identifier of the customer + /// + /// 00000000-0000-0000-0000-000000000000 + [Column("customerId")] + public Guid CustomerId { get; set; } + + /// + /// The year and month for which the statistic is recorded + /// + [Column("yearMonth")] + public DateOnly YearMonth { get; set; } + + /// + /// The count for the recorded statistic + /// + [Column("count")] + public int Count { get; set; } = 1; +} \ No newline at end of file diff --git a/src/Dataport.Terminfinder.Repository.Tests/Dataport.Terminfinder.Repository.Tests.csproj b/src/Dataport.Terminfinder.Repository.Tests/Dataport.Terminfinder.Repository.Tests.csproj index a0b0308..cbdaad5 100644 --- a/src/Dataport.Terminfinder.Repository.Tests/Dataport.Terminfinder.Repository.Tests.csproj +++ b/src/Dataport.Terminfinder.Repository.Tests/Dataport.Terminfinder.Repository.Tests.csproj @@ -28,6 +28,7 @@ + diff --git a/src/Dataport.Terminfinder.Repository.Tests/StatisticRepositoryTests.cs b/src/Dataport.Terminfinder.Repository.Tests/StatisticRepositoryTests.cs new file mode 100644 index 0000000..eb14b50 --- /dev/null +++ b/src/Dataport.Terminfinder.Repository.Tests/StatisticRepositoryTests.cs @@ -0,0 +1,155 @@ +using Microsoft.Data.Sqlite; + +namespace Dataport.Terminfinder.Repository.Tests; + +[TestClass] +public class StatisticRepositoryTests +{ + private static readonly Guid ExpectedCustomerId = Guid.Parse("BE1D657A-4D06-40DB-8443-D67BBB950EE7"); + private static readonly DateOnly ExpectedYearMonth = new(2026, 9, 1); + private const int ExpectedInitialAppointmentCount = 2; + private const int ExpectedInitialParticipantCount = 4; + private const int ExpectedInitialVotingCount = 2; + private const int ExpectedIncrementedAppointmentCount = ExpectedInitialAppointmentCount + 1; + private const int ExpectedIncrementedParticipantCount = ExpectedInitialParticipantCount + 1; + private const int ExpectedIncrementedVotingCount = ExpectedInitialVotingCount + 5; + + [TestMethod] + public void IncrementAppointmentStatisticCount_WhenStatisticIsMissing_CreatesStatisticWithExpectedCount() + { + using var context = CreateContext(); + var sut = CreateSut(context); + + sut.IncrementAppointmentStatisticCount(ExpectedCustomerId); + + var statistic = context.AppointmentStatistics.Single(); + Assert.AreEqual(ExpectedCustomerId, statistic.CustomerId); + Assert.AreEqual(ExpectedYearMonth, statistic.YearMonth); + Assert.AreEqual(1, statistic.Count); + } + + [TestMethod] + public void IncrementAppointmentStatisticCount_WhenStatisticExists_IncreasesCountByOne() + { + using var context = CreateContext(); + AddAppointmentStatistic(context, ExpectedInitialAppointmentCount); + var sut = CreateSut(context); + + sut.IncrementAppointmentStatisticCount(ExpectedCustomerId); + + var statistic = context.AppointmentStatistics.Single(); + Assert.AreEqual(ExpectedIncrementedAppointmentCount, statistic.Count); + } + + [TestMethod] + public void IncrementParticipantStatisticCount_WhenStatisticIsMissing_CreatesStatisticWithExpectedCount() + { + using var context = CreateContext(); + var sut = CreateSut(context); + + sut.IncrementParticipantStatisticCount(ExpectedCustomerId); + + var statistic = context.ParticipantStatistics.Single(); + Assert.AreEqual(ExpectedCustomerId, statistic.CustomerId); + Assert.AreEqual(ExpectedYearMonth, statistic.YearMonth); + Assert.AreEqual(1, statistic.Count); + } + + [TestMethod] + public void IncrementParticipantStatisticCount_WhenStatisticExists_IncreasesCountByOne() + { + using var context = CreateContext(); + AddParticipantStatistic(context, ExpectedInitialParticipantCount); + var sut = CreateSut(context); + + sut.IncrementParticipantStatisticCount(ExpectedCustomerId); + + var statistic = context.ParticipantStatistics.Single(); + Assert.AreEqual(ExpectedIncrementedParticipantCount, statistic.Count); + } + + [TestMethod] + public void IncrementVotingStatisticCount_WhenStatisticIsMissing_CreatesStatisticWithExpectedCount() + { + using var context = CreateContext(); + var sut = CreateSut(context); + + sut.IncrementVotingStatisticCount(ExpectedCustomerId, 3); + + var statistic = context.VotingStatistics.Single(); + Assert.AreEqual(ExpectedCustomerId, statistic.CustomerId); + Assert.AreEqual(ExpectedYearMonth, statistic.YearMonth); + Assert.AreEqual(3, statistic.Count); + } + + [TestMethod] + public void IncrementVotingStatisticCount_WhenStatisticExists_IncreasesCountByProvidedValue() + { + using var context = CreateContext(); + AddVotingStatistic(context, ExpectedInitialVotingCount); + var sut = CreateSut(context); + + sut.IncrementVotingStatisticCount(ExpectedCustomerId, 5); + + var statistic = context.VotingStatistics.Single(); + Assert.AreEqual(ExpectedIncrementedVotingCount, statistic.Count); + } + + [TestMethod] + public void IncrementVotingStatisticCount_WhenCountIsNotPositive_DoesNotCreateAnyExpectedStatistic() + { + using var context = CreateContext(); + var sut = CreateSut(context); + + sut.IncrementVotingStatisticCount(ExpectedCustomerId, 0); + sut.IncrementVotingStatisticCount(ExpectedCustomerId, -1); + + Assert.AreEqual(0, context.VotingStatistics.Count()); + } + + private static StatisticRepository CreateSut(DataContext context) + { + return new StatisticRepository(context, Mock.Of>()); + } + + private static void AddAppointmentStatistic(DataContext context, int count) + { + context.AppointmentStatistics.Add( + new AppointmentStatistic { CustomerId = ExpectedCustomerId, YearMonth = ExpectedYearMonth, Count = count } + ); + context.SaveChanges(); + context.ChangeTracker.Clear(); + } + + private static void AddParticipantStatistic(DataContext context, int count) + { + context.ParticipantStatistics.Add( + new ParticipantStatistic { CustomerId = ExpectedCustomerId, YearMonth = ExpectedYearMonth, Count = count } + ); + context.SaveChanges(); + context.ChangeTracker.Clear(); + } + + private static void AddVotingStatistic(DataContext context, int count) + { + context.VotingStatistics.Add( + new VotingStatistic { CustomerId = ExpectedCustomerId, YearMonth = ExpectedYearMonth, Count = count } + ); + context.SaveChanges(); + context.ChangeTracker.Clear(); + } + + private static DataContext CreateContext() + { + var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); + + var options = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + + var context = new DataContext(options, LoggerFactory.Create(_ => { })); + context.Database.EnsureCreated(); + return context; + } +} \ No newline at end of file diff --git a/src/Dataport.Terminfinder.Repository/DataContext.cs b/src/Dataport.Terminfinder.Repository/DataContext.cs index 193c4b0..711c3c2 100644 --- a/src/Dataport.Terminfinder.Repository/DataContext.cs +++ b/src/Dataport.Terminfinder.Repository/DataContext.cs @@ -180,6 +180,39 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) voting.HasIndex(x => x.SuggestedDateId) .HasDatabaseName("voting_suggesteddateid_ix"); }); + + modelBuilder.Entity(appointmentStatistic => + { + appointmentStatistic + .HasIndex(x => new { x.CustomerId, x.YearMonth }) + .IsUnique(); + + appointmentStatistic + .Property(x => x.Count) + .HasDefaultValue(1); + }); + + modelBuilder.Entity(participantStatistic => + { + participantStatistic + .HasIndex(x => new { x.CustomerId, x.YearMonth }) + .IsUnique(); + + participantStatistic + .Property(x => x.Count) + .HasDefaultValue(1); + }); + + modelBuilder.Entity(votingStatistic => + { + votingStatistic + .HasIndex(x => new { x.CustomerId, x.YearMonth }) + .IsUnique(); + + votingStatistic + .Property(x => x.Count) + .HasDefaultValue(1); + }); } /// @@ -232,4 +265,19 @@ public virtual void SetTracking(bool tracking) /// Participants /// public virtual DbSet Participants { get; set; } + + /// + /// Statistics for appointments + /// + public virtual DbSet AppointmentStatistics { get; set; } + + /// + /// Statistics for participants + /// + public virtual DbSet ParticipantStatistics { get; set; } + + /// + /// Statistics for votings + /// + public virtual DbSet VotingStatistics { get; set; } } \ No newline at end of file diff --git a/src/Dataport.Terminfinder.Repository/IStatisticRepository.cs b/src/Dataport.Terminfinder.Repository/IStatisticRepository.cs new file mode 100644 index 0000000..b4371b9 --- /dev/null +++ b/src/Dataport.Terminfinder.Repository/IStatisticRepository.cs @@ -0,0 +1,32 @@ +namespace Dataport.Terminfinder.Repository; + +/// +/// Business methods for statistics +/// +public interface IStatisticRepository : IRepositoryBase +{ + /// + /// Increment the count of the appointment statistic for the given customer for the current month. + /// If no statistic exists, a new one will be created with count = 1. + /// Appointments are always created as single requests, so no count parameter is needed. + /// + /// + void IncrementAppointmentStatisticCount(Guid customerId); + + /// + /// Increment the count of the participant statistic for the given customer for the current month. + /// If no statistic exists, a new one will be created with count = 1. + /// Participants are always created as single requests, so no count parameter is needed. + /// + /// + void IncrementParticipantStatisticCount(Guid customerId); + + /// + /// Increment the count of the voting statistic for the given customer for the current month. + /// If no statistic exists, a new one will be created with count = 1. + /// Votings can be created in bulk, so the count parameter allows to increment the statistic by more than 1. + /// + /// + /// + void IncrementVotingStatisticCount(Guid customerId, int count = 1); +} \ No newline at end of file diff --git a/src/Dataport.Terminfinder.Repository/Migrations/20260909113548_AddStatistics.Designer.cs b/src/Dataport.Terminfinder.Repository/Migrations/20260909113548_AddStatistics.Designer.cs new file mode 100644 index 0000000..25b21f1 --- /dev/null +++ b/src/Dataport.Terminfinder.Repository/Migrations/20260909113548_AddStatistics.Designer.cs @@ -0,0 +1,496 @@ +// +using System; +using Dataport.Terminfinder.Repository; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Dataport.Terminfinder.Repository.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20260909113548_AddStatistics")] + partial class AddStatistics + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.AppConfig", b => + { + b.Property("Key") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("configkey"); + + b.Property("Value") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .HasColumnName("configvalue"); + + b.HasKey("Key") + .HasName("appconfig_pkey"); + + b.ToTable("appconfig", "public"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.Appointment", b => + { + b.Property("AppointmentId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("appointmentid"); + + b.Property("AdminId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("adminid"); + + b.Property("CreationDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("creationdate") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatorName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("creatorname"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customerid"); + + b.Property("Description") + .HasMaxLength(1500) + .HasColumnType("character varying(1500)") + .HasColumnName("description"); + + b.Property("Password") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("password"); + + b.Property("Place") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .HasColumnName("place"); + + b.Property("StatusIdentifier") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("status"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .HasColumnName("subject"); + + b.HasKey("AppointmentId") + .HasName("appointment_pkey"); + + b.HasIndex("AdminId") + .IsUnique() + .HasDatabaseName("appointment_adminid_ix"); + + b.HasIndex("CustomerId") + .HasDatabaseName("appointment_customerid_ix"); + + b.ToTable("appointment", "public"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.AppointmentStatistic", b => + { + b.Property("AppointmentStatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("appointmentStatisticId"); + + b.Property("Count") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1) + .HasColumnName("count"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customerId"); + + b.Property("YearMonth") + .HasColumnType("date") + .HasColumnName("yearMonth"); + + b.HasKey("AppointmentStatisticId"); + + b.HasIndex("CustomerId", "YearMonth") + .IsUnique(); + + b.ToTable("appointmentStatistic", "public"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.Customer", b => + { + b.Property("CustomerId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("customerid"); + + b.Property("CreationDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("creationdate") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CustomerName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("customername"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("status"); + + b.HasKey("CustomerId") + .HasName("customer_pkey"); + + b.ToTable("customer", "public"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.Participant", b => + { + b.Property("ParticipantId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("participantid"); + + b.Property("AppointmentId") + .HasColumnType("uuid") + .HasColumnName("appointmentid"); + + b.Property("CreationDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("creationdate") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customerid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("name"); + + b.HasKey("ParticipantId") + .HasName("participant_pkey"); + + b.HasIndex("AppointmentId") + .HasDatabaseName("participant_appointmentid_ix"); + + b.HasIndex("CustomerId") + .HasDatabaseName("participant_customerid_ix"); + + b.ToTable("participant", "public"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.ParticipantStatistic", b => + { + b.Property("ParticipantStatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("participantStatisticId"); + + b.Property("Count") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1) + .HasColumnName("count"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customerId"); + + b.Property("YearMonth") + .HasColumnType("date") + .HasColumnName("yearMonth"); + + b.HasKey("ParticipantStatisticId"); + + b.HasIndex("CustomerId", "YearMonth") + .IsUnique(); + + b.ToTable("participantStatistic", "public"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.SuggestedDate", b => + { + b.Property("SuggestedDateId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("suggesteddateid"); + + b.Property("AppointmentId") + .HasColumnType("uuid") + .HasColumnName("appointmentid"); + + b.Property("CreationDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("creationdate") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customerid"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("description"); + + b.Property("EndDate") + .HasColumnType("date") + .HasColumnName("enddate"); + + b.Property("EndTime") + .HasColumnType("time with time zone") + .HasColumnName("endtime"); + + b.Property("StartDate") + .HasColumnType("date") + .HasColumnName("startdate"); + + b.Property("StartTime") + .HasColumnType("time with time zone") + .HasColumnName("starttime"); + + b.HasKey("SuggestedDateId") + .HasName("suggesteddate_pkey"); + + b.HasIndex("AppointmentId") + .HasDatabaseName("suggesteddate_appointmentid_ix"); + + b.HasIndex("CustomerId") + .HasDatabaseName("suggestedDate_customerid_ix"); + + b.HasIndex("EndDate") + .HasDatabaseName("suggesteddate_enddate_ix"); + + b.HasIndex("StartDate") + .HasDatabaseName("suggesteddate_startdate_ix"); + + b.ToTable("suggesteddate", "public"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.Voting", b => + { + b.Property("VotingId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("votingid"); + + b.Property("AppointmentId") + .HasColumnType("uuid") + .HasColumnName("appointmentid"); + + b.Property("CreationDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("creationdate") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customerid"); + + b.Property("ParticipantId") + .HasColumnType("uuid") + .HasColumnName("participantid"); + + b.Property("StatusIdentifier") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("status"); + + b.Property("SuggestedDateId") + .HasColumnType("uuid") + .HasColumnName("suggesteddateid"); + + b.HasKey("VotingId") + .HasName("voting_pkey"); + + b.HasIndex("AppointmentId") + .HasDatabaseName("voting_appointmentid_ix"); + + b.HasIndex("CustomerId") + .HasDatabaseName("voting_customerid_ix"); + + b.HasIndex("ParticipantId") + .HasDatabaseName("voting_participantid_ix"); + + b.HasIndex("SuggestedDateId") + .HasDatabaseName("voting_suggesteddateid_ix"); + + b.ToTable("voting", "public"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.VotingStatistic", b => + { + b.Property("VotingStatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("votingStatisticId"); + + b.Property("Count") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1) + .HasColumnName("count"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customerId"); + + b.Property("YearMonth") + .HasColumnType("date") + .HasColumnName("yearMonth"); + + b.HasKey("VotingStatisticId"); + + b.HasIndex("CustomerId", "YearMonth") + .IsUnique(); + + b.ToTable("votingStatistic", "public"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.Appointment", b => + { + b.HasOne("Dataport.Terminfinder.BusinessObject.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("appointment_customerid_fkey"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.Participant", b => + { + b.HasOne("Dataport.Terminfinder.BusinessObject.Appointment", "Appointment") + .WithMany("Participants") + .HasForeignKey("AppointmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("participant_appointmentid_fkey"); + + b.HasOne("Dataport.Terminfinder.BusinessObject.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("participant_customerid_fkey"); + + b.Navigation("Appointment"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.SuggestedDate", b => + { + b.HasOne("Dataport.Terminfinder.BusinessObject.Appointment", "Appointment") + .WithMany("SuggestedDates") + .HasForeignKey("AppointmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("suggesteddate_appointmentid_fkey"); + + b.HasOne("Dataport.Terminfinder.BusinessObject.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("suggesteddate_customerid_fkey"); + + b.Navigation("Appointment"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.Voting", b => + { + b.HasOne("Dataport.Terminfinder.BusinessObject.Appointment", "Appointment") + .WithMany() + .HasForeignKey("AppointmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("voting_appointmentid_fkey"); + + b.HasOne("Dataport.Terminfinder.BusinessObject.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("voting_customerid_fkey"); + + b.HasOne("Dataport.Terminfinder.BusinessObject.Participant", "Participant") + .WithMany("Votings") + .HasForeignKey("ParticipantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("voting_participantid_fkey"); + + b.HasOne("Dataport.Terminfinder.BusinessObject.SuggestedDate", "SuggestedDate") + .WithMany("Votings") + .HasForeignKey("SuggestedDateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("voting_suggesteddateid_fkey"); + + b.Navigation("Appointment"); + + b.Navigation("Customer"); + + b.Navigation("Participant"); + + b.Navigation("SuggestedDate"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.Appointment", b => + { + b.Navigation("Participants"); + + b.Navigation("SuggestedDates"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.Participant", b => + { + b.Navigation("Votings"); + }); + + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.SuggestedDate", b => + { + b.Navigation("Votings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Dataport.Terminfinder.Repository/Migrations/20260909113548_AddStatistics.cs b/src/Dataport.Terminfinder.Repository/Migrations/20260909113548_AddStatistics.cs new file mode 100644 index 0000000..d94c633 --- /dev/null +++ b/src/Dataport.Terminfinder.Repository/Migrations/20260909113548_AddStatistics.cs @@ -0,0 +1,111 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Dataport.Terminfinder.Repository.Migrations +{ + /// + [ExcludeFromCodeCoverage] + public partial class AddStatistics : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + var sqlInhalt = "update public.appconfig set configvalue='1.4.0' WHERE configkey='version';"; + migrationBuilder.Sql(sqlInhalt, suppressTransaction: true); + + sqlInhalt = "update public.appconfig set configvalue='2026-09-09' WHERE configkey='builddate';"; + migrationBuilder.Sql(sqlInhalt, suppressTransaction: true); + + migrationBuilder.CreateTable( + name: "appointmentStatistic", + schema: "public", + columns: table => new + { + appointmentStatisticId = table.Column(type: "uuid", nullable: false), + customerId = table.Column(type: "uuid", nullable: false), + yearMonth = table.Column(type: "date", nullable: false), + count = table.Column(type: "integer", nullable: false, defaultValue: 1) + }, + constraints: table => + { + table.PrimaryKey("PK_appointmentStatistic", x => x.appointmentStatisticId); + }); + + migrationBuilder.CreateTable( + name: "participantStatistic", + schema: "public", + columns: table => new + { + participantStatisticId = table.Column(type: "uuid", nullable: false), + customerId = table.Column(type: "uuid", nullable: false), + yearMonth = table.Column(type: "date", nullable: false), + count = table.Column(type: "integer", nullable: false, defaultValue: 1) + }, + constraints: table => + { + table.PrimaryKey("PK_participantStatistic", x => x.participantStatisticId); + }); + + migrationBuilder.CreateTable( + name: "votingStatistic", + schema: "public", + columns: table => new + { + votingStatisticId = table.Column(type: "uuid", nullable: false), + customerId = table.Column(type: "uuid", nullable: false), + yearMonth = table.Column(type: "date", nullable: false), + count = table.Column(type: "integer", nullable: false, defaultValue: 1) + }, + constraints: table => + { + table.PrimaryKey("PK_votingStatistic", x => x.votingStatisticId); + }); + + migrationBuilder.CreateIndex( + name: "IX_appointmentStatistic_customerId_yearMonth", + schema: "public", + table: "appointmentStatistic", + columns: new[] { "customerId", "yearMonth" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_participantStatistic_customerId_yearMonth", + schema: "public", + table: "participantStatistic", + columns: new[] { "customerId", "yearMonth" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_votingStatistic_customerId_yearMonth", + schema: "public", + table: "votingStatistic", + columns: new[] { "customerId", "yearMonth" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + var sqlInhalt = "update public.appconfig set configvalue='1.3.0' WHERE configkey='version';"; + migrationBuilder.Sql(sqlInhalt, suppressTransaction: true); + + sqlInhalt = "update public.appconfig set configvalue='2025-11-06' WHERE configkey='builddate';"; + migrationBuilder.Sql(sqlInhalt, suppressTransaction: true); + + migrationBuilder.DropTable( + name: "appointmentStatistic", + schema: "public"); + + migrationBuilder.DropTable( + name: "participantStatistic", + schema: "public"); + + migrationBuilder.DropTable( + name: "votingStatistic", + schema: "public"); + } + } +} diff --git a/src/Dataport.Terminfinder.Repository/Migrations/DataContextModelSnapshot.cs b/src/Dataport.Terminfinder.Repository/Migrations/DataContextModelSnapshot.cs index 28a8204..93a57de 100644 --- a/src/Dataport.Terminfinder.Repository/Migrations/DataContextModelSnapshot.cs +++ b/src/Dataport.Terminfinder.Repository/Migrations/DataContextModelSnapshot.cs @@ -19,7 +19,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("ProductVersion", "10.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -110,6 +110,35 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("appointment", "public"); }); + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.AppointmentStatistic", b => + { + b.Property("AppointmentStatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("appointmentStatisticId"); + + b.Property("Count") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1) + .HasColumnName("count"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customerId"); + + b.Property("YearMonth") + .HasColumnType("date") + .HasColumnName("yearMonth"); + + b.HasKey("AppointmentStatisticId"); + + b.HasIndex("CustomerId", "YearMonth") + .IsUnique(); + + b.ToTable("appointmentStatistic", "public"); + }); + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.Customer", b => { b.Property("CustomerId") @@ -180,6 +209,35 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("participant", "public"); }); + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.ParticipantStatistic", b => + { + b.Property("ParticipantStatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("participantStatisticId"); + + b.Property("Count") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1) + .HasColumnName("count"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customerId"); + + b.Property("YearMonth") + .HasColumnType("date") + .HasColumnName("yearMonth"); + + b.HasKey("ParticipantStatisticId"); + + b.HasIndex("CustomerId", "YearMonth") + .IsUnique(); + + b.ToTable("participantStatistic", "public"); + }); + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.SuggestedDate", b => { b.Property("SuggestedDateId") @@ -293,6 +351,35 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("voting", "public"); }); + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.VotingStatistic", b => + { + b.Property("VotingStatisticId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("votingStatisticId"); + + b.Property("Count") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1) + .HasColumnName("count"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customerId"); + + b.Property("YearMonth") + .HasColumnType("date") + .HasColumnName("yearMonth"); + + b.HasKey("VotingStatisticId"); + + b.HasIndex("CustomerId", "YearMonth") + .IsUnique(); + + b.ToTable("votingStatistic", "public"); + }); + modelBuilder.Entity("Dataport.Terminfinder.BusinessObject.Appointment", b => { b.HasOne("Dataport.Terminfinder.BusinessObject.Customer", "Customer") diff --git a/src/Dataport.Terminfinder.Repository/StatisticRepository.cs b/src/Dataport.Terminfinder.Repository/StatisticRepository.cs new file mode 100644 index 0000000..f8fe522 --- /dev/null +++ b/src/Dataport.Terminfinder.Repository/StatisticRepository.cs @@ -0,0 +1,143 @@ +using Dataport.Terminfinder.BusinessObject; + +namespace Dataport.Terminfinder.Repository; + +/// +public class StatisticRepository + : RepositoryBase, IStatisticRepository +{ + private readonly ILogger _logger; + private readonly DataContext _ctx; + + /// + public StatisticRepository(DataContext ctx, ILogger logger) : base(ctx) + { + _ctx = ctx; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + public void IncrementAppointmentStatisticCount(Guid customerId) + { + var yearMonth = GetCurrentYearMonth(); + _logger.LogDebug( + "Incrementing appointment statistic count for customer {CustomerId} and yearMonth {YearMonth}", + customerId, + yearMonth + ); + + using var transaction = BeginTransaction(); + + var appointmentStatistic = _ctx.AppointmentStatistics + .FirstOrDefault(s => s.CustomerId == customerId && s.YearMonth == yearMonth); + + if (appointmentStatistic == null) + { + _logger.LogDebug( + "No appointment statistic found for customer {CustomerId} and yearMonth {YearMonth}, creating new statistic", + customerId, + yearMonth + ); + + New(Context.AppointmentStatistics, new AppointmentStatistic + { + CustomerId = customerId, YearMonth = yearMonth, Count = 1 + }); + } + else + { + appointmentStatistic.Count++; + Update(Context.AppointmentStatistics, appointmentStatistic); + } + + Save(); + transaction.Commit(); + } + + /// + public void IncrementParticipantStatisticCount(Guid customerId) + { + var yearMonth = GetCurrentYearMonth(); + _logger.LogDebug( + "Incrementing participant statistic count for customer {CustomerId} and yearMonth {YearMonth}", + customerId, + yearMonth + ); + + using var transaction = BeginTransaction(); + + var participantStatistic = _ctx.ParticipantStatistics + .FirstOrDefault(s => s.CustomerId == customerId && s.YearMonth == yearMonth); + + if (participantStatistic == null) + { + _logger.LogDebug( + "No participant statistic found for customer {CustomerId} and yearMonth {YearMonth}, creating new statistic", + customerId, + yearMonth + ); + + New(Context.ParticipantStatistics, new ParticipantStatistic + { + CustomerId = customerId, YearMonth = yearMonth, Count = 1 + }); + } + else + { + participantStatistic.Count++; + Update(Context.ParticipantStatistics, participantStatistic); + } + + Save(); + transaction.Commit(); + } + + /// + public void IncrementVotingStatisticCount(Guid customerId, int count = 1) + { + if (count <= 0) + { + return; + } + + var yearMonth = GetCurrentYearMonth(); + _logger.LogDebug( + "Incrementing voting statistic count for customer {CustomerId} and yearMonth {YearMonth}", + customerId, + yearMonth + ); + + using var transaction = BeginTransaction(); + + var votingStatistic = _ctx.VotingStatistics + .FirstOrDefault(s => s.CustomerId == customerId && s.YearMonth == yearMonth); + + if (votingStatistic == null) + { + _logger.LogDebug( + "No voting statistic found for customer {CustomerId} and yearMonth {YearMonth}, creating new statistic", + customerId, + yearMonth + ); + + New(Context.VotingStatistics, new VotingStatistic + { + CustomerId = customerId, YearMonth = yearMonth, Count = count + }); + } + else + { + votingStatistic.Count += count; + Update(Context.VotingStatistics, votingStatistic); + } + + Save(); + transaction.Commit(); + } + + private static DateOnly GetCurrentYearMonth() + { + var now = DateTime.UtcNow; + return new DateOnly(now.Year, now.Month, 1); + } +} \ No newline at end of file diff --git a/src/Dataport.Terminfinder.WebAPI.Tests/Controllers/AdminControllerTests.cs b/src/Dataport.Terminfinder.WebAPI.Tests/Controllers/AdminControllerTests.cs index 4bab1b6..220101c 100644 --- a/src/Dataport.Terminfinder.WebAPI.Tests/Controllers/AdminControllerTests.cs +++ b/src/Dataport.Terminfinder.WebAPI.Tests/Controllers/AdminControllerTests.cs @@ -203,10 +203,7 @@ public void GetProtection_appointmentExistsAndAppointmentIsNotProtectedByPasswor [TestMethod] public void GetProtection_GuidsAreInvalid_ThrowsException() { - var mockRequestContext = new Mock(); - var mockBusinessLayer = new Mock(); - - var sut = CreateSut(mockBusinessLayer.Object, mockRequestContext.Object); + var sut = CreateSut(); var exceptionCustomerId = Assert.ThrowsExactly(() => sut.GetProtection(ExpectedInvalidGuidString, ExpectedAdminId.ToString())); @@ -397,10 +394,7 @@ public void GetPasswordVerification_AppointmentNotProtected_True() [TestMethod] public void GetPasswordVerification_GuidsInvalid_ThrowsException() { - var mockRequestContext = new Mock(); - var mockBusinessLayer = new Mock(); - - var sut = CreateSut(mockBusinessLayer.Object, mockRequestContext.Object); + var sut = CreateSut(); var exception = Assert.ThrowsExactly(() => sut.GetPasswordVerification(ExpectedInvalidGuidString, ExpectedInvalidGuidString)); diff --git a/src/Dataport.Terminfinder.WebAPI.Tests/Controllers/ParticipantControllerTests.cs b/src/Dataport.Terminfinder.WebAPI.Tests/Controllers/ParticipantControllerTests.cs index 3580ec4..00e8cdc 100644 --- a/src/Dataport.Terminfinder.WebAPI.Tests/Controllers/ParticipantControllerTests.cs +++ b/src/Dataport.Terminfinder.WebAPI.Tests/Controllers/ParticipantControllerTests.cs @@ -137,8 +137,7 @@ public void DeleteParticipants_AppointmentIsPasswordProtectedNoPasswordSubmitted [TestMethod] public void DeleteParticipants_GuidsAreInvalid_ThrowsException() { - var mockBusinessLayer = new Mock(); - var sut = CreateSut(mockBusinessLayer.Object); + var sut = CreateSut(); var exceptionCustomerId = Assert.ThrowsExactly(() => sut.Delete(ExpectedInvalidGuidString, ExpectedAppointmentId.ToString(), ExpectedParticipantId.ToString())); diff --git a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/AdminControllerIntegrationTests.cs b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/AdminControllerIntegrationTests.cs index 5d85426..5b4432c 100644 --- a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/AdminControllerIntegrationTests.cs +++ b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/AdminControllerIntegrationTests.cs @@ -9,7 +9,6 @@ public class AdminControllerIntegrationTests : BaseIntegrationTests { private TestServer _testServer; private IHost _host; - private static readonly Guid ExpectedCustomerId = new("E1E81104-3944-4588-A48E-B64BDE473E1A"); [TestInitialize] public async Task Initialize() @@ -27,6 +26,8 @@ public async Task Initialize() .Build(); await _host.StartAsync(); _testServer = _host.GetTestServer(); + + EnsureCustomerExists(_host); } [TestCleanup] diff --git a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/AppControllerIntegrationTests.cs b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/AppControllerIntegrationTests.cs index ab68fa3..4108ef2 100644 --- a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/AppControllerIntegrationTests.cs +++ b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/AppControllerIntegrationTests.cs @@ -1,3 +1,5 @@ +using Dataport.Terminfinder.Repository; + namespace Dataport.Terminfinder.WebAPI.Tests.IntegrationTests; [TestClass] @@ -23,6 +25,8 @@ public async Task Initialize() .Build(); await _host.StartAsync(); _testServer = _host.GetTestServer(); + + EnsureCustomerExists(_host); } [TestCleanup] @@ -39,11 +43,18 @@ public async Task Cleanup() [TestMethod] public async Task GetAppInfo_Okay() { - var expectedAppInfo = new AppInfo + var expectedAppInfo = new AppInfo(); + + using (var scope = _host.Services.CreateScope()) { - BuildDate = "2025-07-04", - VersionNumber = "1.2.2" - }; + var dataContext = scope.ServiceProvider.GetRequiredService(); + expectedAppInfo.VersionNumber = dataContext.AppConfig + .Single(x => x.Key == "version") + .Value; + expectedAppInfo.BuildDate = dataContext.AppConfig + .Single(x => x.Key == "builddate") + .Value; + } var client = _testServer.CreateClient(); diff --git a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/AppointmentControllerIntegrationTests.cs b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/AppointmentControllerIntegrationTests.cs index fc4f228..bdb40cf 100644 --- a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/AppointmentControllerIntegrationTests.cs +++ b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/AppointmentControllerIntegrationTests.cs @@ -1,4 +1,5 @@ -using Dataport.Terminfinder.WebAPI.Constants; +using Dataport.Terminfinder.Repository; +using Dataport.Terminfinder.WebAPI.Constants; using System.Net.Http.Headers; using System.Text; @@ -10,7 +11,8 @@ public class AppointmentControllerIntegrationTests : BaseIntegrationTests { private TestServer _testServer; private IHost _host; - private static readonly Guid ExpectedCustomerId = new("E1E81104-3944-4588-A48E-B64BDE473E1A"); + + private static readonly DateOnly ExpectedCurrentMonth = new(2026, 9, 1); [TestInitialize] public async Task Initialize() @@ -28,6 +30,8 @@ public async Task Initialize() .Build(); await _host.StartAsync(); _testServer = _host.GetTestServer(); + + EnsureCustomerExists(_host); } [TestCleanup] @@ -45,8 +49,19 @@ public async Task Cleanup() public async Task AddAppointment() { var appointment = CreateTestAppointment(ExpectedCustomerId, Guid.Empty); - var client = _testServer.CreateClient(); + ResetStatistics(); + + // assert statistics before + using (var statsBeforeScope = _host.Services.CreateScope()) + { + var statsBeforeContext = statsBeforeScope.ServiceProvider.GetRequiredService(); + var appointmentStatisticsBefore = statsBeforeContext.AppointmentStatistics + .Where(x => x.CustomerId == ExpectedCustomerId && x.YearMonth == ExpectedCurrentMonth) + .ToList(); + + Assert.IsEmpty(appointmentStatisticsBefore); + } // Act var content = new StringContent(JsonConvert.SerializeObject(appointment), Encoding.UTF8, HttpConstants.TerminfinderMediaTypeJsonV1); @@ -62,8 +77,19 @@ public async Task AddAppointment() Assert.IsNotNull(dto); Assert.IsInstanceOfType(dto, typeof(Appointment)); + // assert statistics after + using (var statsAfterScope = _host.Services.CreateScope()) + { + var statsAfterContext = statsAfterScope.ServiceProvider.GetRequiredService(); + var appointmentStatisticsAfter = statsAfterContext.AppointmentStatistics + .Where(x => x.CustomerId == ExpectedCustomerId && x.YearMonth == ExpectedCurrentMonth) + .ToList(); + + Assert.HasCount(1, appointmentStatisticsAfter); + } + //--- get the appointment - var appointmentId= dto.AppointmentId; + var appointmentId = dto.AppointmentId; // Act response = await client.GetAsync($"appointment/{ExpectedCustomerId}/{appointmentId}"); @@ -88,7 +114,6 @@ public async Task AddAppointment() var expectedSuggestedDates = appointment.SuggestedDates.OrderBy(s => s.StartDate).ToList(); Assert.AreEqual(expectedSuggestedDates[0].StartDate.Date, responseSuggestedDates[0].StartDate.Date); - Assert.AreEqual(((DateTime)expectedSuggestedDates[0].EndDate!).Date, ((DateTime)responseSuggestedDates[0].EndDate!).Date); Assert.IsNull(responseSuggestedDates[0].StartTime); Assert.IsNull(responseSuggestedDates[0].EndTime); @@ -140,8 +165,8 @@ public async Task AddAppointment() public async Task UpdateAppointment() { var appointment = CreateTestAppointment(ExpectedCustomerId, Guid.Empty); - var client = _testServer.CreateClient(); + ResetStatistics(); // Act var content = new StringContent(JsonConvert.SerializeObject(appointment), Encoding.UTF8, HttpConstants.TerminfinderMediaTypeJsonV1); @@ -257,6 +282,82 @@ public async Task UpdateAppointment() Assert.AreEqual(timeExpectedUtf.Minute, timeResponseUtf.Minute); Assert.AreEqual(((DateTime)expectedSuggestedDates[1].EndDate).Date, ((DateTime)responseSuggestedDates[1].EndDate).Date); + // assert statistics before + int participantStatisticBefore; + int votingStatisticBefore; + + using (var statsBeforeScope = _host.Services.CreateScope()) + { + var statsBeforeContext = statsBeforeScope.ServiceProvider.GetRequiredService(); + participantStatisticBefore = statsBeforeContext.ParticipantStatistics + .Where(x => x.CustomerId == ExpectedCustomerId && x.YearMonth == ExpectedCurrentMonth) + .Sum(x => x.Count); + votingStatisticBefore = statsBeforeContext.VotingStatistics + .Where(x => x.CustomerId == ExpectedCustomerId && x.YearMonth == ExpectedCurrentMonth) + .Sum(x => x.Count); + } + + Assert.AreEqual(0, participantStatisticBefore); + Assert.AreEqual(0, votingStatisticBefore); + + var suggestedDate = dto3.SuggestedDates.First(); + var participants = new[] + { + new Participant + { + AppointmentId = appointmentId, + CustomerId = ExpectedCustomerId, + ParticipantId = Guid.Empty, + Name = "Participant", + Votings = new List + { + new() + { + VotingId = Guid.Empty, + CustomerId = ExpectedCustomerId, + AppointmentId = appointmentId, + ParticipantId = Guid.Empty, + SuggestedDateId = suggestedDate.SuggestedDateId, + Status = VotingStatusType.Accepted + }, + new() + { + VotingId = Guid.Empty, + CustomerId = ExpectedCustomerId, + AppointmentId = appointmentId, + ParticipantId = Guid.Empty, + SuggestedDateId = suggestedDate.SuggestedDateId, + Status = VotingStatusType.Declined + } + } + } + }; + + var statsContent = new StringContent(JsonConvert.SerializeObject(participants), Encoding.UTF8, HttpConstants.TerminfinderMediaTypeJsonV1); + var statsResponse = await client.PutAsync($"votings/{ExpectedCustomerId}/{appointmentId}", statsContent); + statsResponse.EnsureSuccessStatusCode(); + + var expectedParticipantStatisticAfter = participants.Count(p => p.ParticipantId == Guid.Empty); + var expectedVotingStatisticAfter = participants.Sum(p => p.Votings.Count(v => v.VotingId == Guid.Empty)); + + // assert statistics after + int participantStatisticAfter; + int votingStatisticAfter; + + using (var statsAfterScope = _host.Services.CreateScope()) + { + var statsAfterContext = statsAfterScope.ServiceProvider.GetRequiredService(); + participantStatisticAfter = statsAfterContext.ParticipantStatistics + .Where(x => x.CustomerId == ExpectedCustomerId && x.YearMonth == ExpectedCurrentMonth) + .Sum(x => x.Count); + votingStatisticAfter = statsAfterContext.VotingStatistics + .Where(x => x.CustomerId == ExpectedCustomerId && x.YearMonth == ExpectedCurrentMonth) + .Sum(x => x.Count); + } + + Assert.AreEqual(expectedParticipantStatisticAfter, participantStatisticAfter); + Assert.AreEqual(expectedVotingStatisticAfter, votingStatisticAfter); + Assert.AreEqual(expectedSuggestedDates[2].StartDate.Date, responseSuggestedDates[2].StartDate.Date); Assert.AreEqual(((DateTime)expectedSuggestedDates[2].EndDate!).Date, ((DateTime)responseSuggestedDates[2].EndDate!).Date); Assert.IsNull(responseSuggestedDates[2].StartTime); @@ -634,4 +735,36 @@ public async Task GetPasswordVerification() Assert.IsTrue(passwordVerificationResult.IsProtectedByPassword); Assert.IsFalse(passwordVerificationResult.IsPasswordValid); } + + private void ResetStatistics() + { + using var scope = _host.Services.CreateScope(); + var dataContext = scope.ServiceProvider.GetRequiredService(); + + var existingAppointmentStatistics = dataContext.AppointmentStatistics + .Where(x => x.CustomerId == ExpectedCustomerId && x.YearMonth == ExpectedCurrentMonth) + .ToList(); + if (existingAppointmentStatistics.Count != 0) + { + dataContext.AppointmentStatistics.RemoveRange(existingAppointmentStatistics); + } + + var existingParticipantStatistics = dataContext.ParticipantStatistics + .Where(x => x.CustomerId == ExpectedCustomerId && x.YearMonth == ExpectedCurrentMonth) + .ToList(); + if (existingParticipantStatistics.Count != 0) + { + dataContext.ParticipantStatistics.RemoveRange(existingParticipantStatistics); + } + + var existingVotingStatistics = dataContext.VotingStatistics + .Where(x => x.CustomerId == ExpectedCustomerId && x.YearMonth == ExpectedCurrentMonth) + .ToList(); + if (existingVotingStatistics.Count != 0) + { + dataContext.VotingStatistics.RemoveRange(existingVotingStatistics); + } + + dataContext.SaveChanges(); + } } \ No newline at end of file diff --git a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/BaseIntegrationTests.cs b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/BaseIntegrationTests.cs index 30dc7d3..df9bcfb 100644 --- a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/BaseIntegrationTests.cs +++ b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/BaseIntegrationTests.cs @@ -1,4 +1,5 @@ -using Dataport.Terminfinder.WebAPI.Constants; +using Dataport.Terminfinder.Repository; +using Dataport.Terminfinder.WebAPI.Constants; using System.Text; namespace Dataport.Terminfinder.WebAPI.Tests.IntegrationTests; @@ -6,6 +7,10 @@ namespace Dataport.Terminfinder.WebAPI.Tests.IntegrationTests; [ExcludeFromCodeCoverage] public abstract class BaseIntegrationTests { + protected static readonly Guid ExpectedCustomerId = new("E1E81104-3944-4588-A48E-B64BDE473E1A"); + protected const string ExpectedCustomerName = "Test"; + protected const string ExpectedCustomerStatus = nameof(AppointmentStatusType.Started); + /// /// ConfigurationBuilder for integrationstest /// @@ -19,6 +24,30 @@ protected static IConfiguration GetConfigurationBuilder() return config; } + protected static void EnsureCustomerExists(IHost host) + { + using var scope = host.Services.CreateScope(); + var dataContext = scope.ServiceProvider.GetRequiredService(); + + var existingCustomer = dataContext.Customers.SingleOrDefault(x => x.CustomerId == ExpectedCustomerId); + if (existingCustomer == null) + { + dataContext.Customers.Add(new Customer + { + CustomerId = ExpectedCustomerId, + CustomerName = ExpectedCustomerName, + Status = ExpectedCustomerStatus + }); + } + else + { + existingCustomer.CustomerName = ExpectedCustomerName; + existingCustomer.Status = ExpectedCustomerStatus; + } + + dataContext.SaveChanges(); + } + protected static Appointment CreateTestAppointment(Guid customerId, Guid adminId, string password = null) { var appointment = new Appointment diff --git a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/CustomerControllerIntegrationTests.cs b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/CustomerControllerIntegrationTests.cs index 21352f1..fc4c6f1 100644 --- a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/CustomerControllerIntegrationTests.cs +++ b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/CustomerControllerIntegrationTests.cs @@ -6,7 +6,6 @@ public class CustomerControllerIntegrationTests : BaseIntegrationTests { private TestServer _testServer; private IHost _host; - private static readonly Guid ExpectedCustomerId = new("E1E81104-3944-4588-A48E-B64BDE473E1A"); [TestInitialize] public async Task Initialize() @@ -24,6 +23,8 @@ public async Task Initialize() .Build(); await _host.StartAsync(); _testServer = _host.GetTestServer(); + + EnsureCustomerExists(_host); } [TestCleanup] diff --git a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/ParticipantControllerIntegrationTests.cs b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/ParticipantControllerIntegrationTests.cs index 5abe61d..b896851 100644 --- a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/ParticipantControllerIntegrationTests.cs +++ b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/ParticipantControllerIntegrationTests.cs @@ -10,7 +10,6 @@ public class ParticipantControllerIntegrationTests : BaseIntegrationTests { private TestServer _testServer; private IHost _host; - private static readonly Guid ExpectedCustomerId = new("E1E81104-3944-4588-A48E-B64BDE473E1A"); [TestInitialize] public async Task Initialize() @@ -28,6 +27,8 @@ public async Task Initialize() .Build(); await _host.StartAsync(); _testServer = _host.GetTestServer(); + + EnsureCustomerExists(_host); } [TestCleanup] diff --git a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/SuggestedDateControllerIntegrationTests.cs b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/SuggestedDateControllerIntegrationTests.cs index e3bbdc8..0d655a0 100644 --- a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/SuggestedDateControllerIntegrationTests.cs +++ b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/SuggestedDateControllerIntegrationTests.cs @@ -6,7 +6,6 @@ public class SuggestedDateControllerIntegrationTests : BaseIntegrationTests { private TestServer _testServer; private IHost _host; - private static readonly Guid ExpectedCustomerId = new("E1E81104-3944-4588-A48E-B64BDE473E1A"); [TestInitialize] public async Task Initialize() @@ -24,6 +23,8 @@ public async Task Initialize() .Build(); await _host.StartAsync(); _testServer = _host.GetTestServer(); + + EnsureCustomerExists(_host); } [TestCleanup] diff --git a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/VotingControllerIntegrationTests.cs b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/VotingControllerIntegrationTests.cs index c5b85e6..56fef0d 100644 --- a/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/VotingControllerIntegrationTests.cs +++ b/src/Dataport.Terminfinder.WebAPI.Tests/IntegrationTests/VotingControllerIntegrationTests.cs @@ -9,7 +9,6 @@ public class VotingControllerIntegrationTests : BaseIntegrationTests { private TestServer _testServer; private IHost _host; - private static readonly Guid ExpectedCustomerId = new("E1E81104-3944-4588-A48E-B64BDE473E1A"); [TestInitialize] public async Task Initialize() @@ -27,6 +26,8 @@ public async Task Initialize() .Build(); await _host.StartAsync(); _testServer = _host.GetTestServer(); + + EnsureCustomerExists(_host); } [TestCleanup] diff --git a/src/Dataport.Terminfinder.WebAPI.Tests/readme.md b/src/Dataport.Terminfinder.WebAPI.Tests/readme.md deleted file mode 100644 index 29dbcc7..0000000 --- a/src/Dataport.Terminfinder.WebAPI.Tests/readme.md +++ /dev/null @@ -1,6 +0,0 @@ -# Terminfinder Integrationtests -To run the integrationtests, first you have to create an new customer with the following customerId: -``` -insert into public.customer -select 'E1E81104-3944-4588-A48E-B64BDE473E1A', 'Test', 'Started' -``` diff --git a/src/Dataport.Terminfinder.WebAPI/.config/dotnet-tools.json b/src/Dataport.Terminfinder.WebAPI/.config/dotnet-tools.json index 5112d4b..5c1165b 100644 --- a/src/Dataport.Terminfinder.WebAPI/.config/dotnet-tools.json +++ b/src/Dataport.Terminfinder.WebAPI/.config/dotnet-tools.json @@ -3,10 +3,11 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "3.1.8", + "version": "10.0.12", "commands": [ "dotnet-ef" - ] + ], + "rollForward": false } } } \ No newline at end of file diff --git a/src/Dataport.Terminfinder.WebAPI/Startup.cs b/src/Dataport.Terminfinder.WebAPI/Startup.cs index e829997..1d335fc 100644 --- a/src/Dataport.Terminfinder.WebAPI/Startup.cs +++ b/src/Dataport.Terminfinder.WebAPI/Startup.cs @@ -80,6 +80,7 @@ public void ConfigureServices(IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient();