notify doctors work, audit work, new product advisory sprints
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.Policy.Engine.AirGap;
|
||||
using StellaOps.Policy.RiskProfile.Models;
|
||||
using Xunit;
|
||||
@@ -10,6 +11,7 @@ public sealed class RiskProfileAirGapExportServiceTests
|
||||
{
|
||||
private readonly FakeCryptoHash _cryptoHash = new();
|
||||
private readonly FakeTimeProvider _timeProvider = new();
|
||||
private readonly IGuidProvider _guidProvider = new SequentialGuidProvider();
|
||||
private readonly NullLogger<RiskProfileAirGapExportService> _logger = new();
|
||||
|
||||
private RiskProfileAirGapExportService CreateService(ISealedModeService? sealedMode = null)
|
||||
@@ -17,6 +19,7 @@ public sealed class RiskProfileAirGapExportServiceTests
|
||||
return new RiskProfileAirGapExportService(
|
||||
_cryptoHash,
|
||||
_timeProvider,
|
||||
_guidProvider,
|
||||
_logger,
|
||||
sealedMode);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Policy.Engine.Attestation;
|
||||
using StellaOps.TestKit.Fixtures;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Tests.Integration;
|
||||
|
||||
public sealed class PolicyEngineApiHostTests : IClassFixture<PolicyEngineWebServiceFixture>
|
||||
{
|
||||
private readonly PolicyEngineWebServiceFixture _factory;
|
||||
|
||||
public PolicyEngineApiHostTests(PolicyEngineWebServiceFixture factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Healthz_ReturnsOk()
|
||||
{
|
||||
using var client = _factory.CreateClient();
|
||||
|
||||
var response = await client.GetAsync("/healthz");
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PolicyLintRules_RequireAuth()
|
||||
{
|
||||
using var client = _factory.CreateClient();
|
||||
|
||||
var response = await client.GetAsync("/api/v1/policy/lint/rules");
|
||||
|
||||
Assert.True(response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PolicyLintRules_WithAuth_ReturnsOk()
|
||||
{
|
||||
using var client = _factory.CreateClient();
|
||||
client.DefaultRequestHeaders.Add(TestAuthHandler.HeaderName, TestAuthHandler.HeaderValue);
|
||||
|
||||
var response = await client.GetAsync("/api/v1/policy/lint/rules");
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PolicySnapshotsApi_RequiresAuth()
|
||||
{
|
||||
using var client = _factory.CreateClient();
|
||||
|
||||
var response = await client.GetAsync("/api/policy/snapshots");
|
||||
|
||||
Assert.True(response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerdictAttestationOptions_BindFromConfiguration()
|
||||
{
|
||||
var options = _factory.Services.GetRequiredService<VerdictAttestationOptions>();
|
||||
|
||||
Assert.True(options.Enabled);
|
||||
Assert.True(options.FailOnError);
|
||||
Assert.True(options.RekorEnabled);
|
||||
Assert.Equal("http://attestor.test", options.AttestorUrl);
|
||||
Assert.Equal(TimeSpan.FromSeconds(15), options.Timeout);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PolicyEngineWebServiceFixture : WebServiceFixture<StellaOps.Policy.Engine.Program>
|
||||
{
|
||||
public PolicyEngineWebServiceFixture()
|
||||
: base(ConfigureServices, ConfigureWebHost)
|
||||
{
|
||||
}
|
||||
|
||||
private static void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.RemoveAll<IHostedService>();
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = TestAuthHandler.SchemeName;
|
||||
options.DefaultChallengeScheme = TestAuthHandler.SchemeName;
|
||||
})
|
||||
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
|
||||
TestAuthHandler.SchemeName,
|
||||
_ => { });
|
||||
}
|
||||
|
||||
private static void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
builder.ConfigureAppConfiguration((_, config) =>
|
||||
{
|
||||
var settings = new Dictionary<string, string?>
|
||||
{
|
||||
["VerdictAttestation:Enabled"] = "true",
|
||||
["VerdictAttestation:FailOnError"] = "true",
|
||||
["VerdictAttestation:RekorEnabled"] = "true",
|
||||
["VerdictAttestation:AttestorUrl"] = "http://attestor.test",
|
||||
["VerdictAttestation:Timeout"] = "00:00:15"
|
||||
};
|
||||
|
||||
config.AddInMemoryCollection(settings);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
public const string SchemeName = "Test";
|
||||
public const string HeaderName = "X-Test-Auth";
|
||||
public const string HeaderValue = "true";
|
||||
|
||||
public TestAuthHandler(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock)
|
||||
: base(options, logger, encoder, clock)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
if (!Request.Headers.TryGetValue(HeaderName, out var value) ||
|
||||
!string.Equals(value, HeaderValue, StringComparison.Ordinal))
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Fail("Missing test auth header."));
|
||||
}
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim("scope", "policy:read"),
|
||||
new Claim("tenant_id", "test-tenant"),
|
||||
new Claim(ClaimTypes.NameIdentifier, "test-user")
|
||||
};
|
||||
|
||||
var identity = new ClaimsIdentity(claims, SchemeName);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
var ticket = new AuthenticationTicket(principal, SchemeName);
|
||||
|
||||
return Task.FromResult(AuthenticateResult.Success(ticket));
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ public sealed class PolicyBundleServiceTests
|
||||
var options = Microsoft.Extensions.Options.Options.Create(new PolicyEngineOptions());
|
||||
var metadataExtractor = new PolicyMetadataExtractor();
|
||||
var compilationService = new PolicyCompilationService(compiler, complexity, metadataExtractor, new StaticOptionsMonitor(options.Value), TimeProvider.System);
|
||||
var repo = new InMemoryPolicyPackRepository();
|
||||
var repo = new InMemoryPolicyPackRepository(TimeProvider.System);
|
||||
return new ServiceHarness(
|
||||
new PolicyBundleService(compilationService, repo, TimeProvider.System),
|
||||
repo);
|
||||
|
||||
@@ -436,7 +436,7 @@ public sealed class PolicyRuntimeEvaluationServiceTests
|
||||
|
||||
private static TestHarness CreateHarness()
|
||||
{
|
||||
var repository = new InMemoryPolicyPackRepository();
|
||||
var repository = new InMemoryPolicyPackRepository(TimeProvider.System);
|
||||
var cacheLogger = NullLogger<InMemoryPolicyEvaluationCache>.Instance;
|
||||
var serviceLogger = NullLogger<PolicyRuntimeEvaluationService>.Instance;
|
||||
var options = Microsoft.Extensions.Options.Options.Create(new PolicyEngineOptions());
|
||||
|
||||
@@ -12,7 +12,7 @@ public sealed class PolicyRuntimeEvaluatorTests
|
||||
[Fact]
|
||||
public async Task EvaluateAsync_ReturnsDeterministicDecisionAndCaches()
|
||||
{
|
||||
var repo = new InMemoryPolicyPackRepository();
|
||||
var repo = new InMemoryPolicyPackRepository(TimeProvider.System);
|
||||
await repo.StoreBundleAsync(
|
||||
"pack-1",
|
||||
1,
|
||||
@@ -41,7 +41,7 @@ public sealed class PolicyRuntimeEvaluatorTests
|
||||
[Fact]
|
||||
public async Task EvaluateAsync_ThrowsWhenBundleMissing()
|
||||
{
|
||||
var evaluator = new PolicyRuntimeEvaluator(new InMemoryPolicyPackRepository());
|
||||
var evaluator = new PolicyRuntimeEvaluator(new InMemoryPolicyPackRepository(TimeProvider.System));
|
||||
var request = new PolicyEvaluationRequest("pack-x", 1, "subject-a");
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => evaluator.EvaluateAsync(request, TestContext.Current.CancellationToken));
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using StellaOps.Policy.Engine.Tenancy;
|
||||
using Xunit;
|
||||
|
||||
@@ -139,11 +140,13 @@ public sealed class TenantContextMiddlewareTests
|
||||
private readonly NullLogger<TenantContextMiddleware> _logger;
|
||||
private readonly TenantContextAccessor _tenantAccessor;
|
||||
private readonly TenantContextOptions _options;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public TenantContextMiddlewareTests()
|
||||
{
|
||||
_logger = NullLogger<TenantContextMiddleware>.Instance;
|
||||
_tenantAccessor = new TenantContextAccessor();
|
||||
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero));
|
||||
_options = new TenantContextOptions
|
||||
{
|
||||
Enabled = true,
|
||||
@@ -166,7 +169,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
MsOptions.Options.Create(_options),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", "tenant-123");
|
||||
|
||||
@@ -192,7 +196,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
MsOptions.Options.Create(_options),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", "tenant-123", "project-456");
|
||||
|
||||
@@ -214,7 +219,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
var middleware = new TenantContextMiddleware(
|
||||
_ => { nextCalled = true; return Task.CompletedTask; },
|
||||
MsOptions.Options.Create(_options),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", tenantId: null);
|
||||
|
||||
@@ -245,7 +251,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
MsOptions.Options.Create(optionsNotRequired),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", tenantId: null);
|
||||
|
||||
@@ -266,7 +273,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
var middleware = new TenantContextMiddleware(
|
||||
_ => { nextCalled = true; return Task.CompletedTask; },
|
||||
MsOptions.Options.Create(_options),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/healthz", tenantId: null);
|
||||
|
||||
@@ -287,7 +295,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
var middleware = new TenantContextMiddleware(
|
||||
_ => { nextCalled = true; return Task.CompletedTask; },
|
||||
MsOptions.Options.Create(disabledOptions),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", tenantId: null);
|
||||
|
||||
@@ -313,7 +322,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
MsOptions.Options.Create(_options),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", tenantId);
|
||||
|
||||
@@ -338,7 +348,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
var middleware = new TenantContextMiddleware(
|
||||
_ => { nextCalled = true; return Task.CompletedTask; },
|
||||
MsOptions.Options.Create(_options),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", tenantId);
|
||||
|
||||
@@ -358,7 +369,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
var middleware = new TenantContextMiddleware(
|
||||
_ => Task.CompletedTask,
|
||||
MsOptions.Options.Create(_options),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", longTenantId);
|
||||
|
||||
@@ -384,7 +396,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
MsOptions.Options.Create(_options),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", "tenant-123", projectId);
|
||||
|
||||
@@ -409,7 +422,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
MsOptions.Options.Create(_options),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", "tenant-123");
|
||||
var claims = new[]
|
||||
@@ -440,7 +454,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
MsOptions.Options.Create(_options),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", "tenant-123");
|
||||
var claims = new[]
|
||||
@@ -471,7 +486,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
MsOptions.Options.Create(_options),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", "tenant-123");
|
||||
var claims = new[] { new Claim("sub", "user-id-123") };
|
||||
@@ -498,7 +514,8 @@ public sealed class TenantContextMiddlewareTests
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
MsOptions.Options.Create(_options),
|
||||
_logger);
|
||||
_logger,
|
||||
_timeProvider);
|
||||
|
||||
var context = CreateHttpContext("/api/risk/profiles", "tenant-123");
|
||||
context.Request.Headers["X-StellaOps-Actor"] = "service-account-123";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.Policy.Engine.Gates;
|
||||
using StellaOps.Policy.Engine.ReachabilityFacts;
|
||||
using StellaOps.Policy.Engine.Vex;
|
||||
@@ -398,6 +399,7 @@ public class VexDecisionEmitterTests
|
||||
gateEvaluator,
|
||||
options,
|
||||
TimeProvider.System,
|
||||
SystemGuidProvider.Instance,
|
||||
NullLogger<VexDecisionEmitter>.Instance);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using MsOptions = Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.Policy.Engine.Gates;
|
||||
using StellaOps.Policy.Engine.ReachabilityFacts;
|
||||
using StellaOps.Policy.Engine.Vex;
|
||||
@@ -499,6 +500,7 @@ public sealed class VexDecisionReachabilityIntegrationTests
|
||||
gateEvaluator,
|
||||
new OptionsMonitorWrapper<VexDecisionEmitterOptions>(options.Value),
|
||||
timeProvider ?? TimeProvider.System,
|
||||
SystemGuidProvider.Instance,
|
||||
NullLogger<VexDecisionEmitter>.Instance);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user