diff --git a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs new file mode 100644 index 00000000..2de8a0bc --- /dev/null +++ b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceScheduleItemRequestTests.cs @@ -0,0 +1,77 @@ +using Moq; +using TutorLizard.BusinessLogic.Models; +using TutorLizard.BusinessLogic.Models.DTOs.Requests; +using TutorLizard.BusinessLogic.Services; + +namespace TutorLizard.BusinessLogic.Tests.Services.Student +{ + public class StudentServiceScheduleItemRequestTests : StudentServiceTestBase + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CreateScheduleItemRequest_ShouldSetIsRemoteCorrectly(bool isRemote) + { + // Arrange + var ad = new Ad + { + Description = "Test Description", + Location = "Test Location", + Subject = "Test Subject", + Title = "Test Title", + TutorId = 2 + }; + var scheduleItem = new ScheduleItem { Id = 1, Ad = ad }; + + SetupMockGetScheduleItemById(scheduleItem); + SetupMockGetAllScheduleItems(new List { scheduleItem }); + + var request = new CreateScheduleItemRequestRequest + { + StudentId = 3, + ScheduleItemId = 1, + IsRemote = isRemote + }; + + MockScheduleItemRequestRepository + .Setup(x => x.Create(It.Is(req => req.IsRemote == isRemote))) + .Returns((ScheduleItemRequest req) => Task.FromResult(req)) + .Verifiable(Times.Once); + + // Act + await StudentService.CreateScheduleItemRequest(request); + + // Assert + MockScheduleItemRequestRepository.VerifyAll(); + } + + [Fact] + public async Task CreateScheduleItemRequest_WhenRequestSent_ShouldReturnSuccess() + { + // Arrange + var scheduleItem = new ScheduleItem { Id = 1 }; + var studentId = 1; + var isRemote = true; + + SetupMockGetScheduleItemById(scheduleItem); + + var request = new CreateScheduleItemRequestRequest + { + StudentId = studentId, + ScheduleItemId = scheduleItem.Id, + IsRemote = isRemote + }; + + // Act + var response = await StudentService.CreateScheduleItemRequest(request); + + // Assert + Assert.True(response.Success); + } + + + + + + } +} diff --git a/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs new file mode 100644 index 00000000..98408524 --- /dev/null +++ b/Tests/TutorLizard.BusinessLogic.Tests/Services/Student/StudentServiceTestBase.cs @@ -0,0 +1,55 @@ +using AutoFixture; +using Moq; +using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; +using TutorLizard.BusinessLogic.Models; +using TutorLizard.BusinessLogic.Services; + +namespace TutorLizard.BusinessLogic.Tests.Services.Student +{ + public class StudentServiceTestBase : TestsWithInMemoryDbBase + { + protected StudentService StudentService; + protected Fixture Fixture = new(); + protected Mock> MockAdRepository = new(); + protected Mock> MockAdRequestRepository = new(); + protected Mock> MockScheduleItemRepository = new(); + protected Mock> MockScheduleItemRequestRepository = new(); + + + protected StudentServiceTestBase() : base() + { + StudentService = new StudentService(MockAdRepository.Object, + MockAdRequestRepository.Object, + MockScheduleItemRepository.Object, + MockScheduleItemRequestRepository.Object); + } + + protected void SetupMockGetScheduleItemById(ScheduleItem? scheduleItem) + { + MockScheduleItemRepository + .Setup(x => x.GetById(It.IsAny())) + .Returns(Task.FromResult(scheduleItem)); + } + + protected void SetupMockGetAllScheduleItems(List scheduleItems) + { + var scheduleItemsInDb = AddEntitiesToInMemoryDb(scheduleItems); + MockScheduleItemRepository + .Setup(x => x.GetAll()) + .Returns(scheduleItemsInDb); + } + + protected IQueryable AddEntitiesToInMemoryDb(List entities) + where TEntity : class + { + DbContext + .Set() + .AddRange(entities); + DbContext.SaveChanges(); + + return DbContext + .Set() + .AsQueryable(); + } + } +} diff --git a/TutorLizard.BusinessLogic/Models/DTOs/Requests/CreateScheduleItemRequestRequest.cs b/TutorLizard.BusinessLogic/Models/DTOs/Requests/CreateScheduleItemRequestRequest.cs index 39784bc7..443fb9cd 100644 --- a/TutorLizard.BusinessLogic/Models/DTOs/Requests/CreateScheduleItemRequestRequest.cs +++ b/TutorLizard.BusinessLogic/Models/DTOs/Requests/CreateScheduleItemRequestRequest.cs @@ -10,5 +10,6 @@ public class CreateScheduleItemRequestRequest { public int StudentId { get; set; } public int ScheduleItemId { get; set; } + public bool IsRemote { get; set; } } } diff --git a/TutorLizard.BusinessLogic/Models/DTOs/Responses/AvailableScheduleForAdResponse.cs b/TutorLizard.BusinessLogic/Models/DTOs/Responses/AvailableScheduleForAdResponse.cs index dc3ae1e9..f82d3b26 100644 --- a/TutorLizard.BusinessLogic/Models/DTOs/Responses/AvailableScheduleForAdResponse.cs +++ b/TutorLizard.BusinessLogic/Models/DTOs/Responses/AvailableScheduleForAdResponse.cs @@ -10,6 +10,7 @@ public class AvailableScheduleForAdResponse { public List Items { get; set; } = new(); public bool IsAccepted { get; set; } = true; + public bool IsRemote { get; set; } public int AdId { get; set; } } } diff --git a/TutorLizard.BusinessLogic/Models/DTOs/Responses/CreateScheduleItemRequestResponse.cs b/TutorLizard.BusinessLogic/Models/DTOs/Responses/CreateScheduleItemRequestResponse.cs index c66eeda4..060abe0c 100644 --- a/TutorLizard.BusinessLogic/Models/DTOs/Responses/CreateScheduleItemRequestResponse.cs +++ b/TutorLizard.BusinessLogic/Models/DTOs/Responses/CreateScheduleItemRequestResponse.cs @@ -10,5 +10,6 @@ public class CreateScheduleItemRequestResponse { public bool Success { get; set; } public int CreatedScheduleItemRequestId { get; set; } + public bool IsRemote { get; set; } } } diff --git a/TutorLizard.BusinessLogic/Services/StudentService.cs b/TutorLizard.BusinessLogic/Services/StudentService.cs index 21ed1c49..104f728d 100644 --- a/TutorLizard.BusinessLogic/Services/StudentService.cs +++ b/TutorLizard.BusinessLogic/Services/StudentService.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore; -using System.Runtime.InteropServices; using TutorLizard.BusinessLogic.Interfaces.Data.Repositories; using TutorLizard.BusinessLogic.Interfaces.Services; using TutorLizard.BusinessLogic.Models; @@ -25,6 +24,7 @@ public StudentService(IDbRepository adRepository, _scheduleItemRequestRepository = scheduleItemRequestRepository; } + #region Schedule public async Task CreateScheduleItemRequest(CreateScheduleItemRequestRequest request) { int studentId = request.StudentId; @@ -48,18 +48,57 @@ public async Task CreateScheduleItemRequest(C ScheduleItemId = scheduleItemId, DateCreated = DateTime.UtcNow, StudentId = studentId, - IsAccepted = false + IsAccepted = false, + IsRemote = request.IsRemote }; await _scheduleItemRequestRepository.Create(scheduleItemRequest); - return new CreateScheduleItemRequestResponse - { + + return new CreateScheduleItemRequestResponse + { Success = true, - CreatedScheduleItemRequestId = scheduleItemRequest.Id, + CreatedScheduleItemRequestId = scheduleItemRequest.Id + }; + } + + public async Task GetAvailableScheduleForAd(AvailableScheduleForAdRequest request) + { + List items = await _scheduleItemRepository.GetAll() + .Where(si => si.Ad.AdRequests.Any(ar => ar.StudentId == request.StudentId && ar.IsAccepted)) + .Select(si => new ScheduleItemDto() + { + AdId = si.AdId, + DateTime = si.DateTime, + Id = si.Id, + Status = si.ScheduleItemRequests.Any(sir => sir.StudentId == request.StudentId && sir.IsAccepted) ? ScheduleItemDto.ScheduleItemRequestStatus.Accepted + : si.ScheduleItemRequests.Any(sir => sir.StudentId == request.StudentId) ? ScheduleItemDto.ScheduleItemRequestStatus.Pending + : ScheduleItemDto.ScheduleItemRequestStatus.RequestNotSent + }) + .ToListAsync(); + + bool isAccepted = await _adRequestRepository.GetAll() + .Where(ar => ar.AdId == request.AdId) + .AnyAsync(ar => ar.StudentId == request.StudentId && ar.IsAccepted); + + bool isRemote = await _adRepository.GetAll() + .Where(ad => ad.Id == request.AdId) + .Select(ad => ad.IsRemote) + .FirstOrDefaultAsync(); + + AvailableScheduleForAdResponse response = new() + { + AdId = request.AdId, + IsAccepted = isAccepted, + IsRemote = isRemote, + Items = items }; + + return response; } + #endregion + #region Ads public async Task ViewAcceptedAds(StudentsAcceptedAdsRequest request) { var studentId = request.StudentId; @@ -142,7 +181,7 @@ public async Task ViewAdRequestStatus(AdRequestStatusRe .FirstOrDefaultAsync(); if (adRequestDetails is null) - return new AdRequestStatusResponse() { IsSuccessful = false }; + return new AdRequestStatusResponse() { IsSuccessful = false }; AdRequestStatusResponse response = new AdRequestStatusResponse() { @@ -233,32 +272,5 @@ public async Task CreateAdRequest(CreateAdRequestReques }; } - public async Task GetAvailableScheduleForAd(AvailableScheduleForAdRequest request) - { - List items = await _scheduleItemRepository.GetAll() - .Where(si => si.Ad.AdRequests.Any(ar => ar.StudentId == request.StudentId && ar.IsAccepted) && si.AdId == request.AdId) - .Select(si => new ScheduleItemDto() - { - AdId = si.AdId, - DateTime = si.DateTime, - Id = si.Id, - Status = si.ScheduleItemRequests.Any(sir => sir.StudentId == request.StudentId && sir.IsAccepted) ? ScheduleItemDto.ScheduleItemRequestStatus.Accepted - : si.ScheduleItemRequests.Any(sir => sir.StudentId == request.StudentId) ? ScheduleItemDto.ScheduleItemRequestStatus.Pending - : ScheduleItemDto.ScheduleItemRequestStatus.RequestNotSent - }) - .ToListAsync(); - - bool isAccepted = await _adRequestRepository.GetAll() - .Where(ar => ar.AdId == request.AdId) - .AnyAsync(ar => ar.StudentId == request.StudentId && ar.IsAccepted); - - AvailableScheduleForAdResponse response = new() - { - AdId = request.AdId, - IsAccepted = isAccepted, - Items = items - }; - - return response; - } + #endregion } diff --git a/TutorLizard.Web/Controllers/StudentController.cs b/TutorLizard.Web/Controllers/StudentController.cs index 8d44471e..6aa88282 100644 --- a/TutorLizard.Web/Controllers/StudentController.cs +++ b/TutorLizard.Web/Controllers/StudentController.cs @@ -77,7 +77,7 @@ public async Task AdRequests(IFormCollection buttonAction) [HttpPost] [ValidateAntiForgeryToken] - public async Task CreateScheduleItemRequest(int scheduleItemId, int adId) + public async Task CreateScheduleItemRequest(int scheduleItemId, int adId, bool isRemote) { int? studentId = _userAuthenticationService.GetLoggedInUserId(); @@ -89,7 +89,8 @@ public async Task CreateScheduleItemRequest(int scheduleItemId, i CreateScheduleItemRequestRequest request = new() { StudentId = (int)studentId, - ScheduleItemId = scheduleItemId + ScheduleItemId = scheduleItemId, + IsRemote = isRemote }; CreateScheduleItemRequestResponse response = await _studentService.CreateScheduleItemRequest(request); diff --git a/TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.Designer.cs b/TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.Designer.cs new file mode 100644 index 00000000..b2e7a1bd --- /dev/null +++ b/TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.Designer.cs @@ -0,0 +1,336 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TutorLizard.BusinessLogic.Data; + +#nullable disable + +namespace TutorLizard.Web.Migrations +{ + [DbContext(typeof(JaszczurContext))] + [Migration("20240602175824_Add-IsRemote-In-StudentService")] + partial class AddIsRemoteInStudentService + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(3000) + .HasColumnType("nvarchar(3000)"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Price") + .HasPrecision(7, 2) + .HasColumnType("decimal(7,2)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TutorId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("TutorId"); + + b.ToTable("Ads"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.AdRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReplyMessage") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ReviewDate") + .HasColumnType("datetime2"); + + b.Property("StudentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AdId"); + + b.HasIndex("StudentId"); + + b.ToTable("AdRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdId") + .HasColumnType("int"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AdId"); + + b.ToTable("ScheduleItems"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItemRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("IsRemote") + .HasColumnType("bit"); + + b.Property("ScheduleItemId") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ScheduleItemId"); + + b.HasIndex("StudentId"); + + b.ToTable("ScheduleItemRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UserType") + .HasColumnType("tinyint"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Category", "Category") + .WithMany("Ads") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("Ads") + .HasForeignKey("TutorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.AdRequest", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Ad", "Ad") + .WithMany("AdRequests") + .HasForeignKey("AdId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("AdRequests") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ad"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.Ad", "Ad") + .WithMany("ScheduleItems") + .HasForeignKey("AdId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ad"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItemRequest", b => + { + b.HasOne("TutorLizard.BusinessLogic.Models.ScheduleItem", "ScheduleItem") + .WithMany("ScheduleItemRequests") + .HasForeignKey("ScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TutorLizard.BusinessLogic.Models.User", "User") + .WithMany("ScheduleItemRequests") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ScheduleItem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Ad", b => + { + b.Navigation("AdRequests"); + + b.Navigation("ScheduleItems"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.Category", b => + { + b.Navigation("Ads"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.ScheduleItem", b => + { + b.Navigation("ScheduleItemRequests"); + }); + + modelBuilder.Entity("TutorLizard.BusinessLogic.Models.User", b => + { + b.Navigation("AdRequests"); + + b.Navigation("Ads"); + + b.Navigation("ScheduleItemRequests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.cs b/TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.cs new file mode 100644 index 00000000..a9856426 --- /dev/null +++ b/TutorLizard.Web/Migrations/20240602175824_Add-IsRemote-In-StudentService.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TutorLizard.Web.Migrations +{ + /// + public partial class AddIsRemoteInStudentService : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml b/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml index 7bb8378f..9ea08090 100644 --- a/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml +++ b/TutorLizard.Web/Views/Shared/Components/AvailableScheduleForAd/Default.cshtml @@ -29,15 +29,21 @@ { Nie wysłano
- @if (item.Status == ScheduleItemRequestStatus.RequestNotSent) - { -
- + + @if (Model.IsRemote) + { +
+ +
+ } + +
- }
}