up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
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
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
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
This commit is contained in:
@@ -1,72 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Mongo2Go;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Concelier.Core.Linksets;
|
||||
using StellaOps.Concelier.Storage.Mongo.Linksets;
|
||||
|
||||
namespace StellaOps.Concelier.Storage.Mongo.Tests;
|
||||
|
||||
public sealed class AdvisoryLinksetStoreTests : IAsyncLifetime
|
||||
{
|
||||
private MongoDbRunner _runner = null!;
|
||||
private IMongoDatabase _database = null!;
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
_runner = MongoDbRunner.Start(singleNodeReplSet: true);
|
||||
var client = new MongoClient(_runner.ConnectionString);
|
||||
_database = client.GetDatabase("lnm-store-tests");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_runner.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpsertAndFetch_RetainsCpesInNormalizedShape()
|
||||
{
|
||||
var collection = _database.GetCollection<AdvisoryLinksetDocument>(MongoStorageDefaults.Collections.AdvisoryLinksets);
|
||||
var store = new ConcelierMongoLinksetStore(collection);
|
||||
|
||||
var linkset = new AdvisoryLinkset(
|
||||
TenantId: "TenantA",
|
||||
Source: "source-A",
|
||||
AdvisoryId: "ADV-1234",
|
||||
ObservationIds: ImmutableArray.Create("obs-1"),
|
||||
Normalized: new AdvisoryLinksetNormalized(
|
||||
Purls: new List<string> { "pkg:npm/lodash@4.17.21" },
|
||||
Cpes: new List<string> { "cpe:2.3:a:lodash:lodash:4.17.21:*:*:*:*:*:*:*" },
|
||||
Versions: new List<string> { "4.17.21" },
|
||||
Ranges: new List<Dictionary<string, object?>>(),
|
||||
Severities: null),
|
||||
Provenance: null,
|
||||
Confidence: null,
|
||||
Conflicts: null,
|
||||
CreatedAt: DateTimeOffset.Parse("2025-11-24T00:00:00Z"),
|
||||
BuiltByJobId: "job-001");
|
||||
|
||||
await store.UpsertAsync(linkset, CancellationToken.None);
|
||||
|
||||
var result = await store.FindByTenantAsync(
|
||||
tenantId: "TenantA",
|
||||
advisoryIds: new[] { "ADV-1234" },
|
||||
sources: new[] { "source-A" },
|
||||
cursor: null,
|
||||
limit: 10,
|
||||
cancellationToken: CancellationToken.None);
|
||||
|
||||
result.Should().ContainSingle();
|
||||
var returned = result.Single();
|
||||
returned.Normalized.Should().NotBeNull();
|
||||
returned.Normalized!.Cpes.Should().ContainSingle()
|
||||
.Which.Should().Be("cpe:2.3:a:lodash:lodash:4.17.21:*:*:*:*:*:*:*");
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Mongo2Go;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Concelier.Storage.Mongo.Migrations;
|
||||
|
||||
namespace StellaOps.Concelier.Storage.Mongo.Tests;
|
||||
|
||||
public sealed class EnsureLinkNotMergeCollectionsMigrationTests : IAsyncLifetime
|
||||
{
|
||||
private MongoDbRunner _runner = null!;
|
||||
private IMongoDatabase _database = null!;
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
_runner = MongoDbRunner.Start(singleNodeReplSet: true);
|
||||
var client = new MongoClient(_runner.ConnectionString);
|
||||
_database = client.GetDatabase("lnm-migration-tests");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_runner.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreatesCollectionsAndIndexesIdempotently()
|
||||
{
|
||||
var migration = new EnsureLinkNotMergeCollectionsMigration();
|
||||
|
||||
await migration.ApplyAsync(_database, CancellationToken.None);
|
||||
await migration.ApplyAsync(_database, CancellationToken.None); // idempotent second run
|
||||
|
||||
var collections = await _database.ListCollectionNames().ToListAsync();
|
||||
collections.Should().Contain(new[]
|
||||
{
|
||||
MongoStorageDefaults.Collections.AdvisoryObservations,
|
||||
MongoStorageDefaults.Collections.AdvisoryLinksets
|
||||
});
|
||||
|
||||
var linksetIndexes = await _database
|
||||
.GetCollection<BsonDocument>(MongoStorageDefaults.Collections.AdvisoryLinksets)
|
||||
.Indexes.List()
|
||||
.ToListAsync();
|
||||
|
||||
linksetIndexes.Should().ContainSingle(i => i["name"] == "linkset_tenant_advisory_source" && i["unique"].AsBoolean);
|
||||
|
||||
var obsIndexes = await _database
|
||||
.GetCollection<BsonDocument>(MongoStorageDefaults.Collections.AdvisoryObservations)
|
||||
.Indexes.List()
|
||||
.ToListAsync();
|
||||
|
||||
obsIndexes.Should().Contain(i => i["name"] == "obs_prov_sourceArtifactSha_unique" && i["unique"].AsBoolean);
|
||||
|
||||
var linksetValidator = await GetValidatorAsync(MongoStorageDefaults.Collections.AdvisoryLinksets);
|
||||
linksetValidator.Should().NotBeNull();
|
||||
}
|
||||
|
||||
private async Task<BsonDocument?> GetValidatorAsync(string collection)
|
||||
{
|
||||
var filter = new BsonDocument("name", collection);
|
||||
var cursor = await _database.ListCollectionsAsync(new ListCollectionsOptions { Filter = filter });
|
||||
var doc = await cursor.FirstOrDefaultAsync();
|
||||
return doc?"options"?"validator"?.AsBsonDocument;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Mongo2Go;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Concelier.Storage.Mongo.Migrations;
|
||||
|
||||
namespace StellaOps.Concelier.Storage.Mongo.Tests;
|
||||
|
||||
public sealed class EnsureOrchestratorCollectionsMigrationTests : IAsyncLifetime
|
||||
{
|
||||
private MongoDbRunner _runner = null!;
|
||||
private IMongoDatabase _database = null!;
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
_runner = MongoDbRunner.Start(singleNodeReplSet: true);
|
||||
var client = new MongoClient(_runner.ConnectionString);
|
||||
_database = client.GetDatabase("orch-migration-tests");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_runner.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreatesOrchestratorCollectionsAndIndexes()
|
||||
{
|
||||
var migration = new EnsureOrchestratorCollectionsMigration();
|
||||
|
||||
await migration.ApplyAsync(_database, CancellationToken.None);
|
||||
|
||||
var collections = await _database.ListCollectionNames().ToListAsync();
|
||||
collections.Should().Contain(
|
||||
new[]
|
||||
{
|
||||
MongoStorageDefaults.Collections.OrchestratorRegistry,
|
||||
MongoStorageDefaults.Collections.OrchestratorCommands,
|
||||
MongoStorageDefaults.Collections.OrchestratorHeartbeats,
|
||||
});
|
||||
|
||||
var registryIndexes = await GetIndexNamesAsync(MongoStorageDefaults.Collections.OrchestratorRegistry);
|
||||
registryIndexes.Should().Contain("orch_registry_tenant_connector");
|
||||
|
||||
var commandIndexes = await GetIndexNamesAsync(MongoStorageDefaults.Collections.OrchestratorCommands);
|
||||
commandIndexes.Should().Contain("orch_cmd_tenant_connector_run_seq");
|
||||
commandIndexes.Should().Contain("orch_cmd_expiresAt_ttl");
|
||||
|
||||
var heartbeatIndexes = await GetIndexNamesAsync(MongoStorageDefaults.Collections.OrchestratorHeartbeats);
|
||||
heartbeatIndexes.Should().Contain("orch_hb_tenant_connector_run_seq");
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyCollection<string>> GetIndexNamesAsync(string collection)
|
||||
{
|
||||
var docs = await _database.GetCollection<BsonDocument>(collection)
|
||||
.Indexes
|
||||
.List()
|
||||
.ToListAsync();
|
||||
|
||||
return docs.Select(d => d["name"].AsString).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Mongo2Go;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Concelier.Storage.Mongo.Migrations;
|
||||
using StellaOps.Concelier.Storage.Mongo.Orchestrator;
|
||||
|
||||
namespace StellaOps.Concelier.Storage.Mongo.Tests;
|
||||
|
||||
public sealed class MongoOrchestratorRegistryStoreTests : IAsyncLifetime
|
||||
{
|
||||
private MongoDbRunner _runner = null!;
|
||||
private IMongoDatabase _database = null!;
|
||||
private MongoOrchestratorRegistryStore _store = null!;
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
_runner = MongoDbRunner.Start(singleNodeReplSet: true);
|
||||
var client = new MongoClient(_runner.ConnectionString);
|
||||
_database = client.GetDatabase("orch-store-tests");
|
||||
|
||||
// ensure collections/indexes present
|
||||
var migration = new EnsureOrchestratorCollectionsMigration();
|
||||
migration.ApplyAsync(_database, CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
_store = new MongoOrchestratorRegistryStore(
|
||||
_database.GetCollection<OrchestratorRegistryDocument>(MongoStorageDefaults.Collections.OrchestratorRegistry),
|
||||
_database.GetCollection<OrchestratorCommandDocument>(MongoStorageDefaults.Collections.OrchestratorCommands),
|
||||
_database.GetCollection<OrchestratorHeartbeatDocument>(MongoStorageDefaults.Collections.OrchestratorHeartbeats));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_runner.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpsertAndFetchRegistryRoundTrips()
|
||||
{
|
||||
var record = new OrchestratorRegistryRecord(
|
||||
Tenant: "tenant-a",
|
||||
ConnectorId: "icscisa",
|
||||
Source: "icscisa",
|
||||
Capabilities: new[] { "observations", "linksets" },
|
||||
AuthRef: "secret:concelier/icscisa/api-key",
|
||||
Schedule: new OrchestratorSchedule("*/30 * * * *", "UTC", 1, 120),
|
||||
RatePolicy: new OrchestratorRatePolicy(60, 10, 30),
|
||||
ArtifactKinds: new[] { "raw-advisory", "linkset" },
|
||||
LockKey: "concelier:tenant-a:icscisa",
|
||||
EgressGuard: new OrchestratorEgressGuard(new[] { "icscert.kisa.or.kr" }, true),
|
||||
CreatedAt: DateTimeOffset.Parse("2025-11-20T00:00:00Z"),
|
||||
UpdatedAt: DateTimeOffset.Parse("2025-11-21T00:00:00Z"));
|
||||
|
||||
await _store.UpsertAsync(record, CancellationToken.None);
|
||||
|
||||
var fetched = await _store.GetAsync("tenant-a", "icscisa", CancellationToken.None);
|
||||
fetched.Should().NotBeNull();
|
||||
fetched!.ConnectorId.Should().Be("icscisa");
|
||||
fetched.Schedule.Cron.Should().Be("*/30 * * * *");
|
||||
fetched.RatePolicy.Burst.Should().Be(10);
|
||||
fetched.EgressGuard.AirgapMode.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EnqueueAndReadCommandsOrdersBySequence()
|
||||
{
|
||||
var runId = Guid.NewGuid();
|
||||
|
||||
var first = new OrchestratorCommandRecord(
|
||||
Tenant: "tenant-a",
|
||||
ConnectorId: "icscisa",
|
||||
RunId: runId,
|
||||
Sequence: 1,
|
||||
Command: OrchestratorCommandKind.Pause,
|
||||
Throttle: null,
|
||||
Backfill: null,
|
||||
CreatedAt: DateTimeOffset.Parse("2025-11-20T00:00:00Z"),
|
||||
ExpiresAt: null);
|
||||
|
||||
var second = new OrchestratorCommandRecord(
|
||||
Tenant: "tenant-a",
|
||||
ConnectorId: "icscisa",
|
||||
RunId: runId,
|
||||
Sequence: 2,
|
||||
Command: OrchestratorCommandKind.Backfill,
|
||||
Throttle: null,
|
||||
Backfill: new OrchestratorBackfillRange("2024-01-01T00:00:00Z", "2024-02-01T00:00:00Z"),
|
||||
CreatedAt: DateTimeOffset.Parse("2025-11-20T00:01:00Z"),
|
||||
ExpiresAt: null);
|
||||
|
||||
await _store.EnqueueCommandAsync(second, CancellationToken.None);
|
||||
await _store.EnqueueCommandAsync(first, CancellationToken.None);
|
||||
|
||||
var commands = await _store.GetPendingCommandsAsync("tenant-a", "icscisa", runId, afterSequence: 0, CancellationToken.None);
|
||||
|
||||
commands.Select(c => c.Sequence).Should().ContainInOrder(1, 2);
|
||||
commands.Last().Backfill!.FromCursor.Should().Be("2024-01-01T00:00:00Z");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AppendsHeartbeats()
|
||||
{
|
||||
var heartbeat = new OrchestratorHeartbeatRecord(
|
||||
Tenant: "tenant-a",
|
||||
ConnectorId: "icscisa",
|
||||
RunId: Guid.NewGuid(),
|
||||
Sequence: 5,
|
||||
Status: OrchestratorHeartbeatStatus.Running,
|
||||
Progress: 42,
|
||||
QueueDepth: 7,
|
||||
LastArtifactHash: "abc",
|
||||
LastArtifactKind: "normalized",
|
||||
ErrorCode: null,
|
||||
RetryAfterSeconds: null,
|
||||
TimestampUtc: DateTimeOffset.Parse("2025-11-21T00:00:00Z"));
|
||||
|
||||
await _store.AppendHeartbeatAsync(heartbeat, CancellationToken.None);
|
||||
|
||||
var count = await _database
|
||||
.GetCollection<OrchestratorHeartbeatDocument>(MongoStorageDefaults.Collections.OrchestratorHeartbeats)
|
||||
.CountDocumentsAsync(FilterDefinition<OrchestratorHeartbeatDocument>.Empty);
|
||||
|
||||
count.Should().Be(1);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="Mongo2Go" Version="4.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../src/Concelier/__Libraries/StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user