< Summary

Information
Class: Elsa.AI.Host.Services.InMemoryAIConversationStore
Assembly: Elsa.AI.Host
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.AI.Host/Services/InMemoryAIConversationStore.cs
Line coverage
92%
Covered lines: 35
Uncovered lines: 3
Coverable lines: 38
Total lines: 76
Line coverage: 92.1%
Branch coverage
85%
Covered branches: 24
Total branches: 28
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%
FindAsync(...)100%44100%
SaveAsync(...)100%11100%
PruneExpired()50%2266.66%
IsExpired(...)80%101083.33%
ValidateOwnership(...)100%66100%
NormalizeTenantId(...)100%22100%
Validate(...)75%4480%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.AI.Host/Services/InMemoryAIConversationStore.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using Elsa.AI.Abstractions.Contracts;
 3using Elsa.AI.Abstractions.Models;
 4
 5namespace Elsa.AI.Host.Services;
 6
 7public class InMemoryAIConversationStore : IAITransientConversationStore
 8{
 489    private readonly ConcurrentDictionary<string, AIConversation> _conversations = new(StringComparer.OrdinalIgnoreCase)
 10
 11    public ValueTask<AIConversation?> FindAsync(string id, CancellationToken cancellationToken = default)
 12    {
 5813        _conversations.TryGetValue(id, out var conversation);
 5814        if (conversation == null || !IsExpired(conversation))
 5615            return ValueTask.FromResult(conversation);
 16
 217        _conversations.TryRemove(id, out _);
 218        conversation = null;
 19
 220        return ValueTask.FromResult(conversation);
 21    }
 22
 23    public ValueTask SaveAsync(AIConversation conversation, CancellationToken cancellationToken = default)
 24    {
 9525        Validate(conversation);
 9426        PruneExpired();
 9427        _conversations.AddOrUpdate(
 9428            conversation.Id,
 9429            conversation,
 9430            (_, existing) =>
 9431            {
 4732                ValidateOwnership(existing, conversation);
 4533                return conversation;
 9434            });
 35
 9236        return ValueTask.CompletedTask;
 37    }
 38
 39    private void PruneExpired()
 40    {
 18841        foreach (var conversation in _conversations.Values.Where(IsExpired))
 042            _conversations.TryRemove(conversation.Id, out _);
 9443    }
 44
 45    private bool IsExpired(AIConversation conversation)
 46    {
 7947        if (conversation.RetentionMode == AIRetentionMode.Ephemeral)
 248            return conversation.Status is AIConversationStatus.Completed or AIConversationStatus.Failed;
 49
 7750        if (conversation.RetentionMode == AIRetentionMode.Durable)
 051            return false;
 52
 7753        var expiresAt = conversation.RetentionExpiresAt;
 7754        return expiresAt.HasValue && expiresAt <= DateTimeOffset.UtcNow;
 55    }
 56
 57    private static void ValidateOwnership(AIConversation existing, AIConversation conversation)
 58    {
 4759        if (!string.Equals(NormalizeTenantId(existing.TenantId), NormalizeTenantId(conversation.TenantId), StringCompari
 160            throw new InvalidOperationException("Cannot overwrite an AI conversation that belongs to another tenant.");
 61
 4662        if (!string.IsNullOrWhiteSpace(existing.UserId) && !string.Equals(existing.UserId, conversation.UserId, StringCo
 163            throw new InvalidOperationException("Cannot overwrite an AI conversation that belongs to another user.");
 4564    }
 65
 9466    private static string NormalizeTenantId(string? tenantId) => tenantId ?? "";
 67
 68    private static void Validate(AIConversation conversation)
 69    {
 9570        if (string.IsNullOrWhiteSpace(conversation.Id))
 071            throw new ArgumentException("A conversation ID is required.", nameof(conversation));
 72
 9573        if (string.IsNullOrWhiteSpace(conversation.UserId))
 174            throw new ArgumentException("A conversation user ID is required.", nameof(conversation));
 9475    }
 76}