Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
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.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 Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user