Skip to content

Commit f251b35

Browse files
committed
Thread ProcessStateChange through ProcessEngine.Start to fulfill nullability contract.
1 parent bb98491 commit f251b35

File tree

7 files changed

+42
-27
lines changed

7 files changed

+42
-27
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ The libraries integrate with:
115115
- Use internal accessibility on types by default
116116
- Use sealed for classes unless we consider inheritance a valid use-case
117117
- Use Nullable Reference Types
118+
- Don't use null forgiveness operator without justification
118119
- Remember to dispose `IDisposable`/`IAsyncDisposable` instances
119120
- We want to minimize external dependencies
120121
- For HTTP APIs we should have `...Request` and `...Response` DTOs (see `LookupPersonRequest.cs` and the corresponding response as an example)

src/Altinn.App.Api/Controllers/InstancesController.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -381,14 +381,17 @@ await _instanceClient.DeleteInstance(
381381
Guid.Parse(instance.Id.Split("/")[1])
382382
);
383383

384-
_logger.LogInformation("Events sent to process engine: {Events}", change?.Events);
384+
if (change is null)
385+
throw new InvalidOperationException("ProcessStateChange was not set by GenerateProcessStartEvents");
386+
385387
ProcessChangeResult startProcessResult = await _processEngine.Start(
386388
instance,
387-
change?.Events,
389+
change,
388390
User,
389391
language: language,
390392
ct: HttpContext.RequestAborted
391393
);
394+
392395
if (!startProcessResult.Success)
393396
return Conflict(startProcessResult.ErrorMessage);
394397

@@ -607,16 +610,22 @@ public async Task<ActionResult<InstanceResponse>> PostSimplified(
607610
}
608611

