< Summary

Information
Class: Elsa.Workflows.Runtime.Comparers.WorkflowTriggerEqualityComparer
Assembly: Elsa.Workflows.Runtime
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/Comparers/WorkflowTriggerEqualityComparer.cs
Line coverage
94%
Covered lines: 37
Uncovered lines: 2
Coverable lines: 39
Total lines: 101
Line coverage: 94.8%
Branch coverage
66%
Covered branches: 4
Total branches: 6
Branch coverage: 66.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%210%
.ctor(...)100%11100%
Equals(...)50%44100%
GetHashCode(...)100%11100%
Serialize(...)100%11100%
NormalizePayload(...)100%22100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/Comparers/WorkflowTriggerEqualityComparer.cs

#LineLine coverage
 1using System.Text.Json;
 2using System.Text.Json.Serialization;
 3using System.Text.Json.Serialization.Metadata;
 4using Elsa.Workflows.Runtime.Entities;
 5using Elsa.Workflows.Serialization.Converters;
 6using Elsa.Workflows.Services;
 7using Elsa.Common.Serialization;
 8
 9namespace Elsa.Workflows.Runtime.Comparers;
 10
 11/// <summary>
 12/// Compares two <see cref="StoredTrigger"/> instances for equality.
 13/// </summary>
 14public class WorkflowTriggerEqualityComparer : IEqualityComparer<StoredTrigger>
 15{
 16    private readonly JsonSerializerOptions _settings;
 17
 18    /// <summary>
 19    /// Initializes a new instance of the <see cref="WorkflowTriggerEqualityComparer"/> class.
 20    /// </summary>
 021    public WorkflowTriggerEqualityComparer() : this(SerializationTypeRegistry.CreateDefault())
 22    {
 023    }
 24
 25    /// <summary>
 26    /// Initializes a new instance of the <see cref="WorkflowTriggerEqualityComparer"/> class.
 27    /// </summary>
 65028    public WorkflowTriggerEqualityComparer(ISerializationTypeRegistry workflowJsonTypeRegistry)
 29    {
 65030        _settings = new()
 65031        {
 65032            // Enables serialization of ValueTuples, which use fields instead of properties.
 65033            IncludeFields = true,
 65034            PropertyNameCaseInsensitive = true,
 65035            // Use camelCase to match IPayloadSerializer, which stores payloads using camelCase.
 65036            // Without this, fresh payloads (typed CLR objects) serialize with PascalCase while
 65037            // DB-loaded payloads (JsonElement) preserve their stored camelCase keys, causing
 65038            // the diff to always report all triggers as changed.
 65039            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
 65040            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
 65041        };
 42
 43        // Mirror the converters used by IPayloadSerializer so that enum, TimeSpan, and
 44        // polymorphic object properties serialize identically to their stored representation.
 65045        _settings.Converters.Add(new JsonStringEnumConverter());
 65046        _settings.Converters.Add(JsonMetadataServices.TimeSpanConverter);
 65047        _settings.Converters.Add(new PolymorphicObjectConverterFactory(workflowJsonTypeRegistry));
 65048        _settings.Converters.Add(new TypeJsonConverter(workflowJsonTypeRegistry));
 65049    }
 50
 51    /// <inheritdoc />
 52    public bool Equals(StoredTrigger? x, StoredTrigger? y)
 53    {
 79954        var xJson = x != null ? Serialize(x) : null;
 79955        var yJson = y != null ? Serialize(y) : null;
 79956        return xJson == yJson;
 57    }
 58
 59    /// <inheritdoc />
 60    public int GetHashCode(StoredTrigger obj)
 61    {
 195662        var json = Serialize(obj);
 195663        return json.GetHashCode();
 64    }
 65
 66    private string Serialize(StoredTrigger storedTrigger)
 67    {
 68        // Normalize the payload to a canonical JSON string so that both typed CLR objects
 69        // and JsonElement instances (from DB round-trips) produce identical output.
 355470        var normalizedPayload = NormalizePayload(storedTrigger.Payload);
 71
 355472        var input = new
 355473        {
 355474            Payload = normalizedPayload,
 355475            storedTrigger.Name,
 355476            storedTrigger.ActivityId,
 355477            storedTrigger.WorkflowDefinitionId,
 355478            storedTrigger.WorkflowDefinitionVersionId,
 355479            storedTrigger.Hash
 355480        };
 355481        return JsonSerializer.Serialize(input, _settings);
 82    }
 83
 84    /// <summary>
 85    /// Normalizes a payload to a canonical JSON string representation.
 86    /// This ensures that typed CLR objects and JsonElements (which preserve their original
 87    /// property name casing from DB storage) produce identical output when compared.
 88    /// </summary>
 89    private string? NormalizePayload(object? payload)
 90    {
 355491        if (payload == null)
 492            return null;
 93
 94        // Serialize using the concrete runtime type rather than object, so that
 95        // PolymorphicObjectConverterFactory (which only handles typeof(object)) does not
 96        // inject a "_type" discriminator field into CLR payloads. Without this,
 97        // fresh CLR payloads produce {"path":"...","_type":"..."} while DB-loaded
 98        // JsonElement payloads produce {"path":"..."}, and the two never compare equal.
 355099        return JsonSerializer.Serialize(payload, payload.GetType(), _settings);
 100    }
 101}