fix(runtime): read InternalState from logPersistenceConfig (#8126)

* fix(runtime): read InternalState from logPersistenceConfig

Honor Studio-written customProperties.logPersistenceConfig.internalState
and fall back to workflow internalState, then default/ResolveMode.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* test(runtime): register activity context in InternalState evaluator tests

ActivityTestFixture.BuildAsync does not add the context to
WorkflowExecutionContext.ActivityExecutionContexts, which
GetPersistenceDefaultsAsync requires.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* fix(runtime): evaluate workflow InternalState with root context

Use the workflow/root ExpressionExecutionContext for workflow-level
internalState (parity with default), and keep the activity context
for the activity override. Look up component records by activity name.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
Sipke Schoorstra 2026-09-13 23:09:36 +02:00 committed by GitHub
parent ab93c67623
commit d4c00f13ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 607 additions and 10 deletions

View file

@ -69,12 +69,18 @@ public class ActivityPropertyLogPersistenceEvaluator : IActivityPropertyLogPersi
public async Task<ActivityLogPersistenceModeMap> EvaluateLogPersistenceModesAsync(ActivityExecutionContext context)
{
var cancellationToken = context.CancellationToken;
var (legacyProps, configProps, defaultMode) = await GetPersistenceDefaultsAsync(context, cancellationToken);
var (legacyProps, configProps, defaultMode, workflow, rootContext) = await GetPersistenceDefaultsAsync(context, cancellationToken);
var map = new ActivityLogPersistenceModeMap();
await EvaluatePropertiesAsync(context, "inputs", context.ActivityDescriptor.Inputs, legacyProps, configProps, defaultMode, map.Inputs, cancellationToken);
await EvaluatePropertiesAsync(context, "outputs", context.ActivityDescriptor.Outputs, legacyProps, configProps, defaultMode, map.Outputs, cancellationToken);
map.InternalState = await EvaluateInternalStateModeAsync(context.ExpressionExecutionContext, context.Activity.CustomProperties, defaultMode, cancellationToken);
map.InternalState = await EvaluateInternalStateModeAsync(
context.ExpressionExecutionContext,
rootContext.ExpressionExecutionContext,
configProps,
workflow.CustomProperties,
defaultMode,
cancellationToken);
return map;
}
@ -82,12 +88,12 @@ public class ActivityPropertyLogPersistenceEvaluator : IActivityPropertyLogPersi
public async Task<Dictionary<string, object>> GetPersistableOutputAsync(ActivityExecutionContext context)
{
var cancellationToken = context.WorkflowExecutionContext.CancellationToken;
var (legacyProps, configProps, defaultMode) = await GetPersistenceDefaultsAsync(context, cancellationToken);
var (legacyProps, configProps, defaultMode, _, _) = await GetPersistenceDefaultsAsync(context, cancellationToken);
var outputs = context.GetOutputs();
return await GetPersistablePropertiesAsync(context, outputs, "outputs", legacyProps, configProps, defaultMode, cancellationToken);
}
private async Task<(IDictionary<string, object> legacyProps, IDictionary<string, object> configProps, LogPersistenceMode defaultMode)> GetPersistenceDefaultsAsync(ActivityExecutionContext context, CancellationToken cancellationToken)
private async Task<(IDictionary<string, object> legacyProps, IDictionary<string, object> configProps, LogPersistenceMode defaultMode, Workflow workflow, ActivityExecutionContext rootContext)> GetPersistenceDefaultsAsync(ActivityExecutionContext context, CancellationToken cancellationToken)
{
var legacyProps = context.Activity.CustomProperties.GetValueOrDefault<IDictionary<string, object>>(LegacyKey, () => new Dictionary<string, object>())!;
var rootContext = context.WorkflowExecutionContext.ActivityExecutionContexts.First(x => x.ParentActivityExecutionContext == null);
@ -95,7 +101,7 @@ public class ActivityPropertyLogPersistenceEvaluator : IActivityPropertyLogPersi
var workflowDefault = await GetDefaultPersistenceModeAsync(rootContext.ExpressionExecutionContext, workflow.CustomProperties, () => _options.Value.LogPersistenceMode, cancellationToken);
var activityDefault = await GetDefaultPersistenceModeAsync(context.ExpressionExecutionContext, context.Activity.CustomProperties, () => workflowDefault, cancellationToken);
var configProps = context.Activity.CustomProperties.GetValueOrDefault<IDictionary<string, object>>(ConfigKey, () => new Dictionary<string, object>())!;
return (legacyProps, configProps, activityDefault);
return (legacyProps, configProps, activityDefault, workflow, rootContext);
}
private async Task EvaluatePropertiesAsync(
@ -136,15 +142,30 @@ public class ActivityPropertyLogPersistenceEvaluator : IActivityPropertyLogPersi
}
private async Task<LogPersistenceMode> EvaluateInternalStateModeAsync(
ExpressionExecutionContext executionContext,
IDictionary<string, object> currentConfig,
ExpressionExecutionContext activityExecutionContext,
ExpressionExecutionContext workflowExecutionContext,
IDictionary<string, object> activityConfig,
IDictionary<string, object> workflowProperties,
LogPersistenceMode defaultMode,
CancellationToken cancellationToken)
{
var configObject = currentConfig.GetValueOrDefault("internalState", () => new Dictionary<string, object>())!;
var workflowConfig = workflowProperties.GetValueOrDefault<IDictionary<string, object>>(ConfigKey, () => new Dictionary<string, object>())!;
var workflowInternalState = await EvaluateInternalStateConfigAsync(workflowExecutionContext, workflowConfig, () => defaultMode, cancellationToken);
return await EvaluateInternalStateConfigAsync(activityExecutionContext, activityConfig, () => workflowInternalState, cancellationToken);
}
private async Task<LogPersistenceMode> EvaluateInternalStateConfigAsync(
ExpressionExecutionContext executionContext,
IDictionary<string, object> configProps,
Func<LogPersistenceMode> defaultFactory,
CancellationToken cancellationToken)
{
var configObject = configProps.GetValueOrDefault("internalState", () => null);
var config = ConvertToConfig(configObject);
if (config != null) return await EvaluateConfigAsync(config, executionContext, () => defaultMode, cancellationToken);
return LogPersistenceMode.Inherit;
if (config != null)
return await EvaluateConfigAsync(config, executionContext, defaultFactory, cancellationToken);
return ResolveMode(LogPersistenceMode.Inherit, defaultFactory);
}
private async Task<Dictionary<string, object>> GetPersistablePropertiesAsync(

View file

@ -54,6 +54,12 @@
<None Update="Scenarios\LogPersistenceModes\input-output-logging-3-child.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Scenarios\LogPersistenceModes\internal-state-logging-activity.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Scenarios\LogPersistenceModes\internal-state-logging-workflow.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Scenarios\WorkflowCompletion\hello-world.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>

View file

@ -0,0 +1,68 @@
using Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts;
using Elsa.Api.Client.Resources.WorkflowDefinitions.Responses;
using Elsa.Testing.Shared.Extensions;
using Elsa.Workflows.Activities;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Helpers;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Entities;
using Elsa.Workflows.Runtime.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.LogPersistenceModes;
public class InternalStateLoggingTests(App app) : AppComponentTest(app)
{
[Fact]
public async Task ActivityInternalState_ShouldBeIndependentOfDefaultMode()
{
var records = await ExecuteAndGetWriteLinesAsync("internal-state-logging-activity");
AssertInternalState(GetRecord(records, "WriteLine1"), textIncluded: false, internalStateIncluded: true);
AssertInternalState(GetRecord(records, "WriteLine2"), textIncluded: true, internalStateIncluded: false);
}
[Fact]
public async Task WorkflowInternalState_ShouldApplyWhenActivityInternalStateIsMissing()
{
var records = await ExecuteAndGetWriteLinesAsync("internal-state-logging-workflow");
AssertInternalState(GetRecord(records, "WriteLine1"), textIncluded: false, internalStateIncluded: true);
AssertInternalState(GetRecord(records, "WriteLine2"), textIncluded: false, internalStateIncluded: false);
}
private static ActivityExecutionRecord GetRecord(IReadOnlyList<ActivityExecutionRecord> records, string activityName)
{
return records.Single(x => x.ActivityName == activityName);
}
private static void AssertInternalState(ActivityExecutionRecord record, bool textIncluded, bool internalStateIncluded)
{
Assert.Equal(textIncluded, record.ActivityState?.ContainsKey(nameof(WriteLine.Text)) == true);
if (internalStateIncluded)
{
Assert.NotNull(record.Properties);
return;
}
Assert.Null(record.Properties);
Assert.Null(record.Payload);
}
private async Task<IReadOnlyList<ActivityExecutionRecord>> ExecuteAndGetWriteLinesAsync(string workflowDefinitionId)
{
var client = WorkflowServer.CreateApiClient<IExecuteWorkflowApi>();
using var response = await client.ExecuteAsync(workflowDefinitionId);
var model = await response.ReadAsJsonAsync<ExecuteWorkflowDefinitionResponse>(WorkflowServer.Services);
var writeLineActivityTypeName = ActivityTypeNameHelper.GenerateTypeName<WriteLine>();
var store = Scope.ServiceProvider.GetRequiredService<IActivityExecutionStore>();
var records = await store.FindManyAsync(new ActivityExecutionRecordFilter
{
WorkflowInstanceId = model.WorkflowState.Id
});
return records.Where(x => x.ActivityType == writeLineActivityTypeName).ToList();
}
}

View file

@ -0,0 +1,129 @@
{
"id": "c1a8e6d04f7b2a91",
"definitionId": "internal-state-logging-activity",
"name": "Internal State Logging Activity",
"createdAt": "2026-09-13T20:00:00+00:00",
"version": 1,
"toolVersion": "3.9.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "f4b21c8e9a6d3057",
"nodeId": "Workflow1:f4b21c8e9a6d3057",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Default exclude, internal state include"
}
},
"id": "a91c3e7b5d204f18",
"nodeId": "Workflow1:f4b21c8e9a6d3057:a91c3e7b5d204f18",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"logPersistenceConfig": {
"default": {
"evaluationMode": "Strategy",
"strategyType": "Elsa.Workflows.LogPersistence.Strategies.Exclude, Elsa.Workflows.Core",
"expression": null
},
"internalState": {
"evaluationMode": "Strategy",
"strategyType": "Elsa.Workflows.LogPersistence.Strategies.Include, Elsa.Workflows.Core",
"expression": null
},
"inputs": {},
"outputs": {}
},
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 0,
"y": 0
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Default include, internal state exclude"
}
},
"id": "b02d4f8c6e315a29",
"nodeId": "Workflow1:f4b21c8e9a6d3057:b02d4f8c6e315a29",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"logPersistenceConfig": {
"default": {
"evaluationMode": "Strategy",
"strategyType": "Elsa.Workflows.LogPersistence.Strategies.Include, Elsa.Workflows.Core",
"expression": null
},
"internalState": {
"evaluationMode": "Strategy",
"strategyType": "Elsa.Workflows.LogPersistence.Strategies.Exclude, Elsa.Workflows.Core",
"expression": null
},
"inputs": {},
"outputs": {}
},
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 220,
"y": 0
}
}
}
}
],
"connections": [
{
"source": {
"activity": "a91c3e7b5d204f18",
"port": "Done"
},
"target": {
"activity": "b02d4f8c6e315a29",
"port": "In"
}
}
]
}
}