609612
instance = await _instanceClient.GetInstance(instance);
613+
614+
if (!processResult.Success)
615+
return Conflict(processResult.ErrorMessage);
616+
610617
var startProcessResult = await _processEngine.Start(
611618
instance,
612-
processResult.ProcessStateChange?.Events,
619+
processResult.ProcessStateChange,
613620
User,
614621
prefill: instansiationInstance.Prefill,
615622
language: language,
616623
ct: HttpContext.RequestAborted
617624
);
625+
618626
if (!startProcessResult.Success)
619627
return Conflict(startProcessResult.ErrorMessage);
628+
620629
instance = startProcessResult.MutatedInstance;
621630
}
622631
catch (Exception exception)
@@ -739,15 +748,20 @@ public async Task<ActionResult> CopyInstance(
739748

740749
targetInstance = await _instanceClient.GetInstance(targetInstance);
741750

751+
if (!startResult.Success)
752+
return Conflict(startResult.ErrorMessage);
753+
742754
var startProcessResult = await _processEngine.Start(
743755
targetInstance,
744-
startResult.ProcessStateChange?.Events,
756+
startResult.ProcessStateChange,
745757
User,
746758
language: language,
747759
ct: HttpContext.RequestAborted
748760
);
761+
749762
if (!startProcessResult.Success)
750763
return Conflict(startProcessResult.ErrorMessage);
764+
751765
targetInstance = startProcessResult.MutatedInstance;
752766

753767
await RegisterEvent("app.instance.created", targetInstance);

src/Altinn.App.Api/Controllers/ProcessController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ public async Task<ActionResult<AppProcessState>> StartProcess(
149149

150150
var startProcessResult = await _processEngine.Start(
151151
instance,
152-
result.ProcessStateChange?.Events,
152+
result.ProcessStateChange,
153153
User,
154154
ct: HttpContext.RequestAborted
155155
);

src/Altinn.App.Core/Internal/Process/Interfaces/IProcessEngine.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ Task<Instance> HandleEventsAndUpdateStorage(
3939

4040
/// <summary>
4141
/// Dispatches process start events to storage and auto-runs any initial service tasks.
42-
/// Call after instance creation and data storage, with the events from <see cref="GenerateProcessStartEvents"/>.
42+
/// Call after instance creation and data storage, with the result from <see cref="GenerateProcessStartEvents"/>.
4343
/// </summary>
4444
Task<ProcessChangeResult> Start(
4545
Instance instance,
46-
List<InstanceEvent>? events,
46+
ProcessStateChange processStateChange,
4747
ClaimsPrincipal user,
4848
Dictionary<string, string>? prefill = null,
4949
string? language = null,

src/Altinn.App.Core/Internal/Process/ProcessEngine.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ out ProcessError? startEventError
153153
/// <inheritdoc/>
154154
public async Task<ProcessChangeResult> Start(
155155
Instance instance,
156-
List<InstanceEvent>? events,
156+
ProcessStateChange processStateChange,
157157
ClaimsPrincipal user,
158158
Dictionary<string, string>? prefill = null,
159159
string? language = null,
@@ -163,10 +163,10 @@ public async Task<ProcessChangeResult> Start(
163163
// HandleEvents mutates instance in-place; we discard the DispatchToStorage
164164
// return (same as callers did before this method existed).
165165
bool isServiceTask = IsServiceTask(instance);
166-
await HandleEventsAndUpdateStorage(instance, isServiceTask ? null : prefill, events);
166+
await HandleEventsAndUpdateStorage(instance, isServiceTask ? null : prefill, processStateChange.Events);
167167

168168
if (!isServiceTask)
169-
return new ProcessChangeResult(instance) { Success = true };
169+
return new ProcessChangeResult(instance) { Success = true, ProcessStateChange = processStateChange };
170170

171171
// Auto-run through initial service tasks, forwarding prefill to the first user task
172172
return await Next(

test/Altinn.App.Api.Tests/Controllers/InstancesController_CopyInstanceTests.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -379,14 +379,14 @@ public async Task CopyInstance_EverythingIsFine_ReturnsRedirect()
379379
.Setup(p => p.GenerateProcessStartEvents(It.IsAny<ProcessStartRequest>()))
380380
.ReturnsAsync(() =>
381381
{
382-
return new ProcessChangeResult() { Success = true };
382+
return new ProcessChangeResult() { Success = true, ProcessStateChange = new() };
383383
});
384384
fixture
385385
.Mock<IProcessEngine>()
386386
.Setup(p =>
387387
p.Start(
388388
It.IsAny<Instance>(),
389-
It.IsAny<List<InstanceEvent>>(),
389+
It.IsAny<ProcessStateChange>(),
390390
It.IsAny<System.Security.Claims.ClaimsPrincipal>(),
391391
It.IsAny<Dictionary<string, string>>(),
392392
It.IsAny<string>(),
@@ -396,7 +396,7 @@ public async Task CopyInstance_EverythingIsFine_ReturnsRedirect()
396396
.ReturnsAsync(
397397
(
398398
Instance inst,
399-
List<InstanceEvent>? _,
399+
ProcessStateChange _,
400400
System.Security.Claims.ClaimsPrincipal _,
401401
Dictionary<string, string>? _,
402402
string? _,
@@ -513,13 +513,13 @@ public async Task CopyInstance_WithBinaryData_CopiesBothFormAndBinaryData()
513513
fixture
514514
.Mock<IProcessEngine>()
515515
.Setup(p => p.GenerateProcessStartEvents(It.IsAny<ProcessStartRequest>()))
516-
.ReturnsAsync(() => new ProcessChangeResult() { Success = true });
516+
.ReturnsAsync(() => new ProcessChangeResult() { Success = true, ProcessStateChange = new() });
517517
fixture
518518
.Mock<IProcessEngine>()
519519
.Setup(p =>
520520
p.Start(
521521
It.IsAny<Instance>(),
522-
It.IsAny<List<InstanceEvent>>(),
522+
It.IsAny<ProcessStateChange>(),
523523
It.IsAny<System.Security.Claims.ClaimsPrincipal>(),
524524
It.IsAny<Dictionary<string, string>>(),
525525
It.IsAny<string>(),
@@ -529,7 +529,7 @@ public async Task CopyInstance_WithBinaryData_CopiesBothFormAndBinaryData()
529529
.ReturnsAsync(
530530
(
531531
Instance inst,
532-
List<InstanceEvent>? _,
532+
ProcessStateChange _,
533533
System.Security.Claims.ClaimsPrincipal _,
534534
Dictionary<string, string>? _,
535535
string? _,
@@ -725,13 +725,13 @@ public async Task CopyInstance_WithExcludedBinaryDataType_SkipsExcludedType()
725725
fixture
726726
.Mock<IProcessEngine>()
727727
.Setup(p => p.GenerateProcessStartEvents(It.IsAny<ProcessStartRequest>()))
728-
.ReturnsAsync(() => new ProcessChangeResult() { Success = true });
728+
.ReturnsAsync(() => new ProcessChangeResult() { Success = true, ProcessStateChange = new() });
729729
fixture
730730
.Mock<IProcessEngine>()
731731
.Setup(p =>
732732
p.Start(
733733
It.IsAny<Instance>(),
734-
It.IsAny<List<InstanceEvent>>(),
734+
It.IsAny<ProcessStateChange>(),
735735
It.IsAny<System.Security.Claims.ClaimsPrincipal>(),
736736
It.IsAny<Dictionary<string, string>>(),
737737
It.IsAny<string>(),
@@ -741,7 +741,7 @@ public async Task CopyInstance_WithExcludedBinaryDataType_SkipsExcludedType()
741741
.ReturnsAsync(
742742
(
743743
Instance inst,
744-
List<InstanceEvent>? _,
744+
ProcessStateChange _,
745745
System.Security.Claims.ClaimsPrincipal _,
746746
Dictionary<string, string>? _,
747747
string? _,
@@ -936,13 +936,13 @@ public async Task CopyInstance_IncludeAttachmentsIsTrue_CopiesBinaryData()
936936
fixture
937937
.Mock<IProcessEngine>()
938938
.Setup(p => p.GenerateProcessStartEvents(It.IsAny<ProcessStartRequest>()))
939-
.ReturnsAsync(() => new ProcessChangeResult() { Success = true });
939+
.ReturnsAsync(() => new ProcessChangeResult() { Success = true, ProcessStateChange = new() });
940940
fixture
941941
.Mock<IProcessEngine>()
942942
.Setup(p =>
943943
p.Start(
944944
It.IsAny<Instance>(),
945-
It.IsAny<List<InstanceEvent>>(),
945+
It.IsAny<ProcessStateChange>(),
946946
It.IsAny<System.Security.Claims.ClaimsPrincipal>(),
947947
It.IsAny<Dictionary<string, string>>(),
948948
It.IsAny<string>(),
@@ -952,7 +952,7 @@ public async Task CopyInstance_IncludeAttachmentsIsTrue_CopiesBinaryData()
952952
.ReturnsAsync(
953953
(
954954
Instance inst,
955-
List<InstanceEvent>? _,
955+
ProcessStateChange _,
956956
System.Security.Claims.ClaimsPrincipal _,
957957
Dictionary<string, string>? _,
958958
string? _,
@@ -1154,13 +1154,13 @@ public async Task CopyInstance_OnlyBinaryData_NotCopiedByDefault()
11541154
fixture
11551155
.Mock<IProcessEngine>()
11561156
.Setup(p => p.GenerateProcessStartEvents(It.IsAny<ProcessStartRequest>()))
1157-
.ReturnsAsync(() => new ProcessChangeResult() { Success = true });
1157+
.ReturnsAsync(() => new ProcessChangeResult() { Success = true, ProcessStateChange = new() });
11581158
fixture
11591159
.Mock<IProcessEngine>()
11601160
.Setup(p =>
11611161
p.Start(
11621162
It.IsAny<Instance>(),
1163-
It.IsAny<List<InstanceEvent>>(),
1163+
It.IsAny<ProcessStateChange>(),
11641164
It.IsAny<System.Security.Claims.ClaimsPrincipal>(),
11651165
It.IsAny<Dictionary<string, string>>(),
11661166
It.IsAny<string>(),
@@ -1170,7 +1170,7 @@ public async Task CopyInstance_OnlyBinaryData_NotCopiedByDefault()
11701170
.ReturnsAsync(
11711171
(
11721172
Instance inst,
1173-
List<InstanceEvent>? _,
1173+
ProcessStateChange _,
11741174
System.Security.Claims.ClaimsPrincipal _,
11751175
Dictionary<string, string>? _,
11761176
string? _,

test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3693,7 +3693,7 @@ namespace Altinn.App.Core.Internal.Process
36933693
System.Threading.Tasks.Task<Altinn.App.Core.Models.Process.ProcessChangeResult> GenerateProcessStartEvents(Altinn.App.Core.Models.Process.ProcessStartRequest processStartRequest);
36943694
System.Threading.Tasks.Task<Altinn.Platform.Storage.Interface.Models.Instance> HandleEventsAndUpdateStorage(Altinn.Platform.Storage.Interface.Models.Instance instance, System.Collections.Generic.Dictionary<string, string>? prefill, System.Collections.Generic.List<Altinn.Platform.Storage.Interface.Models.InstanceEvent>? events);
36953695
System.Threading.Tasks.Task<Altinn.App.Core.Models.Process.ProcessChangeResult> Next(Altinn.App.Core.Models.Process.ProcessNextRequest request, System.Threading.CancellationToken ct = default);
3696-
System.Threading.Tasks.Task<Altinn.App.Core.Models.Process.ProcessChangeResult> Start(Altinn.Platform.Storage.Interface.Models.Instance instance, System.Collections.Generic.List<Altinn.Platform.Storage.Interface.Models.InstanceEvent>? events, System.Security.Claims.ClaimsPrincipal user, System.Collections.Generic.Dictionary<string, string>? prefill = null, string? language = null, System.Threading.CancellationToken ct = default);
3696+
System.Threading.Tasks.Task<Altinn.App.Core.Models.Process.ProcessChangeResult> Start(Altinn.Platform.Storage.Interface.Models.Instance instance, Altinn.App.Core.Models.Process.ProcessStateChange processStateChange, System.Security.Claims.ClaimsPrincipal user, System.Collections.Generic.Dictionary<string, string>? prefill = null, string? language = null, System.Threading.CancellationToken ct = default);
36973697
}
36983698
public interface IProcessEngineAuthorizer
36993699
{
@@ -3740,7 +3740,7 @@ namespace Altinn.App.Core.Internal.Process
37403740
public System.Threading.Tasks.Task<Altinn.App.Core.Models.Process.ProcessChangeResult> GenerateProcessStartEvents(Altinn.App.Core.Models.Process.ProcessStartRequest processStartRequest) { }
37413741
public System.Threading.Tasks.Task<Altinn.Platform.Storage.Interface.Models.Instance> HandleEventsAndUpdateStorage(Altinn.Platform.Storage.Interface.Models.Instance instance, System.Collections.Generic.Dictionary<string, string>? prefill, System.Collections.Generic.List<Altinn.Platform.Storage.Interface.Models.InstanceEvent>? events) { }
37423742
public System.Threading.Tasks.Task<Altinn.App.Core.Models.Process.ProcessChangeResult> Next(Altinn.App.Core.Models.Process.ProcessNextRequest request, System.Threading.CancellationToken ct = default) { }
3743-
public System.Threading.Tasks.Task<Altinn.App.Core.Models.Process.ProcessChangeResult> Start(Altinn.Platform.Storage.Interface.Models.Instance instance, System.Collections.Generic.List<Altinn.Platform.Storage.Interface.Models.InstanceEvent>? events, System.Security.Claims.ClaimsPrincipal user, System.Collections.Generic.Dictionary<string, string>? prefill = null, string? language = null, System.Threading.CancellationToken ct = default) { }
3743+
public System.Threading.Tasks.Task<Altinn.App.Core.Models.Process.ProcessChangeResult> Start(Altinn.Platform.Storage.Interface.Models.Instance instance, Altinn.App.Core.Models.Process.ProcessStateChange processStateChange, System.Security.Claims.ClaimsPrincipal user, System.Collections.Generic.Dictionary<string, string>? prefill = null, string? language = null, System.Threading.CancellationToken ct = default) { }
37443744
}
37453745
public class ProcessEventDispatcher : Altinn.App.Core.Internal.Process.IProcessEventDispatcher
37463746
{

0 commit comments

Comments
 (0)