diff --git a/exercise.tests/IntegrationTests/BaseIntegrationTest.cs b/exercise.tests/IntegrationTests/BaseIntegrationTest.cs index 9a949c4..b3135e1 100644 --- a/exercise.tests/IntegrationTests/BaseIntegrationTest.cs +++ b/exercise.tests/IntegrationTests/BaseIntegrationTest.cs @@ -56,22 +56,25 @@ FROM users u WHERE u.role = 'student' GROUP BY u.id, u.email, u.role, u.passwordhash; */ - protected const string TeacherEmail = "anna.gruber160@example.com"; // has post id 34,35 and comment id 37 - protected const string TeacherPassword = "Neidintulling!l33t"; - protected const int TeacherId = 160; + protected const string TeacherEmail = "reduan.presley47@example.com"; + protected const string TeacherPassword = "lettPassord123!"; + protected const string TeacherUsername = "reduan-presley47"; + protected const int TeacherId = 47; protected const int TeacherPostID = 34; protected const int TeacherCommentID = 37; - - protected const string StudentEmail1 = "jan.larsen9@example.com"; //id 232, has post id 57, 58 and comment id 2 - protected const string StudentPassword1 = "SuperHash!4"; - protected const int StudentId1 = 9; - protected const int StudentPostID1 = 57; - protected const int StudentCommentID1 = 2; - - protected const string StudentEmail2 = "timian.saar85@example.com"; //id 85, has post id 36 and comment id 3 - protected const string StudentPassword2 = "Neidintulling!l33t"; - protected const int StudentPostID2 = 36; - protected const int StudentCommentID2 = 3; + protected const int TeacherCohort = 37; + + protected const string StudentEmail1 = "james.olsen7@example.com"; + protected const string StudentPassword1 = "Timianerkul1!"; + protected const int StudentId1 = 7; + protected const int StudentPostID1 = 21; + protected const int StudentCommentID1 = 23; + + protected const string StudentEmail2 = "kate.delacruz54@example.com"; + protected const string StudentPassword2 = "lettPassord123!"; + protected const int StudentId2 = 54; + protected const int StudentPostID2 = 24; + protected const int StudentCommentID2 = 46; protected async Task LoginAndGetToken(string email, string password, bool success = true, bool longlife = false) diff --git a/exercise.tests/IntegrationTests/CohortTests.cs b/exercise.tests/IntegrationTests/CohortTests.cs index 88eeff0..621800b 100644 --- a/exercise.tests/IntegrationTests/CohortTests.cs +++ b/exercise.tests/IntegrationTests/CohortTests.cs @@ -269,8 +269,8 @@ public async Task CreateCohort(string email, string password, string cohortName, [TestCase(TeacherEmail, TeacherPassword, 350, 1, 1, "User with Id 350 not found.", HttpStatusCode.BadRequest)] [TestCase(TeacherEmail, TeacherPassword, 1, 20, 1, "Cohort with Id 20 not found.", HttpStatusCode.BadRequest)] [TestCase(TeacherEmail, TeacherPassword, 1, 1, 10, "The specified course is not part of this cohort.", HttpStatusCode.BadRequest)] - [TestCase(TeacherEmail, TeacherPassword, 11, 1, 1, "User is already in the specified course in the cohort.", HttpStatusCode.BadRequest)] - [TestCase(StudentEmail1, StudentPassword1, 1, 1, 1, "You are not authorized to add a user to a cohort.", HttpStatusCode.Forbidden)] + [TestCase(TeacherEmail, TeacherPassword, TeacherId, 3, 3, "User is already in the specified course in the cohort.", HttpStatusCode.BadRequest)] + [TestCase(StudentEmail1, StudentPassword1, 1, 3, 1, "You are not authorized to add a user to a cohort.", HttpStatusCode.Forbidden)] public async Task AddUserToCohortAndDeleteUser( string email, string password, @@ -392,9 +392,9 @@ public async Task AddUserToCohortAndDeleteUser( [TestCase(TeacherEmail, TeacherPassword, 350, 1, 1, "User with Id 350 not found.", HttpStatusCode.BadRequest)] [TestCase(TeacherEmail, TeacherPassword, 1, 20, 1, "Cohort with Id 20 not found.", HttpStatusCode.BadRequest)] - [TestCase(TeacherEmail, TeacherPassword, 11, 1, 10, "The specified course is not part of this cohort.", HttpStatusCode.BadRequest)] + [TestCase(TeacherEmail, TeacherPassword, 11, 3, 10, "The specified course is not part of this cohort.", HttpStatusCode.BadRequest)] [TestCase(TeacherEmail, TeacherPassword, 1, 1, 1, "The specified user is not part of this cohort.", HttpStatusCode.BadRequest)] - [TestCase(TeacherEmail, TeacherPassword, 11, 1, 2, "User is in cohort, but is not taking the specified course.", HttpStatusCode.BadRequest)] + [TestCase(TeacherEmail, TeacherPassword, 11, 3, 2, "User is in cohort, but is not taking the specified course.", HttpStatusCode.BadRequest)] [TestCase(StudentEmail1, StudentPassword1, 1, 1, 1, "You are not authorized to delete a user from a cohort.", HttpStatusCode.Forbidden)] public async Task InvalidDeletes( string email, diff --git a/exercise.tests/IntegrationTests/UserTests.cs b/exercise.tests/IntegrationTests/UserTests.cs index 50f92d5..7e2d7a4 100644 --- a/exercise.tests/IntegrationTests/UserTests.cs +++ b/exercise.tests/IntegrationTests/UserTests.cs @@ -308,7 +308,7 @@ public async Task UpdateUserUsernameExists() { var fieldsToUpdate = new Dictionary { - { "username", "nigel-nowak2"} + { "username", TeacherUsername} }; var token = await LoginAndGetToken(TeacherEmail, TeacherPassword); @@ -327,7 +327,7 @@ public async Task UpdateUserGitHubUsernameExists() { var fieldsToUpdate = new UserPatchDTO() { - GithubUsername = "nigel-nowak2" + GithubUsername = TeacherUsername }; int userId = 13; @@ -346,7 +346,7 @@ public async Task UpdateUserEmailExists() { var fieldsToUpdate = new UserPatchDTO() { - Email="nigel.nowak2@example.com" + Email=TeacherEmail }; int userId = 13; diff --git a/exercise.tests/IntegrationTests/ValidationTests.cs b/exercise.tests/IntegrationTests/ValidationTests.cs index f867948..2634d59 100644 --- a/exercise.tests/IntegrationTests/ValidationTests.cs +++ b/exercise.tests/IntegrationTests/ValidationTests.cs @@ -11,26 +11,8 @@ namespace exercise.tests.IntegrationTests /// /// Integration coverage for the validation endpoints, ensuring parity with business rules. /// - public class ValidationTests + public class ValidationTests : BaseIntegrationTest { - private WebApplicationFactory _factory; - private HttpClient _client; - - [SetUp] - public void SetUp() - { - // Arrange - _factory = new WebApplicationFactory(); - _client = _factory.CreateClient(); - } - - [TearDown] - public void TearDown() - { - _client.Dispose(); - _factory.Dispose(); - } - /// /// Ensures the password validation endpoint surfaces the correct status code per input. /// @@ -218,7 +200,7 @@ public async Task ValidateUsernameMessage(string endpoint, string input, string /// The username to query. /// The message expected from the API. /// The HTTP status code that should be returned. - [TestCase("donald-esposito121", "Username is already in use", HttpStatusCode.BadRequest)] + [TestCase(TeacherUsername, "Username is already in use", HttpStatusCode.BadRequest)] [TestCase("does-not-exist5", "Accepted", HttpStatusCode.OK)] public async Task ValidateUsernameExists(string input, string expectedMessage, HttpStatusCode expectedStatusCode) { @@ -249,7 +231,7 @@ public async Task ValidateUsernameExists(string input, string expectedMessage, H /// The GitHub username to query. /// The response message expected. /// The HTTP status the endpoint should emit. - [TestCase("donald-esposito121", "GitHub username is already in use", HttpStatusCode.BadRequest)] + [TestCase(TeacherUsername, "GitHub username is already in use", HttpStatusCode.BadRequest)] [TestCase("does-not-exist5", "Accepted", HttpStatusCode.OK)] public async Task ValidateGitUsernameExists(string input, string expectedMessage, HttpStatusCode expectedStatusCode) { @@ -279,7 +261,7 @@ public async Task ValidateGitUsernameExists(string input, string expectedMessage /// The email to query. /// The message expected in the response. /// The HTTP status the endpoint should emit. - [TestCase("nigel.nowak2@example.com", "Email already exists", HttpStatusCode.BadRequest)] + [TestCase(TeacherEmail, "Email already exists", HttpStatusCode.BadRequest)] [TestCase("valid@email.com.no", "Accepted", HttpStatusCode.OK)] public async Task ValidateEmailExists(string input, string expectedMessage, HttpStatusCode expectedStatusCode) { diff --git a/exercise.wwwapi/DTOs/Cohort/BasicCohortDTO.cs b/exercise.wwwapi/DTOs/Cohort/BasicCohortDTO.cs new file mode 100644 index 0000000..fbea09f --- /dev/null +++ b/exercise.wwwapi/DTOs/Cohort/BasicCohortDTO.cs @@ -0,0 +1,10 @@ +namespace exercise.wwwapi.DTOs.Cohort +{ + public class BasicCohortDTO + { + public int Id { get; set; } + public required string Title { get; init; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + } +} diff --git a/exercise.wwwapi/DTOs/GetUsers/UsersSuccessDTO.cs b/exercise.wwwapi/DTOs/GetUsers/UsersSuccessDTO.cs index af575cd..896f417 100644 --- a/exercise.wwwapi/DTOs/GetUsers/UsersSuccessDTO.cs +++ b/exercise.wwwapi/DTOs/GetUsers/UsersSuccessDTO.cs @@ -6,7 +6,7 @@ namespace exercise.wwwapi.DTOs.GetUsers public class UsersSuccessDTO { [JsonPropertyName("users")] - public List Users { get; set; } + public List Users { get; set; } } } diff --git a/exercise.wwwapi/DTOs/UserDTO.cs b/exercise.wwwapi/DTOs/UserDTO.cs index 49d03d0..35107af 100644 --- a/exercise.wwwapi/DTOs/UserDTO.cs +++ b/exercise.wwwapi/DTOs/UserDTO.cs @@ -1,9 +1,11 @@ -using exercise.wwwapi.Models; +using exercise.wwwapi.DTOs.Cohort; +using exercise.wwwapi.Models; namespace exercise.wwwapi.DTOs { public class UserDTO { + public int Id { get; set; } public string? Username { get; set; } public string? Email { get; set; } public string? FirstName { get; set; } @@ -13,8 +15,8 @@ public class UserDTO public Roles? Role { get; set; } public string? Specialism { get; set; } public string? Bio { get; set; } - public DateTime? StartDate { get; set; } - public DateTime? EndDate { get; set; } public string? Photo { get; set; } + public BasicCohortDTO? Cohort { get; set; } + } } diff --git a/exercise.wwwapi/DTOs/UserPatchDTO.cs b/exercise.wwwapi/DTOs/UserPatchDTO.cs index c12b757..13b46a2 100644 --- a/exercise.wwwapi/DTOs/UserPatchDTO.cs +++ b/exercise.wwwapi/DTOs/UserPatchDTO.cs @@ -12,8 +12,6 @@ public class UserPatchDTO public int? Role { get; set; } public string? Specialism { get; set; } public string? Cohort { get; set; } - public DateTime? StartDate { get; set; } - public DateTime? EndDate { get; set; } public string? Bio { get; set; } public string? Photo { get; set; } } diff --git a/exercise.wwwapi/Data/CohortCourseData.cs b/exercise.wwwapi/Data/CohortCourseData.cs index ed6ee95..cef3a0d 100644 --- a/exercise.wwwapi/Data/CohortCourseData.cs +++ b/exercise.wwwapi/Data/CohortCourseData.cs @@ -4,6 +4,8 @@ namespace exercise.wwwapi.Data { public class CohortCourseData { + DateTime somedate = new DateTime(2020, 12, 05, 0, 0, 0, DateTimeKind.Utc); + private List _courseNames = new List() { "Software Development", @@ -34,7 +36,10 @@ public CohortCourseData(List users) for (int x = 0; x < _cohortNames.Count; x++) { - Cohort cohort = new Cohort() { Id = x+1 , Title = _cohortNames[x] }; + Cohort cohort = new Cohort() { Id = x+1 , Title = _cohortNames[x], + StartDate = somedate.AddDays(-random.Next(0, 30)).AddMinutes(random.Next(400, 1200)), + EndDate = somedate.AddDays(random.Next(31, 365)).AddMinutes(random.Next(400, 1200)), + }; _cohorts.Add(cohort); } diff --git a/exercise.wwwapi/Data/PersonData.cs b/exercise.wwwapi/Data/PersonData.cs index 7347058..b689500 100644 --- a/exercise.wwwapi/Data/PersonData.cs +++ b/exercise.wwwapi/Data/PersonData.cs @@ -115,8 +115,6 @@ public PersonData() Specialism = specialism, Role = role, PasswordHash = password, - StartDate = somedate.AddDays(-userRandom.Next(30, 30)), - EndDate = somedate.AddDays(userRandom.Next(31, 365)), Photo = photo }; diff --git a/exercise.wwwapi/Endpoints/UserEndpoints.cs b/exercise.wwwapi/Endpoints/UserEndpoints.cs index 3341469..7312179 100644 --- a/exercise.wwwapi/Endpoints/UserEndpoints.cs +++ b/exercise.wwwapi/Endpoints/UserEndpoints.cs @@ -9,6 +9,7 @@ using exercise.wwwapi.Repository; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; @@ -25,10 +26,9 @@ public static void ConfigureAuthApi(this WebApplication app) var users = app.MapGroup("users"); users.MapPost("/", Register).WithSummary("Create user"); - users.MapGet("/", GetUsers).WithSummary("Get all users by first name if provided"); + users.MapGet("/", GetUsers).WithSummary("Get all users with name name if provided"); users.MapGet("/{id:int}", GetUserById).WithSummary("Get user by user id"); users.MapPatch("/{id:int}", UpdateUser).WithSummary("Update a user"); - } /// @@ -50,28 +50,28 @@ public static void ConfigureAuthApi(this WebApplication app) [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - private static async Task GetUsers(IRepository repository, ClaimsPrincipal claims, string? name) + private static IResult GetUsers(IRepository repository, IMapper mapper, ClaimsPrincipal claims, string? name) { int? id = claims.UserRealId(); - IEnumerable results = await repository.Get(); + IEnumerable results = repository.GetWithIncludes(q => q.Include(u => u.CohortCourseUsers).ThenInclude(ccu => ccu.Cohort)); string? search = name?.Trim().ToLower(); - - UsersSuccessDTO userData = new UsersSuccessDTO() - { - Users = !string.IsNullOrWhiteSpace(search) + + results = !string.IsNullOrWhiteSpace(search) ? results.Where(i => (i.FirstName.ToLower().Contains(search)) || (i.LastName.ToLower().Contains(search)) || ($"{i.FirstName ?? ""} {i.LastName ?? ""}".ToLower().Contains(search)) ).ToList() - : results.ToList() - }; + : results.ToList(); - ResponseDTO response = new ResponseDTO() + ResponseDTO response = new() { Message = "success", - Data = userData + Data = new UsersSuccessDTO() + { + Users = mapper.Map>(results) + } }; return TypedResults.Ok(response); @@ -132,9 +132,11 @@ private static IResult Login(IRepository repository, IMapper mapper, Login //check if email is in database var emailExists = repository.GetAllFiltered(q => q.Email == request.email); if (emailExists.Count() == 0) return TypedResults.BadRequest(new ResponseDTO() { Message = "Invalid email and/or password provided" }); - - User user = repository.GetAll().FirstOrDefault(u => u.Email == request.email)!; + //User user = repository.GetAll().FirstOrDefault(u => u.Email == request.email)!; + User? user = repository.GetWithIncludes(q => q.Include(u => u.CohortCourseUsers).ThenInclude(ccu => ccu.Cohort)).FirstOrDefault(u => u.Email == request.email); + + if (user == null) return TypedResults.BadRequest(new ResponseDTO() { Message = "Invalid email and/or password provided" }); if (!BCrypt.Net.BCrypt.Verify(request.password, user.PasswordHash)) { @@ -166,9 +168,9 @@ private static IResult Login(IRepository repository, IMapper mapper, Login [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] - private static async Task GetUserById(IRepository repository, ClaimsPrincipal claims, IMapper mapper, int id) + private static IResult GetUserById(IRepository repository, ClaimsPrincipal claims, IMapper mapper, int id) { - var user = repository.GetById(id); + User? user = repository.GetById(id, q => q.Include(u => u.CohortCourseUsers).ThenInclude(ccu => ccu.Cohort)); if (user == null) return TypedResults.NotFound(new ResponseDTO { Message = "User not found" }); ResponseDTO response = new ResponseDTO @@ -190,7 +192,7 @@ private static async Task UpdateUser(IRepository repository, Clai if (userPatch.GetType().GetProperties().Length > 0 && userPatch.GetType().GetProperties().All((p) => p.GetValue(userPatch) == null)) return TypedResults.BadRequest(new ResponseDTO() { Message = "Provide at least one field for update" }); - var user = repository.GetById(id); + User? user = repository.GetById(id, q => q.Include(u => u.CohortCourseUsers).ThenInclude(ccu => ccu.Cohort)); if (user == null) return TypedResults.NotFound(new ResponseDTO { Message = "User not found" }); @@ -251,8 +253,8 @@ private static async Task UpdateUser(IRepository repository, Clai if (userPatch.Specialism != null) user.Specialism = userPatch.Specialism; // TODO: Add cohort support after implementing the Cohort model and adding it to user. //if (userPatch.Cohort != null) user.Cohort = userPatch.Cohort; - if (userPatch.StartDate != null) user.StartDate = (DateTime)userPatch.StartDate; - if (userPatch.EndDate != null) user.EndDate = (DateTime)userPatch.EndDate; + //if (userPatch.StartDate != null) user.StartDate = (DateTime)userPatch.StartDate; + //if (userPatch.EndDate != null) user.EndDate = (DateTime)userPatch.EndDate; if (userPatch.Bio != null) user.Bio = userPatch.Bio; if (userPatch.Photo != null) user.Photo = userPatch.Photo; diff --git a/exercise.wwwapi/Models/Cohort.cs b/exercise.wwwapi/Models/Cohort.cs index ede3787..c803144 100644 --- a/exercise.wwwapi/Models/Cohort.cs +++ b/exercise.wwwapi/Models/Cohort.cs @@ -11,6 +11,11 @@ public class Cohort public int Id { get; set; } [Column("title")] public string Title { get; set; } = string.Empty; + [Column("startDate")] + public DateTime StartDate { get; set; } + + [Column("endDate")] + public DateTime EndDate { get; set; } [JsonIgnore] public ICollection CohortCourses { get; set; } = new List(); } diff --git a/exercise.wwwapi/Models/User.cs b/exercise.wwwapi/Models/User.cs index b324a7f..0d0b3e5 100644 --- a/exercise.wwwapi/Models/User.cs +++ b/exercise.wwwapi/Models/User.cs @@ -41,12 +41,6 @@ public class User [Column("specialism")] public string Specialism { get; set; } = string.Empty; - [Column("startDate")] - public DateTime StartDate { get; set; } - - [Column("endDate")] - public DateTime EndDate { get; set; } - [Column("photo")] public string Photo { get; set; } = string.Empty; diff --git a/exercise.wwwapi/Tools/MappingProfile.cs b/exercise.wwwapi/Tools/MappingProfile.cs index ede4bcb..93883f9 100644 --- a/exercise.wwwapi/Tools/MappingProfile.cs +++ b/exercise.wwwapi/Tools/MappingProfile.cs @@ -11,10 +11,16 @@ public class MappingProfile : Profile { public MappingProfile() { - CreateMap(); + // Might change this if we want to return all the cohorts a user is in OR the active cohort + CreateMap() + .ForMember(dest => dest.Cohort, opt => opt.MapFrom(src => src.CohortCourseUsers.FirstOrDefault().Cohort)); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap() .ForMember(dest => dest.Courses, opt => opt.MapFrom(src => src.CohortCourses));