View file

@ -0,0 +1,122 @@
{
"id": "d2b9f7e15a8c3b04",
"definitionId": "internal-state-logging-workflow",
"name": "Internal State Logging Workflow",
"createdAt": "2026-09-13T20:00:00+00:00",
"version": 1,
"toolVersion": "3.9.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": [],
"logPersistenceConfig": {
"default": {
"evaluationMode": "Strategy",
"strategyType": "Elsa.Workflows.LogPersistence.Strategies.Exclude, Elsa.Workflows.Core",
"expression": null
},
"internalState": {
"evaluationMode": "Strategy",
"strategyType": "Elsa.Workflows.LogPersistence.Strategies.Include, Elsa.Workflows.Core",
"expression": null
}
}
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "e5c32d9f0b7e4168",
"nodeId": "Workflow1:e5c32d9f0b7e4168",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Inherits workflow internal state include"
}
},
"id": "c13e5a9d7f426b30",
"nodeId": "Workflow1:e5c32d9f0b7e4168:c13e5a9d7f426b30",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 0,
"y": 0
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Overrides workflow internal state to exclude"
}
},
"id": "d24f6b0e8a537c41",
"nodeId": "Workflow1:e5c32d9f0b7e4168:d24f6b0e8a537c41",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"logPersistenceConfig": {
"internalState": {
"evaluationMode": "Strategy",
"strategyType": "Elsa.Workflows.LogPersistence.Strategies.Exclude, Elsa.Workflows.Core",
"expression": null
},
"inputs": {},
"outputs": {}
},
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 220,
"y": 0
}
}
}
}
],
"connections": [
{
"source": {
"activity": "c13e5a9d7f426b30",
"port": "Done"
},
"target": {
"activity": "d24f6b0e8a537c41",
"port": "In"
}
}
]
}
}

