Skip to content

Commit

Permalink
[InviteController] Clean up logic and add tests (#2663)
Browse files Browse the repository at this point in the history
  • Loading branch information
imnasnainaec authored Oct 10, 2023
1 parent c0b2bcf commit 47537c4
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 49 deletions.
165 changes: 165 additions & 0 deletions Backend.Tests/Controllers/InviteControllerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
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 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]
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);

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<EmailInvite> { tokenPast, tokenFuture }
}))!.Id;
}

[Test]
public void TestEmailInviteToProject()
{
var data = new EmailInviteData { ProjectId = _projId };
var result = (ObjectResult)_inviteController.EmailInviteToProject(data).Result;
Assert.That(result.Value, Is.Not.Empty);
}

[Test]
public void TestEmailInviteToProjectUnauthorized()
{
_inviteController.ControllerContext.HttpContext = PermissionServiceMock.UnauthorizedHttpContext();
var result = _inviteController.EmailInviteToProject(new EmailInviteData()).Result;
Assert.That(result, Is.InstanceOf<ForbidResult>());
}

[Test]
public void TestEmailInviteToProjectNoProject()
{
var data = new EmailInviteData { ProjectId = MissingId };
var result = _inviteController.EmailInviteToProject(data).Result;
Assert.That(result, Is.InstanceOf<NotFoundObjectResult>());
}

[Test]
public void TestValidateTokenNoProject()
{
var result = _inviteController.ValidateToken(MissingId, _tokenActive).Result;
Assert.That(result, Is.InstanceOf<NotFoundObjectResult>());
}

[Test]
public void TestValidateTokenNoTokenNoUser()
{
var result = _inviteController.ValidateToken(_projId, "not-a-token").Result;
Assert.That(result, Is.InstanceOf<OkObjectResult>());
var value = ((OkObjectResult)result).Value;
Assert.That(value, Is.InstanceOf<EmailInviteStatus>());

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<OkObjectResult>());
var value = ((OkObjectResult)result).Value;
Assert.That(value, Is.InstanceOf<EmailInviteStatus>());

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<OkObjectResult>());
var value = ((OkObjectResult)result).Value;
Assert.That(value, Is.InstanceOf<EmailInviteStatus>());

var status = (EmailInviteStatus)value!;
Assert.That(status.IsTokenValid, Is.True);
Assert.That(status.IsUserValid, Is.False);
}

[Test]
public void TestValidateTokenValidTokenUserAlreadyInProject()
{
var roles = new Dictionary<string, string> { [_projId] = "role-id" };
_userRepo.Create(new User { Email = EmailActive, ProjectRoles = roles });

var result = _inviteController.ValidateToken(_projId, _tokenActive).Result;
Assert.That(result, Is.InstanceOf<OkObjectResult>());
var value = ((OkObjectResult)result).Value;
Assert.That(value, Is.InstanceOf<EmailInviteStatus>());

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<OkObjectResult>());
var value = ((OkObjectResult)result).Value;
Assert.That(value, Is.InstanceOf<EmailInviteStatus>());

var status = (EmailInviteStatus)value!;
Assert.That(status.IsTokenValid, Is.False);
Assert.That(status.IsUserValid, Is.True);
}
}
}
98 changes: 51 additions & 47 deletions Backend/Controllers/InviteController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,82 +73,86 @@ public async Task<IActionResult> 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));
}
}

/// <remarks>
/// This is used in a [FromBody] serializer, so its attributes cannot be set to readonly.
/// </remarks>
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; }
/// <remarks>
/// This is used in a [FromBody] serializer, so its attributes cannot be set to readonly.
/// </remarks>
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 = "";
}
}

/// <remarks>
/// This is used in an OpenAPI return value serializer, so its attributes must be defined as properties.
/// </remarks>
public class EmailInviteStatus
{
[Required]
public bool IsTokenValid { get; set; }
[Required]
public bool IsUserRegistered { get; set; }
/// <remarks>
/// This is used in an OpenAPI return value serializer, so its attributes must be defined as properties.
/// </remarks>
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;
}
}
}
2 changes: 1 addition & 1 deletion src/api/models/email-invite-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ export interface EmailInviteStatus {
* @type {boolean}
* @memberof EmailInviteStatus
*/
isUserRegistered: boolean;
isUserValid: boolean;
}
2 changes: 1 addition & 1 deletion src/components/ProjectInvite/ProjectInvite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function ProjectInvite(): ReactElement {
const validateLink = useCallback(async (): Promise<void> => {
if (project && token) {
const status = await backend.validateLink(project, token);
if (status.isTokenValid && status.isUserRegistered) {
if (status.isTokenValid && status.isUserValid) {
navigate(Path.Login);
return;
}
Expand Down

0 comments on commit 47537c4

Please sign in to comment.