Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[InviteController] Clean up logic and add tests #2663

Merged
merged 5 commits into from
Oct 10, 2023
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
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