View file

@ -0,0 +1,180 @@
using Elsa.Expressions.Contracts;
using Elsa.Expressions.Models;
using Elsa.Extensions;
using Elsa.Testing.Shared;
using Elsa.Workflows.Activities;
using Elsa.Workflows.LogPersistence;
using Elsa.Workflows.LogPersistence.Strategies;
using Elsa.Workflows.Management.Options;
using Elsa.Workflows.Options;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
namespace Elsa.Workflows.Runtime.UnitTests.LogPersistence;
public class ActivityPropertyLogPersistenceEvaluatorTests
{
[Fact]
public async Task Evaluate_HonorsActivityInternalStateInclude_WhenDefaultIsExclude()
{
var activity = CreateWriteLine(defaultMode: typeof(Exclude), internalState: typeof(Include));
var map = await EvaluateAsync(activity);
Assert.Equal(LogPersistenceMode.Include, map.InternalState);
Assert.Equal(LogPersistenceMode.Exclude, map.Inputs[nameof(WriteLine.Text)]);
}
[Fact]
public async Task Evaluate_HonorsActivityInternalStateExclude_WhenDefaultIsInclude()
{
var activity = CreateWriteLine(defaultMode: typeof(Include), internalState: typeof(Exclude));
var map = await EvaluateAsync(activity);
Assert.Equal(LogPersistenceMode.Exclude, map.InternalState);
Assert.Equal(LogPersistenceMode.Include, map.Inputs[nameof(WriteLine.Text)]);
}
[Fact]
public async Task Evaluate_FallsBackToWorkflowInternalState_WhenActivityInternalStateIsMissing()
{
var activity = CreateWriteLine(defaultMode: typeof(Exclude));
var map = await EvaluateAsync(activity, workflow =>
{
workflow.CustomProperties["logPersistenceConfig"] = new Dictionary<string, object>
{
["default"] = Strategy(typeof(Exclude)),
["internalState"] = Strategy(typeof(Include))
};
});
Assert.Equal(LogPersistenceMode.Include, map.InternalState);
Assert.Equal(LogPersistenceMode.Exclude, map.Inputs[nameof(WriteLine.Text)]);
}
[Fact]
public async Task Evaluate_ActivityInternalStateOverridesWorkflowInternalState()
{
var activity = CreateWriteLine(defaultMode: typeof(Exclude), internalState: typeof(Exclude));
var map = await EvaluateAsync(activity, workflow =>
{
workflow.CustomProperties["logPersistenceConfig"] = new Dictionary<string, object>
{
["default"] = Strategy(typeof(Exclude)),
["internalState"] = Strategy(typeof(Include))
};
});
Assert.Equal(LogPersistenceMode.Exclude, map.InternalState);
}
[Fact]
public async Task Evaluate_FallsBackToDefault_WhenInternalStateIsMissing()
{
var activity = CreateWriteLine(defaultMode: typeof(Exclude));
var map = await EvaluateAsync(activity);
Assert.Equal(LogPersistenceMode.Exclude, map.InternalState);
Assert.Equal(LogPersistenceMode.Exclude, map.Inputs[nameof(WriteLine.Text)]);
}
[Fact]
public async Task Evaluate_IgnoresTopLevelCustomPropertiesInternalState()
{
var activity = CreateWriteLine(defaultMode: typeof(Exclude));
activity.CustomProperties["internalState"] = Strategy(typeof(Include));
var map = await EvaluateAsync(activity);
Assert.Equal(LogPersistenceMode.Exclude, map.InternalState);
}
[Fact]
public async Task Evaluate_UsesWorkflowRootContext_ForWorkflowInternalStateExpression()
{
var activity = CreateWriteLine(defaultMode: typeof(Exclude));
var fixture = CreateFixture(activity);
var seed = await fixture.BuildAsync();
var workflowExecutionContext = seed.WorkflowExecutionContext;
var rootContext = await workflowExecutionContext.CreateActivityExecutionContextAsync(workflowExecutionContext.Workflow);
var childContext = await workflowExecutionContext.CreateActivityExecutionContextAsync(activity, new ActivityInvocationOptions
{
Owner = rootContext
});
workflowExecutionContext.AddActivityExecutionContext(rootContext);
workflowExecutionContext.AddActivityExecutionContext(childContext);
workflowExecutionContext.Workflow.CustomProperties["logPersistenceConfig"] = new Dictionary<string, object>
{
["internalState"] = new LogPersistenceConfiguration
{
EvaluationMode = LogPersistenceEvaluationMode.Expression,
Expression = Expression.DelegateExpression(ctx =>
{
ctx.TryGetActivityExecutionContext(out var evaluated);
return evaluated.ParentActivityExecutionContext == null
? LogPersistenceMode.Include
: LogPersistenceMode.Exclude;
})
}
};
var map = await CreateEvaluator(childContext).EvaluateLogPersistenceModesAsync(childContext);
Assert.Equal(LogPersistenceMode.Include, map.InternalState);
}
private static WriteLine CreateWriteLine(Type defaultMode, Type? internalState = null)
{
var config = new Dictionary<string, object>
{
["default"] = Strategy(defaultMode)
};
if (internalState != null)
config["internalState"] = Strategy(internalState);
var activity = new WriteLine("text");
activity.CustomProperties["logPersistenceConfig"] = config;
return activity;
}
private static Dictionary<string, object> Strategy(Type strategyType) => new()
{
["evaluationMode"] = "Strategy",
["strategyType"] = strategyType.GetSimpleAssemblyQualifiedName()
};
private static async Task<ActivityLogPersistenceModeMap> EvaluateAsync(IActivity activity, Action<Workflow>? configureWorkflow = null)
{
var fixture = CreateFixture(activity);
var context = await fixture.BuildAsync();
context.WorkflowExecutionContext.AddActivityExecutionContext(context);
configureWorkflow?.Invoke(context.WorkflowExecutionContext.Workflow);
return await CreateEvaluator(context).EvaluateLogPersistenceModesAsync(context);
}
private static ActivityTestFixture CreateFixture(IActivity activity) =>
new ActivityTestFixture(activity).ConfigureServices(services =>
{
services.AddSingleton<ILogPersistenceStrategy, Include>();
services.AddSingleton<ILogPersistenceStrategy, Exclude>();
services.AddSingleton<ILogPersistenceStrategy, Inherit>();
services.AddSingleton<ILogPersistenceStrategyService, DefaultLogPersistenceStrategyService>();
});
private static ActivityPropertyLogPersistenceEvaluator CreateEvaluator(ActivityExecutionContext context) =>
new(
context.GetRequiredService<ILogPersistenceStrategyService>(),
context.GetRequiredService<IExpressionDescriptorRegistry>(),
context.GetRequiredService<IExpressionEvaluator>(),
Microsoft.Extensions.Options.Options.Create(new ManagementOptions
{
LogPersistenceMode = LogPersistenceMode.Include
}),
NullLogger<ActivityPropertyLogPersistenceEvaluator>.Instance);
}

