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 Jul 25, 2024
1 parent 1d114b2 commit 7aed59e
Show file tree
Hide file tree
Showing 16 changed files with 247 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,15 @@ 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,16 @@
@page "/request-trn/emailinuse"
@model TeachingRecordSystem.AuthorizeAccess.Pages.RequestTrn.EmailInUseModel
@{
}

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

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

namespace TeachingRecordSystem.AuthorizeAccess.Pages.RequestTrn;

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

public void OnGet()
{
}
}

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,28 @@
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
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class CreateCrmTaskBuilder
private string? _description = null;
private DateTime? _dueDate = null;
private TaskState? _stateCode = null;
private string? _emailAddress = null;

public CreateCrmTaskBuilder WithPersonId(Guid personId)
{
Expand All @@ -46,6 +47,17 @@ public CreateCrmTaskBuilder WithCategory(string category)
return this;
}

public CreateCrmTaskBuilder WithEmailAddress(string email)
{
if (_emailAddress is not null && _emailAddress != email)
{
throw new InvalidOperationException("WithEmailAddress has already been set");
}

_emailAddress = email;
return this;
}

public CreateCrmTaskBuilder WithSubject(string subject)
{
if (_subject is not null && _subject != subject)
Expand Down Expand Up @@ -132,7 +144,8 @@ public async Task Execute(TestData crmTestData)
Subject = subject,
Description = description,
ScheduledEnd = _dueDate,
StateCode = stateCode
StateCode = stateCode,
dfeta_EmailAddress = _emailAddress
};

var txnRequestBuilder = RequestBuilder.CreateTransaction(crmTestData.OrganizationService);
Expand Down
3 changes: 2 additions & 1 deletion crm_attributes.json
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@
"regardingobjectid",
"scheduledend",
"statecode",
"subject"
"subject",
"dfeta_EmailAddress"
]
}
Loading

0 comments on commit 7aed59e

Please sign in to comment.