Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public static IServiceCollection AddSupportUiServices(this IServiceCollection se
.AddTransient<RequireClosedAlertFilter>()
.AddTransient<RequireOpenAlertFilter>()
.AddTransient<RedirectWithPersonIdFilter>()
.AddTransient<CheckPersonCanBeMergedFilter>()
.AddSingleton<ITagHelperInitializer<FormTagHelper>, FormTagHelperInitializer>()
.AddSingleton<ITagHelperInitializer<TextInputTagHelper>, TextInputTagHelperInitializer>()
.AddScoped<SupportUiFormContext>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using TeachingRecordSystem.Core.DataStore.Postgres;

namespace TeachingRecordSystem.SupportUi.Infrastructure.Filters;

public class CheckPersonCanBeMergedFilter(TrsDbContext dbContext) : IAsyncResourceFilter
{
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
var currentPerson = context.HttpContext.Features.GetRequiredFeature<CurrentPersonFeature>();
var person = await dbContext.Persons

Check failure on line 13 in TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Filters/CheckPersonCanBeMergedFilter.cs

View workflow job for this annotation

GitHub Actions / SupportUi.Tests test results

TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.MergePerson.CommonPageTests ► Post_PersonAIsDeactivated_ReturnsBadRequest(page: "check-answers")

Failed test found in: TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestResults/_runnervm4c2pk_2026-01-15_11_10_54.trx Error: System.InvalidOperationException : Sequence contains no elements.
Raw output
System.InvalidOperationException : Sequence contains no elements.
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at TeachingRecordSystem.SupportUi.Infrastructure.Filters.CheckPersonCanBeMergedFilter.OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Filters/CheckPersonCanBeMergedFilter.cs:line 13
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at TeachingRecordSystem.WebCommon.Middleware.TransactionScopeMiddleware.InvokeAsync(HttpContext context, TrsDbContext dbContext) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/src/TeachingRecordSystem.WebCommon/Middleware/TransactionScopeMiddleware.cs:line 16
   at Joonasw.AspNetCore.SecurityHeaders.Csp.CspMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpRequestDurationMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpRequestCountMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpInProgressMiddleware.Invoke(HttpContext context)
   at TeachingRecordSystem.SupportUi.Tests.HostFixture.ExecuteScheduledJobsStartupFilter.<>c.<<Configure>b__0_1>d.MoveNext() in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/HostFixture.cs:line 137
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.HttpContextBuilder.<>c__DisplayClass23_0.<<SendAsync>g__RunRequestAsync|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.ClientHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Testing.Handlers.CookieContainerHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.MergePerson.CommonPageTests.Post_PersonAIsDeactivated_ReturnsBadRequest(String page) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/MergePerson/CommonPageTests.cs:line 311
--- End of stack trace from previous location ---

Check failure on line 13 in TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Filters/CheckPersonCanBeMergedFilter.cs

View workflow job for this annotation

GitHub Actions / SupportUi.Tests test results

TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.MergePerson.CommonPageTests ► Post_PersonAIsDeactivated_ReturnsBadRequest(page: "enter-trn")

Failed test found in: TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestResults/_runnervm4c2pk_2026-01-15_11_10_54.trx Error: System.InvalidOperationException : Sequence contains no elements.
Raw output
System.InvalidOperationException : Sequence contains no elements.
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at TeachingRecordSystem.SupportUi.Infrastructure.Filters.CheckPersonCanBeMergedFilter.OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Filters/CheckPersonCanBeMergedFilter.cs:line 13
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at TeachingRecordSystem.WebCommon.Middleware.TransactionScopeMiddleware.InvokeAsync(HttpContext context, TrsDbContext dbContext) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/src/TeachingRecordSystem.WebCommon/Middleware/TransactionScopeMiddleware.cs:line 16
   at Joonasw.AspNetCore.SecurityHeaders.Csp.CspMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpRequestDurationMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpRequestCountMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpInProgressMiddleware.Invoke(HttpContext context)
   at TeachingRecordSystem.SupportUi.Tests.HostFixture.ExecuteScheduledJobsStartupFilter.<>c.<<Configure>b__0_1>d.MoveNext() in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/HostFixture.cs:line 137
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.HttpContextBuilder.<>c__DisplayClass23_0.<<SendAsync>g__RunRequestAsync|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.ClientHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Testing.Handlers.CookieContainerHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.MergePerson.CommonPageTests.Post_PersonAIsDeactivated_ReturnsBadRequest(String page) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/MergePerson/CommonPageTests.cs:line 311
--- End of stack trace from previous location ---

Check failure on line 13 in TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Filters/CheckPersonCanBeMergedFilter.cs

View workflow job for this annotation

GitHub Actions / SupportUi.Tests test results

TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.MergePerson.CommonPageTests ► Post_PersonAIsDeactivated_ReturnsBadRequest(page: "matches")

Failed test found in: TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestResults/_runnervm4c2pk_2026-01-15_11_10_54.trx Error: System.InvalidOperationException : Sequence contains no elements.
Raw output
System.InvalidOperationException : Sequence contains no elements.
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at TeachingRecordSystem.SupportUi.Infrastructure.Filters.CheckPersonCanBeMergedFilter.OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Filters/CheckPersonCanBeMergedFilter.cs:line 13
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at TeachingRecordSystem.WebCommon.Middleware.TransactionScopeMiddleware.InvokeAsync(HttpContext context, TrsDbContext dbContext) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/src/TeachingRecordSystem.WebCommon/Middleware/TransactionScopeMiddleware.cs:line 16
   at Joonasw.AspNetCore.SecurityHeaders.Csp.CspMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpRequestDurationMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpRequestCountMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpInProgressMiddleware.Invoke(HttpContext context)
   at TeachingRecordSystem.SupportUi.Tests.HostFixture.ExecuteScheduledJobsStartupFilter.<>c.<<Configure>b__0_1>d.MoveNext() in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/HostFixture.cs:line 137
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.HttpContextBuilder.<>c__DisplayClass23_0.<<SendAsync>g__RunRequestAsync|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.ClientHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Testing.Handlers.CookieContainerHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.MergePerson.CommonPageTests.Post_PersonAIsDeactivated_ReturnsBadRequest(String page) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/MergePerson/CommonPageTests.cs:line 311
--- End of stack trace from previous location ---

Check failure on line 13 in TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Filters/CheckPersonCanBeMergedFilter.cs

View workflow job for this annotation

GitHub Actions / SupportUi.Tests test results

TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.MergePerson.CommonPageTests ► Post_PersonAIsDeactivated_ReturnsBadRequest(page: "merge")

Failed test found in: TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestResults/_runnervm4c2pk_2026-01-15_11_10_54.trx Error: System.InvalidOperationException : Sequence contains no elements.
Raw output
System.InvalidOperationException : Sequence contains no elements.
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at TeachingRecordSystem.SupportUi.Infrastructure.Filters.CheckPersonCanBeMergedFilter.OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Filters/CheckPersonCanBeMergedFilter.cs:line 13
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at TeachingRecordSystem.WebCommon.Middleware.TransactionScopeMiddleware.InvokeAsync(HttpContext context, TrsDbContext dbContext) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/src/TeachingRecordSystem.WebCommon/Middleware/TransactionScopeMiddleware.cs:line 16
   at Joonasw.AspNetCore.SecurityHeaders.Csp.CspMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpRequestDurationMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpRequestCountMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpInProgressMiddleware.Invoke(HttpContext context)
   at TeachingRecordSystem.SupportUi.Tests.HostFixture.ExecuteScheduledJobsStartupFilter.<>c.<<Configure>b__0_1>d.MoveNext() in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/HostFixture.cs:line 137
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.HttpContextBuilder.<>c__DisplayClass23_0.<<SendAsync>g__RunRequestAsync|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.ClientHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Testing.Handlers.CookieContainerHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.MergePerson.CommonPageTests.Post_PersonAIsDeactivated_ReturnsBadRequest(String page) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/MergePerson/CommonPageTests.cs:line 311
--- End of stack trace from previous location ---

Check failure on line 13 in TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Filters/CheckPersonCanBeMergedFilter.cs

View workflow job for this annotation

GitHub Actions / SupportUi.Tests test results

TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.MergePerson.EnterTrnTests ► Post_PersonAIsDeactivated_ReturnsBadRequest

Failed test found in: TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/TestResults/_runnervm4c2pk_2026-01-15_11_10_54.trx Error: System.InvalidOperationException : Sequence contains no elements.
Raw output
System.InvalidOperationException : Sequence contains no elements.
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at TeachingRecordSystem.SupportUi.Infrastructure.Filters.CheckPersonCanBeMergedFilter.OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Infrastructure/Filters/CheckPersonCanBeMergedFilter.cs:line 13
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at TeachingRecordSystem.WebCommon.Middleware.TransactionScopeMiddleware.InvokeAsync(HttpContext context, TrsDbContext dbContext) in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/src/TeachingRecordSystem.WebCommon/Middleware/TransactionScopeMiddleware.cs:line 16
   at Joonasw.AspNetCore.SecurityHeaders.Csp.CspMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpRequestDurationMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpRequestCountMiddleware.Invoke(HttpContext context)
   at Prometheus.HttpMetrics.HttpInProgressMiddleware.Invoke(HttpContext context)
   at TeachingRecordSystem.SupportUi.Tests.HostFixture.ExecuteScheduledJobsStartupFilter.<>c.<<Configure>b__0_1>d.MoveNext() in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/HostFixture.cs:line 137
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.HttpContextBuilder.<>c__DisplayClass23_0.<<SendAsync>g__RunRequestAsync|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.ClientHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Testing.Handlers.CookieContainerHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.MergePerson.EnterTrnTests.Post_PersonAIsDeactivated_ReturnsBadRequest() in /home/runner/work/teaching-record-system/teaching-record-system/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/MergePerson/EnterTrnTests.cs:line 257
--- End of stack trace from previous location ---
.SingleAsync(p => p.PersonId == currentPerson.PersonId);

var hasOpenAlert = await dbContext.Alerts
.AnyAsync(a => a.PersonId == currentPerson.PersonId && a.EndDate == null);

var hasMandatoryQualification = await dbContext.MandatoryQualifications
.AnyAsync(mq => mq.PersonId == currentPerson.PersonId);

var hasProfessionalStatus =
person.EytsDate.HasValue ||
person.HasEyps ||
person.PqtsDate.HasValue ||
person.QtsDate.HasValue ||
person.QtlsStatus != QtlsStatus.None;

if (hasOpenAlert ||
hasMandatoryQualification ||
hasProfessionalStatus ||
person.InductionStatus != InductionStatus.None)
{
context.Result = new BadRequestResult();
return;
}

await next();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public class CheckPersonExistsFilterFactory : IFilterFactory, IOrderedFilter
{
public bool IsReusable => false;

public int Order => -200;
public int Order => FilterOrders.CheckPersonExistsFilterOrder;

public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
ActivatorUtilities.CreateInstance<CheckPersonExistsFilter>(serviceProvider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public class CheckSupportTaskExistsFilterFactory(bool openOnly, params SupportTa
{
public bool IsReusable => false;

public int Order => -200;
public int Order => FilterOrders.CheckSupportTaskExistsFilterOrder;

public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
ActivatorUtilities.CreateInstance<CheckSupportTaskExistsFilter>(serviceProvider, openOnly, supportTaskTypes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace TeachingRecordSystem.SupportUi.Infrastructure.Filters;

public class CheckUserExistsFilter : IAsyncResourceFilter, IOrderedFilter
{
public int Order => int.MinValue;
public int Order => FilterOrders.CheckUserExistsFilterOrder;

public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace TeachingRecordSystem.SupportUi.Infrastructure.Filters;

public class FilterOrders
{
public const int CheckUserExistsFilterOrder = int.MinValue;
public const int RedirectWithPersonIdFilterOrder = -1000;
public const int RequireFeatureEnabledFilterOrder = -300;
public const int CheckPersonExistsFilterOrder = -200;
public const int CheckMandatoryQualificationExistsFilterOrder = -200;
public const int CheckSupportTaskExistsFilterOrder = -200;
public const int CheckPersonCanBeMergedFilterOrder = -150;
public const int ActivateInstanceFilterOrder = -100; // Form Flow
public const int MissingInstanceFilterOrder = -100; // Form Flow
public const int CheckJourneyStepsFilterOrder = -10; // Form Flow
public const int CheckAlertExistsFilterOrder = 0;
public const int CheckRouteToProfessionalStatusExistsFilterOrder = 0;
public const int RequireOpenAlertFilterOrder = 1;
public const int RequireClosedAlertFilterOrder = 1;

public const int RequireActivePersonFilterOrder = 100;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public partial class RedirectWithPersonIdFilter(TrsDbContext dbContext) : IAsync
[GeneratedRegex(@"^\/persons\/([0-9]{7})($|\/)")]
private static partial Regex _personWithTrnPathRegex();

public static int Order => -1000;
public static int Order => FilterOrders.RedirectWithPersonIdFilterOrder;

public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ public void OnResourceExecuted(ResourceExecutedContext context)
{
}

public int Order => 100;
public int Order => FilterOrders.RequireActivePersonFilterOrder;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace TeachingRecordSystem.SupportUi.Infrastructure.Filters;

public class RequireClosedAlertFilter : IResourceFilter, IOrderedFilter
{
public int Order => 1; // After CheckAlertExistsFilter
public int Order => FilterOrders.RequireClosedAlertFilterOrder; // After CheckAlertExistsFilter

public void OnResourceExecuted(ResourceExecutedContext context)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class RequireFeatureEnabledFilterFactoryAttribute(string featureName) : A
{
public bool IsReusable => false;

public int Order => -300;
public int Order => FilterOrders.RequireFeatureEnabledFilterOrder;

public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
ActivatorUtilities.CreateInstance<RequireFeatureEnabledFilter>(serviceProvider, featureName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace TeachingRecordSystem.SupportUi.Infrastructure.Filters;

public class RequireOpenAlertFilter : IResourceFilter, IOrderedFilter
{
public int Order => 1; // After CheckAlertExistsFilter
public int Order => FilterOrders.RequireOpenAlertFilterOrder; // After CheckAlertExistsFilter

public void OnResourceExecuted(ResourceExecutedContext context)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public void Configure(RazorPagesOptions options)
this.GetFolderPathFromNamespace(),
model =>
{
model.Filters.Add(new ServiceFilterAttribute<CheckMandatoryQualificationExistsFilter>());
model.Filters.Add(new ServiceFilterAttribute<CheckMandatoryQualificationExistsFilter>() { Order = FilterOrders.CheckMandatoryQualificationExistsFilterOrder });
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public void Configure(RazorPagesOptions options)
this.GetFolderPathFromNamespace(),
model =>
{
model.Filters.Add(new ServiceFilterAttribute<CheckMandatoryQualificationExistsFilter>() { Order = -200 });
model.Filters.Add(new ServiceFilterAttribute<CheckMandatoryQualificationExistsFilter>() { Order = FilterOrders.CheckMandatoryQualificationExistsFilterOrder });
});
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using TeachingRecordSystem.SupportUi.Infrastructure.Filters;
using TeachingRecordSystem.SupportUi.Infrastructure.Security;

namespace TeachingRecordSystem.SupportUi.Pages.Persons.MergePerson;
Expand All @@ -12,6 +14,8 @@ public void Configure(RazorPagesOptions options)
this.GetFolderPathFromNamespace(),
model =>
{
model.Filters.Add(new CheckPersonExistsFilterFactory());
model.Filters.Add(new ServiceFilterAttribute<CheckPersonCanBeMergedFilter>() { Order = FilterOrders.CheckPersonCanBeMergedFilterOrder });
model.EndpointMetadata.Add(new AuthorizeAttribute()
{
Policy = AuthorizationPolicies.PersonDataEdit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ public class IndexModel(SupportUiLinkGenerator linkGenerator) : PageModel
[FromRoute]
public Guid PersonId { get; set; }

public IActionResult OnGet() => Redirect(linkGenerator.Persons.MergePerson.EnterTrn(PersonId, JourneyInstance!.InstanceId));
public IActionResult OnGet()
{
return Redirect(linkGenerator.Persons.MergePerson.EnterTrn(PersonId, JourneyInstance!.InstanceId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail;
[AllowDeactivatedPerson]
public class IndexModel(TrsDbContext dbContext, IAuthorizationService authorizationService) : PageModel
{
private static readonly InductionStatus[] _invalidInductionStatusesForMerge = [InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.Failed];

[FromRoute]
public Guid PersonId { get; set; }

Expand All @@ -33,6 +31,7 @@ public async Task OnGetAsync()
.IgnoreQueryFilters()
.Include(p => p.PreviousNames).AsSplitQuery()
.Include(p => p.Alerts).AsSplitQuery()
.Include(p => p.Qualifications).AsSplitQuery()
.SingleAsync(p => p.PersonId == PersonId);

Person = BuildPersonInfo(person);
Expand All @@ -48,12 +47,15 @@ public async Task OnGetAsync()
canEditPersonData &&
Person.IsActive;

var hasMandatoryQualification = person.Qualifications!
.Any(q => q.QualificationType == QualificationType.MandatoryQualification);

CanMerge =
canEditNonPersonOrAlertData &&
!HasOpenAlert &&
!hasMandatoryQualification &&
Person!.IsActive &&
(PersonProfessionalStatus is not PersonProfessionalStatusInfo professionalStatus ||
!_invalidInductionStatusesForMerge.Contains(professionalStatus.InductionStatus));
PersonProfessionalStatus is not PersonProfessionalStatusInfo professionalStatus;

// Person cannot be reactivated if they were deactivated as part of a merge
// where they were merged into another Person (i.e. they were the secondary
Expand Down
Loading
Loading