View file

@ -1,7 +1,9 @@
using Elsa.Common;
using Elsa.Testing.Shared;
using Elsa.Workflows.Activities;
using Elsa.Workflows.LogPersistence;
using Elsa.Workflows.Management.Options;
using Elsa.Workflows.Runtime.Entities;
using Microsoft.Extensions.Options;
using NSubstitute;
@ -59,4 +61,73 @@ public class DefaultActivityExecutionMapperTests
Assert.Equal("context-b", record.SchedulingActivityExecutionId);
Assert.Equal("activity-b", record.SchedulingActivityId);
}
[Fact]
public async Task MapAsync_IncludesPropertiesAndPayload_WhenInternalStateIsInclude_EvenIfInputsAreExcluded()
{
var record = await MapWithPersistenceAsync(
LogPersistenceMode.Include,
inputs: LogPersistenceMode.Exclude);
Assert.NotNull(record.Properties);
Assert.Equal("property-value", record.Properties["InternalKey"]);
Assert.NotNull(record.Payload);
Assert.Equal("journal-value", record.Payload["JournalKey"]);
Assert.False(record.ActivityState?.ContainsKey("Text"));
}
[Fact]
public async Task MapAsync_ExcludesPropertiesAndPayload_WhenInternalStateIsExclude_EvenIfInputsAreIncluded()
{
var record = await MapWithPersistenceAsync(
LogPersistenceMode.Exclude,
inputs: LogPersistenceMode.Include);
Assert.Null(record.Properties);
Assert.Null(record.Payload);
Assert.True(record.ActivityState?.ContainsKey("Text"));
}
private static async Task<ActivityExecutionRecord> MapWithPersistenceAsync(
LogPersistenceMode internalState,
LogPersistenceMode inputs)
{
var mapper = CreateMapper();
var activity = new WriteLine("Test");
var context = await new ActivityTestFixture(activity).BuildAsync();
context.ActivityState["Text"] = "Test";
context.Properties["InternalKey"] = "property-value";
context.JournalData["JournalKey"] = "journal-value";
context.SetLogPersistenceModeMap(new ActivityLogPersistenceModeMap
{
InternalState = internalState,
Inputs = { [nameof(WriteLine.Text)] = inputs }
});
return await mapper.MapAsync(context);
}
private static DefaultActivityExecutionMapper CreateMapper()
{
var safeSerializer = Substitute.For<ISafeSerializer>();
safeSerializer.Serialize(Arg.Any<object>()).Returns("serialized");
var payloadSerializer = Substitute.For<IPayloadSerializer>();
payloadSerializer.Serialize(Arg.Any<object>()).Returns("serialized");
var compressionCodecResolver = Substitute.For<ICompressionCodecResolver>();
var compressionCodec = Substitute.For<ICompressionCodec>();
compressionCodecResolver.Resolve(Arg.Any<string>()).Returns(compressionCodec);
compressionCodec.CompressAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(new ValueTask<string>("compressed"));
var managementOptions = Substitute.For<IOptions<ManagementOptions>>();
managementOptions.Value.Returns(new ManagementOptions());
return new DefaultActivityExecutionMapper(
safeSerializer,
payloadSerializer,
compressionCodecResolver,
managementOptions);
}
}