up
Some checks failed
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
This commit is contained in:
@@ -1,239 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Mongo2Go;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Graph.Indexer.Ingestion.Advisory;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Graph.Indexer.Tests;
|
||||
|
||||
public sealed class MongoGraphDocumentWriterTests : IAsyncLifetime, IDisposable
|
||||
{
|
||||
private readonly MongoTestContext _context;
|
||||
private readonly MongoGraphDocumentWriter? _writer;
|
||||
private readonly IMongoCollection<BsonDocument>? _nodeCollection;
|
||||
private readonly IMongoCollection<BsonDocument>? _edgeCollection;
|
||||
|
||||
public MongoGraphDocumentWriterTests()
|
||||
{
|
||||
_context = MongoTestContext.Create();
|
||||
if (_context.SkipReason is null)
|
||||
{
|
||||
var database = _context.Database ?? throw new InvalidOperationException("MongoDB test context initialized without a database.");
|
||||
_writer = new MongoGraphDocumentWriter(database);
|
||||
_nodeCollection = database.GetCollection<BsonDocument>("graph_nodes");
|
||||
_edgeCollection = database.GetCollection<BsonDocument>("graph_edges");
|
||||
}
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task WriteAsync_upserts_nodes_and_edges()
|
||||
{
|
||||
Skip.If(_context.SkipReason is not null, _context.SkipReason ?? string.Empty);
|
||||
|
||||
var writer = _writer!;
|
||||
var nodeCollection = _nodeCollection!;
|
||||
var edgeCollection = _edgeCollection!;
|
||||
|
||||
var snapshot = LoadSnapshot();
|
||||
var transformer = new AdvisoryLinksetTransformer();
|
||||
var batch = transformer.Transform(snapshot);
|
||||
|
||||
await writer.WriteAsync(batch, CancellationToken.None);
|
||||
|
||||
var nodes = await nodeCollection
|
||||
.Find(FilterDefinition<BsonDocument>.Empty)
|
||||
.ToListAsync();
|
||||
var edges = await edgeCollection
|
||||
.Find(FilterDefinition<BsonDocument>.Empty)
|
||||
.ToListAsync();
|
||||
|
||||
nodes.Should().HaveCount(batch.Nodes.Length);
|
||||
edges.Should().HaveCount(batch.Edges.Length);
|
||||
|
||||
// Write the same batch again to ensure idempotency through upsert.
|
||||
await writer.WriteAsync(batch, CancellationToken.None);
|
||||
|
||||
var nodesAfter = await nodeCollection
|
||||
.Find(Builders<BsonDocument>.Filter.Empty)
|
||||
.ToListAsync();
|
||||
var edgesAfter = await edgeCollection
|
||||
.Find(Builders<BsonDocument>.Filter.Empty)
|
||||
.ToListAsync();
|
||||
|
||||
nodesAfter.Should().HaveCount(batch.Nodes.Length);
|
||||
edgesAfter.Should().HaveCount(batch.Edges.Length);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task WriteAsync_replaces_existing_documents()
|
||||
{
|
||||
Skip.If(_context.SkipReason is not null, _context.SkipReason ?? string.Empty);
|
||||
|
||||
var writer = _writer!;
|
||||
var edgeCollection = _edgeCollection!;
|
||||
|
||||
var snapshot = LoadSnapshot();
|
||||
var transformer = new AdvisoryLinksetTransformer();
|
||||
var batch = transformer.Transform(snapshot);
|
||||
|
||||
await writer.WriteAsync(batch, CancellationToken.None);
|
||||
|
||||
// change provenance offset to ensure replacement occurs
|
||||
var snapshotJson = JsonSerializer.Serialize(snapshot);
|
||||
var document = JsonNode.Parse(snapshotJson)!.AsObject();
|
||||
document["eventOffset"] = snapshot.EventOffset + 10;
|
||||
var mutated = document.Deserialize<AdvisoryLinksetSnapshot>(new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
})!;
|
||||
var mutatedBatch = transformer.Transform(mutated);
|
||||
|
||||
await writer.WriteAsync(mutatedBatch, CancellationToken.None);
|
||||
|
||||
var edges = await edgeCollection
|
||||
.Find(FilterDefinition<BsonDocument>.Empty)
|
||||
.ToListAsync();
|
||||
|
||||
edges.Should().HaveCount(1);
|
||||
edges.Single()["provenance"]["event_offset"].AsInt64.Should().Be(mutated.EventOffset);
|
||||
}
|
||||
|
||||
private static AdvisoryLinksetSnapshot LoadSnapshot()
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "Fixtures", "v1", "linkset-snapshot.json");
|
||||
return JsonSerializer.Deserialize<AdvisoryLinksetSnapshot>(File.ReadAllText(path), new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
})!;
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => Task.CompletedTask;
|
||||
|
||||
public Task DisposeAsync() => _context.DisposeAsync().AsTask();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.Dispose();
|
||||
}
|
||||
|
||||
private sealed class MongoTestContext : IAsyncDisposable, IDisposable
|
||||
{
|
||||
private const string ExternalMongoEnv = "STELLAOPS_TEST_MONGO_URI";
|
||||
private const string DefaultLocalMongo = "mongodb://127.0.0.1:27017";
|
||||
|
||||
private readonly bool _ownsDatabase;
|
||||
private readonly string? _databaseName;
|
||||
|
||||
private MongoTestContext(IMongoClient? client, IMongoDatabase? database, MongoDbRunner? runner, bool ownsDatabase, string? skipReason)
|
||||
{
|
||||
Client = client;
|
||||
Database = database;
|
||||
Runner = runner;
|
||||
_ownsDatabase = ownsDatabase;
|
||||
_databaseName = database?.DatabaseNamespace.DatabaseName;
|
||||
SkipReason = skipReason;
|
||||
}
|
||||
|
||||
public IMongoClient? Client { get; }
|
||||
public IMongoDatabase? Database { get; }
|
||||
public MongoDbRunner? Runner { get; }
|
||||
public string? SkipReason { get; }
|
||||
|
||||
public static MongoTestContext Create()
|
||||
{
|
||||
// 1) Explicit override via env var (CI/local scripted).
|
||||
var uri = Environment.GetEnvironmentVariable(ExternalMongoEnv);
|
||||
if (TryCreateExternal(uri, out var externalContext))
|
||||
{
|
||||
return externalContext!;
|
||||
}
|
||||
|
||||
// 2) Try localhost default.
|
||||
if (TryCreateExternal(DefaultLocalMongo, out externalContext))
|
||||
{
|
||||
return externalContext!;
|
||||
}
|
||||
|
||||
// 3) Fallback to Mongo2Go embedded runner.
|
||||
if (TryCreateEmbedded(out var embeddedContext))
|
||||
{
|
||||
return embeddedContext!;
|
||||
}
|
||||
|
||||
return new MongoTestContext(null, null, null, ownsDatabase: false,
|
||||
skipReason: "MongoDB unavailable: set STELLAOPS_TEST_MONGO_URI or run mongod on 127.0.0.1:27017.");
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (Runner is not null)
|
||||
{
|
||||
Runner.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ownsDatabase && Client is not null && _databaseName is not null)
|
||||
{
|
||||
await Client.DropDatabaseAsync(_databaseName).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Runner?.Dispose();
|
||||
if (_ownsDatabase && Client is not null && _databaseName is not null)
|
||||
{
|
||||
Client.DropDatabase(_databaseName);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryCreateExternal(string? uri, out MongoTestContext? context)
|
||||
{
|
||||
context = null;
|
||||
if (string.IsNullOrWhiteSpace(uri))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var client = new MongoClient(uri);
|
||||
var dbName = $"graph-indexer-tests-{Guid.NewGuid():N}";
|
||||
var database = client.GetDatabase(dbName);
|
||||
// Ping to ensure connectivity.
|
||||
database.RunCommand<BsonDocument>(new BsonDocument("ping", 1));
|
||||
context = new MongoTestContext(client, database, runner: null, ownsDatabase: true, skipReason: null);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryCreateEmbedded(out MongoTestContext? context)
|
||||
{
|
||||
context = null;
|
||||
try
|
||||
{
|
||||
var runner = MongoDbRunner.Start(singleNodeReplSet: true);
|
||||
var client = new MongoClient(runner.ConnectionString);
|
||||
var database = client.GetDatabase("graph-indexer-tests");
|
||||
context = new MongoTestContext(client, database, runner, ownsDatabase: false, skipReason: null);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,4 @@
|
||||
# StellaOps Graph Indexer Tests
|
||||
|
||||
The Graph Indexer integration tests exercise the Mongo-backed document writer.
|
||||
To run the suite locally (or in CI) you **must** point the tests at a reachable MongoDB instance.
|
||||
|
||||
## Required environment
|
||||
|
||||
```bash
|
||||
export STELLAOPS_TEST_MONGO_URI="mongodb://user:pass@host:27017/test-db"
|
||||
```
|
||||
|
||||
The harness will try the connection string above first, then fall back to `mongodb://127.0.0.1:27017`, and finally to an embedded MongoDB instance via Mongo2Go. If neither the URI nor a local `mongod` is reachable, the Mongo writer tests are skipped with a diagnostic message.
|
||||
|
||||
CI pipelines are configured to fail early when `STELLAOPS_TEST_MONGO_URI` is missing so that the integration coverage always runs with a known database.
|
||||
The Graph Indexer tests now run entirely in-memory and no longer require MongoDB.
|
||||
No special environment variables are needed to execute the suite locally or in CI.
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Mongo2Go" Version="4.1.0" />
|
||||
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user