Files
git.stella-ops.org/src/Concelier/__Tests/StellaOps.Concelier.Persistence.Tests/SourceStateRepositoryTests.cs
2026-01-08 08:54:27 +02:00

204 lines
6.2 KiB
C#

using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Persistence.Postgres.Models;
using StellaOps.Concelier.Persistence.Postgres;
using StellaOps.Concelier.Persistence.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Concelier.Persistence.Tests;
/// <summary>
/// Integration tests for <see cref="SourceStateRepository"/>.
/// </summary>
[Collection(ConcelierPostgresCollection.Name)]
public sealed class SourceStateRepositoryTests : IAsyncLifetime
{
private readonly ConcelierPostgresFixture _fixture;
private readonly ConcelierDataSource _dataSource;
private readonly SourceRepository _sourceRepository;
private readonly SourceStateRepository _repository;
public SourceStateRepositoryTests(ConcelierPostgresFixture fixture)
{
_fixture = fixture;
var options = fixture.CreateOptions();
_dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
_sourceRepository = new SourceRepository(_dataSource, NullLogger<SourceRepository>.Instance);
_repository = new SourceStateRepository(_dataSource, NullLogger<SourceStateRepository>.Instance);
}
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
public async ValueTask DisposeAsync() => await _dataSource.DisposeAsync();
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UpsertAsync_ShouldCreateNewState()
{
// Arrange
var source = await CreateTestSourceAsync();
var state = new SourceStateEntity
{
Id = Guid.NewGuid(),
SourceId = source.Id,
LastSyncAt = DateTimeOffset.UtcNow,
LastSuccessAt = DateTimeOffset.UtcNow,
Cursor = """{"lastModified": "2025-01-01T00:00:00Z"}""",
ErrorCount = 0,
SyncCount = 1
};
// Act
var result = await _repository.UpsertAsync(state);
// Assert
result.Should().NotBeNull();
result.SourceId.Should().Be(source.Id);
result.Cursor.Should().Contain("lastModified");
result.SyncCount.Should().Be(1);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetBySourceIdAsync_ShouldReturnState_WhenExists()
{
// Arrange
var source = await CreateTestSourceAsync();
var state = new SourceStateEntity
{
Id = Guid.NewGuid(),
SourceId = source.Id,
LastSyncAt = DateTimeOffset.UtcNow
};
await _repository.UpsertAsync(state);
// Act
var result = await _repository.GetBySourceIdAsync(source.Id);
// Assert
result.Should().NotBeNull();
result!.SourceId.Should().Be(source.Id);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetBySourceIdAsync_ShouldReturnNull_WhenNotExists()
{
// Act
var result = await _repository.GetBySourceIdAsync(Guid.NewGuid());
// Assert
result.Should().BeNull();
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UpsertAsync_ShouldUpdateExistingState()
{
// Arrange
var source = await CreateTestSourceAsync();
var state = new SourceStateEntity
{
Id = Guid.NewGuid(),
SourceId = source.Id,
LastSyncAt = DateTimeOffset.UtcNow.AddHours(-1),
ErrorCount = 0,
SyncCount = 1
};
await _repository.UpsertAsync(state);
// Create updated version (same source_id triggers update)
var updatedState = new SourceStateEntity
{
Id = Guid.NewGuid(), // Different ID but same source_id
SourceId = source.Id,
LastSyncAt = DateTimeOffset.UtcNow,
LastSuccessAt = DateTimeOffset.UtcNow,
Cursor = """{"page": 10}""",
ErrorCount = 0,
SyncCount = 2
};
// Act
var result = await _repository.UpsertAsync(updatedState);
// Assert
result.Should().NotBeNull();
result.LastSuccessAt.Should().NotBeNull();
result.Cursor.Should().Contain("page");
result.SyncCount.Should().Be(2);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UpsertAsync_ShouldTrackErrorCount()
{
// Arrange
var source = await CreateTestSourceAsync();
var state = new SourceStateEntity
{
Id = Guid.NewGuid(),
SourceId = source.Id,
LastSyncAt = DateTimeOffset.UtcNow,
ErrorCount = 3,
LastError = "Connection failed"
};
// Act
var result = await _repository.UpsertAsync(state);
// Assert
result.Should().NotBeNull();
result.ErrorCount.Should().Be(3);
result.LastError.Should().Be("Connection failed");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UpsertAsync_ShouldTrackSyncMetrics()
{
// Arrange
var source = await CreateTestSourceAsync();
var syncTime = DateTimeOffset.UtcNow;
var state = new SourceStateEntity
{
Id = Guid.NewGuid(),
SourceId = source.Id,
LastSyncAt = syncTime,
LastSuccessAt = syncTime,
SyncCount = 100,
ErrorCount = 2
};
// Act
var result = await _repository.UpsertAsync(state);
// Assert
result.Should().NotBeNull();
result.SyncCount.Should().Be(100);
result.LastSyncAt.Should().BeCloseTo(syncTime, TimeSpan.FromSeconds(1));
result.LastSuccessAt.Should().BeCloseTo(syncTime, TimeSpan.FromSeconds(1));
}
private async Task<SourceEntity> CreateTestSourceAsync()
{
var id = Guid.NewGuid();
var key = $"source-{id:N}"[..20];
var source = new SourceEntity
{
Id = id,
Key = key,
Name = $"Test Source {key}",
SourceType = "nvd",
Priority = 100,
Enabled = true
};
return await _sourceRepository.UpsertAsync(source);
}
}