< Summary

Information
Class: Elsa.Alterations.Core.Stores.MemoryAlterationPlanStore
Assembly: Elsa.Alterations.Core
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Alterations.Core/Stores/MemoryAlterationPlanStore.cs
Line coverage
100%
Covered lines: 28
Uncovered lines: 0
Coverable lines: 28
Total lines: 96
Line coverage: 100%
Branch coverage
85%
Covered branches: 12
Total branches: 14
Branch coverage: 85.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
SaveAsync(...)100%11100%
FindAsync(...)100%11100%
CountAsync(...)100%11100%
Filter(...)100%11100%
get_CurrentTenantId()50%44100%
EnsureIdAvailable(...)100%44100%
CanReplace(...)100%11100%
ApplyCurrentTenant(...)100%66100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Alterations.Core/Stores/MemoryAlterationPlanStore.cs

#LineLine coverage
 1using Elsa.Alterations.Core.Contracts;
 2using Elsa.Alterations.Core.Entities;
 3using Elsa.Alterations.Core.Filters;
 4using Elsa.Common.Entities;
 5using Elsa.Common.Multitenancy;
 6using Elsa.Common.Services;
 7
 8namespace Elsa.Alterations.Core.Stores;
 9
 10/// <summary>
 11/// A memory-based store for alteration plans.
 12/// </summary>
 13/// <remarks>
 14/// Ambient tenant is applied here rather than in callers.
 15/// EF owns that via <c>SetTenantIdFilter</c> / <c>ApplyTenantId</c>; Memory must compensate.
 16/// Alterations contracts have no TenantAgnostic flag, so isolation always applies (EF query filter).
 17/// </remarks>
 18public class MemoryAlterationPlanStore : IAlterationPlanStore
 19{
 20    private readonly MemoryStore<AlterationPlan> _store;
 21    private readonly ITenantAccessor? _tenantAccessor;
 22
 23    /// <summary>
 24    /// Initializes a new instance of the <see cref="MemoryAlterationPlanStore"/> class.
 25    /// </summary>
 2326    public MemoryAlterationPlanStore(MemoryStore<AlterationPlan> store, ITenantAccessor? tenantAccessor = null)
 27    {
 2328        _store = store;
 2329        _tenantAccessor = tenantAccessor;
 2330    }
 31
 32    /// <inheritdoc />
 33    public Task SaveAsync(AlterationPlan plan, CancellationToken cancellationToken = default)
 34    {
 3935        lock (_store.Sync)
 36        {
 3937            ApplyCurrentTenant(plan);
 3938            EnsureIdAvailable(plan);
 7039            _store.Save(plan, x => x.Id);
 3540        }
 41
 3542        return Task.CompletedTask;
 43    }
 44
 45    /// <inheritdoc />
 46    public Task<AlterationPlan?> FindAsync(AlterationPlanFilter filter, CancellationToken cancellationToken = default)
 47    {
 6648        var entity = _store.Query(query => Filter(query, filter)).FirstOrDefault();
 3349        return Task.FromResult(entity);
 50    }
 51
 52    /// <inheritdoc />
 53    public Task<long> CountAsync(AlterationPlanFilter filter, CancellationToken cancellationToken = default)
 54    {
 2255        var count = _store.Query(query => Filter(query, filter)).LongCount();
 1156        return Task.FromResult(count);
 57    }
 58
 59    /// <remarks>
 60    /// Ambient tenant is applied here rather than in <see cref="AlterationPlanFilter.Apply"/>.
 61    /// EF owns that via <c>SetTenantIdFilter</c>; Memory must compensate.
 62    /// </remarks>
 63    private IQueryable<AlterationPlan> Filter(IQueryable<AlterationPlan> query, AlterationPlanFilter filter) =>
 4464        filter.Apply(query.WhereVisibleToTenant(CurrentTenantId));
 65
 5166    private string CurrentTenantId => _tenantAccessor?.TenantId ?? Tenant.DefaultTenantId;
 67
 68    private void EnsureIdAvailable(AlterationPlan plan)
 69    {
 7570        var existing = _store.Find(x => x.Id == plan.Id);
 71
 3972        if (existing is null)
 3273            return;
 74
 775        if (!CanReplace(existing, plan))
 476            throw AlterationStoreConflict.HiddenPlanId(plan.Id);
 77
 78        // An accepted update may change the payload, but it must not rehome the row.
 379        plan.TenantId = existing.TenantId;
 380    }
 81
 82    /// <summary>
 83    /// <c>*</c> is visible to every tenant, but only an agnostic writer may replace it.
 84    /// Named tenants may upsert their own visible rows.
 85    /// </summary>
 86    private bool CanReplace(Entity existing, Entity incoming) =>
 787        TenantVisibility.CanReplaceOwnedRow(existing.TenantId, incoming.TenantId, CurrentTenantId);
 88
 89    private void ApplyCurrentTenant(Entity entity)
 90    {
 3991        if (entity.TenantId == Tenant.AgnosticTenantId || _tenantAccessor is null)
 1192            return;
 93
 2894        entity.TenantId ??= _tenantAccessor.TenantId;
 2895    }
 96}