Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions exercise.tests/IntegrationTests/BaseIntegrationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> LoginAndGetToken(string email, string password, bool success = true, bool longlife = false)
Expand Down
8 changes: 4 additions & 4 deletions exercise.tests/IntegrationTests/CohortTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions exercise.tests/IntegrationTests/UserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ public async Task UpdateUserUsernameExists()
{
var fieldsToUpdate = new Dictionary<string, object?>
{
{ "username", "nigel-nowak2"}
{ "username", TeacherUsername}
};

var token = await LoginAndGetToken(TeacherEmail, TeacherPassword);
Expand All @@ -327,7 +327,7 @@ public async Task UpdateUserGitHubUsernameExists()
{
var fieldsToUpdate = new UserPatchDTO()
{
GithubUsername = "nigel-nowak2"
GithubUsername = TeacherUsername
};

int userId = 13;
Expand All @@ -346,7 +346,7 @@ public async Task UpdateUserEmailExists()
{
var fieldsToUpdate = new UserPatchDTO()
{
Email="nigel.nowak2@example.com"
Email=TeacherEmail
};

int userId = 13;
Expand Down
26 changes: 4 additions & 22 deletions exercise.tests/IntegrationTests/ValidationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,8 @@ namespace exercise.tests.IntegrationTests
/// <summary>
/// Integration coverage for the validation endpoints, ensuring parity with business rules.
/// </summary>
public class ValidationTests
public class ValidationTests : BaseIntegrationTest
{
private WebApplicationFactory<Program> _factory;
private HttpClient _client;

[SetUp]
public void SetUp()
{
// Arrange
_factory = new WebApplicationFactory<Program>();
_client = _factory.CreateClient();
}

[TearDown]
public void TearDown()
{
_client.Dispose();
_factory.Dispose();
}

/// <summary>
/// Ensures the password validation endpoint surfaces the correct status code per input.
/// </summary>
Expand Down Expand Up @@ -218,7 +200,7 @@ public async Task ValidateUsernameMessage(string endpoint, string input, string
/// <param name="input">The username to query.</param>
/// <param name="expectedMessage">The message expected from the API.</param>
/// <param name="expectedStatusCode">The HTTP status code that should be returned.</param>
[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)
{
Expand Down Expand Up @@ -249,7 +231,7 @@ public async Task ValidateUsernameExists(string input, string expectedMessage, H
/// <param name="input">The GitHub username to query.</param>
/// <param name="expectedMessage">The response message expected.</param>
/// <param name="expectedStatusCode">The HTTP status the endpoint should emit.</param>
[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)
{
Expand Down Expand Up @@ -279,7 +261,7 @@ public async Task ValidateGitUsernameExists(string input, string expectedMessage
/// <param name="input">The email to query.</param>
/// <param name="expectedMessage">The message expected in the response.</param>
/// <param name="expectedStatusCode">The HTTP status the endpoint should emit.</param>
[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)
{
Expand Down
10 changes: 10 additions & 0 deletions exercise.wwwapi/DTOs/Cohort/BasicCohortDTO.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
2 changes: 1 addition & 1 deletion exercise.wwwapi/DTOs/GetUsers/UsersSuccessDTO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace exercise.wwwapi.DTOs.GetUsers
public class UsersSuccessDTO
{
[JsonPropertyName("users")]
public List<User> Users { get; set; }
public List<UserDTO> Users { get; set; }
}

}
8 changes: 5 additions & 3 deletions exercise.wwwapi/DTOs/UserDTO.cs
Original file line number Diff line number Diff line change
@@ -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; }
Expand All @@ -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; }

}
}
2 changes: 0 additions & 2 deletions exercise.wwwapi/DTOs/UserPatchDTO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
Expand Down
7 changes: 6 additions & 1 deletion exercise.wwwapi/Data/CohortCourseData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> _courseNames = new List<string>()
{
"Software Development",
Expand Down Expand Up @@ -34,7 +36,10 @@ public CohortCourseData(List<User> 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);
}

Expand Down
2 changes: 0 additions & 2 deletions exercise.wwwapi/Data/PersonData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
40 changes: 21 additions & 19 deletions exercise.wwwapi/Endpoints/UserEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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");

}

/// <summary>
Expand All @@ -50,28 +50,28 @@ public static void ConfigureAuthApi(this WebApplication app)
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
private static async Task<IResult> GetUsers(IRepository<User> repository, ClaimsPrincipal claims, string? name)
private static IResult GetUsers(IRepository<User> repository, IMapper mapper, ClaimsPrincipal claims, string? name)
{
int? id = claims.UserRealId();

IEnumerable<User> results = await repository.Get();
IEnumerable<User> 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<UsersSuccessDTO> response = new ResponseDTO<UsersSuccessDTO>()
ResponseDTO<UsersSuccessDTO> response = new()
{
Message = "success",
Data = userData
Data = new UsersSuccessDTO()
{
Users = mapper.Map<List<UserDTO>>(results)
}
};

return TypedResults.Ok(response);
Expand Down Expand Up @@ -132,9 +132,11 @@ private static IResult Login(IRepository<User> 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<string>() { 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<string>() { Message = "Invalid email and/or password provided" });

if (!BCrypt.Net.BCrypt.Verify(request.password, user.PasswordHash))
{
Expand Down Expand Up @@ -166,9 +168,9 @@ private static IResult Login(IRepository<User> repository, IMapper mapper, Login
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
private static async Task<IResult> GetUserById(IRepository<User> repository, ClaimsPrincipal claims, IMapper mapper, int id)
private static IResult GetUserById(IRepository<User> 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<string> { Message = "User not found" });

ResponseDTO<UserDTO> response = new ResponseDTO<UserDTO>
Expand All @@ -190,7 +192,7 @@ private static async Task<IResult> UpdateUser(IRepository<User> repository, Clai
if (userPatch.GetType().GetProperties().Length > 0 && userPatch.GetType().GetProperties().All((p) => p.GetValue(userPatch) == null))
return TypedResults.BadRequest(new ResponseDTO<string>() { 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<string> { Message = "User not found" });

Expand Down Expand Up @@ -251,8 +253,8 @@ private static async Task<IResult> UpdateUser(IRepository<User> 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;

Expand Down
5 changes: 5 additions & 0 deletions exercise.wwwapi/Models/Cohort.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CohortCourse> CohortCourses { get; set; } = new List<CohortCourse>();
}
Expand Down
6 changes: 0 additions & 6 deletions exercise.wwwapi/Models/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
8 changes: 7 additions & 1 deletion exercise.wwwapi/Tools/MappingProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<User, UserDTO>();
// Might change this if we want to return all the cohorts a user is in OR the active cohort
CreateMap<User, UserDTO>()
.ForMember(dest => dest.Cohort, opt => opt.MapFrom(src => src.CohortCourseUsers.FirstOrDefault().Cohort));

CreateMap<User, UserBasicDTO>();

CreateMap<Post, PostDTO>();

CreateMap<Cohort, BasicCohortDTO>();

CreateMap<Cohort, CohortDTO>()
.ForMember(dest => dest.Courses, opt => opt.MapFrom(src => src.CohortCourses));

Expand Down
Loading