Add comprehensive security tests for OWASP A02, A05, A07, and A08 categories
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (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
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (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
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management. - Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management. - Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support. - Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
// =============================================================================
|
||||
// DeterministicTestFixtures.cs
|
||||
// Deterministic test fixtures for TTFS testing
|
||||
// Part of Task T15: Create deterministic test fixtures
|
||||
// =============================================================================
|
||||
|
||||
using StellaOps.Orchestrator.Core.Domain;
|
||||
|
||||
namespace StellaOps.Orchestrator.Tests.Ttfs;
|
||||
|
||||
/// <summary>
|
||||
/// Deterministic test fixtures for TTFS (Time-To-First-Signal) testing.
|
||||
/// Uses frozen timestamps and pre-generated UUIDs for reproducibility.
|
||||
/// </summary>
|
||||
public static class DeterministicTestFixtures
|
||||
{
|
||||
/// <summary>
|
||||
/// Frozen timestamp used across all fixtures.
|
||||
/// </summary>
|
||||
public static readonly DateTimeOffset FrozenTimestamp =
|
||||
new(2025, 12, 4, 12, 0, 0, TimeSpan.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Deterministic seed for reproducible random generation.
|
||||
/// </summary>
|
||||
public const int DeterministicSeed = 42;
|
||||
|
||||
/// <summary>
|
||||
/// Pre-generated deterministic UUIDs.
|
||||
/// </summary>
|
||||
public static class Ids
|
||||
{
|
||||
public static readonly Guid TenantId = Guid.Parse("11111111-1111-1111-1111-111111111111");
|
||||
public static readonly Guid RunId = Guid.Parse("22222222-2222-2222-2222-222222222222");
|
||||
public static readonly Guid JobId = Guid.Parse("33333333-3333-3333-3333-333333333333");
|
||||
public static readonly Guid SourceId = Guid.Parse("44444444-4444-4444-4444-444444444444");
|
||||
public static readonly Guid SignatureId = Guid.Parse("55555555-5555-5555-5555-555555555555");
|
||||
|
||||
public const string TenantIdString = "test-tenant-deterministic";
|
||||
public const string CorrelationId = "corr-deterministic-001";
|
||||
public const string SignalId = "sig-deterministic-001";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deterministic digest values.
|
||||
/// </summary>
|
||||
public static class Digests
|
||||
{
|
||||
/// <summary>64-character lowercase hex digest (SHA-256).</summary>
|
||||
public const string PayloadDigest = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
|
||||
/// <summary>Image digest reference.</summary>
|
||||
public const string ImageDigest = "sha256:abc123def456789012345678901234567890123456789012345678901234abcd";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deterministic Run for testing.
|
||||
/// </summary>
|
||||
public static Run CreateRun(
|
||||
Guid? runId = null,
|
||||
string? tenantId = null,
|
||||
RunStatus status = RunStatus.Pending,
|
||||
DateTimeOffset? createdAt = null)
|
||||
{
|
||||
return new Run(
|
||||
RunId: runId ?? Ids.RunId,
|
||||
TenantId: tenantId ?? Ids.TenantIdString,
|
||||
ProjectId: null,
|
||||
SourceId: Ids.SourceId,
|
||||
RunType: "scan",
|
||||
Status: status,
|
||||
CorrelationId: Ids.CorrelationId,
|
||||
TotalJobs: 1,
|
||||
CompletedJobs: 0,
|
||||
SucceededJobs: 0,
|
||||
FailedJobs: 0,
|
||||
CreatedAt: createdAt ?? FrozenTimestamp,
|
||||
StartedAt: null,
|
||||
CompletedAt: null,
|
||||
CreatedBy: "system",
|
||||
Metadata: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deterministic Job for testing.
|
||||
/// </summary>
|
||||
public static Job CreateJob(
|
||||
Guid? jobId = null,
|
||||
Guid? runId = null,
|
||||
string? tenantId = null,
|
||||
JobStatus status = JobStatus.Scheduled,
|
||||
DateTimeOffset? createdAt = null)
|
||||
{
|
||||
return new Job(
|
||||
JobId: jobId ?? Ids.JobId,
|
||||
TenantId: tenantId ?? Ids.TenantIdString,
|
||||
ProjectId: null,
|
||||
RunId: runId ?? Ids.RunId,
|
||||
JobType: "scan.image",
|
||||
Status: status,
|
||||
Priority: 0,
|
||||
Attempt: 1,
|
||||
MaxAttempts: 3,
|
||||
PayloadDigest: Digests.PayloadDigest,
|
||||
Payload: "{}",
|
||||
IdempotencyKey: "idem-deterministic-001",
|
||||
CorrelationId: Ids.CorrelationId,
|
||||
LeaseId: null,
|
||||
WorkerId: null,
|
||||
TaskRunnerId: null,
|
||||
LeaseUntil: null,
|
||||
CreatedAt: createdAt ?? FrozenTimestamp,
|
||||
ScheduledAt: createdAt ?? FrozenTimestamp,
|
||||
LeasedAt: null,
|
||||
CompletedAt: null,
|
||||
NotBefore: null,
|
||||
Reason: null,
|
||||
ReplayOf: null,
|
||||
CreatedBy: "system");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deterministic FirstSignal for testing.
|
||||
/// </summary>
|
||||
public static FirstSignal CreateFirstSignal(
|
||||
FirstSignalKind kind = FirstSignalKind.Queued,
|
||||
FirstSignalPhase phase = FirstSignalPhase.Resolve,
|
||||
bool cacheHit = false,
|
||||
string source = "cold_start",
|
||||
LastKnownOutcome? lastKnownOutcome = null)
|
||||
{
|
||||
return new FirstSignal
|
||||
{
|
||||
Version = "1.0",
|
||||
SignalId = Ids.SignalId,
|
||||
JobId = Ids.JobId,
|
||||
Timestamp = FrozenTimestamp,
|
||||
Kind = kind,
|
||||
Phase = phase,
|
||||
Scope = new FirstSignalScope
|
||||
{
|
||||
Type = "image",
|
||||
Id = Digests.ImageDigest
|
||||
},
|
||||
Summary = GetSummaryForKind(kind),
|
||||
EtaSeconds = kind == FirstSignalKind.Queued ? 120 : null,
|
||||
LastKnownOutcome = lastKnownOutcome,
|
||||
NextActions = GetActionsForKind(kind),
|
||||
Diagnostics = new FirstSignalDiagnostics
|
||||
{
|
||||
CacheHit = cacheHit,
|
||||
Source = source,
|
||||
CorrelationId = Ids.CorrelationId
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deterministic LastKnownOutcome for testing.
|
||||
/// </summary>
|
||||
public static LastKnownOutcome CreateLastKnownOutcome(
|
||||
string confidence = "high",
|
||||
int hitCount = 15)
|
||||
{
|
||||
return new LastKnownOutcome
|
||||
{
|
||||
SignatureId = Ids.SignatureId.ToString(),
|
||||
ErrorCode = "EDEPNOTFOUND",
|
||||
Token = "EDEPNOTFOUND",
|
||||
Excerpt = "Could not resolve dependency @types/node@^18.0.0",
|
||||
Confidence = confidence,
|
||||
FirstSeenAt = FrozenTimestamp.AddDays(-3),
|
||||
HitCount = hitCount
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deterministic failed FirstSignal with LastKnownOutcome.
|
||||
/// </summary>
|
||||
public static FirstSignal CreateFailedSignalWithOutcome()
|
||||
{
|
||||
return CreateFirstSignal(
|
||||
kind: FirstSignalKind.Failed,
|
||||
phase: FirstSignalPhase.Analyze,
|
||||
cacheHit: false,
|
||||
source: "failure_index",
|
||||
lastKnownOutcome: CreateLastKnownOutcome());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deterministic succeeded FirstSignal.
|
||||
/// </summary>
|
||||
public static FirstSignal CreateSucceededSignal()
|
||||
{
|
||||
return CreateFirstSignal(
|
||||
kind: FirstSignalKind.Succeeded,
|
||||
phase: FirstSignalPhase.Report,
|
||||
cacheHit: true,
|
||||
source: "snapshot");
|
||||
}
|
||||
|
||||
private static string GetSummaryForKind(FirstSignalKind kind)
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
FirstSignalKind.Queued => "Job queued, waiting for available worker",
|
||||
FirstSignalKind.Started => "Analysis started",
|
||||
FirstSignalKind.Phase => "Processing in progress",
|
||||
FirstSignalKind.Blocked => "Blocked by policy: critical-vuln-gate",
|
||||
FirstSignalKind.Failed => "Analysis failed: dependency resolution error",
|
||||
FirstSignalKind.Succeeded => "Scan completed: 3 critical, 12 high, 45 medium findings",
|
||||
FirstSignalKind.Canceled => "Job canceled by user",
|
||||
FirstSignalKind.Unavailable => "Signal unavailable",
|
||||
_ => "Unknown state"
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyList<NextAction>? GetActionsForKind(FirstSignalKind kind)
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
FirstSignalKind.Failed => new[]
|
||||
{
|
||||
new NextAction
|
||||
{
|
||||
Type = "open_logs",
|
||||
Label = "View Logs",
|
||||
Target = $"/logs/{Ids.JobId}"
|
||||
},
|
||||
new NextAction
|
||||
{
|
||||
Type = "retry",
|
||||
Label = "Retry Job",
|
||||
Target = $"/retry/{Ids.JobId}"
|
||||
}
|
||||
},
|
||||
FirstSignalKind.Succeeded => new[]
|
||||
{
|
||||
new NextAction
|
||||
{
|
||||
Type = "open_job",
|
||||
Label = "View Results",
|
||||
Target = $"/jobs/{Ids.JobId}"
|
||||
}
|
||||
},
|
||||
FirstSignalKind.Blocked => new[]
|
||||
{
|
||||
new NextAction
|
||||
{
|
||||
Type = "docs",
|
||||
Label = "Policy Details",
|
||||
Target = "/docs/policies/critical-vuln-gate"
|
||||
}
|
||||
},
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seeded random number generator for deterministic test data.
|
||||
/// </summary>
|
||||
public sealed class SeededRandom
|
||||
{
|
||||
private readonly Random _random;
|
||||
|
||||
public SeededRandom(int seed = DeterministicTestFixtures.DeterministicSeed)
|
||||
{
|
||||
_random = new Random(seed);
|
||||
}
|
||||
|
||||
public int Next() => _random.Next();
|
||||
public int Next(int maxValue) => _random.Next(maxValue);
|
||||
public int Next(int minValue, int maxValue) => _random.Next(minValue, maxValue);
|
||||
public double NextDouble() => _random.NextDouble();
|
||||
|
||||
/// <summary>
|
||||
/// Generates a deterministic GUID based on the seed.
|
||||
/// </summary>
|
||||
public Guid NextGuid()
|
||||
{
|
||||
var bytes = new byte[16];
|
||||
_random.NextBytes(bytes);
|
||||
return new Guid(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a deterministic hex string.
|
||||
/// </summary>
|
||||
public string NextHexString(int length)
|
||||
{
|
||||
var bytes = new byte[length / 2];
|
||||
_random.NextBytes(bytes);
|
||||
return Convert.ToHexString(bytes).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// =============================================================================
|
||||
// DeterministicTestFixturesTests.cs
|
||||
// Tests for deterministic test fixtures
|
||||
// Part of Task T15: Create deterministic test fixtures
|
||||
// =============================================================================
|
||||
|
||||
using StellaOps.Orchestrator.Core.Domain;
|
||||
|
||||
namespace StellaOps.Orchestrator.Tests.Ttfs;
|
||||
|
||||
public sealed class DeterministicTestFixturesTests
|
||||
{
|
||||
[Fact]
|
||||
public void FrozenTimestamp_IsCorrectDate()
|
||||
{
|
||||
var expected = new DateTimeOffset(2025, 12, 4, 12, 0, 0, TimeSpan.Zero);
|
||||
Assert.Equal(expected, DeterministicTestFixtures.FrozenTimestamp);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Ids_AreConsistent_AcrossMultipleCalls()
|
||||
{
|
||||
var runId1 = DeterministicTestFixtures.Ids.RunId;
|
||||
var runId2 = DeterministicTestFixtures.Ids.RunId;
|
||||
|
||||
Assert.Equal(runId1, runId2);
|
||||
Assert.Equal("22222222-2222-2222-2222-222222222222", runId1.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateRun_ReturnsDeterministicRun()
|
||||
{
|
||||
var run1 = DeterministicTestFixtures.CreateRun();
|
||||
var run2 = DeterministicTestFixtures.CreateRun();
|
||||
|
||||
Assert.Equal(run1.RunId, run2.RunId);
|
||||
Assert.Equal(run1.TenantId, run2.TenantId);
|
||||
Assert.Equal(run1.CreatedAt, run2.CreatedAt);
|
||||
Assert.Equal(run1.CorrelationId, run2.CorrelationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateJob_ReturnsDeterministicJob()
|
||||
{
|
||||
var job1 = DeterministicTestFixtures.CreateJob();
|
||||
var job2 = DeterministicTestFixtures.CreateJob();
|
||||
|
||||
Assert.Equal(job1.JobId, job2.JobId);
|
||||
Assert.Equal(job1.TenantId, job2.TenantId);
|
||||
Assert.Equal(job1.PayloadDigest, job2.PayloadDigest);
|
||||
Assert.Equal(64, job1.PayloadDigest.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateFirstSignal_ReturnsDeterministicSignal()
|
||||
{
|
||||
var signal1 = DeterministicTestFixtures.CreateFirstSignal();
|
||||
var signal2 = DeterministicTestFixtures.CreateFirstSignal();
|
||||
|
||||
Assert.Equal(signal1.SignalId, signal2.SignalId);
|
||||
Assert.Equal(signal1.JobId, signal2.JobId);
|
||||
Assert.Equal(signal1.Timestamp, signal2.Timestamp);
|
||||
Assert.Equal(signal1.Diagnostics.CorrelationId, signal2.Diagnostics.CorrelationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateFailedSignalWithOutcome_IncludesLastKnownOutcome()
|
||||
{
|
||||
var signal = DeterministicTestFixtures.CreateFailedSignalWithOutcome();
|
||||
|
||||
Assert.Equal(FirstSignalKind.Failed, signal.Kind);
|
||||
Assert.NotNull(signal.LastKnownOutcome);
|
||||
Assert.Equal("EDEPNOTFOUND", signal.LastKnownOutcome.ErrorCode);
|
||||
Assert.Equal("high", signal.LastKnownOutcome.Confidence);
|
||||
Assert.Equal(15, signal.LastKnownOutcome.HitCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateSucceededSignal_HasCorrectProperties()
|
||||
{
|
||||
var signal = DeterministicTestFixtures.CreateSucceededSignal();
|
||||
|
||||
Assert.Equal(FirstSignalKind.Succeeded, signal.Kind);
|
||||
Assert.Equal(FirstSignalPhase.Report, signal.Phase);
|
||||
Assert.True(signal.Diagnostics.CacheHit);
|
||||
Assert.Equal("snapshot", signal.Diagnostics.Source);
|
||||
Assert.Null(signal.LastKnownOutcome);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SeededRandom_ProducesDeterministicSequence()
|
||||
{
|
||||
var rng1 = new SeededRandom(42);
|
||||
var rng2 = new SeededRandom(42);
|
||||
|
||||
var values1 = Enumerable.Range(0, 10).Select(_ => rng1.Next()).ToList();
|
||||
var values2 = Enumerable.Range(0, 10).Select(_ => rng2.Next()).ToList();
|
||||
|
||||
Assert.Equal(values1, values2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SeededRandom_NextGuid_ProducesDeterministicGuids()
|
||||
{
|
||||
var rng1 = new SeededRandom(42);
|
||||
var rng2 = new SeededRandom(42);
|
||||
|
||||
var guid1 = rng1.NextGuid();
|
||||
var guid2 = rng2.NextGuid();
|
||||
|
||||
Assert.Equal(guid1, guid2);
|
||||
Assert.NotEqual(Guid.Empty, guid1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SeededRandom_NextHexString_ProducesDeterministicStrings()
|
||||
{
|
||||
var rng1 = new SeededRandom(42);
|
||||
var rng2 = new SeededRandom(42);
|
||||
|
||||
var hex1 = rng1.NextHexString(64);
|
||||
var hex2 = rng2.NextHexString(64);
|
||||
|
||||
Assert.Equal(hex1, hex2);
|
||||
Assert.Equal(64, hex1.Length);
|
||||
Assert.Matches("^[a-f0-9]+$", hex1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Digests_AreValidFormats()
|
||||
{
|
||||
// PayloadDigest should be 64-char hex
|
||||
Assert.Equal(64, DeterministicTestFixtures.Digests.PayloadDigest.Length);
|
||||
Assert.Matches("^[a-f0-9]+$", DeterministicTestFixtures.Digests.PayloadDigest);
|
||||
|
||||
// ImageDigest should be sha256: prefixed
|
||||
Assert.StartsWith("sha256:", DeterministicTestFixtures.Digests.ImageDigest);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user