Add unit tests for PhpFrameworkSurface and PhpPharScanner
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
- Implement comprehensive tests for PhpFrameworkSurface, covering scenarios such as empty surfaces, presence of routes, controllers, middlewares, CLI commands, cron jobs, and event listeners. - Validate metadata creation for route counts, HTTP methods, protected and public routes, and route patterns. - Introduce tests for PhpPharScanner, including handling of non-existent files, null or empty paths, invalid PHAR files, and minimal PHAR structures. - Ensure correct computation of SHA256 for valid PHAR files and validate the properties of PhpPharArchive, PhpPharEntry, and PhpPharScanResult.
This commit is contained in:
@@ -0,0 +1,498 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using StellaOps.Findings.Ledger.Domain;
|
||||
using StellaOps.Findings.Ledger.Infrastructure;
|
||||
using StellaOps.Findings.Ledger.Infrastructure.Attestation;
|
||||
using StellaOps.Findings.Ledger.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Findings.Ledger.Tests.Attestation;
|
||||
|
||||
public class AttestationPointerServiceTests
|
||||
{
|
||||
private readonly Mock<ILedgerEventRepository> _ledgerEventRepository;
|
||||
private readonly Mock<ILedgerEventWriteService> _writeService;
|
||||
private readonly InMemoryAttestationPointerRepository _repository;
|
||||
private readonly FakeTimeProvider _timeProvider;
|
||||
private readonly AttestationPointerService _service;
|
||||
|
||||
public AttestationPointerServiceTests()
|
||||
{
|
||||
_ledgerEventRepository = new Mock<ILedgerEventRepository>();
|
||||
_writeService = new Mock<ILedgerEventWriteService>();
|
||||
_repository = new InMemoryAttestationPointerRepository();
|
||||
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 1, 12, 0, 0, TimeSpan.Zero));
|
||||
|
||||
_writeService.Setup(w => w.AppendAsync(It.IsAny<LedgerEventDraft>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((LedgerEventDraft draft, CancellationToken _) =>
|
||||
{
|
||||
var record = new LedgerEventRecord(
|
||||
draft.TenantId,
|
||||
draft.ChainId,
|
||||
draft.SequenceNumber,
|
||||
draft.EventId,
|
||||
draft.EventType,
|
||||
draft.PolicyVersion,
|
||||
draft.FindingId,
|
||||
draft.ArtifactId,
|
||||
draft.SourceRunId,
|
||||
draft.ActorId,
|
||||
draft.ActorType,
|
||||
draft.OccurredAt,
|
||||
draft.RecordedAt,
|
||||
draft.Payload,
|
||||
"event-hash",
|
||||
draft.ProvidedPreviousHash ?? LedgerEventConstants.EmptyHash,
|
||||
"merkle-leaf-hash",
|
||||
draft.CanonicalEnvelope.ToJsonString());
|
||||
return LedgerWriteResult.Success(record);
|
||||
});
|
||||
|
||||
_service = new AttestationPointerService(
|
||||
_ledgerEventRepository.Object,
|
||||
_writeService.Object,
|
||||
_repository,
|
||||
_timeProvider,
|
||||
NullLogger<AttestationPointerService>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreatePointer_CreatesNewPointer()
|
||||
{
|
||||
var input = new AttestationPointerInput(
|
||||
TenantId: "tenant-1",
|
||||
FindingId: "finding-123",
|
||||
AttestationType: AttestationType.DsseEnvelope,
|
||||
Relationship: AttestationRelationship.VerifiedBy,
|
||||
AttestationRef: new AttestationRef(
|
||||
Digest: "sha256:abc123def456789012345678901234567890123456789012345678901234abcd",
|
||||
AttestationId: Guid.NewGuid(),
|
||||
StorageUri: "s3://attestations/test.json",
|
||||
PayloadType: "application/vnd.in-toto+json",
|
||||
PredicateType: "https://slsa.dev/provenance/v1"),
|
||||
VerificationResult: new VerificationResult(
|
||||
Verified: true,
|
||||
VerifiedAt: _timeProvider.GetUtcNow(),
|
||||
Verifier: "stellaops-attestor",
|
||||
VerifierVersion: "2025.01.0"),
|
||||
CreatedBy: "test-system");
|
||||
|
||||
var result = await _service.CreatePointerAsync(input);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.NotNull(result.PointerId);
|
||||
Assert.NotNull(result.LedgerEventId);
|
||||
Assert.Null(result.Error);
|
||||
|
||||
var saved = await _repository.GetByIdAsync("tenant-1", result.PointerId!.Value, CancellationToken.None);
|
||||
Assert.NotNull(saved);
|
||||
Assert.Equal(input.FindingId, saved!.FindingId);
|
||||
Assert.Equal(input.AttestationType, saved.AttestationType);
|
||||
Assert.Equal(input.AttestationRef.Digest, saved.AttestationRef.Digest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreatePointer_IsIdempotent()
|
||||
{
|
||||
var input = new AttestationPointerInput(
|
||||
TenantId: "tenant-1",
|
||||
FindingId: "finding-456",
|
||||
AttestationType: AttestationType.SlsaProvenance,
|
||||
Relationship: AttestationRelationship.AttestedBy,
|
||||
AttestationRef: new AttestationRef(
|
||||
Digest: "sha256:def456789012345678901234567890123456789012345678901234567890abcd"),
|
||||
CreatedBy: "test-system");
|
||||
|
||||
var result1 = await _service.CreatePointerAsync(input);
|
||||
var result2 = await _service.CreatePointerAsync(input);
|
||||
|
||||
Assert.True(result1.Success);
|
||||
Assert.True(result2.Success);
|
||||
Assert.Equal(result1.PointerId, result2.PointerId);
|
||||
|
||||
var pointers = await _repository.GetByFindingIdAsync("tenant-1", "finding-456", CancellationToken.None);
|
||||
Assert.Single(pointers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPointers_ReturnsAllPointersForFinding()
|
||||
{
|
||||
var input1 = new AttestationPointerInput(
|
||||
TenantId: "tenant-1",
|
||||
FindingId: "finding-multi",
|
||||
AttestationType: AttestationType.DsseEnvelope,
|
||||
Relationship: AttestationRelationship.VerifiedBy,
|
||||
AttestationRef: new AttestationRef(
|
||||
Digest: "sha256:aaa111222333444555666777888999000111222333444555666777888999000a"));
|
||||
|
||||
var input2 = new AttestationPointerInput(
|
||||
TenantId: "tenant-1",
|
||||
FindingId: "finding-multi",
|
||||
AttestationType: AttestationType.VexAttestation,
|
||||
Relationship: AttestationRelationship.DerivedFrom,
|
||||
AttestationRef: new AttestationRef(
|
||||
Digest: "sha256:bbb111222333444555666777888999000111222333444555666777888999000b"));
|
||||
|
||||
await _service.CreatePointerAsync(input1);
|
||||
await _service.CreatePointerAsync(input2);
|
||||
|
||||
var pointers = await _service.GetPointersAsync("tenant-1", "finding-multi");
|
||||
|
||||
Assert.Equal(2, pointers.Count);
|
||||
Assert.Contains(pointers, p => p.AttestationType == AttestationType.DsseEnvelope);
|
||||
Assert.Contains(pointers, p => p.AttestationType == AttestationType.VexAttestation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSummary_CalculatesCorrectCounts()
|
||||
{
|
||||
var verified = new AttestationPointerInput(
|
||||
TenantId: "tenant-1",
|
||||
FindingId: "finding-summary",
|
||||
AttestationType: AttestationType.DsseEnvelope,
|
||||
Relationship: AttestationRelationship.VerifiedBy,
|
||||
AttestationRef: new AttestationRef(
|
||||
Digest: "sha256:ver111222333444555666777888999000111222333444555666777888999000a"),
|
||||
VerificationResult: new VerificationResult(Verified: true, VerifiedAt: _timeProvider.GetUtcNow()));
|
||||
|
||||
var unverified = new AttestationPointerInput(
|
||||
TenantId: "tenant-1",
|
||||
FindingId: "finding-summary",
|
||||
AttestationType: AttestationType.SbomAttestation,
|
||||
Relationship: AttestationRelationship.DerivedFrom,
|
||||
AttestationRef: new AttestationRef(
|
||||
Digest: "sha256:unv111222333444555666777888999000111222333444555666777888999000b"));
|
||||
|
||||
await _service.CreatePointerAsync(verified);
|
||||
await _service.CreatePointerAsync(unverified);
|
||||
|
||||
var summary = await _service.GetSummaryAsync("tenant-1", "finding-summary");
|
||||
|
||||
Assert.Equal("finding-summary", summary.FindingId);
|
||||
Assert.Equal(2, summary.AttestationCount);
|
||||
Assert.Equal(1, summary.VerifiedCount);
|
||||
Assert.Equal(OverallVerificationStatus.PartiallyVerified, summary.OverallVerificationStatus);
|
||||
Assert.Contains(AttestationType.DsseEnvelope, summary.AttestationTypes);
|
||||
Assert.Contains(AttestationType.SbomAttestation, summary.AttestationTypes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Search_FiltersByAttestationType()
|
||||
{
|
||||
var input1 = new AttestationPointerInput(
|
||||
TenantId: "tenant-1",
|
||||
FindingId: "finding-search-1",
|
||||
AttestationType: AttestationType.DsseEnvelope,
|
||||
Relationship: AttestationRelationship.VerifiedBy,
|
||||
AttestationRef: new AttestationRef(
|
||||
Digest: "sha256:sea111222333444555666777888999000111222333444555666777888999000a"));
|
||||
|
||||
var input2 = new AttestationPointerInput(
|
||||
TenantId: "tenant-1",
|
||||
FindingId: "finding-search-2",
|
||||
AttestationType: AttestationType.SlsaProvenance,
|
||||
Relationship: AttestationRelationship.AttestedBy,
|
||||
AttestationRef: new AttestationRef(
|
||||
Digest: "sha256:sea222333444555666777888999000111222333444555666777888999000111b"));
|
||||
|
||||
await _service.CreatePointerAsync(input1);
|
||||
await _service.CreatePointerAsync(input2);
|
||||
|
||||
var query = new AttestationPointerQuery(
|
||||
TenantId: "tenant-1",
|
||||
AttestationTypes: new[] { AttestationType.DsseEnvelope });
|
||||
|
||||
var results = await _service.SearchAsync(query);
|
||||
|
||||
Assert.Single(results);
|
||||
Assert.Equal(AttestationType.DsseEnvelope, results[0].AttestationType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateVerificationResult_UpdatesExistingPointer()
|
||||
{
|
||||
var input = new AttestationPointerInput(
|
||||
TenantId: "tenant-1",
|
||||
FindingId: "finding-update",
|
||||
AttestationType: AttestationType.DsseEnvelope,
|
||||
Relationship: AttestationRelationship.VerifiedBy,
|
||||
AttestationRef: new AttestationRef(
|
||||
Digest: "sha256:upd111222333444555666777888999000111222333444555666777888999000a"));
|
||||
|
||||
var createResult = await _service.CreatePointerAsync(input);
|
||||
Assert.True(createResult.Success);
|
||||
|
||||
var verificationResult = new VerificationResult(
|
||||
Verified: true,
|
||||
VerifiedAt: _timeProvider.GetUtcNow(),
|
||||
Verifier: "external-verifier",
|
||||
VerifierVersion: "1.0.0",
|
||||
Checks: new[]
|
||||
{
|
||||
new VerificationCheck(VerificationCheckType.SignatureValid, true, "ECDSA verified"),
|
||||
new VerificationCheck(VerificationCheckType.CertificateValid, true, "Chain verified")
|
||||
});
|
||||
|
||||
var success = await _service.UpdateVerificationResultAsync(
|
||||
"tenant-1", createResult.PointerId!.Value, verificationResult);
|
||||
|
||||
Assert.True(success);
|
||||
|
||||
var updated = await _repository.GetByIdAsync("tenant-1", createResult.PointerId!.Value, CancellationToken.None);
|
||||
Assert.NotNull(updated?.VerificationResult);
|
||||
Assert.True(updated.VerificationResult!.Verified);
|
||||
Assert.Equal("external-verifier", updated.VerificationResult.Verifier);
|
||||
Assert.Equal(2, updated.VerificationResult.Checks!.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TenantIsolation_PreventsAccessAcrossTenants()
|
||||
{
|
||||
var input = new AttestationPointerInput(
|
||||
TenantId: "tenant-a",
|
||||
FindingId: "finding-isolated",
|
||||
AttestationType: AttestationType.DsseEnvelope,
|
||||
Relationship: AttestationRelationship.VerifiedBy,
|
||||
AttestationRef: new AttestationRef(
|
||||
Digest: "sha256:iso111222333444555666777888999000111222333444555666777888999000a"));
|
||||
|
||||
var result = await _service.CreatePointerAsync(input);
|
||||
Assert.True(result.Success);
|
||||
|
||||
var fromTenantA = await _service.GetPointersAsync("tenant-a", "finding-isolated");
|
||||
var fromTenantB = await _service.GetPointersAsync("tenant-b", "finding-isolated");
|
||||
|
||||
Assert.Single(fromTenantA);
|
||||
Assert.Empty(fromTenantB);
|
||||
}
|
||||
|
||||
private sealed class FakeTimeProvider : TimeProvider
|
||||
{
|
||||
private DateTimeOffset _now;
|
||||
|
||||
public FakeTimeProvider(DateTimeOffset now) => _now = now;
|
||||
|
||||
public override DateTimeOffset GetUtcNow() => _now;
|
||||
|
||||
public void Advance(TimeSpan duration) => _now = _now.Add(duration);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In-memory implementation for testing.
|
||||
/// </summary>
|
||||
internal sealed class InMemoryAttestationPointerRepository : IAttestationPointerRepository
|
||||
{
|
||||
private readonly List<AttestationPointerRecord> _records = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
public Task InsertAsync(AttestationPointerRecord record, CancellationToken cancellationToken)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_records.Add(record);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<AttestationPointerRecord?> GetByIdAsync(string tenantId, Guid pointerId, CancellationToken cancellationToken)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var result = _records.FirstOrDefault(r => r.TenantId == tenantId && r.PointerId == pointerId);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<AttestationPointerRecord>> GetByFindingIdAsync(string tenantId, string findingId, CancellationToken cancellationToken)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var results = _records
|
||||
.Where(r => r.TenantId == tenantId && r.FindingId == findingId)
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.ToList();
|
||||
return Task.FromResult<IReadOnlyList<AttestationPointerRecord>>(results);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<AttestationPointerRecord>> GetByDigestAsync(string tenantId, string digest, CancellationToken cancellationToken)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var results = _records
|
||||
.Where(r => r.TenantId == tenantId && r.AttestationRef.Digest == digest)
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.ToList();
|
||||
return Task.FromResult<IReadOnlyList<AttestationPointerRecord>>(results);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<AttestationPointerRecord>> SearchAsync(AttestationPointerQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var results = _records.Where(r => r.TenantId == query.TenantId);
|
||||
|
||||
if (query.FindingIds is { Count: > 0 })
|
||||
{
|
||||
results = results.Where(r => query.FindingIds.Contains(r.FindingId));
|
||||
}
|
||||
|
||||
if (query.AttestationTypes is { Count: > 0 })
|
||||
{
|
||||
results = results.Where(r => query.AttestationTypes.Contains(r.AttestationType));
|
||||
}
|
||||
|
||||
if (query.VerificationStatus.HasValue)
|
||||
{
|
||||
results = query.VerificationStatus.Value switch
|
||||
{
|
||||
AttestationVerificationFilter.Verified =>
|
||||
results.Where(r => r.VerificationResult?.Verified == true),
|
||||
AttestationVerificationFilter.Unverified =>
|
||||
results.Where(r => r.VerificationResult is null),
|
||||
AttestationVerificationFilter.Failed =>
|
||||
results.Where(r => r.VerificationResult?.Verified == false),
|
||||
_ => results
|
||||
};
|
||||
}
|
||||
|
||||
if (query.CreatedAfter.HasValue)
|
||||
{
|
||||
results = results.Where(r => r.CreatedAt >= query.CreatedAfter.Value);
|
||||
}
|
||||
|
||||
if (query.CreatedBefore.HasValue)
|
||||
{
|
||||
results = results.Where(r => r.CreatedAt <= query.CreatedBefore.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.SignerIdentity))
|
||||
{
|
||||
results = results.Where(r => r.AttestationRef.SignerInfo?.Subject == query.SignerIdentity);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.PredicateType))
|
||||
{
|
||||
results = results.Where(r => r.AttestationRef.PredicateType == query.PredicateType);
|
||||
}
|
||||
|
||||
var list = results
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.Skip(query.Offset)
|
||||
.Take(query.Limit)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult<IReadOnlyList<AttestationPointerRecord>>(list);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<FindingAttestationSummary> GetSummaryAsync(string tenantId, string findingId, CancellationToken cancellationToken)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var pointers = _records.Where(r => r.TenantId == tenantId && r.FindingId == findingId).ToList();
|
||||
|
||||
if (pointers.Count == 0)
|
||||
{
|
||||
return Task.FromResult(new FindingAttestationSummary(
|
||||
findingId, 0, 0, null, Array.Empty<AttestationType>(), OverallVerificationStatus.NoAttestations));
|
||||
}
|
||||
|
||||
var verifiedCount = pointers.Count(p => p.VerificationResult?.Verified == true);
|
||||
var latest = pointers.Max(p => p.CreatedAt);
|
||||
var types = pointers.Select(p => p.AttestationType).Distinct().ToList();
|
||||
|
||||
var status = pointers.Count switch
|
||||
{
|
||||
0 => OverallVerificationStatus.NoAttestations,
|
||||
_ when verifiedCount == pointers.Count => OverallVerificationStatus.AllVerified,
|
||||
_ when verifiedCount > 0 => OverallVerificationStatus.PartiallyVerified,
|
||||
_ => OverallVerificationStatus.NoneVerified
|
||||
};
|
||||
|
||||
return Task.FromResult(new FindingAttestationSummary(
|
||||
findingId, pointers.Count, verifiedCount, latest, types, status));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<FindingAttestationSummary>> GetSummariesAsync(string tenantId, IReadOnlyList<string> findingIds, CancellationToken cancellationToken)
|
||||
{
|
||||
var tasks = findingIds.Select(fid => GetSummaryAsync(tenantId, fid, cancellationToken));
|
||||
return Task.WhenAll(tasks).ContinueWith(t => (IReadOnlyList<FindingAttestationSummary>)t.Result.ToList());
|
||||
}
|
||||
|
||||
public Task<bool> ExistsAsync(string tenantId, string findingId, string digest, AttestationType attestationType, CancellationToken cancellationToken)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var exists = _records.Any(r =>
|
||||
r.TenantId == tenantId &&
|
||||
r.FindingId == findingId &&
|
||||
r.AttestationRef.Digest == digest &&
|
||||
r.AttestationType == attestationType);
|
||||
return Task.FromResult(exists);
|
||||
}
|
||||
}
|
||||
|
||||
public Task UpdateVerificationResultAsync(string tenantId, Guid pointerId, VerificationResult verificationResult, CancellationToken cancellationToken)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var idx = _records.FindIndex(r => r.TenantId == tenantId && r.PointerId == pointerId);
|
||||
if (idx >= 0)
|
||||
{
|
||||
var old = _records[idx];
|
||||
_records[idx] = old with { VerificationResult = verificationResult };
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<int> GetCountAsync(string tenantId, string findingId, CancellationToken cancellationToken)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var count = _records.Count(r => r.TenantId == tenantId && r.FindingId == findingId);
|
||||
return Task.FromResult(count);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<string>> GetFindingIdsWithAttestationsAsync(string tenantId, AttestationVerificationFilter? verificationFilter, IReadOnlyList<AttestationType>? attestationTypes, int limit, int offset, CancellationToken cancellationToken)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var results = _records.Where(r => r.TenantId == tenantId);
|
||||
|
||||
if (attestationTypes is { Count: > 0 })
|
||||
{
|
||||
results = results.Where(r => attestationTypes.Contains(r.AttestationType));
|
||||
}
|
||||
|
||||
if (verificationFilter.HasValue)
|
||||
{
|
||||
results = verificationFilter.Value switch
|
||||
{
|
||||
AttestationVerificationFilter.Verified =>
|
||||
results.Where(r => r.VerificationResult?.Verified == true),
|
||||
AttestationVerificationFilter.Unverified =>
|
||||
results.Where(r => r.VerificationResult is null),
|
||||
AttestationVerificationFilter.Failed =>
|
||||
results.Where(r => r.VerificationResult?.Verified == false),
|
||||
_ => results
|
||||
};
|
||||
}
|
||||
|
||||
var list = results
|
||||
.Select(r => r.FindingId)
|
||||
.Distinct()
|
||||
.OrderBy(f => f)
|
||||
.Skip(offset)
|
||||
.Take(limit)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult<IReadOnlyList<string>>(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user