Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -8,6 +8,7 @@ using StellaOps.TaskRunner.Storage.Postgres.Repositories;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Storage.Postgres.Tests;
|
||||
|
||||
[Collection(TaskRunnerPostgresCollection.Name)]
|
||||
@@ -41,7 +42,8 @@ public sealed class PostgresPackRunStateStoreTests : IAsyncLifetime
|
||||
await _dataSource.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetAsync_ReturnsNullForUnknownRunId()
|
||||
{
|
||||
// Act
|
||||
@@ -51,7 +53,8 @@ public sealed class PostgresPackRunStateStoreTests : IAsyncLifetime
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAndGet_RoundTripsState()
|
||||
{
|
||||
// Arrange
|
||||
@@ -70,7 +73,8 @@ public sealed class PostgresPackRunStateStoreTests : IAsyncLifetime
|
||||
fetched.Steps.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAsync_UpdatesExistingState()
|
||||
{
|
||||
// Arrange
|
||||
@@ -88,7 +92,8 @@ public sealed class PostgresPackRunStateStoreTests : IAsyncLifetime
|
||||
fetched!.PlanHash.Should().Be("sha256:hash2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ListAsync_ReturnsAllStates()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -4,11 +4,13 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.TaskRunner.WebService.Deprecation;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class ApiDeprecationTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DeprecatedEndpoint_PathPattern_MatchesExpected()
|
||||
{
|
||||
var endpoint = new DeprecatedEndpoint
|
||||
@@ -25,7 +27,8 @@ public sealed class ApiDeprecationTests
|
||||
Assert.NotNull(endpoint.SunsetAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ApiDeprecationOptions_DefaultValues_AreCorrect()
|
||||
{
|
||||
var options = new ApiDeprecationOptions();
|
||||
@@ -36,7 +39,8 @@ public sealed class ApiDeprecationTests
|
||||
Assert.Empty(options.DeprecatedEndpoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LoggingDeprecationNotificationService_GetUpcoming_FiltersCorrectly()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
@@ -73,7 +77,8 @@ public sealed class ApiDeprecationTests
|
||||
Assert.Equal("/v1/soon/*", upcoming[0].EndpointPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LoggingDeprecationNotificationService_GetUpcoming_OrdersBySunsetDate()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
@@ -100,7 +105,8 @@ public sealed class ApiDeprecationTests
|
||||
Assert.Equal("/v1/third/*", upcoming[2].EndpointPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DeprecationInfo_DaysUntilSunset_CalculatesCorrectly()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
@@ -118,7 +124,8 @@ public sealed class ApiDeprecationTests
|
||||
Assert.Equal("/v2/test/*", info.ReplacementPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DeprecationNotification_RecordProperties_AreAccessible()
|
||||
{
|
||||
var notification = new DeprecationNotification(
|
||||
@@ -135,7 +142,8 @@ public sealed class ApiDeprecationTests
|
||||
Assert.Equal(2, notification.AffectedConsumerIds?.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PathPattern_WildcardToRegex_MatchesSingleSegment()
|
||||
{
|
||||
var pattern = "^" + Regex.Escape("/v1/packs/*")
|
||||
@@ -148,7 +156,8 @@ public sealed class ApiDeprecationTests
|
||||
Assert.DoesNotMatch(pattern, "/v2/packs/foo");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PathPattern_DoubleWildcard_MatchesMultipleSegments()
|
||||
{
|
||||
var pattern = "^" + Regex.Escape("/v1/legacy/**")
|
||||
|
||||
@@ -2,11 +2,13 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.TaskRunner.Core.Evidence;
|
||||
using StellaOps.TaskRunner.Core.Events;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class BundleImportEvidenceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BundleImportHashChain_Compute_CreatesDeterministicHash()
|
||||
{
|
||||
var input = new BundleImportInputManifest(
|
||||
@@ -41,7 +43,8 @@ public sealed class BundleImportEvidenceTests
|
||||
Assert.StartsWith("sha256:", chain1.RootHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BundleImportHashChain_Compute_DifferentInputsProduceDifferentHashes()
|
||||
{
|
||||
var input1 = new BundleImportInputManifest(
|
||||
@@ -78,7 +81,8 @@ public sealed class BundleImportEvidenceTests
|
||||
Assert.NotEqual(chain1.InputsHash, chain2.InputsHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task BundleImportEvidenceService_CaptureAsync_StoresEvidence()
|
||||
{
|
||||
var store = new InMemoryPackRunEvidenceStore();
|
||||
@@ -96,7 +100,8 @@ public sealed class BundleImportEvidenceTests
|
||||
Assert.Equal(1, store.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task BundleImportEvidenceService_CaptureAsync_CreatesCorrectMaterials()
|
||||
{
|
||||
var store = new InMemoryPackRunEvidenceStore();
|
||||
@@ -120,7 +125,8 @@ public sealed class BundleImportEvidenceTests
|
||||
Assert.Contains(snapshot.Materials, m => m.Section == "hashchain");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task BundleImportEvidenceService_CaptureAsync_SetsCorrectMetadata()
|
||||
{
|
||||
var store = new InMemoryPackRunEvidenceStore();
|
||||
@@ -141,7 +147,8 @@ public sealed class BundleImportEvidenceTests
|
||||
Assert.Equal("2", snapshot.Metadata["outputCount"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task BundleImportEvidenceService_CaptureAsync_EmitsTimelineEvent()
|
||||
{
|
||||
var store = new InMemoryPackRunEvidenceStore();
|
||||
@@ -165,7 +172,8 @@ public sealed class BundleImportEvidenceTests
|
||||
Assert.Equal("bundle.import.evidence_captured", evt.EventType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task BundleImportEvidenceService_GetAsync_ReturnsEvidence()
|
||||
{
|
||||
var store = new InMemoryPackRunEvidenceStore();
|
||||
@@ -183,7 +191,8 @@ public sealed class BundleImportEvidenceTests
|
||||
Assert.Equal(evidence.TenantId, retrieved.TenantId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task BundleImportEvidenceService_GetAsync_ReturnsNullForMissingJob()
|
||||
{
|
||||
var store = new InMemoryPackRunEvidenceStore();
|
||||
@@ -196,7 +205,8 @@ public sealed class BundleImportEvidenceTests
|
||||
Assert.Null(retrieved);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task BundleImportEvidenceService_ExportToPortableBundleAsync_CreatesFile()
|
||||
{
|
||||
var store = new InMemoryPackRunEvidenceStore();
|
||||
@@ -230,7 +240,8 @@ public sealed class BundleImportEvidenceTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task BundleImportEvidenceService_ExportToPortableBundleAsync_FailsForMissingJob()
|
||||
{
|
||||
var store = new InMemoryPackRunEvidenceStore();
|
||||
@@ -249,7 +260,8 @@ public sealed class BundleImportEvidenceTests
|
||||
Assert.Contains("No evidence found", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BundleImportEvidence_RecordProperties_AreAccessible()
|
||||
{
|
||||
var evidence = CreateTestEvidence();
|
||||
@@ -264,7 +276,8 @@ public sealed class BundleImportEvidenceTests
|
||||
Assert.NotNull(evidence.ValidationResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BundleImportValidationResult_RecordProperties_AreAccessible()
|
||||
{
|
||||
var result = new BundleImportValidationResult(
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class BundleIngestionStepExecutorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ValidBundle_CopiesAndSucceeds()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
@@ -41,7 +42,8 @@ public sealed class BundleIngestionStepExecutorTests
|
||||
Assert.Contains(checksum, metadata, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ChecksumMismatch_Fails()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
@@ -64,10 +66,12 @@ public sealed class BundleIngestionStepExecutorTests
|
||||
Assert.Contains("Checksum mismatch", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_MissingChecksum_Fails()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
using StellaOps.TestKit;
|
||||
var source = Path.Combine(temp.Path, "bundle.tgz");
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
await File.WriteAllTextAsync(source, "bundle-data", ct);
|
||||
@@ -86,7 +90,8 @@ public sealed class BundleIngestionStepExecutorTests
|
||||
Assert.Contains("Checksum is required", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_UnknownUses_NoOpSuccess()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using StellaOps.TaskRunner.Core.Execution;
|
||||
using StellaOps.TaskRunner.Infrastructure.Execution;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class FilePackRunLogStoreTests : IDisposable
|
||||
@@ -12,7 +13,8 @@ public sealed class FilePackRunLogStoreTests : IDisposable
|
||||
rootPath = Path.Combine(Path.GetTempPath(), "StellaOps_TaskRunnerTests", Guid.NewGuid().ToString("n"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AppendAndReadAsync_RoundTripsEntriesInOrder()
|
||||
{
|
||||
var store = new FilePackRunLogStore(rootPath);
|
||||
@@ -61,7 +63,8 @@ public sealed class FilePackRunLogStoreTests : IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExistsAsync_ReturnsFalseWhenNoLogPresent()
|
||||
{
|
||||
var store = new FilePackRunLogStore(rootPath);
|
||||
|
||||
@@ -3,11 +3,13 @@ using StellaOps.TaskRunner.Core.Execution;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
using StellaOps.TaskRunner.Infrastructure.Execution;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class FilePackRunStateStoreTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAndGetAsync_RoundTripsState()
|
||||
{
|
||||
var directory = CreateTempDirectory();
|
||||
@@ -34,7 +36,8 @@ public sealed class FilePackRunStateStoreTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ListAsync_ReturnsStatesInDeterministicOrder()
|
||||
{
|
||||
var directory = CreateTempDirectory();
|
||||
|
||||
@@ -5,7 +5,8 @@ namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class FilesystemPackRunArtifactReaderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ListAsync_ReturnsEmpty_WhenManifestMissing()
|
||||
{
|
||||
using var temp = new TempDir();
|
||||
@@ -17,10 +18,12 @@ public sealed class FilesystemPackRunArtifactReaderTests
|
||||
Assert.Empty(results);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ListAsync_ParsesManifestAndSortsByName()
|
||||
{
|
||||
using var temp = new TempDir();
|
||||
using StellaOps.TestKit;
|
||||
var runId = "run-1";
|
||||
var manifestPath = Path.Combine(temp.Path, "run-1", "artifact-manifest.json");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)!);
|
||||
|
||||
@@ -6,6 +6,7 @@ using StellaOps.TaskRunner.Core.Planning;
|
||||
using StellaOps.TaskRunner.Infrastructure.Execution;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class FilesystemPackRunArtifactUploaderTests : IDisposable
|
||||
@@ -17,7 +18,8 @@ public sealed class FilesystemPackRunArtifactUploaderTests : IDisposable
|
||||
artifactsRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("n"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CopiesFileOutputs()
|
||||
{
|
||||
var sourceFile = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():n}.txt");
|
||||
@@ -43,7 +45,8 @@ public sealed class FilesystemPackRunArtifactUploaderTests : IDisposable
|
||||
Assert.Equal("files/bundle.txt", manifest.Outputs[0].StoredPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RecordsMissingFilesWithoutThrowing()
|
||||
{
|
||||
var uploader = CreateUploader();
|
||||
@@ -57,7 +60,8 @@ public sealed class FilesystemPackRunArtifactUploaderTests : IDisposable
|
||||
Assert.Equal("missing", manifest.Outputs[0].Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WritesExpressionOutputsAsJson()
|
||||
{
|
||||
var uploader = CreateUploader();
|
||||
|
||||
@@ -2,11 +2,13 @@ using System.Text.Json;
|
||||
using StellaOps.AirGap.Policy;
|
||||
using StellaOps.TaskRunner.Infrastructure.Execution;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class FilesystemPackRunDispatcherTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TryDequeueAsync_BlocksJob_WhenEgressPolicyDeniesDestination()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "StellaOps_TaskRunnerTests", Guid.NewGuid().ToString("n"));
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using StellaOps.TaskRunner.WebService;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class OpenApiMetadataFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_ProducesExpectedDefaults()
|
||||
{
|
||||
var metadata = OpenApiMetadataFactory.Create();
|
||||
@@ -20,7 +22,8 @@ public sealed class OpenApiMetadataFactoryTests
|
||||
Assert.True(hashPart.All(c => char.IsDigit(c) || (c >= 'a' && c <= 'f')));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_AllowsOverrideUrl()
|
||||
{
|
||||
var metadata = OpenApiMetadataFactory.Create("/docs/openapi.json");
|
||||
@@ -28,7 +31,8 @@ public sealed class OpenApiMetadataFactoryTests
|
||||
Assert.Equal("/docs/openapi.json", metadata.SpecUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_SignatureIncludesAllComponents()
|
||||
{
|
||||
var metadata1 = OpenApiMetadataFactory.Create("/path1");
|
||||
@@ -38,7 +42,8 @@ public sealed class OpenApiMetadataFactoryTests
|
||||
Assert.NotEqual(metadata1.Signature, metadata2.Signature);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_ETagIsDeterministic()
|
||||
{
|
||||
var metadata1 = OpenApiMetadataFactory.Create();
|
||||
|
||||
@@ -2,11 +2,13 @@ using System.Text.Json.Nodes;
|
||||
using StellaOps.TaskRunner.Core.Execution;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class PackRunApprovalCoordinatorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_FromPlan_PopulatesApprovals()
|
||||
{
|
||||
var plan = BuildPlan();
|
||||
@@ -18,7 +20,8 @@ public sealed class PackRunApprovalCoordinatorTests
|
||||
Assert.Equal(PackRunApprovalStatus.Pending, approvals[0].Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Approve_AllowsResumeWhenLastApprovalCompletes()
|
||||
{
|
||||
var plan = BuildPlan();
|
||||
@@ -31,7 +34,8 @@ public sealed class PackRunApprovalCoordinatorTests
|
||||
Assert.Equal("approver-1", result.State.ActorId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Reject_DoesNotResumeAndMarksState()
|
||||
{
|
||||
var plan = BuildPlan();
|
||||
@@ -44,7 +48,8 @@ public sealed class PackRunApprovalCoordinatorTests
|
||||
Assert.Equal("Not safe", result.State.Summary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BuildNotifications_UsesRequirements()
|
||||
{
|
||||
var plan = BuildPlan();
|
||||
@@ -57,7 +62,8 @@ public sealed class PackRunApprovalCoordinatorTests
|
||||
Assert.Contains("Packs.Approve", notification.RequiredGrants);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BuildPolicyNotifications_ProducesGateMetadata()
|
||||
{
|
||||
var plan = BuildPolicyPlan();
|
||||
|
||||
@@ -4,11 +4,13 @@ using StellaOps.TaskRunner.Core.Execution;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
using StellaOps.TaskRunner.Infrastructure.Execution;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class PackRunApprovalDecisionServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ApplyAsync_ApprovingLastGateSchedulesResume()
|
||||
{
|
||||
var plan = TestPlanFactory.CreatePlan();
|
||||
@@ -48,7 +50,8 @@ public sealed class PackRunApprovalDecisionServiceTests
|
||||
Assert.Equal(PackRunApprovalStatus.Approved, approvalStore.LastUpdated?.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ApplyAsync_ReturnsNotFoundWhenStateMissing()
|
||||
{
|
||||
var approvalStore = new InMemoryApprovalStore(new Dictionary<string, IReadOnlyList<PackRunApprovalState>>());
|
||||
@@ -69,7 +72,8 @@ public sealed class PackRunApprovalDecisionServiceTests
|
||||
Assert.False(scheduler.ScheduledContexts.Any());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ApplyAsync_ReturnsPlanHashMismatchWhenIncorrect()
|
||||
{
|
||||
var plan = TestPlanFactory.CreatePlan();
|
||||
@@ -107,7 +111,8 @@ public sealed class PackRunApprovalDecisionServiceTests
|
||||
Assert.False(scheduler.ScheduledContexts.Any());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ApplyAsync_ReturnsPlanHashMismatchWhenFormatInvalid()
|
||||
{
|
||||
var plan = TestPlanFactory.CreatePlan();
|
||||
|
||||
@@ -3,11 +3,13 @@ using StellaOps.TaskRunner.Core.Attestation;
|
||||
using StellaOps.TaskRunner.Core.Events;
|
||||
using StellaOps.TaskRunner.Core.Evidence;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class PackRunAttestationTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GenerateAsync_CreatesAttestationWithSubjects()
|
||||
{
|
||||
var store = new InMemoryPackRunAttestationStore();
|
||||
@@ -45,7 +47,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.NotNull(result.Attestation.Envelope);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GenerateAsync_WithoutSigner_CreatesPendingAttestation()
|
||||
{
|
||||
var store = new InMemoryPackRunAttestationStore();
|
||||
@@ -79,7 +82,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.Null(result.Attestation.Envelope);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GenerateAsync_EmitsTimelineEvent()
|
||||
{
|
||||
var store = new InMemoryPackRunAttestationStore();
|
||||
@@ -115,7 +119,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.Equal(PackRunAttestationEventTypes.AttestationCreated, evt.EventType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ValidatesSubjectsMatch()
|
||||
{
|
||||
var store = new InMemoryPackRunAttestationStore();
|
||||
@@ -161,7 +166,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.Equal(PackRunRevocationStatus.NotRevoked, verifyResult.RevocationStatus);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_DetectsMismatchedSubjects()
|
||||
{
|
||||
var store = new InMemoryPackRunAttestationStore();
|
||||
@@ -213,7 +219,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.Contains(verifyResult.Errors, e => e.Contains("Missing subjects"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_DetectsRevokedAttestation()
|
||||
{
|
||||
var store = new InMemoryPackRunAttestationStore();
|
||||
@@ -264,7 +271,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.Equal(PackRunRevocationStatus.Revoked, verifyResult.RevocationStatus);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ReturnsErrorForNonExistentAttestation()
|
||||
{
|
||||
var store = new InMemoryPackRunAttestationStore();
|
||||
@@ -286,7 +294,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.Contains(verifyResult.Errors, e => e.Contains("not found"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ListByRunAsync_ReturnsAttestationsForRun()
|
||||
{
|
||||
var store = new InMemoryPackRunAttestationStore();
|
||||
@@ -321,7 +330,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.All(attestations, a => Assert.Equal("run-007", a.RunId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetEnvelopeAsync_ReturnsEnvelopeForSignedAttestation()
|
||||
{
|
||||
var store = new InMemoryPackRunAttestationStore();
|
||||
@@ -354,7 +364,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.Single(envelope.Signatures);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PackRunAttestationSubject_FromArtifact_ParsesSha256Prefix()
|
||||
{
|
||||
var artifact = new PackRunArtifactReference(
|
||||
@@ -369,7 +380,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.Equal("abcdef123456", subject.Digest["sha256"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PackRunAttestation_ComputeStatementDigest_IsDeterministic()
|
||||
{
|
||||
var subjects = new List<PackRunAttestationSubject>
|
||||
@@ -399,7 +411,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.StartsWith("sha256:", digest1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PackRunDsseEnvelope_ComputeDigest_IsDeterministic()
|
||||
{
|
||||
var envelope = new PackRunDsseEnvelope(
|
||||
@@ -414,7 +427,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.StartsWith("sha256:", digest1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GenerateAsync_WithExternalParameters_IncludesInPredicate()
|
||||
{
|
||||
var store = new InMemoryPackRunAttestationStore();
|
||||
@@ -450,7 +464,8 @@ public sealed class PackRunAttestationTests
|
||||
Assert.Contains("manifestUrl", result.Attestation.PredicateJson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GenerateAsync_WithResolvedDependencies_IncludesInPredicate()
|
||||
{
|
||||
var store = new InMemoryPackRunAttestationStore();
|
||||
|
||||
@@ -6,6 +6,7 @@ using StellaOps.TaskRunner.Core.Execution.Simulation;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -21,7 +22,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
|
||||
#region PackRunEvidenceSnapshot Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_WithMaterials_ComputesMerkleRoot()
|
||||
{
|
||||
// Arrange
|
||||
@@ -49,7 +51,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.StartsWith("sha256:", snapshot.RootHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_WithEmptyMaterials_ReturnsZeroHash()
|
||||
{
|
||||
// Act
|
||||
@@ -64,7 +67,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Equal("sha256:" + new string('0', 64), snapshot.RootHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_WithMetadata_StoresMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -89,7 +93,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Equal("value2", snapshot.Metadata["key2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_SameMaterials_ProducesDeterministicHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -111,7 +116,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Equal(snapshot1.RootHash, snapshot2.RootHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_MaterialOrderDoesNotAffectHash()
|
||||
{
|
||||
// Arrange - materials in different order
|
||||
@@ -140,7 +146,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Equal(snapshot1.RootHash, snapshot2.RootHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToJson_AndFromJson_RoundTrips()
|
||||
{
|
||||
// Arrange
|
||||
@@ -167,7 +174,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
|
||||
#region PackRunEvidenceMaterial Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FromString_ComputesSha256Hash()
|
||||
{
|
||||
// Act
|
||||
@@ -182,7 +190,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Equal(13, material.SizeBytes); // "Hello, World!" is 13 bytes
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FromJson_ComputesSha256Hash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -198,7 +207,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Equal("application/json", material.MediaType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FromContent_WithAttributes_StoresAttributes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -214,7 +224,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Equal("step-001", material.Attributes["stepId"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CanonicalPath_CombinesSectionAndPath()
|
||||
{
|
||||
// Act
|
||||
@@ -228,7 +239,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
|
||||
#region InMemoryPackRunEvidenceStore Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Store_AndGet_ReturnsSnapshot()
|
||||
{
|
||||
// Arrange
|
||||
@@ -248,7 +260,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Equal(snapshot.RootHash, retrieved.RootHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Get_NonExistent_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -261,7 +274,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ListByRun_ReturnsMatchingSnapshots()
|
||||
{
|
||||
// Arrange
|
||||
@@ -291,7 +305,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.All(results, s => Assert.Equal(TestRunId, s.RunId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ListByKind_ReturnsMatchingSnapshots()
|
||||
{
|
||||
// Arrange
|
||||
@@ -324,7 +339,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.All(results, s => Assert.Equal(PackRunEvidenceSnapshotKind.StepExecution, s.Kind));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Verify_ValidSnapshot_ReturnsValid()
|
||||
{
|
||||
// Arrange
|
||||
@@ -349,7 +365,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Null(result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Verify_NonExistent_ReturnsInvalid()
|
||||
{
|
||||
// Arrange
|
||||
@@ -367,7 +384,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
|
||||
#region PackRunRedactionGuard Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactTranscript_RedactsSensitiveOutput()
|
||||
{
|
||||
// Arrange
|
||||
@@ -393,7 +411,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Contains("[REDACTED", redacted.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactTranscript_PreservesNonSensitiveOutput()
|
||||
{
|
||||
// Arrange
|
||||
@@ -418,7 +437,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Equal("Build completed successfully", redacted.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactIdentity_RedactsEmail()
|
||||
{
|
||||
// Arrange
|
||||
@@ -433,7 +453,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Contains("[", redacted); // Contains redaction markers
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactIdentity_HashesNonEmailIdentity()
|
||||
{
|
||||
// Arrange
|
||||
@@ -447,7 +468,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.EndsWith("]", redacted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactApproval_RedactsApproverAndComments()
|
||||
{
|
||||
// Arrange
|
||||
@@ -470,7 +492,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Contains("[REDACTED", redacted.Comments);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RedactValue_ReturnsHashedValue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -485,7 +508,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.DoesNotContain("super-secret-value", redacted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NoOpRedactionGuard_PreservesAllData()
|
||||
{
|
||||
// Arrange
|
||||
@@ -515,7 +539,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
|
||||
#region PackRunEvidenceSnapshotService Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CaptureRunCompletion_StoresSnapshot()
|
||||
{
|
||||
// Arrange
|
||||
@@ -544,7 +569,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Equal(1, store.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CaptureRunCompletion_WithTranscripts_IncludesRedactedTranscripts()
|
||||
{
|
||||
// Arrange
|
||||
@@ -574,7 +600,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.NotNull(transcriptMaterial);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CaptureStepExecution_CapturesTranscript()
|
||||
{
|
||||
// Arrange
|
||||
@@ -599,7 +626,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Contains(result.Snapshot.Materials, m => m.Section == "transcript");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CaptureApprovalDecision_CapturesApproval()
|
||||
{
|
||||
// Arrange
|
||||
@@ -629,7 +657,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Contains(result.Snapshot.Materials, m => m.Section == "approval");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CapturePolicyEvaluation_CapturesEvaluation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -659,7 +688,8 @@ public sealed class PackRunEvidenceSnapshotTests
|
||||
Assert.Contains(result.Snapshot.Materials, m => m.Section == "policy");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CaptureRunCompletion_EmitsTimelineEvent()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -2,11 +2,13 @@ using System.Text.Json.Nodes;
|
||||
using StellaOps.TaskRunner.Core.Execution;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class PackRunExecutionGraphBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_GeneratesParallelMetadata()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Parallel);
|
||||
@@ -31,7 +33,8 @@ public sealed class PackRunExecutionGraphBuilderTests
|
||||
Assert.All(parallel.Children, child => Assert.Equal(PackRunStepKind.Run, child.Kind));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_PreservesMapIterationsAndDisabledSteps()
|
||||
{
|
||||
var planner = new TaskPackPlanner();
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using StellaOps.TaskRunner.Core.Execution;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class PackRunGateStateUpdaterTests
|
||||
@@ -10,7 +11,8 @@ public sealed class PackRunGateStateUpdaterTests
|
||||
private static readonly DateTimeOffset RequestedAt = DateTimeOffset.UnixEpoch;
|
||||
private static readonly DateTimeOffset UpdateTimestamp = DateTimeOffset.UnixEpoch.AddMinutes(5);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Apply_ApprovedGate_ClearsReasonAndSucceeds()
|
||||
{
|
||||
var plan = BuildApprovalPlan();
|
||||
@@ -30,7 +32,8 @@ public sealed class PackRunGateStateUpdaterTests
|
||||
Assert.Equal(UpdateTimestamp, gate.LastTransitionAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Apply_RejectedGate_FlagsFailure()
|
||||
{
|
||||
var plan = BuildApprovalPlan();
|
||||
@@ -50,7 +53,8 @@ public sealed class PackRunGateStateUpdaterTests
|
||||
Assert.Equal(UpdateTimestamp, gate.LastTransitionAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Apply_PolicyGate_ClearsPendingReason()
|
||||
{
|
||||
var plan = BuildPolicyPlan();
|
||||
|
||||
@@ -3,11 +3,13 @@ using Microsoft.Extensions.Time.Testing;
|
||||
using StellaOps.TaskRunner.Core.Events;
|
||||
using StellaOps.TaskRunner.Core.IncidentMode;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class PackRunIncidentModeTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_ActivatesIncidentModeSuccessfully()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -34,7 +36,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.NotNull(result.Status.ExpiresAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_WithoutDuration_CreatesIndefiniteIncidentMode()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -57,7 +60,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.Null(result.Status.ExpiresAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ActivateAsync_EmitsTimelineEvent()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -88,7 +92,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.Equal(PackRunIncidentEventTypes.IncidentModeActivated, evt.EventType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DeactivateAsync_DeactivatesIncidentMode()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -118,7 +123,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.False(status.Active);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetStatusAsync_ReturnsInactiveForUnknownRun()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -132,7 +138,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.Equal(IncidentEscalationLevel.None, status.Level);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetStatusAsync_AutoDeactivatesExpiredIncidentMode()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -161,7 +168,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.False(status.Active);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HandleSloBreachAsync_ActivatesIncidentModeFromBreach()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -190,7 +198,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.Contains("error_rate_5m", result.Status.ActivationReason!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HandleSloBreachAsync_MapsSeverityToLevel()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -228,7 +237,8 @@ public sealed class PackRunIncidentModeTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HandleSloBreachAsync_ReturnsErrorForMissingResourceId()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -254,7 +264,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.Contains("No resource ID", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EscalateAsync_IncreasesEscalationLevel()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -286,7 +297,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.Contains("Escalated", result.Status.ActivationReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EscalateAsync_FailsWhenNotInIncidentMode()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -304,7 +316,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.Contains("not active", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EscalateAsync_FailsWhenNewLevelIsLowerOrEqual()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -333,7 +346,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.Contains("Cannot escalate", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetSettingsForLevel_ReturnsCorrectSettings()
|
||||
{
|
||||
var store = new InMemoryPackRunIncidentModeStore();
|
||||
@@ -356,7 +370,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.Equal(365, criticalSettings.RetentionPolicy.LogRetentionDays);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PackRunIncidentModeStatus_Inactive_ReturnsDefaultValues()
|
||||
{
|
||||
var inactive = PackRunIncidentModeStatus.Inactive();
|
||||
@@ -371,7 +386,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.False(inactive.DebugCaptureSettings.CaptureActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentRetentionPolicy_Extended_HasLongerRetention()
|
||||
{
|
||||
var defaultPolicy = IncidentRetentionPolicy.Default();
|
||||
@@ -382,7 +398,8 @@ public sealed class PackRunIncidentModeTests
|
||||
Assert.True(extendedPolicy.ArtifactRetentionDays > defaultPolicy.ArtifactRetentionDays);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IncidentTelemetrySettings_Enhanced_HasHigherSampling()
|
||||
{
|
||||
var defaultSettings = IncidentTelemetrySettings.Default();
|
||||
|
||||
@@ -3,11 +3,13 @@ using StellaOps.TaskRunner.Core.Execution;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class PackRunProcessorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ProcessNewRunAsync_PersistsApprovalsAndPublishesNotifications()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Sample);
|
||||
@@ -28,7 +30,8 @@ public sealed class PackRunProcessorTests
|
||||
Assert.Empty(publisher.Policies);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ProcessNewRunAsync_NoApprovals_ResumesImmediately()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Output);
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class PackRunProvenanceWriterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Filesystem_writer_emits_manifest()
|
||||
{
|
||||
var (context, state) = CreateRunState();
|
||||
@@ -27,6 +28,7 @@ public sealed class PackRunProvenanceWriterTests
|
||||
Assert.True(File.Exists(path));
|
||||
|
||||
using var document = JsonDocument.Parse(await File.ReadAllTextAsync(path, ct));
|
||||
using StellaOps.TestKit;
|
||||
var root = document.RootElement;
|
||||
Assert.Equal("run-test", root.GetProperty("runId").GetString());
|
||||
Assert.Equal("tenant-alpha", root.GetProperty("tenantId").GetString());
|
||||
|
||||
@@ -3,11 +3,13 @@ using StellaOps.TaskRunner.Core.Execution;
|
||||
using StellaOps.TaskRunner.Core.Execution.Simulation;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class PackRunSimulationEngineTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Simulate_IdentifiesGateStatuses()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.PolicyGate);
|
||||
@@ -24,7 +26,8 @@ public sealed class PackRunSimulationEngineTests
|
||||
Assert.Equal(PackRunSimulationStatus.Pending, run.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Simulate_MarksDisabledStepsAndOutputs()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Sample);
|
||||
@@ -47,7 +50,8 @@ public sealed class PackRunSimulationEngineTests
|
||||
Assert.Equal(PackRunExecutionGraph.DefaultFailurePolicy.BackoffSeconds, result.FailurePolicy.BackoffSeconds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Simulate_ProjectsOutputsAndRuntimeFlags()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Output);
|
||||
@@ -73,7 +77,8 @@ public sealed class PackRunSimulationEngineTests
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Simulate_LoopStep_SetsWillIterateStatus()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Loop);
|
||||
@@ -99,7 +104,8 @@ public sealed class PackRunSimulationEngineTests
|
||||
Assert.Equal("collect", loopStep.LoopInfo.AggregationMode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Simulate_ConditionalStep_SetsWillBranchStatus()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Conditional);
|
||||
@@ -123,7 +129,8 @@ public sealed class PackRunSimulationEngineTests
|
||||
Assert.True(conditionalStep.ConditionalInfo.OutputUnion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Simulate_PolicyGateStep_HasPolicyInfo()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.PolicyGate);
|
||||
|
||||
@@ -3,11 +3,13 @@ using StellaOps.TaskRunner.Core.Execution.Simulation;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
using StellaOps.TaskRunner.Core.TaskPacks;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class PackRunStateFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateInitialState_AssignsGateReasons()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Sample);
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
using StellaOps.TaskRunner.Core.Execution;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class PackRunStepStateMachineTests
|
||||
{
|
||||
private static readonly TaskPackPlanFailurePolicy RetryTwicePolicy = new(MaxAttempts: 3, BackoffSeconds: 5, ContinueOnError: false);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Start_FromPending_SetsRunning()
|
||||
{
|
||||
var state = PackRunStepStateMachine.Create();
|
||||
@@ -17,7 +19,8 @@ public sealed class PackRunStepStateMachineTests
|
||||
Assert.Equal(0, started.Attempts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CompleteSuccess_IncrementsAttempts()
|
||||
{
|
||||
var state = PackRunStepStateMachine.Create();
|
||||
@@ -29,7 +32,8 @@ public sealed class PackRunStepStateMachineTests
|
||||
Assert.Null(completed.NextAttemptAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RegisterFailure_SchedulesRetryUntilMaxAttempts()
|
||||
{
|
||||
var state = PackRunStepStateMachine.Create();
|
||||
@@ -54,7 +58,8 @@ public sealed class PackRunStepStateMachineTests
|
||||
Assert.Null(terminalFailure.State.NextAttemptAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Skip_FromPending_SetsSkipped()
|
||||
{
|
||||
var state = PackRunStepStateMachine.Create();
|
||||
|
||||
@@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.TaskRunner.Core.Events;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -18,7 +19,8 @@ public sealed class PackRunTimelineEventTests
|
||||
|
||||
#region Domain Model Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_WithRequiredFields_GeneratesValidEvent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -45,7 +47,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Null(evt.EventSeq);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_WithPayload_ComputesHashAndNormalizes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -69,7 +72,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal(64 + 7, evt.PayloadHash.Length); // sha256: prefix + 64 hex chars
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_WithStepId_SetsStepId()
|
||||
{
|
||||
// Act
|
||||
@@ -86,7 +90,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal(TestStepId, evt.StepId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_WithEvidencePointer_SetsPointer()
|
||||
{
|
||||
// Arrange
|
||||
@@ -108,7 +113,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal("sha256:def456", evt.EvidencePointer.BundleDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void WithReceivedAt_CreatesCopyWithTimestamp()
|
||||
{
|
||||
// Arrange
|
||||
@@ -131,7 +137,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal(evt.EventId, updated.EventId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void WithSequence_CreatesCopyWithSequence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -151,7 +158,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal(42, updated.EventSeq);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToJson_SerializesEvent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -174,7 +182,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Contains(TestStepId, json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FromJson_DeserializesEvent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -200,7 +209,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal(original.StepId, deserialized.StepId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GenerateIdempotencyKey_ReturnsConsistentKey()
|
||||
{
|
||||
// Arrange
|
||||
@@ -226,7 +236,8 @@ public sealed class PackRunTimelineEventTests
|
||||
|
||||
#region Event Types Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PackRunEventTypes_HasExpectedValues()
|
||||
{
|
||||
Assert.Equal("pack.started", PackRunEventTypes.PackStarted);
|
||||
@@ -237,7 +248,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal("pack.step.failed", PackRunEventTypes.StepFailed);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("pack.started", true)]
|
||||
[InlineData("pack.step.completed", true)]
|
||||
[InlineData("scan.completed", false)]
|
||||
@@ -251,7 +263,8 @@ public sealed class PackRunTimelineEventTests
|
||||
|
||||
#region Evidence Pointer Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EvidencePointer_Bundle_CreatesCorrectType()
|
||||
{
|
||||
var bundleId = Guid.NewGuid();
|
||||
@@ -262,7 +275,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal("sha256:abc", pointer.BundleDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EvidencePointer_Attestation_CreatesCorrectType()
|
||||
{
|
||||
var pointer = PackRunEvidencePointer.Attestation("subject:uri", "sha256:abc");
|
||||
@@ -272,7 +286,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal("sha256:abc", pointer.AttestationDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EvidencePointer_Manifest_CreatesCorrectType()
|
||||
{
|
||||
var pointer = PackRunEvidencePointer.Manifest("https://example.com/manifest", "/locker/path");
|
||||
@@ -286,7 +301,8 @@ public sealed class PackRunTimelineEventTests
|
||||
|
||||
#region In-Memory Sink Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InMemorySink_WriteAsync_StoresEvent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -309,7 +325,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal(1, sink.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InMemorySink_WriteAsync_Deduplicates()
|
||||
{
|
||||
// Arrange
|
||||
@@ -333,7 +350,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal(1, sink.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InMemorySink_AssignsMonotonicSequence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -365,7 +383,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal(2, result2.Sequence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InMemorySink_WriteBatchAsync_StoresMultiple()
|
||||
{
|
||||
// Arrange
|
||||
@@ -389,7 +408,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal(3, sink.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InMemorySink_GetEventsForRun_FiltersCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -423,7 +443,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal("run-2", run2Events[0].RunId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InMemorySink_Clear_RemovesAll()
|
||||
{
|
||||
// Arrange
|
||||
@@ -447,7 +468,8 @@ public sealed class PackRunTimelineEventTests
|
||||
|
||||
#region Emitter Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Emitter_EmitPackStartedAsync_CreatesEvent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -474,7 +496,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal(1, sink.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Emitter_EmitPackCompletedAsync_CreatesEvent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -497,7 +520,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal(PackRunEventTypes.PackCompleted, result.Event.EventType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Emitter_EmitPackFailedAsync_CreatesEventWithError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -523,7 +547,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Contains("failureReason", result.Event.Attributes!.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Emitter_EmitStepStartedAsync_IncludesAttempt()
|
||||
{
|
||||
// Arrange
|
||||
@@ -550,7 +575,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal("2", result.Event.Attributes!["attempt"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Emitter_EmitStepCompletedAsync_IncludesDuration()
|
||||
{
|
||||
// Arrange
|
||||
@@ -577,7 +603,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Contains("durationMs", result.Event.Attributes!.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Emitter_EmitStepFailedAsync_IncludesError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -605,7 +632,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal("Connection timeout", result.Event.Attributes!["error"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Emitter_EmitBatchAsync_OrdersEventsDeterministically()
|
||||
{
|
||||
// Arrange
|
||||
@@ -637,7 +665,8 @@ public sealed class PackRunTimelineEventTests
|
||||
Assert.Equal(PackRunEventTypes.StepStarted, stored[2].EventType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Emitter_EmitBatchAsync_HandlesDuplicates()
|
||||
{
|
||||
// Arrange
|
||||
@@ -673,7 +702,8 @@ public sealed class PackRunTimelineEventTests
|
||||
|
||||
#region Null Sink Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task NullSink_WriteAsync_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Options;
|
||||
using StellaOps.TaskRunner.Core.AirGap;
|
||||
using StellaOps.TaskRunner.Core.TaskPacks;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class SealedInstallEnforcerTests
|
||||
@@ -26,7 +27,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnforceAsync_WhenPackDoesNotRequireSealedInstall_ReturnsAllowed()
|
||||
{
|
||||
var statusProvider = new MockAirGapStatusProvider(SealedModeStatus.Unsealed());
|
||||
@@ -44,7 +46,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.Equal("Pack does not require sealed install", result.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnforceAsync_WhenEnforcementDisabled_ReturnsAllowed()
|
||||
{
|
||||
var statusProvider = new MockAirGapStatusProvider(SealedModeStatus.Unsealed());
|
||||
@@ -62,7 +65,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.Equal("Enforcement disabled", result.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnforceAsync_WhenSealedRequiredButEnvironmentNotSealed_ReturnsDenied()
|
||||
{
|
||||
var statusProvider = new MockAirGapStatusProvider(SealedModeStatus.Unsealed());
|
||||
@@ -83,7 +87,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.False(result.Violation.ActualSealed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnforceAsync_WhenSealedRequiredAndEnvironmentSealed_ReturnsAllowed()
|
||||
{
|
||||
var status = new SealedModeStatus(
|
||||
@@ -118,7 +123,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.Equal("Sealed install requirements satisfied", result.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnforceAsync_WhenBundleVersionBelowMinimum_ReturnsDenied()
|
||||
{
|
||||
var status = new SealedModeStatus(
|
||||
@@ -159,7 +165,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.Equal("min_bundle_version", result.RequirementViolations[0].Requirement);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnforceAsync_WhenAdvisoryTooStale_ReturnsDenied()
|
||||
{
|
||||
var status = new SealedModeStatus(
|
||||
@@ -205,7 +212,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.Equal("max_advisory_staleness_hours", result.RequirementViolations[0].Requirement);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnforceAsync_WhenTimeAnchorMissing_ReturnsDenied()
|
||||
{
|
||||
var status = new SealedModeStatus(
|
||||
@@ -246,7 +254,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.Equal("require_time_anchor", result.RequirementViolations[0].Requirement);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnforceAsync_WhenTimeAnchorInvalid_ReturnsDenied()
|
||||
{
|
||||
var status = new SealedModeStatus(
|
||||
@@ -286,7 +295,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.Contains(result.RequirementViolations, v => v.Requirement == "require_time_anchor");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnforceAsync_WhenStatusProviderFails_ReturnsDenied()
|
||||
{
|
||||
var statusProvider = new FailingAirGapStatusProvider();
|
||||
@@ -305,7 +315,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.Contains("Failed to verify", result.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedModeStatus_Unsealed_ReturnsCorrectDefaults()
|
||||
{
|
||||
var status = SealedModeStatus.Unsealed();
|
||||
@@ -316,7 +327,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.Null(status.BundleVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedModeStatus_Unavailable_ReturnsCorrectDefaults()
|
||||
{
|
||||
var status = SealedModeStatus.Unavailable();
|
||||
@@ -325,7 +337,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.Equal("unavailable", status.Mode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedRequirements_Default_HasExpectedValues()
|
||||
{
|
||||
var defaults = SealedRequirements.Default;
|
||||
@@ -337,7 +350,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.True(defaults.RequireSignatureVerification);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EnforcementResult_CreateAllowed_SetsProperties()
|
||||
{
|
||||
var result = SealedInstallEnforcementResult.CreateAllowed("Test message");
|
||||
@@ -349,7 +363,8 @@ public sealed class SealedInstallEnforcerTests
|
||||
Assert.Null(result.RequirementViolations);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EnforcementResult_CreateDenied_SetsProperties()
|
||||
{
|
||||
var violation = new SealedInstallViolation("pack-1", "1.0.0", true, false, "Seal the environment");
|
||||
|
||||
@@ -4,11 +4,13 @@ using System.Text.Json.Nodes;
|
||||
using StellaOps.AirGap.Policy;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class TaskPackPlannerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Plan_WithSequentialSteps_ComputesDeterministicHash()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Sample);
|
||||
@@ -39,7 +41,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.Equal(plan.Hash, resultB.Plan!.Hash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PlanHash_IsPrefixedSha256Digest()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Sample);
|
||||
@@ -54,7 +57,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.True(hex.All(c => Uri.IsHexDigit(c)), "Hash contains non-hex characters.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Plan_WhenConditionEvaluatesFalse_DisablesStep()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Sample);
|
||||
@@ -70,7 +74,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.False(result.Plan!.Steps[2].Enabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Plan_WithStepReferences_MarksParametersAsRuntime()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.StepReference);
|
||||
@@ -85,7 +90,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.Equal("steps.prepare.outputs.summary", referenceParameters["sourceSummary"].Expression);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Plan_WithMapStep_ExpandsIterations()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Map);
|
||||
@@ -106,7 +112,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.Equal("alpha", mapStep.Children![0].Parameters!["item"].Value!.GetValue<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CollectApprovalRequirements_GroupsGates()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Sample);
|
||||
@@ -124,7 +131,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.Contains(notifications, hint => hint.Type == "approval-request" && hint.StepId == plan.Steps[1].Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Plan_WithSecretReference_RecordsSecretMetadata()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Secret);
|
||||
@@ -140,7 +148,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.Equal("secrets.apiKey", param.Expression);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Plan_WithOutputs_ProjectsResolvedValues()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.Output);
|
||||
@@ -162,7 +171,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.Equal("steps.generate.outputs.evidence", evidence.Expression.Expression);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Plan_WithFailurePolicy_PopulatesPlanFailure()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.FailurePolicy);
|
||||
@@ -177,7 +187,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.False(plan.FailurePolicy.ContinueOnError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PolicyGateHints_IncludeRuntimeMetadata()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.PolicyGate);
|
||||
@@ -196,7 +207,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.Equal("steps.prepare.outputs.evidence", evidence.Expression);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Plan_SealedMode_BlocksUndeclaredEgress()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.EgressBlocked);
|
||||
@@ -212,7 +224,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.Contains(result.Errors, error => error.Message.Contains("example.com", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Plan_WhenRequiredInputMissing_ReturnsError()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.RequiredInput);
|
||||
@@ -223,7 +236,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.NotEmpty(result.Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Plan_SealedMode_AllowsDeclaredEgress()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.EgressAllowed);
|
||||
@@ -240,7 +254,8 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.True(result.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Plan_SealedMode_RuntimeUrlWithoutDeclaration_ReturnsError()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.EgressRuntime);
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class TaskRunnerClientTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StreamingLogReader_ParsesNdjsonLines()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
@@ -27,7 +28,8 @@ public sealed class TaskRunnerClientTests
|
||||
Assert.Equal("Starting", entries[0].Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StreamingLogReader_SkipsEmptyLines()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
@@ -43,7 +45,8 @@ public sealed class TaskRunnerClientTests
|
||||
Assert.Equal(2, entries.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StreamingLogReader_SkipsMalformedLines()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
@@ -54,6 +57,7 @@ public sealed class TaskRunnerClientTests
|
||||
""";
|
||||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
|
||||
|
||||
using StellaOps.TestKit;
|
||||
var entries = await StreamingLogReader.CollectAsync(stream, ct);
|
||||
|
||||
Assert.Equal(2, entries.Count);
|
||||
@@ -61,7 +65,8 @@ public sealed class TaskRunnerClientTests
|
||||
Assert.Equal("AlsoValid", entries[1].Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StreamingLogReader_FilterByLevel_FiltersCorrectly()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
@@ -84,7 +89,8 @@ public sealed class TaskRunnerClientTests
|
||||
Assert.DoesNotContain(filtered, e => e.Level == "info");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StreamingLogReader_GroupByStep_GroupsCorrectly()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
@@ -104,7 +110,8 @@ public sealed class TaskRunnerClientTests
|
||||
Assert.Single(groups["(global)"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Paginator_IteratesAllPages()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
@@ -129,7 +136,8 @@ public sealed class TaskRunnerClientTests
|
||||
Assert.Equal(allItems, collected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Paginator_GetPage_ReturnsCorrectPage()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
@@ -151,7 +159,8 @@ public sealed class TaskRunnerClientTests
|
||||
Assert.Equal(11, page2.Items[0]); // Items 11-20
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PaginatorExtensions_TakeAsync_TakesCorrectNumber()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
@@ -167,7 +176,8 @@ public sealed class TaskRunnerClientTests
|
||||
Assert.Equal(new[] { 1, 2, 3, 4, 5 }, taken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PaginatorExtensions_SkipAsync_SkipsCorrectNumber()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
@@ -183,7 +193,8 @@ public sealed class TaskRunnerClientTests
|
||||
Assert.Equal(new[] { 6, 7, 8, 9, 10 }, skipped);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PackRunLifecycleHelper_TerminalStatuses_IncludesExpectedStatuses()
|
||||
{
|
||||
Assert.Contains("completed", PackRunLifecycleHelper.TerminalStatuses);
|
||||
@@ -194,7 +205,8 @@ public sealed class TaskRunnerClientTests
|
||||
Assert.DoesNotContain("pending", PackRunLifecycleHelper.TerminalStatuses);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PackRunModels_CreatePackRunRequest_SerializesCorrectly()
|
||||
{
|
||||
var request = new CreatePackRunRequest(
|
||||
@@ -210,7 +222,8 @@ public sealed class TaskRunnerClientTests
|
||||
Assert.Equal("value", request.Inputs["key"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PackRunModels_SimulatedStep_HasCorrectProperties()
|
||||
{
|
||||
var loopInfo = new LoopInfo("{{ inputs.items }}", "item", 100);
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.TaskRunner.Core.Tenancy;
|
||||
using StellaOps.TaskRunner.Infrastructure.Tenancy;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -12,7 +13,8 @@ public sealed class TenantEnforcementTests
|
||||
{
|
||||
#region TenantContext Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TenantContext_RequiresTenantId()
|
||||
{
|
||||
Assert.ThrowsAny<ArgumentException>(() =>
|
||||
@@ -25,7 +27,8 @@ public sealed class TenantEnforcementTests
|
||||
new TenantContext(" ", "project-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TenantContext_RequiresProjectId()
|
||||
{
|
||||
Assert.ThrowsAny<ArgumentException>(() =>
|
||||
@@ -38,7 +41,8 @@ public sealed class TenantEnforcementTests
|
||||
new TenantContext("tenant-1", " "));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TenantContext_TrimsIds()
|
||||
{
|
||||
var context = new TenantContext(" tenant-1 ", " project-1 ");
|
||||
@@ -47,7 +51,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal("project-1", context.ProjectId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TenantContext_GeneratesStoragePrefix()
|
||||
{
|
||||
var context = new TenantContext("Tenant-1", "Project-1");
|
||||
@@ -55,7 +60,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal("tenant-1/project-1", context.StoragePrefix);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TenantContext_GeneratesFlatPrefix()
|
||||
{
|
||||
var context = new TenantContext("Tenant-1", "Project-1");
|
||||
@@ -63,7 +69,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal("tenant-1_project-1", context.FlatPrefix);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TenantContext_GeneratesLoggingScope()
|
||||
{
|
||||
var context = new TenantContext("tenant-1", "project-1");
|
||||
@@ -73,7 +80,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal("project-1", scope["ProjectId"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TenantContext_DefaultRestrictionsAreNone()
|
||||
{
|
||||
var context = new TenantContext("tenant-1", "project-1");
|
||||
@@ -88,7 +96,8 @@ public sealed class TenantEnforcementTests
|
||||
|
||||
#region StoragePathResolver Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void StoragePathResolver_HierarchicalPaths()
|
||||
{
|
||||
var options = new TenantStoragePathOptions
|
||||
@@ -113,7 +122,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Contains("tenant-1", logsPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void StoragePathResolver_FlatPaths()
|
||||
{
|
||||
var options = new TenantStoragePathOptions
|
||||
@@ -130,7 +140,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Contains("tenant-1_project-1_run-123", statePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void StoragePathResolver_HashedPaths()
|
||||
{
|
||||
var options = new TenantStoragePathOptions
|
||||
@@ -148,7 +159,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Contains("project-1", basePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void StoragePathResolver_ValidatesPathOwnership()
|
||||
{
|
||||
var options = new TenantStoragePathOptions
|
||||
@@ -173,7 +185,8 @@ public sealed class TenantEnforcementTests
|
||||
|
||||
#region EgressPolicy Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EgressPolicy_AllowsByDefault()
|
||||
{
|
||||
var options = new TenantEgressPolicyOptions { AllowByDefault = true };
|
||||
@@ -185,7 +198,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.True(result.IsAllowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EgressPolicy_BlocksGlobalBlocklist()
|
||||
{
|
||||
var options = new TenantEgressPolicyOptions
|
||||
@@ -202,7 +216,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal(EgressBlockReason.GlobalPolicy, result.BlockReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EgressPolicy_BlocksSuspendedTenants()
|
||||
{
|
||||
var options = new TenantEgressPolicyOptions { AllowByDefault = true };
|
||||
@@ -218,7 +233,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal(EgressBlockReason.TenantSuspended, result.BlockReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EgressPolicy_BlocksRestrictedTenants()
|
||||
{
|
||||
var options = new TenantEgressPolicyOptions { AllowByDefault = true };
|
||||
@@ -234,7 +250,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal(EgressBlockReason.TenantRestriction, result.BlockReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EgressPolicy_AllowsRestrictedTenantAllowlist()
|
||||
{
|
||||
var options = new TenantEgressPolicyOptions { AllowByDefault = true };
|
||||
@@ -255,7 +272,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.False(blockedResult.IsAllowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EgressPolicy_SupportsWildcardDomains()
|
||||
{
|
||||
var options = new TenantEgressPolicyOptions
|
||||
@@ -271,7 +289,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.False(result.IsAllowed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EgressPolicy_RecordsAttempts()
|
||||
{
|
||||
var auditLog = new InMemoryEgressAuditLog();
|
||||
@@ -298,7 +317,8 @@ public sealed class TenantEnforcementTests
|
||||
|
||||
#region TenantEnforcer Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TenantEnforcer_RequiresTenantId()
|
||||
{
|
||||
var enforcer = CreateTenantEnforcer();
|
||||
@@ -310,7 +330,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal(TenantEnforcementFailureKind.MissingTenantId, result.FailureKind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TenantEnforcer_RequiresProjectId()
|
||||
{
|
||||
var options = new TenancyEnforcementOptions { RequireProjectId = true };
|
||||
@@ -323,7 +344,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal(TenantEnforcementFailureKind.MissingProjectId, result.FailureKind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TenantEnforcer_BlocksSuspendedTenants()
|
||||
{
|
||||
var tenantProvider = new InMemoryTenantContextProvider();
|
||||
@@ -343,7 +365,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal(TenantEnforcementFailureKind.TenantSuspended, result.FailureKind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TenantEnforcer_BlocksReadOnlyTenants()
|
||||
{
|
||||
var tenantProvider = new InMemoryTenantContextProvider();
|
||||
@@ -362,7 +385,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal(TenantEnforcementFailureKind.TenantReadOnly, result.FailureKind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TenantEnforcer_EnforcesConcurrentRunLimit()
|
||||
{
|
||||
var tenantProvider = new InMemoryTenantContextProvider();
|
||||
@@ -385,7 +409,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal(TenantEnforcementFailureKind.MaxConcurrentRunsReached, result.FailureKind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TenantEnforcer_AllowsWithinConcurrentLimit()
|
||||
{
|
||||
var tenantProvider = new InMemoryTenantContextProvider();
|
||||
@@ -407,7 +432,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.NotNull(result.Tenant);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TenantEnforcer_TracksRunStartCompletion()
|
||||
{
|
||||
var runTracker = new InMemoryConcurrentRunTracker();
|
||||
@@ -427,7 +453,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal(0, await enforcer.GetConcurrentRunCountAsync(tenant));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TenantEnforcer_CreatesExecutionContext()
|
||||
{
|
||||
var tenantProvider = new InMemoryTenantContextProvider();
|
||||
@@ -446,7 +473,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Contains("tenant-1", context.LoggingScope["TenantId"].ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TenantEnforcer_ThrowsOnInvalidRequest()
|
||||
{
|
||||
var enforcer = CreateTenantEnforcer();
|
||||
@@ -460,7 +488,8 @@ public sealed class TenantEnforcementTests
|
||||
|
||||
#region ConcurrentRunTracker Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConcurrentRunTracker_TracksMultipleTenants()
|
||||
{
|
||||
var tracker = new InMemoryConcurrentRunTracker();
|
||||
@@ -474,7 +503,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal(0, await tracker.GetCountAsync("tenant-3"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConcurrentRunTracker_PreventsDoubleIncrement()
|
||||
{
|
||||
var tracker = new InMemoryConcurrentRunTracker();
|
||||
@@ -485,7 +515,8 @@ public sealed class TenantEnforcementTests
|
||||
Assert.Equal(1, await tracker.GetCountAsync("tenant-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConcurrentRunTracker_HandlesNonExistentDecrement()
|
||||
{
|
||||
var tracker = new InMemoryConcurrentRunTracker();
|
||||
|
||||
Reference in New Issue
Block a user