Phase 3C: Deployment pipeline & Store-and-Forward engine
Deployment Manager (WP-1–8, WP-16): - DeploymentService: full pipeline (flatten→validate→send→track→audit) - OperationLockManager: per-instance concurrency control - StateTransitionValidator: Enabled/Disabled/NotDeployed transition matrix - ArtifactDeploymentService: broadcast to all sites with per-site results - Deployment identity (GUID + revision hash), idempotency, staleness detection - Instance lifecycle commands (disable/enable/delete) with deduplication Store-and-Forward (WP-9–15): - StoreAndForwardStorage: SQLite persistence, 3 categories, no max buffer - StoreAndForwardService: fixed-interval retry, transient-only buffering, parking - ReplicationService: async best-effort to standby (fire-and-forget) - Parked message management (query/retry/discard from central) - Messages survive instance deletion, S&F drains on disable 620 tests pass (+79 new), zero warnings.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NSubstitute;
|
||||
using ScadaLink.Commons.Entities.Sites;
|
||||
using ScadaLink.Commons.Interfaces.Repositories;
|
||||
using ScadaLink.Commons.Interfaces.Services;
|
||||
using ScadaLink.Commons.Messages.Artifacts;
|
||||
using ScadaLink.Communication;
|
||||
|
||||
namespace ScadaLink.DeploymentManager.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// WP-7: Tests for system-wide artifact deployment.
|
||||
/// </summary>
|
||||
public class ArtifactDeploymentServiceTests
|
||||
{
|
||||
private readonly ISiteRepository _siteRepo;
|
||||
private readonly IDeploymentManagerRepository _deploymentRepo;
|
||||
private readonly IAuditService _audit;
|
||||
|
||||
public ArtifactDeploymentServiceTests()
|
||||
{
|
||||
_siteRepo = Substitute.For<ISiteRepository>();
|
||||
_deploymentRepo = Substitute.For<IDeploymentManagerRepository>();
|
||||
_audit = Substitute.For<IAuditService>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeployToAllSitesAsync_NoSites_ReturnsFailure()
|
||||
{
|
||||
_siteRepo.GetAllSitesAsync().Returns(new List<Site>());
|
||||
|
||||
var service = CreateService();
|
||||
var command = CreateCommand();
|
||||
|
||||
var result = await service.DeployToAllSitesAsync(command, "admin");
|
||||
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Contains("No sites", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SiteArtifactResult_ContainsSiteInfo()
|
||||
{
|
||||
var result = new SiteArtifactResult("site1", "Site One", true, null);
|
||||
Assert.Equal("site1", result.SiteId);
|
||||
Assert.Equal("Site One", result.SiteName);
|
||||
Assert.True(result.Success);
|
||||
Assert.Null(result.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArtifactDeploymentSummary_CountsCorrectly()
|
||||
{
|
||||
var results = new List<SiteArtifactResult>
|
||||
{
|
||||
new("s1", "Site1", true, null),
|
||||
new("s2", "Site2", false, "error"),
|
||||
new("s3", "Site3", true, null)
|
||||
};
|
||||
var summary = new ArtifactDeploymentSummary("dep1", results, 2, 1);
|
||||
|
||||
Assert.Equal(2, summary.SuccessCount);
|
||||
Assert.Equal(1, summary.FailureCount);
|
||||
Assert.Equal(3, summary.SiteResults.Count);
|
||||
}
|
||||
|
||||
private ArtifactDeploymentService CreateService()
|
||||
{
|
||||
var comms = new CommunicationService(
|
||||
Options.Create(new CommunicationOptions()),
|
||||
NullLogger<CommunicationService>.Instance);
|
||||
|
||||
return new ArtifactDeploymentService(
|
||||
_siteRepo, _deploymentRepo, comms, _audit,
|
||||
Options.Create(new DeploymentManagerOptions()),
|
||||
NullLogger<ArtifactDeploymentService>.Instance);
|
||||
}
|
||||
|
||||
private static DeployArtifactsCommand CreateCommand()
|
||||
{
|
||||
return new DeployArtifactsCommand(
|
||||
"dep1", null, null, null, null, DateTimeOffset.UtcNow);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user