using w4c_workflows.Services;
using w4c_workflows.Services.Messaging;
namespace w4c_workflows.Tests;
/// In-memory capturing enqueued task.run fields.
internal sealed class FakeJobQueue : IJobQueue
{
public List<(string TenantId, IReadOnlyDictionary Fields)> Enqueued { get; } = new();
public List<(string TenantId, IReadOnlyDictionary Fields, string Reason)> DeadLettered { get; } = new();
public Task EnsureGroupAsync(string tenantId, CancellationToken ct) => Task.CompletedTask;
public Task EnqueueAsync(string tenantId, IReadOnlyDictionary fields, CancellationToken ct)
{
Enqueued.Add((tenantId, fields));
return Task.FromResult(Enqueued.Count.ToString());
}
public Task> ReadGroupAsync(string tenantId, string consumer, int count, CancellationToken ct)
=> Task.FromResult>(Array.Empty());
public Task> ClaimPendingAsync(string tenantId, string consumer, TimeSpan minIdle, int count, CancellationToken ct)
=> Task.FromResult>(Array.Empty());
public Task AckAsync(string tenantId, string messageId, CancellationToken ct) => Task.FromResult(0L);
public Task DeadLetterAsync(string tenantId, string messageId, IReadOnlyDictionary fields, string reason, CancellationToken ct)
{
DeadLettered.Add((tenantId, fields, reason));
return Task.CompletedTask;
}
public Task PublishDeadLetterAsync(string tenantId, IReadOnlyDictionary fields, string reason, CancellationToken ct)
{
DeadLettered.Add((tenantId, fields, reason));
return Task.CompletedTask;
}
public List> EnqueuedFor(string tenantId)
=> Enqueued.Where(x => x.TenantId == tenantId).Select(x => x.Fields).ToList();
public List<(IReadOnlyDictionary Fields, string Reason)> DeadLetteredFor(string tenantId)
=> DeadLettered.Where(x => x.TenantId == tenantId).Select(x => (x.Fields, x.Reason)).ToList();
}
///
/// In-memory . Stream names resolve the {tenant}
/// placeholder exactly like the real Redis transport; tests seed result messages
/// with and the engine reads + acks them.
///
internal sealed class FakeEventBus : IEventBus
{
private readonly Dictionary> _messages = new();
private int _nextId = 1;
public List Acked { get; } = new();
public List<(string TenantId, string Stream, IReadOnlyDictionary Fields)> Published { get; } = new();
public void SeedResult(string tenantId, IReadOnlyDictionary fields)
=> Seed(Streams.Results.Replace("{tenant}", tenantId), fields);
public void Seed(string streamKey, IReadOnlyDictionary fields)
{
if (!_messages.TryGetValue(streamKey, out var queue))
{
queue = new Queue();
_messages[streamKey] = queue;
}
queue.Enqueue(new StreamMessage((_nextId++).ToString(), fields));
}
private static string Resolve(string stream, string tenantId) => stream.Replace("{tenant}", tenantId);
public Task PublishAsync(string tenantId, string stream, IReadOnlyDictionary fields, CancellationToken ct)
{
Published.Add((tenantId, stream, fields));
return Task.FromResult((_nextId++).ToString());
}
public Task EnsureGroupAsync(string tenantId, string stream, CancellationToken ct) => Task.CompletedTask;
public Task> ReadGroupAsync(string tenantId, string stream, string consumer, int count, CancellationToken ct)
{
var key = Resolve(stream, tenantId);
var list = new List();
if (_messages.TryGetValue(key, out var queue))
while (list.Count < count && queue.Count > 0)
list.Add(queue.Dequeue());
return Task.FromResult>(list);
}
public Task> ClaimPendingAsync(string tenantId, string stream, string consumer, TimeSpan minIdle, int count, CancellationToken ct)
=> Task.FromResult>(Array.Empty());
public Task AckAsync(string tenantId, string stream, string messageId, CancellationToken ct)
{
Acked.Add(messageId);
return Task.FromResult(1L);
}
}
/// In-memory keyed by tenant + run id.
internal sealed class FakeLeaseService : ILeaseService
{
private readonly Dictionary _held = new();
public Task AcquireAsync(string tenantId, string runId, string owner, TimeSpan ttl)
{
var key = $"{tenantId}:{runId}";
if (_held.ContainsKey(key))
return Task.FromResult(false);
_held[key] = owner;
return Task.FromResult(true);
}
public Task RenewAsync(string tenantId, string runId, string owner, TimeSpan ttl)
=> Task.FromResult(_held.TryGetValue($"{tenantId}:{runId}", out var held) && held == owner);
public Task ReleaseAsync(string tenantId, string runId, string owner)
{
var key = $"{tenantId}:{runId}";
if (_held.TryGetValue(key, out var held) && held == owner)
{
_held.Remove(key);
return Task.FromResult(true);
}
return Task.FromResult(false);
}
}