Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -13,6 +13,7 @@ using MicrosoftOptions = Microsoft.Extensions.Options;
|
||||
using StellaOps.Graph.Indexer.Storage.Postgres.Repositories;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Storage.Postgres.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -44,7 +45,8 @@ public sealed class GraphQueryDeterminismTests : IAsyncLifetime
|
||||
|
||||
#region Result Ordering Determinism
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task MultipleIdempotencyQueries_ReturnSameOrder()
|
||||
{
|
||||
// Arrange - Insert multiple tokens
|
||||
@@ -81,7 +83,8 @@ public sealed class GraphQueryDeterminismTests : IAsyncLifetime
|
||||
results1.Should().AllBeEquivalentTo(true, "All tokens should be marked as seen");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConcurrentQueries_ProduceDeterministicResults()
|
||||
{
|
||||
// Arrange
|
||||
@@ -103,7 +106,8 @@ public sealed class GraphQueryDeterminismTests : IAsyncLifetime
|
||||
|
||||
#region Input Stability
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SameInput_ProducesSameHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -119,7 +123,8 @@ public sealed class GraphQueryDeterminismTests : IAsyncLifetime
|
||||
hash2.Should().Be(hash3, "Hash should be stable across multiple computations");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ShuffledInputs_ProduceSameCanonicalOrdering()
|
||||
{
|
||||
// Arrange
|
||||
@@ -135,7 +140,8 @@ public sealed class GraphQueryDeterminismTests : IAsyncLifetime
|
||||
"Shuffled inputs should produce identical canonical ordering");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Timestamps_DoNotAffectOrdering()
|
||||
{
|
||||
// Arrange - Insert tokens at "different" times (same logical batch)
|
||||
@@ -164,7 +170,8 @@ public sealed class GraphQueryDeterminismTests : IAsyncLifetime
|
||||
|
||||
#region Cross-Tenant Isolation with Determinism
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CrossTenant_QueriesRemainIsolated()
|
||||
{
|
||||
// Arrange - Create tokens that could collide without tenant isolation
|
||||
|
||||
@@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using MicrosoftOptions = Microsoft.Extensions.Options;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Storage.Postgres.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -35,7 +36,8 @@ public sealed class GraphStorageMigrationTests : IAsyncLifetime
|
||||
|
||||
#region Schema Structure Verification
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Schema_ContainsRequiredTables()
|
||||
{
|
||||
// Arrange
|
||||
@@ -59,7 +61,8 @@ public sealed class GraphStorageMigrationTests : IAsyncLifetime
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Schema_GraphNodes_HasRequiredColumns()
|
||||
{
|
||||
// Arrange
|
||||
@@ -76,7 +79,8 @@ public sealed class GraphStorageMigrationTests : IAsyncLifetime
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Schema_GraphEdges_HasRequiredColumns()
|
||||
{
|
||||
// Arrange
|
||||
@@ -97,7 +101,8 @@ public sealed class GraphStorageMigrationTests : IAsyncLifetime
|
||||
|
||||
#region Index Verification
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Schema_HasTenantIndexOnNodes()
|
||||
{
|
||||
// Act
|
||||
@@ -108,7 +113,8 @@ public sealed class GraphStorageMigrationTests : IAsyncLifetime
|
||||
"graph_nodes should have tenant index for multi-tenant queries");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Schema_HasTenantIndexOnEdges()
|
||||
{
|
||||
// Act
|
||||
@@ -123,7 +129,8 @@ public sealed class GraphStorageMigrationTests : IAsyncLifetime
|
||||
|
||||
#region Migration Safety
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Migration_Assembly_IsReachable()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -134,7 +141,8 @@ public sealed class GraphStorageMigrationTests : IAsyncLifetime
|
||||
assembly.GetTypes().Should().Contain(t => t.Name.Contains("Migration") || t.Name.Contains("DataSource"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Migration_SupportsIdempotentExecution()
|
||||
{
|
||||
// Act - Running migrations again should be idempotent
|
||||
|
||||
@@ -4,6 +4,7 @@ using MicrosoftOptions = Microsoft.Extensions.Options;
|
||||
using StellaOps.Graph.Indexer.Storage.Postgres.Repositories;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Storage.Postgres.Tests;
|
||||
|
||||
[Collection(GraphIndexerPostgresCollection.Name)]
|
||||
@@ -29,7 +30,8 @@ public sealed class PostgresIdempotencyStoreTests : IAsyncLifetime
|
||||
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HasSeenAsync_ReturnsFalseForNewToken()
|
||||
{
|
||||
// Arrange
|
||||
@@ -42,7 +44,8 @@ public sealed class PostgresIdempotencyStoreTests : IAsyncLifetime
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task MarkSeenAsync_ThenHasSeenAsync_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -56,7 +59,8 @@ public sealed class PostgresIdempotencyStoreTests : IAsyncLifetime
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task MarkSeenAsync_AllowsDifferentTokens()
|
||||
{
|
||||
// Arrange
|
||||
@@ -74,7 +78,8 @@ public sealed class PostgresIdempotencyStoreTests : IAsyncLifetime
|
||||
seen2.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task MarkSeenAsync_IsIdempotent()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Graph.Indexer.Storage.Postgres\StellaOps.Graph.Indexer.Storage.Postgres.csproj" />
|
||||
<ProjectReference Include="..\..\__Tests\__Libraries\StellaOps.Infrastructure.Postgres.Testing\StellaOps.Infrastructure.Postgres.Testing.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace StellaOps.Graph.Api.Tests;
|
||||
|
||||
public class AuditLoggerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LogsAndCapsSize()
|
||||
{
|
||||
var logger = new InMemoryAuditLogger();
|
||||
@@ -27,6 +28,7 @@ public class AuditLoggerTests
|
||||
Assert.True(recent.Count <= 100);
|
||||
// First entry is the most recent (minute 509). Verify using total minutes from epoch.
|
||||
var minutesFromEpoch = (int)(recent.First().Timestamp - DateTimeOffset.UnixEpoch).TotalMinutes;
|
||||
using StellaOps.TestKit;
|
||||
Assert.Equal(509, minutesFromEpoch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ using StellaOps.Graph.Api.Contracts;
|
||||
using StellaOps.Graph.Api.Services;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Api.Tests;
|
||||
|
||||
public class DiffServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DiffAsync_EmitsAddedRemovedChangedAndStats()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository();
|
||||
@@ -34,7 +36,8 @@ public class DiffServiceTests
|
||||
Assert.Contains(lines, l => l.Contains("\"type\":\"stats\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DiffAsync_WhenSnapshotMissing_ReturnsError()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository();
|
||||
|
||||
@@ -4,11 +4,13 @@ using StellaOps.Graph.Api.Contracts;
|
||||
using StellaOps.Graph.Api.Services;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Api.Tests;
|
||||
|
||||
public class ExportServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Export_ReturnsManifestAndDownloadablePayload()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository();
|
||||
@@ -28,7 +30,8 @@ public class ExportServiceTests
|
||||
Assert.Equal(job.Sha256, fetched!.Sha256);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Export_IncludesEdgesWhenRequested()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository();
|
||||
@@ -41,7 +44,8 @@ public class ExportServiceTests
|
||||
Assert.Contains("\"type\":\"edge\"", text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Export_RespectsSnapshotSelection()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository();
|
||||
|
||||
@@ -16,6 +16,8 @@ using StellaOps.Graph.Api.Contracts;
|
||||
using StellaOps.Graph.Api.Services;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Api.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -61,7 +63,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
|
||||
#region GRAPH-5100-006: Contract Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Query_ReturnsNdjsonFormat()
|
||||
{
|
||||
// Arrange
|
||||
@@ -88,7 +91,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Query_ReturnsNodeTypeInResponse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -109,7 +113,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
lines.Should().Contain(l => l.Contains("\"type\":\"node\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Query_WithEdges_ReturnsEdgeTypeInResponse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -131,7 +136,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
lines.Should().Contain(l => l.Contains("\"type\":\"edge\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Query_WithStats_ReturnsStatsTypeInResponse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -153,7 +159,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
lines.Should().Contain(l => l.Contains("\"type\":\"stats\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Query_ReturnsCursorInResponse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -174,7 +181,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
lines.Should().Contain(l => l.Contains("\"type\":\"cursor\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Query_EmptyResult_ReturnsEmptyCursor()
|
||||
{
|
||||
// Arrange
|
||||
@@ -195,7 +203,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
lines.Should().Contain(l => l.Contains("\"type\":\"cursor\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Query_BudgetExceeded_ReturnsErrorResponse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -222,7 +231,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
|
||||
#region GRAPH-5100-007: Auth Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AuthScope_GraphRead_IsRequired()
|
||||
{
|
||||
// This is a validation test - actual scope enforcement is in middleware
|
||||
@@ -233,7 +243,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
expectedScope.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AuthScope_GraphWrite_IsRequired()
|
||||
{
|
||||
// This is a validation test - actual scope enforcement is in middleware
|
||||
@@ -243,7 +254,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
expectedScope.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Query_ReturnsOnlyRequestedTenantData()
|
||||
{
|
||||
// Arrange - Request tenant1 data
|
||||
@@ -264,7 +276,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
lines.Should().NotContain(l => l.Contains("tenant2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Query_CrossTenant_ReturnsOnlyOwnData()
|
||||
{
|
||||
// Arrange - Request tenant2 data (which has only 1 artifact)
|
||||
@@ -286,7 +299,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
nodesFound.Should().Be(1, "tenant2 has only 1 artifact");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Query_InvalidTenant_ReturnsEmptyResults()
|
||||
{
|
||||
// Arrange
|
||||
@@ -312,7 +326,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
|
||||
#region GRAPH-5100-008: OTel Trace Assertions
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Query_EmitsActivityWithTenantId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -344,7 +359,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Query_MetricsIncludeTenantDimension()
|
||||
{
|
||||
// Arrange
|
||||
@@ -391,7 +407,8 @@ public sealed class GraphApiContractTests : IDisposable
|
||||
tags.Should().NotBeEmpty("Metrics should be recorded during query");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphMetrics_HasExpectedInstruments()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -4,11 +4,13 @@ using StellaOps.Graph.Api.Contracts;
|
||||
using StellaOps.Graph.Api.Services;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Api.Tests;
|
||||
|
||||
public sealed class LineageServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetLineageAsync_ReturnsSbomAndArtifactChain()
|
||||
{
|
||||
var repository = new InMemoryGraphRepository();
|
||||
|
||||
@@ -6,11 +6,13 @@ using StellaOps.Graph.Api.Contracts;
|
||||
using StellaOps.Graph.Api.Services;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Api.Tests;
|
||||
|
||||
public class LoadTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DeterministicOrdering_WithSyntheticGraph_RemainsStable()
|
||||
{
|
||||
var builder = new SyntheticGraphBuilder(seed: 42, nodeCount: 1000, edgeCount: 2000);
|
||||
@@ -35,7 +37,8 @@ public class LoadTests
|
||||
Assert.Equal(linesRun1, linesRun2); // strict deterministic ordering
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void QueryValidator_FuzzesInvalidInputs()
|
||||
{
|
||||
var rand = new Random(123);
|
||||
|
||||
@@ -7,11 +7,14 @@ using StellaOps.Graph.Api.Contracts;
|
||||
using StellaOps.Graph.Api.Services;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Api.Tests;
|
||||
|
||||
public class MetricsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task BudgetDeniedCounter_IncrementsOnEdgeBudgetExceeded()
|
||||
{
|
||||
using var metrics = new GraphMetrics();
|
||||
@@ -51,7 +54,8 @@ public class MetricsTests
|
||||
Assert.Equal(1, count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task OverlayCacheCounters_RecordHitsAndMisses()
|
||||
{
|
||||
// Start the listener before creating metrics so it can subscribe to instrument creation
|
||||
|
||||
@@ -4,11 +4,13 @@ using StellaOps.Graph.Api.Contracts;
|
||||
using StellaOps.Graph.Api.Services;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Api.Tests;
|
||||
|
||||
public class PathServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FindPathsAsync_ReturnsShortestPathWithinDepth()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository();
|
||||
@@ -35,7 +37,8 @@ public class PathServiceTests
|
||||
Assert.Contains(lines, l => l.Contains("\"type\":\"stats\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FindPathsAsync_WhenNoPath_ReturnsErrorTile()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository();
|
||||
|
||||
@@ -5,11 +5,14 @@ using StellaOps.Graph.Api.Contracts;
|
||||
using StellaOps.Graph.Api.Services;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Api.Tests;
|
||||
|
||||
public class QueryServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task QueryAsync_EmitsNodesEdgesStatsAndCursor()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository();
|
||||
@@ -36,7 +39,8 @@ public class QueryServiceTests
|
||||
Assert.Contains(lines, l => l.Contains("\"type\":\"cursor\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task QueryAsync_ReturnsBudgetExceededError()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository();
|
||||
@@ -60,7 +64,8 @@ public class QueryServiceTests
|
||||
Assert.Contains("GRAPH_BUDGET_EXCEEDED", lines[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task QueryAsync_IncludesOverlaysAndSamplesExplainOnce()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository(new[]
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using StellaOps.Graph.Api.Services;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Api.Tests;
|
||||
|
||||
internal sealed class FakeClock : IClock
|
||||
@@ -11,7 +12,8 @@ internal sealed class FakeClock : IClock
|
||||
|
||||
public class RateLimiterServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AllowsWithinWindowUpToLimit()
|
||||
{
|
||||
var clock = new FakeClock { UtcNow = DateTimeOffset.UnixEpoch };
|
||||
@@ -22,7 +24,8 @@ public class RateLimiterServiceTests
|
||||
Assert.False(limiter.Allow("t1", "/r"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ResetsAfterWindow()
|
||||
{
|
||||
var clock = new FakeClock { UtcNow = DateTimeOffset.UnixEpoch };
|
||||
|
||||
@@ -6,6 +6,8 @@ using StellaOps.Graph.Api.Services;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Api.Tests;
|
||||
|
||||
public class SearchServiceTests
|
||||
@@ -18,7 +20,8 @@ public class SearchServiceTests
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SearchAsync_ReturnsNodeAndCursorTiles()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository(new[]
|
||||
@@ -49,7 +52,8 @@ public class SearchServiceTests
|
||||
Assert.False(string.IsNullOrEmpty(ExtractNodeId(firstNodeLine)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SearchAsync_RespectsCursorAndLimit()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository(new[]
|
||||
@@ -89,7 +93,8 @@ public class SearchServiceTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SearchAsync_PrefersExactThenPrefixThenContains()
|
||||
{
|
||||
var repo = new InMemoryGraphRepository(new[]
|
||||
@@ -110,7 +115,8 @@ public class SearchServiceTests
|
||||
Assert.Contains("gn:t:component:example", lines.First());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task QueryAsync_RespectsTileBudgetAndEmitsCursor()
|
||||
{
|
||||
// Test that budget limits output when combined with pagination.
|
||||
@@ -145,7 +151,8 @@ public class SearchServiceTests
|
||||
Assert.Equal(1, nodeCount); // Only 1 node due to Limit=1
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task QueryAsync_HonorsNodeAndEdgeBudgets()
|
||||
{
|
||||
// Test that node and edge budgets deny queries when exceeded.
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using System.Linq;
|
||||
using StellaOps.Graph.Indexer.Analytics;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Tests;
|
||||
|
||||
public sealed class GraphAnalyticsEngineTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Compute_IsDeterministic_ForLinearGraph()
|
||||
{
|
||||
var snapshot = GraphAnalyticsTestData.CreateLinearSnapshot();
|
||||
|
||||
@@ -2,11 +2,14 @@ using System.Collections.Immutable;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Graph.Indexer.Analytics;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Tests;
|
||||
|
||||
public sealed class GraphAnalyticsPipelineTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunAsync_WritesClustersAndCentrality()
|
||||
{
|
||||
var snapshot = GraphAnalyticsTestData.CreateLinearSnapshot();
|
||||
|
||||
@@ -7,11 +7,14 @@ using Microsoft.Extensions.Options;
|
||||
using StellaOps.Graph.Indexer.Incremental;
|
||||
using StellaOps.Graph.Indexer.Ingestion.Sbom;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Tests;
|
||||
|
||||
public sealed class GraphChangeStreamProcessorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ApplyStreamAsync_SkipsDuplicates_AndRetries()
|
||||
{
|
||||
var tenant = "tenant-a";
|
||||
|
||||
@@ -10,6 +10,7 @@ using FluentAssertions;
|
||||
using StellaOps.Graph.Indexer.Documents;
|
||||
using StellaOps.Graph.Indexer.Ingestion.Sbom;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -22,7 +23,8 @@ public sealed class GraphCoreLogicTests
|
||||
{
|
||||
#region GRAPH-5100-001: Graph Construction Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphConstruction_FromEvents_CreatesCorrectNodeCount()
|
||||
{
|
||||
// Arrange
|
||||
@@ -51,7 +53,8 @@ public sealed class GraphCoreLogicTests
|
||||
result.Adjacency.Nodes.Should().HaveCount(4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphConstruction_FromEvents_CreatesCorrectEdgeCount()
|
||||
{
|
||||
// Arrange
|
||||
@@ -84,7 +87,8 @@ public sealed class GraphCoreLogicTests
|
||||
libANode.IncomingEdges.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphConstruction_PreservesNodeAttributes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -111,7 +115,8 @@ public sealed class GraphCoreLogicTests
|
||||
axiosNode.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphConstruction_HandlesDuplicateNodeIds_Deterministically()
|
||||
{
|
||||
// Arrange
|
||||
@@ -138,7 +143,8 @@ public sealed class GraphCoreLogicTests
|
||||
compNodes.Should().HaveCountGreaterOrEqualTo(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphConstruction_EmptyGraph_ReturnsEmptyAdjacency()
|
||||
{
|
||||
// Arrange
|
||||
@@ -159,7 +165,8 @@ public sealed class GraphCoreLogicTests
|
||||
|
||||
#region GRAPH-5100-002: Graph Traversal Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphTraversal_DirectPath_ReturnsCorrectPath()
|
||||
{
|
||||
// Arrange
|
||||
@@ -177,7 +184,8 @@ public sealed class GraphCoreLogicTests
|
||||
path.Should().BeEquivalentTo(new[] { "node-0", "node-1", "node-2" });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphTraversal_NoPath_ReturnsEmpty()
|
||||
{
|
||||
// Arrange - Disconnected graph
|
||||
@@ -199,7 +207,8 @@ public sealed class GraphCoreLogicTests
|
||||
path.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphTraversal_SelfLoop_ReturnsEmptyPath()
|
||||
{
|
||||
// Arrange
|
||||
@@ -223,7 +232,8 @@ public sealed class GraphCoreLogicTests
|
||||
path.Should().Contain("self");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphTraversal_MultiplePaths_ReturnsAPath()
|
||||
{
|
||||
// Arrange - Diamond graph: A → B, A → C, B → D, C → D
|
||||
@@ -259,7 +269,8 @@ public sealed class GraphCoreLogicTests
|
||||
|
||||
#region GRAPH-5100-003: Graph Filtering Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphFilter_ByNodeType_ReturnsCorrectSubgraph()
|
||||
{
|
||||
// Arrange
|
||||
@@ -290,7 +301,8 @@ public sealed class GraphCoreLogicTests
|
||||
componentNodes.Should().Contain(n => n.NodeId == "comp-2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphFilter_ByEdgeType_ReturnsCorrectSubgraph()
|
||||
{
|
||||
// Arrange
|
||||
@@ -317,7 +329,8 @@ public sealed class GraphCoreLogicTests
|
||||
dependencyNodes.Should().Contain(n => n.NodeId == "root");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphFilter_ByAttribute_ReturnsMatchingNodes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -345,7 +358,8 @@ public sealed class GraphCoreLogicTests
|
||||
criticalNodes.Single().NodeId.Should().Be("critical");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphFilter_EmptyFilter_ReturnsAllNodes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -372,7 +386,8 @@ public sealed class GraphCoreLogicTests
|
||||
allNodes.Should().HaveCount(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GraphFilter_NoMatches_ReturnsEmptySubgraph()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Graph.Indexer.Schema;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Tests;
|
||||
|
||||
public sealed class GraphIdentityTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeNodeId_IsDeterministic_WhenTupleOrderChanges()
|
||||
{
|
||||
var tupleA = ImmutableDictionary<string, string>.Empty
|
||||
@@ -25,7 +27,8 @@ public sealed class GraphIdentityTests
|
||||
Assert.StartsWith("gn:tenant-a:component:", idA);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeEdgeId_IsCaseInsensitiveExceptFingerprintFields()
|
||||
{
|
||||
var tupleLower = ImmutableDictionary<string, string>.Empty
|
||||
|
||||
@@ -11,6 +11,7 @@ using StellaOps.Graph.Indexer.Documents;
|
||||
using StellaOps.Graph.Indexer.Ingestion.Sbom;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -21,7 +22,8 @@ public sealed class GraphIndexerEndToEndTests
|
||||
{
|
||||
#region End-to-End SBOM Ingestion Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestSbom_ProducesArtifactNode()
|
||||
{
|
||||
// Arrange
|
||||
@@ -37,7 +39,8 @@ public sealed class GraphIndexerEndToEndTests
|
||||
result.Adjacency.Nodes.Should().Contain(n => n.NodeId.Contains("artifact"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestSbom_ProducesComponentNodes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -53,7 +56,8 @@ public sealed class GraphIndexerEndToEndTests
|
||||
result.Adjacency.Nodes.Should().Contain(n => n.NodeId.Contains("component"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestSbom_ProducesDependencyEdges()
|
||||
{
|
||||
// Arrange
|
||||
@@ -71,7 +75,8 @@ public sealed class GraphIndexerEndToEndTests
|
||||
rootNode!.OutgoingEdges.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestSbom_PreservesDigestInformation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -90,7 +95,8 @@ public sealed class GraphIndexerEndToEndTests
|
||||
result.SbomDigest.Should().Be(sbomDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestSbom_PreservesTenantIsolation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -118,7 +124,8 @@ public sealed class GraphIndexerEndToEndTests
|
||||
|
||||
#region Graph Tile Generation Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestSbom_GeneratesManifestHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -135,7 +142,8 @@ public sealed class GraphIndexerEndToEndTests
|
||||
result.ManifestHash.Should().StartWith("sha256:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestSbom_ManifestHashIsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
@@ -152,7 +160,8 @@ public sealed class GraphIndexerEndToEndTests
|
||||
result1.ManifestHash.Should().Be(result2.ManifestHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestSbom_ShuffledInputs_ProduceSameManifestHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -192,7 +201,8 @@ public sealed class GraphIndexerEndToEndTests
|
||||
|
||||
#region Complex SBOM Scenarios
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestSbom_DeepDependencyChain_ProducesCorrectGraph()
|
||||
{
|
||||
// Arrange - Create a deep dependency chain: root → a → b → c → d → e
|
||||
@@ -233,7 +243,8 @@ public sealed class GraphIndexerEndToEndTests
|
||||
depE.OutgoingEdges.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestSbom_DiamondDependency_HandlesCorrectly()
|
||||
{
|
||||
// Arrange - Diamond: root → a, root → b, a → c, b → c
|
||||
@@ -267,7 +278,8 @@ public sealed class GraphIndexerEndToEndTests
|
||||
depC.IncomingEdges.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IngestSbom_CircularDependency_HandlesGracefully()
|
||||
{
|
||||
// Arrange - Circular: a → b → c → a
|
||||
|
||||
@@ -3,11 +3,13 @@ using StellaOps.Graph.Indexer.Ingestion.Inspector;
|
||||
using StellaOps.Graph.Indexer.Schema;
|
||||
using Xunit.Sdk;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Tests;
|
||||
|
||||
public sealed class GraphInspectorTransformerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Transform_BuildsNodesAndEdges_FromInspectorSnapshot()
|
||||
{
|
||||
var snapshot = new GraphInspectorSnapshot
|
||||
@@ -143,7 +145,8 @@ public sealed class GraphInspectorTransformerTests
|
||||
Assert.Equal(6000, dependsOn["provenance"]!["event_offset"]!.GetValue<long>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Transform_AcceptsPublishedSample()
|
||||
{
|
||||
var samplePath = LocateRepoFile("docs/modules/graph/contracts/examples/graph.inspect.v1.sample.json");
|
||||
|
||||
@@ -3,11 +3,13 @@ using System.Linq;
|
||||
using StellaOps.Graph.Indexer.Analytics;
|
||||
using StellaOps.Graph.Indexer.Ingestion.Sbom;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Tests;
|
||||
|
||||
public sealed class GraphOverlayExporterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_WritesDeterministicNdjson()
|
||||
{
|
||||
var snapshot = GraphAnalyticsTestData.CreateLinearSnapshot();
|
||||
|
||||
@@ -3,11 +3,13 @@ using System.Text.Json.Nodes;
|
||||
using StellaOps.Graph.Indexer.Documents;
|
||||
using StellaOps.Graph.Indexer.Ingestion.Sbom;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Tests;
|
||||
|
||||
public sealed class GraphSnapshotBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_ProducesDeterministicAdjacencyOrdering()
|
||||
{
|
||||
var snapshot = new SbomSnapshot
|
||||
@@ -51,7 +53,8 @@ public sealed class GraphSnapshotBuilderTests
|
||||
Assert.Equal(new[] { "edge-b" }, result.Adjacency.Nodes.Single(n => n.NodeId == "node-b").IncomingEdges.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_ComputesStableManifestHash_ForShuffledInputs()
|
||||
{
|
||||
var snapshot = new SbomSnapshot
|
||||
|
||||
@@ -4,11 +4,13 @@ using System.Text.Json.Nodes;
|
||||
using StellaOps.Graph.Indexer.Ingestion.Sbom;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Tests;
|
||||
|
||||
public sealed class SbomLineageTransformerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Transform_adds_lineage_edges_when_present()
|
||||
{
|
||||
var snapshot = new SbomSnapshot
|
||||
|
||||
@@ -4,11 +4,13 @@ using StellaOps.Graph.Indexer.Documents;
|
||||
using StellaOps.Graph.Indexer.Ingestion.Sbom;
|
||||
using StellaOps.Graph.Indexer.Schema;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Graph.Indexer.Tests;
|
||||
|
||||
public sealed class SbomSnapshotExporterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_WritesCanonicalFilesWithStableHash()
|
||||
{
|
||||
var snapshot = new SbomSnapshot
|
||||
|
||||
Reference in New Issue
Block a user