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,292 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Policy.Persistence.Postgres;
|
||||
using StellaOps.Policy.Persistence.Postgres.Models;
|
||||
using StellaOps.Policy.Persistence.Postgres.Repositories;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Policy.Persistence.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for pack versioning workflow scenarios (PG-T4.8.2).
|
||||
/// Validates the complete lifecycle of pack versioning including:
|
||||
/// - Creating pack versions
|
||||
/// - Activating/deactivating versions
|
||||
/// - Rolling back to previous versions
|
||||
/// - Version history preservation
|
||||
/// </summary>
|
||||
[Collection(PolicyPostgresCollection.Name)]
|
||||
public sealed class PackVersioningWorkflowTests : IAsyncLifetime
|
||||
{
|
||||
private readonly PolicyPostgresFixture _fixture;
|
||||
private readonly PackRepository _packRepository;
|
||||
private readonly RuleRepository _ruleRepository;
|
||||
private readonly string _tenantId = Guid.NewGuid().ToString();
|
||||
|
||||
public PackVersioningWorkflowTests(PolicyPostgresFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
options.SchemaName = fixture.SchemaName;
|
||||
var dataSource = new PolicyDataSource(Options.Create(options), NullLogger<PolicyDataSource>.Instance);
|
||||
_packRepository = new PackRepository(dataSource, NullLogger<PackRepository>.Instance);
|
||||
_ruleRepository = new RuleRepository(dataSource, NullLogger<RuleRepository>.Instance);
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VersionWorkflow_CreateUpdateActivate_MaintainsVersionIntegrity()
|
||||
{
|
||||
// Arrange - Create initial pack
|
||||
var pack = new PackEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = "versioned-pack",
|
||||
DisplayName = "Versioned Policy Pack",
|
||||
Description = "Pack for version testing",
|
||||
ActiveVersion = 1,
|
||||
IsBuiltin = false
|
||||
};
|
||||
await _packRepository.CreateAsync(pack);
|
||||
|
||||
// Act - Update to version 2
|
||||
await _packRepository.SetActiveVersionAsync(_tenantId, pack.Id, 2);
|
||||
var afterV2 = await _packRepository.GetByIdAsync(_tenantId, pack.Id);
|
||||
|
||||
// Assert
|
||||
afterV2.Should().NotBeNull();
|
||||
afterV2!.ActiveVersion.Should().Be(2);
|
||||
|
||||
// Act - Update to version 3
|
||||
await _packRepository.SetActiveVersionAsync(_tenantId, pack.Id, 3);
|
||||
var afterV3 = await _packRepository.GetByIdAsync(_tenantId, pack.Id);
|
||||
|
||||
// Assert
|
||||
afterV3.Should().NotBeNull();
|
||||
afterV3!.ActiveVersion.Should().Be(3);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VersionWorkflow_RollbackVersion_RestoresPreviousVersion()
|
||||
{
|
||||
// Arrange - Create pack at version 3
|
||||
var pack = new PackEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = "rollback-pack",
|
||||
ActiveVersion = 3,
|
||||
IsBuiltin = false
|
||||
};
|
||||
await _packRepository.CreateAsync(pack);
|
||||
|
||||
// Act - Rollback to version 2
|
||||
await _packRepository.SetActiveVersionAsync(_tenantId, pack.Id, 2);
|
||||
var afterRollback = await _packRepository.GetByIdAsync(_tenantId, pack.Id);
|
||||
|
||||
// Assert
|
||||
afterRollback.Should().NotBeNull();
|
||||
afterRollback!.ActiveVersion.Should().Be(2);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VersionWorkflow_MultiplePacksDifferentVersions_Isolated()
|
||||
{
|
||||
// Arrange - Create multiple packs with different versions
|
||||
var pack1 = new PackEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = "pack-a",
|
||||
ActiveVersion = 1
|
||||
};
|
||||
var pack2 = new PackEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = "pack-b",
|
||||
ActiveVersion = 5
|
||||
};
|
||||
await _packRepository.CreateAsync(pack1);
|
||||
await _packRepository.CreateAsync(pack2);
|
||||
|
||||
// Act - Update pack1 only
|
||||
await _packRepository.SetActiveVersionAsync(_tenantId, pack1.Id, 10);
|
||||
|
||||
// Assert - pack2 should be unaffected
|
||||
var fetchedPack1 = await _packRepository.GetByIdAsync(_tenantId, pack1.Id);
|
||||
var fetchedPack2 = await _packRepository.GetByIdAsync(_tenantId, pack2.Id);
|
||||
|
||||
fetchedPack1!.ActiveVersion.Should().Be(10);
|
||||
fetchedPack2!.ActiveVersion.Should().Be(5);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VersionWorkflow_DeprecatedPackVersionStillReadable()
|
||||
{
|
||||
// Arrange - Create and deprecate pack
|
||||
var pack = new PackEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = "deprecated-version-pack",
|
||||
ActiveVersion = 3,
|
||||
IsDeprecated = false
|
||||
};
|
||||
await _packRepository.CreateAsync(pack);
|
||||
|
||||
// Act - Deprecate the pack
|
||||
await _packRepository.DeprecateAsync(_tenantId, pack.Id);
|
||||
var deprecated = await _packRepository.GetByIdAsync(_tenantId, pack.Id);
|
||||
|
||||
// Assert - Version should still be readable
|
||||
deprecated.Should().NotBeNull();
|
||||
deprecated!.IsDeprecated.Should().BeTrue();
|
||||
deprecated.ActiveVersion.Should().Be(3);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VersionWorkflow_ConcurrentVersionUpdates_LastWriteWins()
|
||||
{
|
||||
// Arrange - Create pack
|
||||
var pack = new PackEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = "concurrent-version-pack",
|
||||
ActiveVersion = 1
|
||||
};
|
||||
await _packRepository.CreateAsync(pack);
|
||||
|
||||
// Act - Simulate concurrent updates
|
||||
var tasks = new[]
|
||||
{
|
||||
_packRepository.SetActiveVersionAsync(_tenantId, pack.Id, 2),
|
||||
_packRepository.SetActiveVersionAsync(_tenantId, pack.Id, 3),
|
||||
_packRepository.SetActiveVersionAsync(_tenantId, pack.Id, 4)
|
||||
};
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
// Assert - One of the versions should win
|
||||
var final = await _packRepository.GetByIdAsync(_tenantId, pack.Id);
|
||||
final.Should().NotBeNull();
|
||||
final!.ActiveVersion.Should().BeOneOf(2, 3, 4);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VersionWorkflow_DeterministicOrdering_VersionsReturnConsistently()
|
||||
{
|
||||
// Arrange - Create multiple packs
|
||||
var packs = Enumerable.Range(1, 5).Select(i => new PackEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = $"ordered-pack-{i}",
|
||||
ActiveVersion = i
|
||||
}).ToList();
|
||||
|
||||
foreach (var pack in packs)
|
||||
{
|
||||
await _packRepository.CreateAsync(pack);
|
||||
}
|
||||
|
||||
// Act - Fetch multiple times
|
||||
var results1 = await _packRepository.GetAllAsync(_tenantId);
|
||||
var results2 = await _packRepository.GetAllAsync(_tenantId);
|
||||
var results3 = await _packRepository.GetAllAsync(_tenantId);
|
||||
|
||||
// Assert - Order should be deterministic
|
||||
var names1 = results1.Select(p => p.Name).ToList();
|
||||
var names2 = results2.Select(p => p.Name).ToList();
|
||||
var names3 = results3.Select(p => p.Name).ToList();
|
||||
|
||||
names1.Should().Equal(names2);
|
||||
names2.Should().Equal(names3);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VersionWorkflow_UpdateTimestampProgresses_OnVersionChange()
|
||||
{
|
||||
// Arrange
|
||||
var pack = new PackEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = "timestamp-version-pack",
|
||||
ActiveVersion = 1
|
||||
};
|
||||
await _packRepository.CreateAsync(pack);
|
||||
var created = await _packRepository.GetByIdAsync(_tenantId, pack.Id);
|
||||
var initialUpdatedAt = created!.UpdatedAt;
|
||||
|
||||
// Small delay to ensure timestamp difference
|
||||
await Task.Delay(10);
|
||||
|
||||
// Act - Update version
|
||||
await _packRepository.SetActiveVersionAsync(_tenantId, pack.Id, 2);
|
||||
var updated = await _packRepository.GetByIdAsync(_tenantId, pack.Id);
|
||||
|
||||
// Assert - UpdatedAt should have progressed
|
||||
updated!.UpdatedAt.Should().BeOnOrAfter(initialUpdatedAt);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VersionWorkflow_ZeroVersionAllowed_AsInitialState()
|
||||
{
|
||||
// Arrange - Create pack with version 0 (no active version)
|
||||
var pack = new PackEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = "zero-version-pack",
|
||||
ActiveVersion = 0
|
||||
};
|
||||
|
||||
// Act
|
||||
await _packRepository.CreateAsync(pack);
|
||||
var fetched = await _packRepository.GetByIdAsync(_tenantId, pack.Id);
|
||||
|
||||
// Assert
|
||||
fetched.Should().NotBeNull();
|
||||
fetched!.ActiveVersion.Should().Be(0);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VersionWorkflow_BuiltinPackVersioning_WorksLikeCustomPacks()
|
||||
{
|
||||
// Arrange - Create builtin pack
|
||||
var builtinPack = new PackEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
Name = "builtin-versioned",
|
||||
ActiveVersion = 1,
|
||||
IsBuiltin = true
|
||||
};
|
||||
await _packRepository.CreateAsync(builtinPack);
|
||||
|
||||
// Act - Update version
|
||||
await _packRepository.SetActiveVersionAsync(_tenantId, builtinPack.Id, 2);
|
||||
var updated = await _packRepository.GetByIdAsync(_tenantId, builtinPack.Id);
|
||||
|
||||
// Assert
|
||||
updated.Should().NotBeNull();
|
||||
updated!.ActiveVersion.Should().Be(2);
|
||||
updated.IsBuiltin.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user