// ----------------------------------------------------------------------------- // TestManifestRepository.cs // Purpose: Test-only in-memory implementation of Storage.Repositories.IScanManifestRepository // ----------------------------------------------------------------------------- using System.Collections.Concurrent; using StellaOps.Scanner.Storage.Entities; using StellaOps.Scanner.Storage.Repositories; namespace StellaOps.Scanner.WebService.Services; /// /// In-memory implementation of IScanManifestRepository for testing. /// public sealed class TestManifestRepository : StellaOps.Scanner.Storage.Repositories.IScanManifestRepository { private readonly ConcurrentDictionary _manifestsByScanId = new(); private readonly ConcurrentDictionary _manifestsByHash = new(StringComparer.OrdinalIgnoreCase); public Task GetByHashAsync(string manifestHash, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(_manifestsByHash.TryGetValue(manifestHash, out var manifest) ? manifest : null); } public Task GetByScanIdAsync(Guid scanId, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(_manifestsByScanId.TryGetValue(scanId, out var manifest) ? manifest : null); } public Task SaveAsync(ScanManifestRow manifest, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(manifest); cancellationToken.ThrowIfCancellationRequested(); _manifestsByScanId[manifest.ScanId] = manifest; _manifestsByHash[manifest.ManifestHash] = manifest; return Task.FromResult(manifest); } public Task MarkCompletedAsync(Guid manifestId, DateTimeOffset completedAt, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); foreach (var manifest in _manifestsByScanId.Values) { if (manifest.ManifestId == manifestId) { manifest.ScanCompletedAt = completedAt; break; } } return Task.CompletedTask; } } /// /// In-memory implementation of IProofBundleRepository for testing. /// public sealed class TestProofBundleRepository : StellaOps.Scanner.Storage.Repositories.IProofBundleRepository { private readonly ConcurrentDictionary _bundlesByRootHash = new(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary> _bundlesByScanId = new(); public Task GetByRootHashAsync(string rootHash, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(_bundlesByRootHash.TryGetValue(rootHash, out var bundle) ? bundle : null); } public Task> GetByScanIdAsync(Guid scanId, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); if (_bundlesByScanId.TryGetValue(scanId, out var bundles)) { return Task.FromResult>(bundles.ToList()); } return Task.FromResult>(Array.Empty()); } public Task SaveAsync(ProofBundleRow bundle, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(bundle); cancellationToken.ThrowIfCancellationRequested(); _bundlesByRootHash[bundle.RootHash] = bundle; var scanBundles = _bundlesByScanId.GetOrAdd(bundle.ScanId, _ => new List()); lock (scanBundles) { // Replace existing if same root hash, otherwise add var existingIndex = scanBundles.FindIndex(b => string.Equals(b.RootHash, bundle.RootHash, StringComparison.OrdinalIgnoreCase)); if (existingIndex >= 0) { scanBundles[existingIndex] = bundle; } else { scanBundles.Add(bundle); } } return Task.FromResult(bundle); } public Task DeleteExpiredAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); var now = DateTimeOffset.UtcNow; var expired = _bundlesByRootHash.Values .Where(b => b.ExpiresAt.HasValue && b.ExpiresAt.Value < now) .ToList(); foreach (var bundle in expired) { _bundlesByRootHash.TryRemove(bundle.RootHash, out _); if (_bundlesByScanId.TryGetValue(bundle.ScanId, out var scanBundles)) { lock (scanBundles) { scanBundles.RemoveAll(b => string.Equals(b.RootHash, bundle.RootHash, StringComparison.OrdinalIgnoreCase)); } } } return Task.FromResult(expired.Count); } }