From 05d0b9a4cc3d659cce2f7ad97c3781b316a75eaf Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Mon, 2 Oct 2023 18:47:01 -0400 Subject: [PATCH 1/3] Create InviteControllerTests.cs --- .../Controllers/InviteControllerTests.cs | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 Backend.Tests/Controllers/InviteControllerTests.cs diff --git a/Backend.Tests/Controllers/InviteControllerTests.cs b/Backend.Tests/Controllers/InviteControllerTests.cs new file mode 100644 index 0000000000..6ac3fdec3e --- /dev/null +++ b/Backend.Tests/Controllers/InviteControllerTests.cs @@ -0,0 +1,83 @@ +using System; +using System.Threading.Tasks; +using Backend.Tests.Mocks; +using BackendFramework.Controllers; +using BackendFramework.Interfaces; +using BackendFramework.Models; +using BackendFramework.Services; +using Microsoft.AspNetCore.Mvc; +using NUnit.Framework; + +namespace Backend.Tests.Controllers +{ + public class InviteControllerTests : IDisposable + { + private IProjectRepository _projRepo = null!; + private IUserRepository _userRepo = null!; + private IInviteService _inviteService = null!; + private IPermissionService _permissionService = null!; + private InviteController _inviteController = null!; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _inviteController?.Dispose(); + } + } + + private string _projId = null!; + private const string MissingId = "MISSING_ID"; + + [SetUp] + public async Task Setup() + { + _projRepo = new ProjectRepositoryMock(); + _userRepo = new UserRepositoryMock(); + _permissionService = new PermissionServiceMock(); + _inviteService = new InviteService( + _projRepo, _userRepo, _permissionService, new UserRoleRepositoryMock(), new EmailServiceMock()); + _inviteController = new InviteController(_inviteService, _projRepo, _userRepo, _permissionService); + + _projId = (await _projRepo.Create(new Project { Name = "InviteControllerTests" }))!.Id; + } + + [Test] + public void TestEmailInviteToProject() + { + var data = new InviteController.EmailInviteData { ProjectId = _projId }; + var result = (ObjectResult)_inviteController.EmailInviteToProject(data).Result; + Assert.That(result.Value, Is.Not.Empty); + } + + [Test] + public void TestEmailInviteToProjectUnauthorized() + { + var data = new InviteController.EmailInviteData(); + _inviteController.ControllerContext.HttpContext = PermissionServiceMock.UnauthorizedHttpContext(); + var result = _inviteController.EmailInviteToProject(data).Result; + Assert.That(result, Is.InstanceOf()); + } + + [Test] + public void TestEmailInviteToProjectNoProject() + { + var data = new InviteController.EmailInviteData { ProjectId = MissingId }; + var result = _inviteController.EmailInviteToProject(data).Result; + Assert.That(result, Is.InstanceOf()); + } + + [Test] + public void TestValidateTokenNoProject() + { + var result = _inviteController.ValidateToken(MissingId, "token").Result; + Assert.That(result, Is.InstanceOf()); + } + } +} From a0fadfe06448c290b40ca9e28e671560315ced7c Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 3 Oct 2023 11:00:23 -0400 Subject: [PATCH 2/3] Clean up ValidateToken logic and expand tests --- .../Controllers/InviteControllerTests.cs | 94 ++++++++++++++++-- Backend/Controllers/InviteController.cs | 98 ++++++++++--------- src/api/models/email-invite-status.ts | 2 +- .../ProjectInvite/ProjectInvite.tsx | 2 +- 4 files changed, 141 insertions(+), 55 deletions(-) diff --git a/Backend.Tests/Controllers/InviteControllerTests.cs b/Backend.Tests/Controllers/InviteControllerTests.cs index 6ac3fdec3e..b871caee32 100644 --- a/Backend.Tests/Controllers/InviteControllerTests.cs +++ b/Backend.Tests/Controllers/InviteControllerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Backend.Tests.Mocks; using BackendFramework.Controllers; @@ -33,6 +34,10 @@ protected virtual void Dispose(bool disposing) } private string _projId = null!; + private string _tokenActive = null!; + private string _tokenExpired = null!; + private const string EmailActive = "active@token.email"; + private const string EmailExpired = "expired@token.email"; private const string MissingId = "MISSING_ID"; [SetUp] @@ -45,13 +50,21 @@ public async Task Setup() _projRepo, _userRepo, _permissionService, new UserRoleRepositoryMock(), new EmailServiceMock()); _inviteController = new InviteController(_inviteService, _projRepo, _userRepo, _permissionService); - _projId = (await _projRepo.Create(new Project { Name = "InviteControllerTests" }))!.Id; + var tokenPast = new EmailInvite(-1) { Email = EmailExpired }; + _tokenExpired = tokenPast.Token; + var tokenFuture = new EmailInvite(1) { Email = EmailActive }; + _tokenActive = tokenFuture.Token; + _projId = (await _projRepo.Create(new Project + { + Name = "InviteControllerTests", + InviteTokens = new List { tokenPast, tokenFuture } + }))!.Id; } [Test] public void TestEmailInviteToProject() { - var data = new InviteController.EmailInviteData { ProjectId = _projId }; + var data = new EmailInviteData { ProjectId = _projId }; var result = (ObjectResult)_inviteController.EmailInviteToProject(data).Result; Assert.That(result.Value, Is.Not.Empty); } @@ -59,16 +72,15 @@ public void TestEmailInviteToProject() [Test] public void TestEmailInviteToProjectUnauthorized() { - var data = new InviteController.EmailInviteData(); _inviteController.ControllerContext.HttpContext = PermissionServiceMock.UnauthorizedHttpContext(); - var result = _inviteController.EmailInviteToProject(data).Result; + var result = _inviteController.EmailInviteToProject(new EmailInviteData()).Result; Assert.That(result, Is.InstanceOf()); } [Test] public void TestEmailInviteToProjectNoProject() { - var data = new InviteController.EmailInviteData { ProjectId = MissingId }; + var data = new EmailInviteData { ProjectId = MissingId }; var result = _inviteController.EmailInviteToProject(data).Result; Assert.That(result, Is.InstanceOf()); } @@ -76,8 +88,78 @@ public void TestEmailInviteToProjectNoProject() [Test] public void TestValidateTokenNoProject() { - var result = _inviteController.ValidateToken(MissingId, "token").Result; + var result = _inviteController.ValidateToken(MissingId, _tokenActive).Result; Assert.That(result, Is.InstanceOf()); } + + [Test] + public void TestValidateTokenNoTokenNoUser() + { + var result = _inviteController.ValidateToken(_projId, "not-a-token").Result; + Assert.That(result, Is.InstanceOf()); + var value = ((OkObjectResult)result).Value; + Assert.That(value, Is.InstanceOf()); + + var status = (EmailInviteStatus)value!; + Assert.That(status.IsTokenValid, Is.False); + Assert.That(status.isUserValid, Is.False); + } + + [Test] + public void TestValidateTokenExpiredTokenNoUser() + { + var result = _inviteController.ValidateToken(_projId, _tokenExpired).Result; + Assert.That(result, Is.InstanceOf()); + var value = ((OkObjectResult)result).Value; + Assert.That(value, Is.InstanceOf()); + + var status = (EmailInviteStatus)value!; + Assert.That(status.IsTokenValid, Is.False); + Assert.That(status.isUserValid, Is.False); + } + + [Test] + public void TestValidateTokenValidTokenNoUser() + { + var result = _inviteController.ValidateToken(_projId, _tokenActive).Result; + Assert.That(result, Is.InstanceOf()); + var value = ((OkObjectResult)result).Value; + Assert.That(value, Is.InstanceOf()); + + var status = (EmailInviteStatus)value!; + Assert.That(status.IsTokenValid, Is.True); + Assert.That(status.isUserValid, Is.False); + } + + [Test] + public void TestValidateTokenValidTokenUserAlreadyInProject() + { + var roles = new Dictionary { [_projId] = "role-id" }; + _userRepo.Create(new User { Email = EmailActive, ProjectRoles = roles }); + + var result = _inviteController.ValidateToken(_projId, _tokenActive).Result; + Assert.That(result, Is.InstanceOf()); + var value = ((OkObjectResult)result).Value; + Assert.That(value, Is.InstanceOf()); + + var status = (EmailInviteStatus)value!; + Assert.That(status.IsTokenValid, Is.True); + Assert.That(status.isUserValid, Is.False); + } + + [Test] + public void TestValidateTokenExpiredTokenUserAvailable() + { + _userRepo.Create(new User { Email = EmailExpired }); + + var result = _inviteController.ValidateToken(_projId, _tokenExpired).Result; + Assert.That(result, Is.InstanceOf()); + var value = ((OkObjectResult)result).Value; + Assert.That(value, Is.InstanceOf()); + + var status = (EmailInviteStatus)value!; + Assert.That(status.IsTokenValid, Is.False); + Assert.That(status.isUserValid, Is.True); + } } } diff --git a/Backend/Controllers/InviteController.cs b/Backend/Controllers/InviteController.cs index f4f5b78d1b..63153b4c1f 100644 --- a/Backend/Controllers/InviteController.cs +++ b/Backend/Controllers/InviteController.cs @@ -73,82 +73,86 @@ public async Task ValidateToken(string projectId, string token) var tokenObj = new EmailInvite(); foreach (var tok in project.InviteTokens) { - if (tok.Token == token && DateTime.Now < tok.ExpireTime) + if (tok.Token == token) { - isTokenValid = true; tokenObj = tok; + if (DateTime.Now < tok.ExpireTime) + { + isTokenValid = true; + } break; } } var users = await _userRepo.GetAllUsers(); var currentUser = new User(); - var isUserRegistered = false; + var isUserRegisteredAndNotInProject = false; foreach (var user in users) { if (user.Email == tokenObj.Email) { currentUser = user; - isUserRegistered = true; + if (!user.ProjectRoles.ContainsKey(projectId)) + { + isUserRegisteredAndNotInProject = true; + } break; } } - var status = new EmailInviteStatus(isTokenValid, isUserRegistered); - if (isTokenValid && !isUserRegistered) + var status = new EmailInviteStatus(isTokenValid, isUserRegisteredAndNotInProject); + if (!isTokenValid || !isUserRegisteredAndNotInProject) { return Ok(status); } - if (isTokenValid && isUserRegistered - && !currentUser.ProjectRoles.ContainsKey(projectId) - && await _inviteService.RemoveTokenAndCreateUserRole(project, currentUser, tokenObj)) + if (await _inviteService.RemoveTokenAndCreateUserRole(project, currentUser, tokenObj)) { return Ok(status); } - return Ok(new EmailInviteStatus(false, false)); + return Ok(new EmailInviteStatus(false, true)); } + } - /// - /// This is used in a [FromBody] serializer, so its attributes cannot be set to readonly. - /// - public class EmailInviteData - { - [Required] - public string EmailAddress { get; set; } - [Required] - public string Message { get; set; } - [Required] - public string ProjectId { get; set; } - [Required] - public Role Role { get; set; } - [Required] - public string Domain { get; set; } + /// + /// This is used in a [FromBody] serializer, so its attributes cannot be set to readonly. + /// + public class EmailInviteData + { + [Required] + public string EmailAddress { get; set; } + [Required] + public string Message { get; set; } + [Required] + public string ProjectId { get; set; } + [Required] + public Role Role { get; set; } + [Required] + public string Domain { get; set; } - public EmailInviteData() - { - EmailAddress = ""; - Message = ""; - ProjectId = ""; - Role = Role.Harvester; - Domain = ""; - } + public EmailInviteData() + { + EmailAddress = ""; + Message = ""; + ProjectId = ""; + Role = Role.Harvester; + Domain = ""; } + } - /// - /// This is used in an OpenAPI return value serializer, so its attributes must be defined as properties. - /// - public class EmailInviteStatus - { - [Required] - public bool IsTokenValid { get; set; } - [Required] - public bool IsUserRegistered { get; set; } + /// + /// This is used in an OpenAPI return value serializer, so its attributes must be defined as properties. + /// + public class EmailInviteStatus + { + [Required] + public bool IsTokenValid { get; set; } + [Required] + public bool isUserValid { get; set; } - public EmailInviteStatus(bool isTokenValid, bool isUserRegistered) - { - IsTokenValid = isTokenValid; - IsUserRegistered = isUserRegistered; - } + public EmailInviteStatus(bool isTokenValid, bool isUserRegistered) + { + IsTokenValid = isTokenValid; + isUserValid = isUserRegistered; } } } diff --git a/src/api/models/email-invite-status.ts b/src/api/models/email-invite-status.ts index 51bababf90..ed840bbe91 100644 --- a/src/api/models/email-invite-status.ts +++ b/src/api/models/email-invite-status.ts @@ -29,5 +29,5 @@ export interface EmailInviteStatus { * @type {boolean} * @memberof EmailInviteStatus */ - isUserRegistered: boolean; + isUserValid: boolean; } diff --git a/src/components/ProjectInvite/ProjectInvite.tsx b/src/components/ProjectInvite/ProjectInvite.tsx index 9f30bbb738..fcbe7c3d5c 100644 --- a/src/components/ProjectInvite/ProjectInvite.tsx +++ b/src/components/ProjectInvite/ProjectInvite.tsx @@ -24,7 +24,7 @@ export default function ProjectInvite(): ReactElement { const validateLink = useCallback(async (): Promise => { if (project && token) { const status = await backend.validateLink(project, token); - if (status.isTokenValid && status.isUserRegistered) { + if (status.isTokenValid && status.isUserValid) { navigate(Path.Login); return; } From ae377cea0753bc88881b5bc1f86da1b9019d3c2d Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 6 Oct 2023 13:26:54 -0400 Subject: [PATCH 3/3] Fix property case --- Backend.Tests/Controllers/InviteControllerTests.cs | 10 +++++----- Backend/Controllers/InviteController.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Backend.Tests/Controllers/InviteControllerTests.cs b/Backend.Tests/Controllers/InviteControllerTests.cs index b871caee32..1da286f377 100644 --- a/Backend.Tests/Controllers/InviteControllerTests.cs +++ b/Backend.Tests/Controllers/InviteControllerTests.cs @@ -102,7 +102,7 @@ public void TestValidateTokenNoTokenNoUser() var status = (EmailInviteStatus)value!; Assert.That(status.IsTokenValid, Is.False); - Assert.That(status.isUserValid, Is.False); + Assert.That(status.IsUserValid, Is.False); } [Test] @@ -115,7 +115,7 @@ public void TestValidateTokenExpiredTokenNoUser() var status = (EmailInviteStatus)value!; Assert.That(status.IsTokenValid, Is.False); - Assert.That(status.isUserValid, Is.False); + Assert.That(status.IsUserValid, Is.False); } [Test] @@ -128,7 +128,7 @@ public void TestValidateTokenValidTokenNoUser() var status = (EmailInviteStatus)value!; Assert.That(status.IsTokenValid, Is.True); - Assert.That(status.isUserValid, Is.False); + Assert.That(status.IsUserValid, Is.False); } [Test] @@ -144,7 +144,7 @@ public void TestValidateTokenValidTokenUserAlreadyInProject() var status = (EmailInviteStatus)value!; Assert.That(status.IsTokenValid, Is.True); - Assert.That(status.isUserValid, Is.False); + Assert.That(status.IsUserValid, Is.False); } [Test] @@ -159,7 +159,7 @@ public void TestValidateTokenExpiredTokenUserAvailable() var status = (EmailInviteStatus)value!; Assert.That(status.IsTokenValid, Is.False); - Assert.That(status.isUserValid, Is.True); + Assert.That(status.IsUserValid, Is.True); } } } diff --git a/Backend/Controllers/InviteController.cs b/Backend/Controllers/InviteController.cs index 63153b4c1f..5feee3c85a 100644 --- a/Backend/Controllers/InviteController.cs +++ b/Backend/Controllers/InviteController.cs @@ -147,12 +147,12 @@ public class EmailInviteStatus [Required] public bool IsTokenValid { get; set; } [Required] - public bool isUserValid { get; set; } + public bool IsUserValid { get; set; } public EmailInviteStatus(bool isTokenValid, bool isUserRegistered) { IsTokenValid = isTokenValid; - isUserValid = isUserRegistered; + IsUserValid = isUserRegistered; } } }