// -----------------------------------------------------------------------------
// PolicyQueryDeterminismTests.cs
// Sprint: SPRINT_5100_0009_0004_policy_tests
// Task: POLICY-5100-009
// Description: Model S1 query determinism tests for Policy retrieval ordering
// -----------------------------------------------------------------------------
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Policy.Storage.Postgres.Models;
using StellaOps.Policy.Storage.Postgres.Repositories;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Policy.Storage.Postgres.Tests;
///
/// Query determinism tests for Policy storage operations.
/// Implements Model S1 (Storage/Postgres) test requirements:
/// - Explicit ORDER BY checks for all list queries
/// - Same inputs → stable ordering
/// - Repeated queries return consistent results
///
[Collection(PolicyPostgresCollection.Name)]
[Trait("Category", TestCategories.Integration)]
[Trait("Category", "QueryDeterminism")]
public sealed class PolicyQueryDeterminismTests : IAsyncLifetime
{
private readonly PolicyPostgresFixture _fixture;
private PolicyDataSource _dataSource = null!;
private PackRepository _packRepository = null!;
private PackVersionRepository _packVersionRepository = null!;
private RiskProfileRepository _riskProfileRepository = null!;
private RuleRepository _ruleRepository = null!;
private PolicyAuditRepository _auditRepository = null!;
private readonly string _tenantId = Guid.NewGuid().ToString();
public PolicyQueryDeterminismTests(PolicyPostgresFixture fixture)
{
_fixture = fixture;
}
public async Task InitializeAsync()
{
await _fixture.TruncateAllTablesAsync();
var options = _fixture.Fixture.CreateOptions();
options.SchemaName = _fixture.SchemaName;
_dataSource = new PolicyDataSource(Options.Create(options), NullLogger.Instance);
_packRepository = new PackRepository(_dataSource, NullLogger.Instance);
_packVersionRepository = new PackVersionRepository(_dataSource, NullLogger.Instance);
_riskProfileRepository = new RiskProfileRepository(_dataSource, NullLogger.Instance);
_ruleRepository = new RuleRepository(_dataSource, NullLogger.Instance);
_auditRepository = new PolicyAuditRepository(_dataSource, NullLogger.Instance);
}
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
public async Task GetAllPacks_MultipleQueries_ReturnsDeterministicOrder()
{
// Arrange
var packs = new[]
{
await CreatePackAsync("pack-c"),
await CreatePackAsync("pack-a"),
await CreatePackAsync("pack-b"),
await CreatePackAsync("pack-e"),
await CreatePackAsync("pack-d")
};
// Act - Query multiple times
var results1 = await _packRepository.GetAllAsync(_tenantId);
var results2 = await _packRepository.GetAllAsync(_tenantId);
var results3 = await _packRepository.GetAllAsync(_tenantId);
// Assert - All queries should return same order
var ids1 = results1.Select(p => p.Id).ToList();
var ids2 = results2.Select(p => p.Id).ToList();
var ids3 = results3.Select(p => p.Id).ToList();
ids1.Should().Equal(ids2);
ids2.Should().Equal(ids3);
}
[Fact]
public async Task GetPackVersions_MultipleQueries_ReturnsDeterministicOrder()
{
// Arrange
var pack = await CreatePackAsync("version-order-test");
// Create versions in non-sequential order
await CreatePackVersionAsync(pack.Id, 3, publish: true);
await CreatePackVersionAsync(pack.Id, 1, publish: true);
await CreatePackVersionAsync(pack.Id, 5, publish: true);
await CreatePackVersionAsync(pack.Id, 2, publish: true);
await CreatePackVersionAsync(pack.Id, 4, publish: true);
// Act - Query multiple times
var results1 = await _packVersionRepository.GetByPackIdAsync(pack.Id, publishedOnly: true);
var results2 = await _packVersionRepository.GetByPackIdAsync(pack.Id, publishedOnly: true);
var results3 = await _packVersionRepository.GetByPackIdAsync(pack.Id, publishedOnly: true);
// Assert - All queries should return same order
var versions1 = results1.Select(v => v.Version).ToList();
var versions2 = results2.Select(v => v.Version).ToList();
var versions3 = results3.Select(v => v.Version).ToList();
versions1.Should().Equal(versions2);
versions2.Should().Equal(versions3);
// Should be ordered by version descending (newest first)
versions1.Should().BeInDescendingOrder();
}
[Fact]
public async Task GetRiskProfiles_MultipleQueries_ReturnsDeterministicOrder()
{
// Arrange
var profiles = new[]
{
await CreateRiskProfileAsync("profile-z"),
await CreateRiskProfileAsync("profile-a"),
await CreateRiskProfileAsync("profile-m"),
await CreateRiskProfileAsync("profile-b"),
await CreateRiskProfileAsync("profile-y")
};
// Act - Query multiple times
var results1 = await _riskProfileRepository.GetAllAsync(_tenantId);
var results2 = await _riskProfileRepository.GetAllAsync(_tenantId);
var results3 = await _riskProfileRepository.GetAllAsync(_tenantId);
// Assert - All queries should return same order
var ids1 = results1.Select(p => p.Id).ToList();
var ids2 = results2.Select(p => p.Id).ToList();
var ids3 = results3.Select(p => p.Id).ToList();
ids1.Should().Equal(ids2);
ids2.Should().Equal(ids3);
}
[Fact]
public async Task GetRules_MultipleQueries_ReturnsDeterministicOrder()
{
// Arrange
var pack = await CreatePackAsync("rules-order-test");
var version = await CreatePackVersionAsync(pack.Id, 1, publish: true);
var rules = new[]
{
await CreateRuleAsync(version.Id, "rule-zebra"),
await CreateRuleAsync(version.Id, "rule-alpha"),
await CreateRuleAsync(version.Id, "rule-gamma"),
await CreateRuleAsync(version.Id, "rule-beta"),
await CreateRuleAsync(version.Id, "rule-delta")
};
// Act - Query multiple times
var results1 = await _ruleRepository.GetByVersionIdAsync(version.Id);
var results2 = await _ruleRepository.GetByVersionIdAsync(version.Id);
var results3 = await _ruleRepository.GetByVersionIdAsync(version.Id);
// Assert - All queries should return same order
var ids1 = results1.Select(r => r.Id).ToList();
var ids2 = results2.Select(r => r.Id).ToList();
var ids3 = results3.Select(r => r.Id).ToList();
ids1.Should().Equal(ids2);
ids2.Should().Equal(ids3);
}
[Fact]
public async Task GetAuditEntries_MultipleQueries_ReturnsDeterministicOrder()
{
// Arrange
for (int i = 0; i < 5; i++)
{
await CreateAuditEntryAsync($"action-{i}");
}
// Act - Query multiple times
var results1 = await _auditRepository.GetRecentAsync(_tenantId, 10);
var results2 = await _auditRepository.GetRecentAsync(_tenantId, 10);
var results3 = await _auditRepository.GetRecentAsync(_tenantId, 10);
// Assert - All queries should return same order
var ids1 = results1.Select(a => a.Id).ToList();
var ids2 = results2.Select(a => a.Id).ToList();
var ids3 = results3.Select(a => a.Id).ToList();
ids1.Should().Equal(ids2);
ids2.Should().Equal(ids3);
}
[Fact]
public async Task ConcurrentQueries_SamePack_AllReturnIdenticalResults()
{
// Arrange
var pack = await CreatePackAsync("concurrent-test");
await CreatePackVersionAsync(pack.Id, 1, publish: true);
await CreatePackVersionAsync(pack.Id, 2, publish: true);
// Act - 20 concurrent queries
var tasks = Enumerable.Range(0, 20)
.Select(_ => _packVersionRepository.GetByPackIdAsync(pack.Id, publishedOnly: true))
.ToList();
var results = await Task.WhenAll(tasks);
// Assert - All should return identical order
var firstOrder = results[0].Select(v => v.Version).ToList();
results.Should().AllSatisfy(r =>
{
r.Select(v => v.Version).ToList().Should().Equal(firstOrder);
});
}
[Fact]
public async Task GetLatestVersion_MultipleQueries_ReturnsConsistentResult()
{
// Arrange
var pack = await CreatePackAsync("latest-consistent-test");
await CreatePackVersionAsync(pack.Id, 1, publish: true);
await CreatePackVersionAsync(pack.Id, 2, publish: true);
await CreatePackVersionAsync(pack.Id, 3, publish: true);
// Act - Query multiple times
var results = new List();
for (int i = 0; i < 10; i++)
{
results.Add(await _packVersionRepository.GetLatestAsync(pack.Id));
}
// Assert - All should return version 3
results.Should().AllSatisfy(r =>
{
r.Should().NotBeNull();
r!.Version.Should().Be(3);
});
}
[Fact]
public async Task GetById_MultipleQueries_ReturnsConsistentResult()
{
// Arrange
var pack = await CreatePackAsync("get-by-id-test");
// Act - Query multiple times
var results = new List();
for (int i = 0; i < 10; i++)
{
results.Add(await _packRepository.GetByIdAsync(_tenantId, pack.Id));
}
// Assert - All should return identical pack
results.Should().AllSatisfy(r =>
{
r.Should().NotBeNull();
r!.Id.Should().Be(pack.Id);
r.Name.Should().Be("get-by-id-test");
});
}
[Fact]
public async Task GetByName_MultipleQueries_ReturnsConsistentResult()
{
// Arrange
var pack = await CreatePackAsync("name-lookup-test");
// Act - Query multiple times
var results = new List();
for (int i = 0; i < 10; i++)
{
results.Add(await _packRepository.GetByNameAsync(_tenantId, "name-lookup-test"));
}
// Assert - All should return same pack
results.Should().AllSatisfy(r =>
{
r.Should().NotBeNull();
r!.Id.Should().Be(pack.Id);
});
}
[Fact]
public async Task EmptyTenant_GetAllPacks_ReturnsEmptyConsistently()
{
// Arrange
var emptyTenantId = Guid.NewGuid().ToString();
// Act - Query empty tenant multiple times
var results = new List>();
for (int i = 0; i < 5; i++)
{
results.Add(await _packRepository.GetAllAsync(emptyTenantId));
}
// Assert - All should return empty
results.Should().AllSatisfy(r => r.Should().BeEmpty());
}
[Fact]
public async Task TenantIsolation_PacksInDifferentTenants_QueriesReturnOnlyOwnTenant()
{
// Arrange
var tenant1 = Guid.NewGuid().ToString();
var tenant2 = Guid.NewGuid().ToString();
var pack1 = await CreatePackAsync("tenant1-pack", tenant1);
var pack2 = await CreatePackAsync("tenant2-pack", tenant2);
// Act
var tenant1Packs = await _packRepository.GetAllAsync(tenant1);
var tenant2Packs = await _packRepository.GetAllAsync(tenant2);
// Assert
tenant1Packs.Should().HaveCount(1);
tenant1Packs[0].Id.Should().Be(pack1.Id);
tenant2Packs.Should().HaveCount(1);
tenant2Packs[0].Id.Should().Be(pack2.Id);
}
private async Task CreatePackAsync(string name, string? tenantId = null)
{
var pack = new PackEntity
{
Id = Guid.NewGuid(),
TenantId = tenantId ?? _tenantId,
Name = name,
DisplayName = $"Display {name}",
IsBuiltin = false
};
await _packRepository.CreateAsync(pack);
return pack;
}
private async Task CreatePackVersionAsync(Guid packId, int version, bool publish = false)
{
var packVersion = new PackVersionEntity
{
Id = Guid.NewGuid(),
PackId = packId,
Version = version,
Description = $"Version {version}",
RulesHash = $"rules-hash-{version}-{Guid.NewGuid():N}",
IsPublished = false
};
var created = await _packVersionRepository.CreateAsync(packVersion);
if (publish)
{
await _packVersionRepository.PublishAsync(created.Id, "test-publisher");
created = (await _packVersionRepository.GetByIdAsync(created.Id))!;
}
return created;
}
private async Task CreateRiskProfileAsync(string name)
{
var profile = new RiskProfileEntity
{
Id = Guid.NewGuid(),
TenantId = _tenantId,
Name = name,
DisplayName = $"Display {name}",
Version = 1,
PolicyContent = """{"rules": []}""",
ContentHash = $"hash-{Guid.NewGuid():N}"
};
await _riskProfileRepository.CreateAsync(profile);
return profile;
}
private async Task CreateRuleAsync(Guid versionId, string name)
{
var rule = new RuleEntity
{
Id = Guid.NewGuid(),
PackVersionId = versionId,
Name = name,
DisplayName = $"Display {name}",
Severity = "HIGH",
RuleContent = """{"condition": "always"}""",
ContentHash = $"hash-{Guid.NewGuid():N}"
};
await _ruleRepository.CreateAsync(rule);
return rule;
}
private async Task CreateAuditEntryAsync(string action)
{
var audit = new PolicyAuditEntity
{
Id = Guid.NewGuid(),
TenantId = _tenantId,
Action = action,
Actor = "test-user",
EntityType = "Pack",
EntityId = Guid.NewGuid().ToString(),
Timestamp = DateTimeOffset.UtcNow,
Details = """{"test": true}"""
};
await _auditRepository.CreateAsync(audit);
return audit;
}
}