Skip to content

Commit

Permalink
Prevent resubmitting trn request for same email
Browse files Browse the repository at this point in the history
  • Loading branch information
MrKevJoy committed Aug 13, 2024
1 parent 3deed24 commit 72ef0ab
Show file tree
Hide file tree
Showing 17 changed files with 297 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public string RequestTrnAddress(JourneyInstanceId journeyInstanceId, bool? fromC
public string RequestTrnCheckAnswers(JourneyInstanceId journeyInstanceId) =>
GetRequiredPathByPage("/RequestTrn/CheckAnswers", journeyInstanceId: journeyInstanceId);

public string RequestTrnEmailInUse(JourneyInstanceId journeyInstanceId) =>
GetRequiredPathByPage("/RequestTrn/EmailInUse", journeyInstanceId: journeyInstanceId);

public string RequestTrnSubmitted(JourneyInstanceId journeyInstanceId) =>
GetRequiredPathByPage("/RequestTrn/Submitted", journeyInstanceId: journeyInstanceId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ await crmQueryDispatcher.ExecuteQuery(
Description = description,
EvidenceFileName = JourneyInstance!.State.EvidenceFileName!,
EvidenceFileContent = stream,
EvidenceFileMimeType = evidenceFileMimeType
EvidenceFileMimeType = evidenceFileMimeType,
EmailAddress = JourneyInstance!.State.Email!
});

await JourneyInstance!.UpdateStateAsync(state => state.HasPendingTrnRequest = true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using TeachingRecordSystem.Core.Dqt;
using TeachingRecordSystem.Core.Dqt.Queries;
using TeachingRecordSystem.UiCommon.FormFlow;
using EmailAddress = TeachingRecordSystem.AuthorizeAccess.DataAnnotations.EmailAddressAttribute;

namespace TeachingRecordSystem.AuthorizeAccess.Pages.RequestTrn;

[Journey(RequestTrnJourneyState.JourneyName), RequireJourneyInstance]
public class EmailModel(AuthorizeAccessLinkGenerator linkGenerator) : PageModel
public class EmailModel(AuthorizeAccessLinkGenerator linkGenerator, ICrmQueryDispatcher crmQueryDispatcher) : PageModel
{
public JourneyInstance<RequestTrnJourneyState>? JourneyInstance { get; set; }

Expand All @@ -30,6 +32,14 @@ public async Task<IActionResult> OnPost()

await JourneyInstance!.UpdateStateAsync(state => state.Email = Email);

var openTasks = await crmQueryDispatcher.ExecuteQuery(
new GetOpenTasksForEmailAddressQuery(EmailAddress: JourneyInstance!.State.Email!));

if (openTasks.Any())
{
return Redirect(linkGenerator.RequestTrnEmailInUse(JourneyInstance!.InstanceId));
}

return FromCheckAnswers == true ?
Redirect(linkGenerator.RequestTrnCheckAnswers(JourneyInstance!.InstanceId)) :
Redirect(linkGenerator.RequestTrnName(JourneyInstance.InstanceId));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@page "/request-trn/emailinuse"
@model TeachingRecordSystem.AuthorizeAccess.Pages.RequestTrn.EmailInUseModel
@{
ViewBag.Title = "Email Address in use";
}

@section BeforeContent {
<govuk-back-link href="@(LinkGenerator.RequestTrnEmail(Model.JourneyInstance!.InstanceId))" />
}

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<h1 class="govuk-heading-l">You’ve already submitted a request for a TRN</h1>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using TeachingRecordSystem.UiCommon.FormFlow;

namespace TeachingRecordSystem.AuthorizeAccess.Pages.RequestTrn;

[Journey(RequestTrnJourneyState.JourneyName), RequireJourneyInstance]
public class EmailInUseModel(AuthorizeAccessLinkGenerator linkGenerator) : PageModel
{
public JourneyInstance<RequestTrnJourneyState>? JourneyInstance { get; set; }

public override void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
var state = JourneyInstance!.State;
if (state.Email is null)
{
context.Result = Redirect(linkGenerator.RequestTrnEmail(JourneyInstance.InstanceId));
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -23294,6 +23294,7 @@ public static class Fields
public const string Id = "activityid";
public const string Category = "category";
public const string Description = "description";
public const string dfeta_EmailAddress = "dfeta_emailaddress";
public const string dfeta_potentialduplicateid = "dfeta_potentialduplicateid";
public const string ModifiedBy = "modifiedby";
public const string ModifiedOn = "modifiedon";
Expand Down Expand Up @@ -23441,6 +23442,26 @@ public string Description
}
}

/// <summary>
///
/// </summary>
[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("dfeta_emailaddress")]
public string dfeta_EmailAddress
{
[System.Diagnostics.DebuggerNonUserCode()]
get
{
return this.GetAttributeValue<string>("dfeta_emailaddress");
}
[System.Diagnostics.DebuggerNonUserCode()]
set
{
this.OnPropertyChanging("dfeta_EmailAddress");
this.SetAttributeValue("dfeta_emailaddress", value);
this.OnPropertyChanged("dfeta_EmailAddress");
}
}

/// <summary>
/// Unique identifier for Person associated with Task.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public record CreateTrnRequestTaskQuery : ICrmQuery<Guid>
public required string EvidenceFileName { get; init; }
public required Stream EvidenceFileContent { get; init; }
public required string EvidenceFileMimeType { get; init; }
public required string EmailAddress { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TeachingRecordSystem.Core.Dqt.Queries;

public record GetOpenTasksForEmailAddressQuery(string EmailAddress) : ICrmQuery<CrmTask[]>;
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public async Task<Guid> Execute(CreateTrnRequestTaskQuery query, IOrganizationSe
{
Id = Guid.NewGuid(),
Subject = "Notification for TRA Support Team - TRN request",
Description = query.Description
Description = query.Description,
dfeta_EmailAddress = query.EmailAddress
};

var annotationBody = await StreamHelper.GetBase64EncodedFileContent(query.EvidenceFileContent);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.PowerPlatform.Dataverse.Client;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using TeachingRecordSystem.Core.Dqt.Queries;

namespace TeachingRecordSystem.Core.Dqt.QueryHandlers;

public class GetOpenTasksForEmailAddressHandler : ICrmQueryHandler<GetOpenTasksForEmailAddressQuery, CrmTask[]>
{
public async Task<CrmTask[]> Execute(GetOpenTasksForEmailAddressQuery query, IOrganizationServiceAsync organizationService)
{
var queryExpression = new QueryExpression()
{
EntityName = CrmTask.EntityLogicalName,
ColumnSet = new ColumnSet(
CrmTask.Fields.dfeta_EmailAddress, CrmTask.Fields.StateCode)
};
queryExpression.Criteria.AddCondition(CrmTask.Fields.dfeta_EmailAddress, ConditionOperator.Equal, query.EmailAddress);
queryExpression.Criteria.AddCondition(CrmTask.Fields.StateCode, ConditionOperator.Equal, (int)TaskState.Open);

var request = new RetrieveMultipleRequest()
{
Query = queryExpression
};

var response = await organizationService.RetrieveMultipleAsync(queryExpression);
return response.Entities.Select(e => e.ToEntity<CrmTask>()).ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace TeachingRecordSystem.AuthorizeAccess.Tests.PageTests.RequestTrn;

public class EmailInUseTests(HostFixture hostFixture) : TestBase(hostFixture)
{
[Fact]
public async Task Get_WithoutEmailAddress_RedirectsToEmail()
{
// Arrange
var state = CreateNewState();
state.HasPendingTrnRequest = true;
var journeyInstance = await CreateJourneyInstance(state);

var request = new HttpRequestMessage(HttpMethod.Get, $"/request-trn/emailinuse?{journeyInstance.GetUniqueIdQueryParameter()}");

// Act
var response = await HttpClient.SendAsync(request);

// Assert
Assert.Equal(StatusCodes.Status302Found, (int)response.StatusCode);
Assert.Equal($"/request-trn/email?{journeyInstance.GetUniqueIdQueryParameter()}", response.Headers.Location?.OriginalString);
}

[Fact]
public async Task Get_WithEmailAddress_RendersExpectedContent()
{
// Arrange
var email = Faker.Internet.Email();
var state = CreateNewState(email);
var journeyInstance = await CreateJourneyInstance(state);
var person = await TestData.CreatePerson();
await TestData.CreateCrmTask(x =>
{
x.WithPersonId(person.ContactId);
x.WithEmailAddress(email);
});

var request = new HttpRequestMessage(HttpMethod.Get, $"/request-trn/emailinuse?{journeyInstance.GetUniqueIdQueryParameter()}");

// Act
var response = await HttpClient.SendAsync(request);

// Assert
var doc = await AssertEx.HtmlResponse(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ public async Task Post_InvalidFormatEmailAddress_ReturnsError()
await AssertEx.HtmlResponseHasError(response, "Email", "Enter an email address in the correct format, like [email protected]");
}


[Fact]
public async Task Post_ValidRequestWithValidData_RedirectsToNamePage()
{
Expand All @@ -140,4 +139,34 @@ public async Task Post_ValidRequestWithValidData_RedirectsToNamePage()
Assert.Equal(StatusCodes.Status302Found, (int)response.StatusCode);
Assert.Equal($"/request-trn/name?{journeyInstance.GetUniqueIdQueryParameter()}", response.Headers.Location?.OriginalString);
}

[Fact]
public async Task Post_RequestForEmailWithOpenTasks_RedirectsToEmailInUse()
{
// Arrange
var email = Faker.Internet.Email();
var state = CreateNewState();
var journeyInstance = await CreateJourneyInstance(state);
var person = await TestData.CreatePerson();
await TestData.CreateCrmTask(x =>
{
x.WithPersonId(person.ContactId);
x.WithEmailAddress(email);
});

var request = new HttpRequestMessage(HttpMethod.Post, $"/request-trn/email?{journeyInstance.GetUniqueIdQueryParameter()}")
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "Email", email }
})
};

// Act
var response = await HttpClient.SendAsync(request);

// Assert
Assert.Equal(StatusCodes.Status302Found, (int)response.StatusCode);
Assert.Equal($"/request-trn/emailinuse?{journeyInstance.GetUniqueIdQueryParameter()}", response.Headers.Location?.OriginalString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ public async Task QueryExecutesSuccessfully()
var evidenceFileName = $"evidence-{uniqueId}.jpg";
var evidenceFileContent = new MemoryStream(TestCommon.TestData.JpegImage);
var evidenceFileMimeType = "image/jpeg";
var email = Faker.Internet.Email();

var query = new CreateTrnRequestTaskQuery()
{
Description = description,
EvidenceFileName = evidenceFileName,
EvidenceFileContent = evidenceFileContent,
EvidenceFileMimeType = evidenceFileMimeType
EvidenceFileMimeType = evidenceFileMimeType,
EmailAddress = email
};

// Act
Expand All @@ -43,6 +45,7 @@ public async Task QueryExecutesSuccessfully()
Assert.NotNull(createdCrmTask);
Assert.Equal("Notification for TRA Support Team - TRN request", createdCrmTask.Subject);
Assert.Equal(description, createdCrmTask.Description);
Assert.Equal(createdCrmTask.dfeta_EmailAddress, email);

var createdAnnotation = ctx.AnnotationSet.SingleOrDefault(i => i.GetAttributeValue<string>(Annotation.Fields.FileName) == evidenceFileName);
Assert.NotNull(createdAnnotation);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
namespace TeachingRecordSystem.Core.Dqt.CrmIntegrationTests.QueryTests;

public class GetOpenTasksForEmailAddressTests : IAsyncLifetime
{
private readonly CrmClientFixture.TestDataScope _dataScope;
private readonly CrmQueryDispatcher _crmQueryDispatcher;

public GetOpenTasksForEmailAddressTests(CrmClientFixture crmClientFixture)
{
_dataScope = crmClientFixture.CreateTestDataScope();
_crmQueryDispatcher = crmClientFixture.CreateQueryDispatcher();
}

public Task InitializeAsync() => Task.CompletedTask;

public async Task DisposeAsync() => await _dataScope.DisposeAsync();

[Fact]
public async Task WhenCalled_WithEmailWithNoOpenTasks_ReturnsEmpty()
{
// Arrange
var emailWithNoOpenTasks = Faker.Internet.Email();

// Act
var result = await _crmQueryDispatcher.ExecuteQuery(new GetOpenTasksForEmailAddressQuery(emailWithNoOpenTasks));

// Assert
Assert.Empty(result);
}

[Fact]
public async Task WhenCalled_WithEmailWithOpenTasks_ReturnsTasks()
{
// Arrange
var person = await _dataScope.TestData.CreatePerson();
var emailWithOpenTasks = Faker.Internet.Email();
await _dataScope.TestData.CreateCrmTask(x =>
{
x.WithPersonId(person.ContactId);
x.WithEmailAddress(emailWithOpenTasks);
});

// Act
var result = await _crmQueryDispatcher.ExecuteQuery(new GetOpenTasksForEmailAddressQuery(emailWithOpenTasks));

// Assert
Assert.NotEmpty(result);
Assert.Collection(result, item1 =>
{
Assert.Equal(emailWithOpenTasks, item1.dfeta_EmailAddress);
});
}

[Fact]
public async Task WhenCalled_WithEmailWithCompletedTasks_ReturnsEmpty()
{
// Arrange
var person = await _dataScope.TestData.CreatePerson();
var emailWithOpenTasks = Faker.Internet.Email();
await _dataScope.TestData.CreateCrmTask(x =>
{
x.WithPersonId(person.ContactId);
x.WithEmailAddress(emailWithOpenTasks);
x.WithCompletedStatus();
});

// Act
var result = await _crmQueryDispatcher.ExecuteQuery(new GetOpenTasksForEmailAddressQuery(emailWithOpenTasks));

// Assert
Assert.Empty(result);
}

[Fact]
public async Task WhenCalled_WithEmailWithCancelledTasks_ReturnsEmpty()
{
// Arrange
var person = await _dataScope.TestData.CreatePerson();
var emailWithOpenTasks = Faker.Internet.Email();
await _dataScope.TestData.CreateCrmTask(x =>
{
x.WithPersonId(person.ContactId);
x.WithEmailAddress(emailWithOpenTasks);
x.WithCanceledStatus();
});

// Act
var result = await _crmQueryDispatcher.ExecuteQuery(new GetOpenTasksForEmailAddressQuery(emailWithOpenTasks));

// Assert
Assert.Empty(result);
}
}
Loading

0 comments on commit 72ef0ab

Please sign in to comment.