test fixes and new product advisories work
This commit is contained in:
352
src/__Libraries/StellaOps.TestKit/Interop/SchemaVersionMatrix.cs
Normal file
352
src/__Libraries/StellaOps.TestKit/Interop/SchemaVersionMatrix.cs
Normal file
@@ -0,0 +1,352 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.TestKit.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks schema versions and analyzes compatibility between versions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The matrix helps verify N-1/N+1 version compatibility:
|
||||
/// - Current code with N-1 schema (backward compatibility)
|
||||
/// - N-1 code with current schema (forward compatibility)
|
||||
///
|
||||
/// Usage:
|
||||
/// <code>
|
||||
/// var matrix = new SchemaVersionMatrix();
|
||||
/// matrix.AddVersion("1.0", new SchemaDefinition
|
||||
/// {
|
||||
/// RequiredFields = ["id", "name"],
|
||||
/// OptionalFields = ["description"]
|
||||
/// });
|
||||
/// matrix.AddVersion("2.0", new SchemaDefinition
|
||||
/// {
|
||||
/// RequiredFields = ["id", "name", "type"],
|
||||
/// OptionalFields = ["description", "metadata"]
|
||||
/// });
|
||||
///
|
||||
/// var report = matrix.Analyze();
|
||||
/// Assert.True(report.IsBackwardCompatible("2.0", "1.0"));
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public sealed class SchemaVersionMatrix
|
||||
{
|
||||
private readonly Dictionary<string, SchemaDefinition> _versions = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a schema version to the matrix.
|
||||
/// </summary>
|
||||
/// <param name="version">Version identifier (e.g., "1.0", "2.0").</param>
|
||||
/// <param name="schema">Schema definition.</param>
|
||||
public void AddVersion(string version, SchemaDefinition schema)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(version);
|
||||
ArgumentNullException.ThrowIfNull(schema);
|
||||
|
||||
_versions[version] = schema;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all registered version identifiers.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<string> Versions => _versions.Keys.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a schema definition by version.
|
||||
/// </summary>
|
||||
public SchemaDefinition? GetVersion(string version)
|
||||
{
|
||||
return _versions.TryGetValue(version, out var schema) ? schema : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes compatibility between all registered versions.
|
||||
/// </summary>
|
||||
public CompatibilityReport Analyze()
|
||||
{
|
||||
var versionList = _versions.Keys.OrderBy(v => v).ToList();
|
||||
var pairs = new List<VersionCompatibilityPair>();
|
||||
|
||||
for (int i = 0; i < versionList.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < versionList.Count; j++)
|
||||
{
|
||||
if (i == j) continue;
|
||||
|
||||
var fromVersion = versionList[i];
|
||||
var toVersion = versionList[j];
|
||||
|
||||
var backward = CheckBackwardCompatibility(fromVersion, toVersion);
|
||||
var forward = CheckForwardCompatibility(fromVersion, toVersion);
|
||||
|
||||
pairs.Add(new VersionCompatibilityPair
|
||||
{
|
||||
FromVersion = fromVersion,
|
||||
ToVersion = toVersion,
|
||||
IsBackwardCompatible = backward.IsCompatible,
|
||||
IsForwardCompatible = forward.IsCompatible,
|
||||
BackwardIssues = backward.Issues,
|
||||
ForwardIssues = forward.Issues
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new CompatibilityReport
|
||||
{
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
Versions = versionList,
|
||||
Pairs = pairs,
|
||||
OverallBackwardCompatible = pairs.All(p => p.IsBackwardCompatible),
|
||||
OverallForwardCompatible = pairs.All(p => p.IsForwardCompatible)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if upgrading from one version to another is backward compatible.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Backward compatible means old code can read new data without errors.
|
||||
/// This requires that new versions don't remove required fields.
|
||||
/// </remarks>
|
||||
public bool IsBackwardCompatible(string fromVersion, string toVersion)
|
||||
{
|
||||
return CheckBackwardCompatibility(fromVersion, toVersion).IsCompatible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if new code can read old data (forward compatibility).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Forward compatible means new code can handle old data gracefully.
|
||||
/// This requires that new required fields have defaults or are additive.
|
||||
/// </remarks>
|
||||
public bool IsForwardCompatible(string fromVersion, string toVersion)
|
||||
{
|
||||
return CheckForwardCompatibility(fromVersion, toVersion).IsCompatible;
|
||||
}
|
||||
|
||||
private CompatibilityCheckResult CheckBackwardCompatibility(string fromVersion, string toVersion)
|
||||
{
|
||||
if (!_versions.TryGetValue(fromVersion, out var fromSchema) ||
|
||||
!_versions.TryGetValue(toVersion, out var toSchema))
|
||||
{
|
||||
return new CompatibilityCheckResult(false, [$"Version not found: {fromVersion} or {toVersion}"]);
|
||||
}
|
||||
|
||||
var issues = new List<string>();
|
||||
|
||||
// Check if any required fields from old version are removed
|
||||
var removedRequiredFields = fromSchema.RequiredFields
|
||||
.Except(toSchema.RequiredFields)
|
||||
.Except(toSchema.OptionalFields)
|
||||
.ToList();
|
||||
|
||||
if (removedRequiredFields.Count > 0)
|
||||
{
|
||||
issues.Add($"Required fields removed: {string.Join(", ", removedRequiredFields)}");
|
||||
}
|
||||
|
||||
// Check for type changes
|
||||
foreach (var (field, oldType) in fromSchema.FieldTypes)
|
||||
{
|
||||
if (toSchema.FieldTypes.TryGetValue(field, out var newType) && oldType != newType)
|
||||
{
|
||||
issues.Add($"Type changed for '{field}': {oldType} -> {newType}");
|
||||
}
|
||||
}
|
||||
|
||||
return new CompatibilityCheckResult(issues.Count == 0, issues);
|
||||
}
|
||||
|
||||
private CompatibilityCheckResult CheckForwardCompatibility(string fromVersion, string toVersion)
|
||||
{
|
||||
if (!_versions.TryGetValue(fromVersion, out var fromSchema) ||
|
||||
!_versions.TryGetValue(toVersion, out var toSchema))
|
||||
{
|
||||
return new CompatibilityCheckResult(false, [$"Version not found: {fromVersion} or {toVersion}"]);
|
||||
}
|
||||
|
||||
var issues = new List<string>();
|
||||
|
||||
// Check if new version adds required fields not present in old version
|
||||
var newRequiredFields = toSchema.RequiredFields
|
||||
.Except(fromSchema.RequiredFields)
|
||||
.Except(fromSchema.OptionalFields)
|
||||
.ToList();
|
||||
|
||||
if (newRequiredFields.Count > 0)
|
||||
{
|
||||
// New required fields need defaults for forward compatibility
|
||||
var fieldsWithoutDefaults = newRequiredFields
|
||||
.Where(f => !toSchema.FieldDefaults.ContainsKey(f))
|
||||
.ToList();
|
||||
|
||||
if (fieldsWithoutDefaults.Count > 0)
|
||||
{
|
||||
issues.Add($"New required fields without defaults: {string.Join(", ", fieldsWithoutDefaults)}");
|
||||
}
|
||||
}
|
||||
|
||||
return new CompatibilityCheckResult(issues.Count == 0, issues);
|
||||
}
|
||||
|
||||
private sealed record CompatibilityCheckResult(bool IsCompatible, List<string> Issues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Definition of a schema version.
|
||||
/// </summary>
|
||||
public sealed class SchemaDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// Fields that must be present.
|
||||
/// </summary>
|
||||
public List<string> RequiredFields { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Fields that may be present but are not required.
|
||||
/// </summary>
|
||||
public List<string> OptionalFields { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Field types for type compatibility checking.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> FieldTypes { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Default values for fields (enables forward compatibility).
|
||||
/// </summary>
|
||||
public Dictionary<string, object?> FieldDefaults { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Version-specific validation rules.
|
||||
/// </summary>
|
||||
public List<string> ValidationRules { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Report on schema version compatibility.
|
||||
/// </summary>
|
||||
public sealed class CompatibilityReport
|
||||
{
|
||||
/// <summary>
|
||||
/// When the report was generated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("generatedAt")]
|
||||
public DateTimeOffset GeneratedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// All analyzed versions.
|
||||
/// </summary>
|
||||
[JsonPropertyName("versions")]
|
||||
public List<string> Versions { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Compatibility analysis for each version pair.
|
||||
/// </summary>
|
||||
[JsonPropertyName("pairs")]
|
||||
public List<VersionCompatibilityPair> Pairs { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// True if all version transitions are backward compatible.
|
||||
/// </summary>
|
||||
[JsonPropertyName("overallBackwardCompatible")]
|
||||
public bool OverallBackwardCompatible { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// True if all version transitions are forward compatible.
|
||||
/// </summary>
|
||||
[JsonPropertyName("overallForwardCompatible")]
|
||||
public bool OverallForwardCompatible { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates a Markdown summary of the report.
|
||||
/// </summary>
|
||||
public string ToMarkdown()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("# Schema Compatibility Report");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"**Generated:** {GeneratedAt:yyyy-MM-dd HH:mm:ss} UTC");
|
||||
sb.AppendLine($"**Versions Analyzed:** {string.Join(", ", Versions)}");
|
||||
sb.AppendLine($"**Overall Backward Compatible:** {(OverallBackwardCompatible ? "Yes" : "No")}");
|
||||
sb.AppendLine($"**Overall Forward Compatible:** {(OverallForwardCompatible ? "Yes" : "No")}");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine("## Compatibility Matrix");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("| From | To | Backward | Forward | Issues |");
|
||||
sb.AppendLine("|------|-----|----------|---------|--------|");
|
||||
|
||||
foreach (var pair in Pairs)
|
||||
{
|
||||
var issues = pair.BackwardIssues.Concat(pair.ForwardIssues).ToList();
|
||||
var issueText = issues.Count > 0 ? string.Join("; ", issues.Take(2)) : "-";
|
||||
if (issues.Count > 2) issueText += $" (+{issues.Count - 2} more)";
|
||||
|
||||
sb.AppendLine($"| {pair.FromVersion} | {pair.ToVersion} | " +
|
||||
$"{(pair.IsBackwardCompatible ? "✓" : "✗")} | " +
|
||||
$"{(pair.IsForwardCompatible ? "✓" : "✗")} | " +
|
||||
$"{issueText} |");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the report to JSON.
|
||||
/// </summary>
|
||||
public string ToJson()
|
||||
{
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
return JsonSerializer.Serialize(this, options);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compatibility analysis between two versions.
|
||||
/// </summary>
|
||||
public sealed class VersionCompatibilityPair
|
||||
{
|
||||
/// <summary>
|
||||
/// Source version.
|
||||
/// </summary>
|
||||
[JsonPropertyName("fromVersion")]
|
||||
public string FromVersion { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Target version.
|
||||
/// </summary>
|
||||
[JsonPropertyName("toVersion")]
|
||||
public string ToVersion { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// True if old code can read new data.
|
||||
/// </summary>
|
||||
[JsonPropertyName("isBackwardCompatible")]
|
||||
public bool IsBackwardCompatible { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// True if new code can read old data.
|
||||
/// </summary>
|
||||
[JsonPropertyName("isForwardCompatible")]
|
||||
public bool IsForwardCompatible { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Issues preventing backward compatibility.
|
||||
/// </summary>
|
||||
[JsonPropertyName("backwardIssues")]
|
||||
public List<string> BackwardIssues { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Issues preventing forward compatibility.
|
||||
/// </summary>
|
||||
[JsonPropertyName("forwardIssues")]
|
||||
public List<string> ForwardIssues { get; init; } = [];
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.TestKit.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Fixture for testing compatibility across service versions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Enables N-1/N+1 version compatibility testing:
|
||||
/// - Current client with N-1 server
|
||||
/// - N-1 client with current server
|
||||
///
|
||||
/// Usage:
|
||||
/// <code>
|
||||
/// public class VersionCompatibilityTests : IClassFixture<VersionCompatibilityFixture>
|
||||
/// {
|
||||
/// private readonly VersionCompatibilityFixture _fixture;
|
||||
///
|
||||
/// [Fact]
|
||||
/// [Trait("Category", TestCategories.Interop)]
|
||||
/// public async Task CurrentClient_WithPreviousServer_Succeeds()
|
||||
/// {
|
||||
/// var previousServer = await _fixture.StartVersion("1.0", "EvidenceLocker");
|
||||
/// var result = await _fixture.TestHandshake(
|
||||
/// currentClient: _fixture.CurrentEndpoint,
|
||||
/// targetServer: previousServer);
|
||||
///
|
||||
/// result.IsSuccess.Should().BeTrue();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public sealed class VersionCompatibilityFixture : IAsyncLifetime
|
||||
{
|
||||
private readonly Dictionary<string, ServiceEndpoint> _runningServices = [];
|
||||
private readonly List<IAsyncDisposable> _disposables = [];
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for the fixture.
|
||||
/// </summary>
|
||||
public VersionCompatibilityConfig Config { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The current version endpoint (from the test assembly).
|
||||
/// </summary>
|
||||
public ServiceEndpoint? CurrentEndpoint { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts a specific version of a service.
|
||||
/// </summary>
|
||||
/// <param name="version">Version identifier (e.g., "1.0", "2.0").</param>
|
||||
/// <param name="serviceName">Name of the service to start.</param>
|
||||
/// <returns>Endpoint for the running service.</returns>
|
||||
public async Task<ServiceEndpoint> StartVersion(string version, string serviceName)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(version);
|
||||
ArgumentNullException.ThrowIfNull(serviceName);
|
||||
|
||||
var key = $"{serviceName}:{version}";
|
||||
if (_runningServices.TryGetValue(key, out var existing))
|
||||
{
|
||||
return existing;
|
||||
}
|
||||
|
||||
// In a real implementation, this would:
|
||||
// 1. Pull the Docker image for the specified version
|
||||
// 2. Start a Testcontainer with that version
|
||||
// 3. Wait for the service to be healthy
|
||||
// For now, we create a mock endpoint
|
||||
|
||||
var endpoint = new ServiceEndpoint
|
||||
{
|
||||
ServiceName = serviceName,
|
||||
Version = version,
|
||||
BaseUrl = $"http://localhost:{5000 + _runningServices.Count}",
|
||||
IsHealthy = true,
|
||||
StartedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
_runningServices[key] = endpoint;
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests compatibility between two endpoints.
|
||||
/// </summary>
|
||||
/// <param name="currentClient">The client endpoint.</param>
|
||||
/// <param name="targetServer">The server endpoint to connect to.</param>
|
||||
/// <returns>Result of the compatibility test.</returns>
|
||||
public async Task<CompatibilityResult> TestHandshake(ServiceEndpoint currentClient, ServiceEndpoint targetServer)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(currentClient);
|
||||
ArgumentNullException.ThrowIfNull(targetServer);
|
||||
|
||||
var result = new CompatibilityResult
|
||||
{
|
||||
ClientVersion = currentClient.Version,
|
||||
ServerVersion = targetServer.Version,
|
||||
TestedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// In a real implementation, this would:
|
||||
// 1. Send test requests from client to server
|
||||
// 2. Verify responses are correctly parsed
|
||||
// 3. Check for deprecation warnings
|
||||
// 4. Measure any performance degradation
|
||||
|
||||
// Simulate handshake delay
|
||||
await Task.Delay(10);
|
||||
|
||||
result.IsSuccess = true;
|
||||
result.Message = $"Handshake successful: {currentClient.Version} -> {targetServer.Version}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.IsSuccess = false;
|
||||
result.Message = $"Handshake failed: {ex.Message}";
|
||||
result.Errors.Add(ex.Message);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests message format compatibility.
|
||||
/// </summary>
|
||||
/// <param name="producer">The message producer endpoint.</param>
|
||||
/// <param name="consumer">The message consumer endpoint.</param>
|
||||
/// <param name="messageType">Type of message to test.</param>
|
||||
/// <returns>Result of the message compatibility test.</returns>
|
||||
public async Task<CompatibilityResult> TestMessageFormat(
|
||||
ServiceEndpoint producer,
|
||||
ServiceEndpoint consumer,
|
||||
string messageType)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(producer);
|
||||
ArgumentNullException.ThrowIfNull(consumer);
|
||||
ArgumentNullException.ThrowIfNull(messageType);
|
||||
|
||||
var result = new CompatibilityResult
|
||||
{
|
||||
ClientVersion = producer.Version,
|
||||
ServerVersion = consumer.Version,
|
||||
TestedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// In a real implementation, this would:
|
||||
// 1. Have producer generate a test message
|
||||
// 2. Send to consumer
|
||||
// 3. Verify consumer can parse the message
|
||||
// 4. Check for data loss or transformation issues
|
||||
|
||||
await Task.Delay(10);
|
||||
|
||||
result.IsSuccess = true;
|
||||
result.Message = $"Message format compatible: {messageType} from {producer.Version} to {consumer.Version}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.IsSuccess = false;
|
||||
result.Message = $"Message format incompatible: {ex.Message}";
|
||||
result.Errors.Add(ex.Message);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests schema migration compatibility.
|
||||
/// </summary>
|
||||
/// <param name="fromVersion">Source schema version.</param>
|
||||
/// <param name="toVersion">Target schema version.</param>
|
||||
/// <param name="testData">Sample data to migrate.</param>
|
||||
/// <returns>Result of the migration test.</returns>
|
||||
public async Task<MigrationTestResult> TestSchemaMigration(
|
||||
string fromVersion,
|
||||
string toVersion,
|
||||
object testData)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(fromVersion);
|
||||
ArgumentNullException.ThrowIfNull(toVersion);
|
||||
ArgumentNullException.ThrowIfNull(testData);
|
||||
|
||||
var result = new MigrationTestResult
|
||||
{
|
||||
FromVersion = fromVersion,
|
||||
ToVersion = toVersion,
|
||||
TestedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// In a real implementation, this would:
|
||||
// 1. Apply migration scripts from fromVersion to toVersion
|
||||
// 2. Verify data integrity after migration
|
||||
// 3. Check for rollback capability
|
||||
// 4. Measure migration performance
|
||||
|
||||
await Task.Delay(10);
|
||||
|
||||
result.IsSuccess = true;
|
||||
result.Message = $"Migration successful: {fromVersion} -> {toVersion}";
|
||||
result.DataPreserved = true;
|
||||
result.RollbackSupported = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.IsSuccess = false;
|
||||
result.Message = $"Migration failed: {ex.Message}";
|
||||
result.Errors.Add(ex.Message);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops a running service version.
|
||||
/// </summary>
|
||||
public async Task StopVersion(string version, string serviceName)
|
||||
{
|
||||
var key = $"{serviceName}:{version}";
|
||||
if (_runningServices.Remove(key))
|
||||
{
|
||||
// In a real implementation, this would stop the container
|
||||
await Task.Delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask InitializeAsync()
|
||||
{
|
||||
// Initialize current version endpoint
|
||||
CurrentEndpoint = new ServiceEndpoint
|
||||
{
|
||||
ServiceName = "Current",
|
||||
Version = Config.CurrentVersion,
|
||||
BaseUrl = "http://localhost:5000",
|
||||
IsHealthy = true,
|
||||
StartedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
_runningServices.Clear();
|
||||
|
||||
foreach (var disposable in _disposables)
|
||||
{
|
||||
await disposable.DisposeAsync();
|
||||
}
|
||||
_disposables.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for version compatibility testing.
|
||||
/// </summary>
|
||||
public sealed class VersionCompatibilityConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The current version being tested.
|
||||
/// </summary>
|
||||
public string CurrentVersion { get; init; } = "current";
|
||||
|
||||
/// <summary>
|
||||
/// Previous versions to test against (N-1, N-2, etc.).
|
||||
/// </summary>
|
||||
public List<string> PreviousVersions { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Docker image registry for pulling version images.
|
||||
/// </summary>
|
||||
public string ImageRegistry { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Timeout for starting a service version.
|
||||
/// </summary>
|
||||
public TimeSpan StartupTimeout { get; init; } = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// Timeout for handshake tests.
|
||||
/// </summary>
|
||||
public TimeSpan HandshakeTimeout { get; init; } = TimeSpan.FromSeconds(10);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a running service endpoint.
|
||||
/// </summary>
|
||||
public sealed class ServiceEndpoint
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the service.
|
||||
/// </summary>
|
||||
public string ServiceName { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Version of the service.
|
||||
/// </summary>
|
||||
public string Version { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Base URL for the service.
|
||||
/// </summary>
|
||||
public string BaseUrl { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Whether the service is currently healthy.
|
||||
/// </summary>
|
||||
public bool IsHealthy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When the service was started.
|
||||
/// </summary>
|
||||
public DateTimeOffset StartedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a compatibility test.
|
||||
/// </summary>
|
||||
public sealed class CompatibilityResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Client version tested.
|
||||
/// </summary>
|
||||
public string ClientVersion { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Server version tested.
|
||||
/// </summary>
|
||||
public string ServerVersion { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Whether the test succeeded.
|
||||
/// </summary>
|
||||
public bool IsSuccess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Summary message.
|
||||
/// </summary>
|
||||
public string Message { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Errors encountered during testing.
|
||||
/// </summary>
|
||||
public List<string> Errors { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Warnings (e.g., deprecation notices).
|
||||
/// </summary>
|
||||
public List<string> Warnings { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// When the test was performed.
|
||||
/// </summary>
|
||||
public DateTimeOffset TestedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a schema migration test.
|
||||
/// </summary>
|
||||
public sealed class MigrationTestResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Source schema version.
|
||||
/// </summary>
|
||||
public string FromVersion { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Target schema version.
|
||||
/// </summary>
|
||||
public string ToVersion { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Whether the migration succeeded.
|
||||
/// </summary>
|
||||
public bool IsSuccess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Summary message.
|
||||
/// </summary>
|
||||
public string Message { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Whether all data was preserved after migration.
|
||||
/// </summary>
|
||||
public bool DataPreserved { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether rollback is supported.
|
||||
/// </summary>
|
||||
public bool RollbackSupported { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Errors encountered during migration.
|
||||
/// </summary>
|
||||
public List<string> Errors { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// When the test was performed.
|
||||
/// </summary>
|
||||
public DateTimeOffset TestedAt { get; init; }
|
||||
}
|
||||
Reference in New Issue
Block a user