consolidate the tests locations
This commit is contained in:
@@ -1,163 +0,0 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.AirGap.Controller.Domain;
|
||||
using StellaOps.AirGap.Controller.Options;
|
||||
using StellaOps.AirGap.Controller.Services;
|
||||
using StellaOps.AirGap.Controller.Stores;
|
||||
using StellaOps.AirGap.Importer.Validation;
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
using StellaOps.AirGap.Time.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.AirGap.Controller.Tests;
|
||||
|
||||
public class AirGapStartupDiagnosticsHostedServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Blocks_when_allowlist_missing_for_sealed_state()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var store = new InMemoryAirGapStateStore();
|
||||
await store.SetAsync(new AirGapState
|
||||
{
|
||||
TenantId = "default",
|
||||
Sealed = true,
|
||||
PolicyHash = "policy-x",
|
||||
TimeAnchor = new TimeAnchor(now, "rough", "rough", "fp", "digest"),
|
||||
StalenessBudget = new StalenessBudget(60, 120)
|
||||
});
|
||||
|
||||
var trustDir = CreateTrustMaterial();
|
||||
var options = BuildOptions(trustDir);
|
||||
options.EgressAllowlist = null; // simulate missing config section
|
||||
|
||||
var service = CreateService(store, options, now);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.StartAsync(CancellationToken.None));
|
||||
Assert.Contains("egress-allowlist-missing", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Passes_when_materials_present_and_anchor_fresh()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var store = new InMemoryAirGapStateStore();
|
||||
await store.SetAsync(new AirGapState
|
||||
{
|
||||
TenantId = "default",
|
||||
Sealed = true,
|
||||
PolicyHash = "policy-ok",
|
||||
TimeAnchor = new TimeAnchor(now.AddMinutes(-1), "rough", "rough", "fp", "digest"),
|
||||
StalenessBudget = new StalenessBudget(300, 600)
|
||||
});
|
||||
|
||||
var trustDir = CreateTrustMaterial();
|
||||
var options = BuildOptions(trustDir, new[] { "127.0.0.1/32" });
|
||||
|
||||
var service = CreateService(store, options, now);
|
||||
|
||||
await service.StartAsync(CancellationToken.None); // should not throw
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Blocks_when_anchor_is_stale()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var store = new InMemoryAirGapStateStore();
|
||||
await store.SetAsync(new AirGapState
|
||||
{
|
||||
TenantId = "default",
|
||||
Sealed = true,
|
||||
PolicyHash = "policy-stale",
|
||||
TimeAnchor = new TimeAnchor(now.AddHours(-2), "rough", "rough", "fp", "digest"),
|
||||
StalenessBudget = new StalenessBudget(60, 90)
|
||||
});
|
||||
|
||||
var trustDir = CreateTrustMaterial();
|
||||
var options = BuildOptions(trustDir, new[] { "10.0.0.0/24" });
|
||||
|
||||
var service = CreateService(store, options, now);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.StartAsync(CancellationToken.None));
|
||||
Assert.Contains("time-anchor-stale", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Blocks_when_rotation_pending_without_dual_approval()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var store = new InMemoryAirGapStateStore();
|
||||
await store.SetAsync(new AirGapState
|
||||
{
|
||||
TenantId = "default",
|
||||
Sealed = true,
|
||||
PolicyHash = "policy-rot",
|
||||
TimeAnchor = new TimeAnchor(now, "rough", "rough", "fp", "digest"),
|
||||
StalenessBudget = new StalenessBudget(120, 240)
|
||||
});
|
||||
|
||||
var trustDir = CreateTrustMaterial();
|
||||
var options = BuildOptions(trustDir, new[] { "10.10.0.0/16" });
|
||||
options.Rotation.PendingKeys["k-new"] = Convert.ToBase64String(new byte[] { 1, 2, 3 });
|
||||
options.Rotation.ActiveKeys["k-old"] = Convert.ToBase64String(new byte[] { 9, 9, 9 });
|
||||
options.Rotation.ApproverIds.Add("approver-1");
|
||||
|
||||
var service = CreateService(store, options, now);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.StartAsync(CancellationToken.None));
|
||||
Assert.Contains("rotation:rotation-dual-approval-required", ex.Message);
|
||||
}
|
||||
|
||||
private static AirGapStartupOptions BuildOptions(string trustDir, string[]? allowlist = null)
|
||||
{
|
||||
return new AirGapStartupOptions
|
||||
{
|
||||
TenantId = "default",
|
||||
EgressAllowlist = allowlist,
|
||||
Trust = new TrustMaterialOptions
|
||||
{
|
||||
RootJsonPath = Path.Combine(trustDir, "root.json"),
|
||||
SnapshotJsonPath = Path.Combine(trustDir, "snapshot.json"),
|
||||
TimestampJsonPath = Path.Combine(trustDir, "timestamp.json")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static AirGapStartupDiagnosticsHostedService CreateService(IAirGapStateStore store, AirGapStartupOptions options, DateTimeOffset now)
|
||||
{
|
||||
return new AirGapStartupDiagnosticsHostedService(
|
||||
store,
|
||||
new StalenessCalculator(),
|
||||
new FixedTimeProvider(now),
|
||||
Microsoft.Extensions.Options.Options.Create(options),
|
||||
NullLogger<AirGapStartupDiagnosticsHostedService>.Instance,
|
||||
new AirGapTelemetry(NullLogger<AirGapTelemetry>.Instance),
|
||||
new TufMetadataValidator(),
|
||||
new RootRotationPolicy());
|
||||
}
|
||||
|
||||
private static string CreateTrustMaterial()
|
||||
{
|
||||
var dir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "airgap-trust-" + Guid.NewGuid().ToString("N"))).FullName;
|
||||
var expires = DateTimeOffset.UtcNow.AddDays(1).ToString("O");
|
||||
const string hash = "abc123";
|
||||
|
||||
File.WriteAllText(Path.Combine(dir, "root.json"), $"{{\"version\":1,\"expiresUtc\":\"{expires}\"}}");
|
||||
File.WriteAllText(Path.Combine(dir, "snapshot.json"), $"{{\"version\":1,\"expiresUtc\":\"{expires}\",\"meta\":{{\"snapshot\":{{\"hashes\":{{\"sha256\":\"{hash}\"}}}}}}}}");
|
||||
File.WriteAllText(Path.Combine(dir, "timestamp.json"), $"{{\"version\":1,\"expiresUtc\":\"{expires}\",\"snapshot\":{{\"meta\":{{\"hashes\":{{\"sha256\":\"{hash}\"}}}}}}}}");
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
private sealed class FixedTimeProvider : TimeProvider
|
||||
{
|
||||
private readonly DateTimeOffset _now;
|
||||
|
||||
public FixedTimeProvider(DateTimeOffset now)
|
||||
{
|
||||
_now = now;
|
||||
}
|
||||
|
||||
public override DateTimeOffset GetUtcNow() => _now;
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
using StellaOps.AirGap.Controller.Services;
|
||||
using StellaOps.AirGap.Controller.Stores;
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
using StellaOps.AirGap.Time.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.AirGap.Controller.Tests;
|
||||
|
||||
public class AirGapStateServiceTests
|
||||
{
|
||||
private readonly AirGapStateService _service;
|
||||
private readonly InMemoryAirGapStateStore _store = new();
|
||||
private readonly StalenessCalculator _calculator = new();
|
||||
|
||||
public AirGapStateServiceTests()
|
||||
{
|
||||
_service = new AirGapStateService(_store, _calculator);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Seal_sets_state_and_computes_staleness()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var anchor = new TimeAnchor(now.AddMinutes(-2), "roughtime", "roughtime", "fp", "digest");
|
||||
var budget = new StalenessBudget(60, 120);
|
||||
|
||||
await _service.SealAsync("tenant-a", "policy-1", anchor, budget, now);
|
||||
var status = await _service.GetStatusAsync("tenant-a", now);
|
||||
|
||||
Assert.True(status.State.Sealed);
|
||||
Assert.Equal("policy-1", status.State.PolicyHash);
|
||||
Assert.Equal("tenant-a", status.State.TenantId);
|
||||
Assert.True(status.Staleness.AgeSeconds > 0);
|
||||
Assert.True(status.Staleness.IsWarning);
|
||||
Assert.Equal(120 - status.Staleness.AgeSeconds, status.Staleness.SecondsRemaining);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Unseal_clears_sealed_flag_and_updates_timestamp()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
await _service.SealAsync("default", "hash", TimeAnchor.Unknown, StalenessBudget.Default, now);
|
||||
|
||||
var later = now.AddMinutes(1);
|
||||
await _service.UnsealAsync("default", later);
|
||||
var status = await _service.GetStatusAsync("default", later);
|
||||
|
||||
Assert.False(status.State.Sealed);
|
||||
Assert.Equal(later, status.State.LastTransitionAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Seal_persists_drift_baseline_seconds()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var anchor = new TimeAnchor(now.AddMinutes(-5), "roughtime", "roughtime", "fp", "digest");
|
||||
var budget = StalenessBudget.Default;
|
||||
|
||||
var state = await _service.SealAsync("tenant-drift", "policy-drift", anchor, budget, now);
|
||||
|
||||
Assert.Equal(300, state.DriftBaselineSeconds); // 5 minutes = 300 seconds
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Seal_creates_default_content_budgets_when_not_provided()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var anchor = new TimeAnchor(now.AddMinutes(-1), "roughtime", "roughtime", "fp", "digest");
|
||||
var budget = new StalenessBudget(120, 240);
|
||||
|
||||
var state = await _service.SealAsync("tenant-content", "policy-content", anchor, budget, now);
|
||||
|
||||
Assert.Contains("advisories", state.ContentBudgets.Keys);
|
||||
Assert.Contains("vex", state.ContentBudgets.Keys);
|
||||
Assert.Contains("policy", state.ContentBudgets.Keys);
|
||||
Assert.Equal(budget, state.ContentBudgets["advisories"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Seal_uses_provided_content_budgets()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var anchor = new TimeAnchor(now.AddMinutes(-1), "roughtime", "roughtime", "fp", "digest");
|
||||
var budget = StalenessBudget.Default;
|
||||
var contentBudgets = new Dictionary<string, StalenessBudget>
|
||||
{
|
||||
{ "advisories", new StalenessBudget(30, 60) },
|
||||
{ "vex", new StalenessBudget(60, 120) }
|
||||
};
|
||||
|
||||
var state = await _service.SealAsync("tenant-custom", "policy-custom", anchor, budget, now, contentBudgets);
|
||||
|
||||
Assert.Equal(new StalenessBudget(30, 60), state.ContentBudgets["advisories"]);
|
||||
Assert.Equal(new StalenessBudget(60, 120), state.ContentBudgets["vex"]);
|
||||
Assert.Equal(budget, state.ContentBudgets["policy"]); // Falls back to default
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStatus_returns_per_content_staleness()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var anchor = new TimeAnchor(now.AddSeconds(-45), "roughtime", "roughtime", "fp", "digest");
|
||||
var budget = StalenessBudget.Default;
|
||||
var contentBudgets = new Dictionary<string, StalenessBudget>
|
||||
{
|
||||
{ "advisories", new StalenessBudget(30, 60) },
|
||||
{ "vex", new StalenessBudget(60, 120) },
|
||||
{ "policy", new StalenessBudget(100, 200) }
|
||||
};
|
||||
|
||||
await _service.SealAsync("tenant-content-status", "policy-content-status", anchor, budget, now, contentBudgets);
|
||||
var status = await _service.GetStatusAsync("tenant-content-status", now);
|
||||
|
||||
Assert.NotEmpty(status.ContentStaleness);
|
||||
Assert.True(status.ContentStaleness["advisories"].IsWarning); // 45s >= 30s warning
|
||||
Assert.False(status.ContentStaleness["advisories"].IsBreach); // 45s < 60s breach
|
||||
Assert.False(status.ContentStaleness["vex"].IsWarning); // 45s < 60s warning
|
||||
Assert.False(status.ContentStaleness["policy"].IsWarning); // 45s < 100s warning
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
using StellaOps.AirGap.Controller.Domain;
|
||||
using StellaOps.AirGap.Controller.Stores;
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.AirGap.Controller.Tests;
|
||||
|
||||
public class InMemoryAirGapStateStoreTests
|
||||
{
|
||||
private readonly InMemoryAirGapStateStore _store = new();
|
||||
|
||||
[Fact]
|
||||
public async Task Upsert_and_read_state_by_tenant()
|
||||
{
|
||||
var state = new AirGapState
|
||||
{
|
||||
TenantId = "tenant-x",
|
||||
Sealed = true,
|
||||
PolicyHash = "hash-1",
|
||||
TimeAnchor = new TimeAnchor(DateTimeOffset.UtcNow, "roughtime", "roughtime", "fp", "digest"),
|
||||
StalenessBudget = new StalenessBudget(10, 20),
|
||||
LastTransitionAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
await _store.SetAsync(state);
|
||||
|
||||
var stored = await _store.GetAsync("tenant-x");
|
||||
Assert.True(stored.Sealed);
|
||||
Assert.Equal("hash-1", stored.PolicyHash);
|
||||
Assert.Equal("tenant-x", stored.TenantId);
|
||||
Assert.Equal(state.TimeAnchor.TokenDigest, stored.TimeAnchor.TokenDigest);
|
||||
Assert.Equal(10, stored.StalenessBudget.WarningSeconds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Enforces_singleton_per_tenant()
|
||||
{
|
||||
var first = new AirGapState { TenantId = "tenant-y", Sealed = true, PolicyHash = "h1" };
|
||||
var second = new AirGapState { TenantId = "tenant-y", Sealed = false, PolicyHash = "h2" };
|
||||
|
||||
await _store.SetAsync(first);
|
||||
await _store.SetAsync(second);
|
||||
|
||||
var stored = await _store.GetAsync("tenant-y");
|
||||
Assert.Equal("h2", stored.PolicyHash);
|
||||
Assert.False(stored.Sealed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Defaults_to_unknown_when_missing()
|
||||
{
|
||||
var stored = await _store.GetAsync("absent");
|
||||
Assert.False(stored.Sealed);
|
||||
Assert.Equal("absent", stored.TenantId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Parallel_upserts_keep_single_document()
|
||||
{
|
||||
var tasks = Enumerable.Range(0, 20).Select(i =>
|
||||
{
|
||||
var state = new AirGapState
|
||||
{
|
||||
TenantId = "tenant-parallel",
|
||||
Sealed = i % 2 == 0,
|
||||
PolicyHash = $"hash-{i}"
|
||||
};
|
||||
return _store.SetAsync(state);
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
var stored = await _store.GetAsync("tenant-parallel");
|
||||
Assert.StartsWith("hash-", stored.PolicyHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Multi_tenant_updates_do_not_collide()
|
||||
{
|
||||
var tenants = Enumerable.Range(0, 5).Select(i => $"t-{i}").ToArray();
|
||||
|
||||
var tasks = tenants.Select(t => _store.SetAsync(new AirGapState
|
||||
{
|
||||
TenantId = t,
|
||||
Sealed = true,
|
||||
PolicyHash = $"hash-{t}"
|
||||
}));
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
foreach (var t in tenants)
|
||||
{
|
||||
var stored = await _store.GetAsync(t);
|
||||
Assert.Equal($"hash-{t}", stored.PolicyHash);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Staleness_round_trip_matches_budget()
|
||||
{
|
||||
var anchor = new TimeAnchor(DateTimeOffset.UtcNow.AddMinutes(-3), "roughtime", "roughtime", "fp", "digest");
|
||||
var budget = new StalenessBudget(60, 600);
|
||||
await _store.SetAsync(new AirGapState
|
||||
{
|
||||
TenantId = "tenant-staleness",
|
||||
Sealed = true,
|
||||
PolicyHash = "hash-s",
|
||||
TimeAnchor = anchor,
|
||||
StalenessBudget = budget,
|
||||
LastTransitionAt = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
var stored = await _store.GetAsync("tenant-staleness");
|
||||
Assert.Equal(anchor.TokenDigest, stored.TimeAnchor.TokenDigest);
|
||||
Assert.Equal(budget.WarningSeconds, stored.StalenessBudget.WarningSeconds);
|
||||
Assert.Equal(budget.BreachSeconds, stored.StalenessBudget.BreachSeconds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Multi_tenant_states_preserve_transition_times()
|
||||
{
|
||||
var tenants = new[] { "a", "b", "c" };
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
foreach (var t in tenants)
|
||||
{
|
||||
await _store.SetAsync(new AirGapState
|
||||
{
|
||||
TenantId = t,
|
||||
Sealed = true,
|
||||
PolicyHash = $"ph-{t}",
|
||||
LastTransitionAt = now
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var t in tenants)
|
||||
{
|
||||
var state = await _store.GetAsync(t);
|
||||
Assert.Equal(now, state.LastTransitionAt);
|
||||
Assert.Equal($"ph-{t}", state.PolicyHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
using StellaOps.AirGap.Controller.Endpoints.Contracts;
|
||||
using StellaOps.AirGap.Controller.Services;
|
||||
using StellaOps.AirGap.Controller.Stores;
|
||||
using StellaOps.AirGap.Importer.Contracts;
|
||||
using StellaOps.AirGap.Importer.Validation;
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
using StellaOps.AirGap.Time.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.AirGap.Controller.Tests;
|
||||
|
||||
public class ReplayVerificationServiceTests
|
||||
{
|
||||
private readonly ReplayVerificationService _service;
|
||||
private readonly AirGapStateService _stateService;
|
||||
private readonly StalenessCalculator _staleness = new();
|
||||
private readonly InMemoryAirGapStateStore _store = new();
|
||||
|
||||
public ReplayVerificationServiceTests()
|
||||
{
|
||||
_stateService = new AirGapStateService(_store, _staleness);
|
||||
_service = new ReplayVerificationService(_stateService, new ReplayVerifier());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Passes_full_recompute_when_hashes_match()
|
||||
{
|
||||
var now = DateTimeOffset.Parse("2025-12-02T01:00:00Z");
|
||||
await _stateService.SealAsync("tenant-a", "policy-x", TimeAnchor.Unknown, StalenessBudget.Default, now);
|
||||
|
||||
var request = new VerifyRequest
|
||||
{
|
||||
Depth = ReplayDepth.FullRecompute,
|
||||
ManifestSha256 = new string('a', 64),
|
||||
BundleSha256 = new string('b', 64),
|
||||
ComputedManifestSha256 = new string('a', 64),
|
||||
ComputedBundleSha256 = new string('b', 64),
|
||||
ManifestCreatedAt = now.AddHours(-2),
|
||||
StalenessWindowHours = 24,
|
||||
BundlePolicyHash = "policy-x"
|
||||
};
|
||||
|
||||
var result = await _service.VerifyAsync("tenant-a", request, now);
|
||||
|
||||
Assert.True(result.IsValid);
|
||||
Assert.Equal("full-recompute-passed", result.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Detects_stale_manifest()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var request = new VerifyRequest
|
||||
{
|
||||
Depth = ReplayDepth.HashOnly,
|
||||
ManifestSha256 = new string('a', 64),
|
||||
BundleSha256 = new string('b', 64),
|
||||
ComputedManifestSha256 = new string('a', 64),
|
||||
ComputedBundleSha256 = new string('b', 64),
|
||||
ManifestCreatedAt = now.AddHours(-30),
|
||||
StalenessWindowHours = 12
|
||||
};
|
||||
|
||||
var result = await _service.VerifyAsync("default", request, now);
|
||||
|
||||
Assert.False(result.IsValid);
|
||||
Assert.Equal("manifest-stale", result.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Policy_freeze_requires_matching_policy()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
await _stateService.SealAsync("tenant-b", "sealed-policy", TimeAnchor.Unknown, StalenessBudget.Default, now);
|
||||
|
||||
var request = new VerifyRequest
|
||||
{
|
||||
Depth = ReplayDepth.PolicyFreeze,
|
||||
ManifestSha256 = new string('a', 64),
|
||||
BundleSha256 = new string('b', 64),
|
||||
ComputedManifestSha256 = new string('a', 64),
|
||||
ComputedBundleSha256 = new string('b', 64),
|
||||
ManifestCreatedAt = now,
|
||||
StalenessWindowHours = 48,
|
||||
BundlePolicyHash = "bundle-policy"
|
||||
};
|
||||
|
||||
var result = await _service.VerifyAsync("tenant-b", request, now);
|
||||
|
||||
Assert.False(result.IsValid);
|
||||
Assert.Equal("policy-hash-drift", result.Reason);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../src/AirGap/StellaOps.AirGap.Controller/StellaOps.AirGap.Controller.csproj" />
|
||||
<Compile Include="../../shared/*.cs" Link="Shared/%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user