using Microsoft.Extensions.Logging.Abstractions; using MongoDB.Driver; using StellaOps.Concelier.Core.Jobs; using StellaOps.Concelier.Storage.Mongo; namespace StellaOps.Concelier.Storage.Mongo.Tests; [Collection("mongo-fixture")] public sealed class MongoJobStoreTests : IClassFixture { private readonly MongoIntegrationFixture _fixture; public MongoJobStoreTests(MongoIntegrationFixture fixture) { _fixture = fixture; } [Fact] public async Task CreateStartCompleteLifecycle() { await ResetCollectionAsync(); var collection = _fixture.Database.GetCollection(MongoStorageDefaults.Collections.Jobs); var store = new MongoJobStore(collection, NullLogger.Instance); var request = new JobRunCreateRequest( Kind: "mongo:test", Trigger: "unit", Parameters: new Dictionary { ["scope"] = "lifecycle" }, ParametersHash: "abc", Timeout: TimeSpan.FromSeconds(5), LeaseDuration: TimeSpan.FromSeconds(2), CreatedAt: DateTimeOffset.UtcNow); var created = await store.CreateAsync(request, CancellationToken.None); Assert.Equal(JobRunStatus.Pending, created.Status); var started = await store.TryStartAsync(created.RunId, DateTimeOffset.UtcNow, CancellationToken.None); Assert.NotNull(started); Assert.Equal(JobRunStatus.Running, started!.Status); var completed = await store.TryCompleteAsync(created.RunId, new JobRunCompletion(JobRunStatus.Succeeded, DateTimeOffset.UtcNow, null), CancellationToken.None); Assert.NotNull(completed); Assert.Equal(JobRunStatus.Succeeded, completed!.Status); var recent = await store.GetRecentRunsAsync("mongo:test", 10, CancellationToken.None); var snapshot = Assert.Single(recent); Assert.Equal(JobRunStatus.Succeeded, snapshot.Status); var active = await store.GetActiveRunsAsync(CancellationToken.None); Assert.Empty(active); var last = await store.GetLastRunAsync("mongo:test", CancellationToken.None); Assert.NotNull(last); Assert.Equal(completed.RunId, last!.RunId); } [Fact] public async Task StartAndFailRunHonorsStateTransitions() { await ResetCollectionAsync(); var collection = _fixture.Database.GetCollection(MongoStorageDefaults.Collections.Jobs); var store = new MongoJobStore(collection, NullLogger.Instance); var request = new JobRunCreateRequest( Kind: "mongo:failure", Trigger: "unit", Parameters: new Dictionary(), ParametersHash: null, Timeout: null, LeaseDuration: null, CreatedAt: DateTimeOffset.UtcNow); var created = await store.CreateAsync(request, CancellationToken.None); var firstStart = await store.TryStartAsync(created.RunId, DateTimeOffset.UtcNow, CancellationToken.None); Assert.NotNull(firstStart); // Second start attempt should be rejected once running. var secondStart = await store.TryStartAsync(created.RunId, DateTimeOffset.UtcNow.AddSeconds(1), CancellationToken.None); Assert.Null(secondStart); var failure = await store.TryCompleteAsync( created.RunId, new JobRunCompletion(JobRunStatus.Failed, DateTimeOffset.UtcNow.AddSeconds(2), "boom"), CancellationToken.None); Assert.NotNull(failure); Assert.Equal("boom", failure!.Error); Assert.Equal(JobRunStatus.Failed, failure.Status); } [Fact] public async Task CompletingUnknownRunReturnsNull() { await ResetCollectionAsync(); var collection = _fixture.Database.GetCollection(MongoStorageDefaults.Collections.Jobs); var store = new MongoJobStore(collection, NullLogger.Instance); var result = await store.TryCompleteAsync(Guid.NewGuid(), new JobRunCompletion(JobRunStatus.Succeeded, DateTimeOffset.UtcNow, null), CancellationToken.None); Assert.Null(result); } private async Task ResetCollectionAsync() { try { await _fixture.Database.DropCollectionAsync(MongoStorageDefaults.Collections.Jobs); } catch (MongoCommandException ex) when (ex.CodeName == "NamespaceNotFound" || ex.Message.Contains("ns not found", StringComparison.OrdinalIgnoreCase)) { } } }