feat: wire ExternalSystem, Database, and Notify APIs into script runtime
IServiceProvider now flows through the actor chain (DeploymentManagerActor → InstanceActor → ScriptActor → ScriptExecutionActor) so scripts can resolve IExternalSystemClient, IDatabaseGateway, and INotificationDeliveryService from DI. ScriptGlobals exposes ExternalSystem, Database, Notify, and Scripts as top-level properties so scripts can use them without the Instance. prefix.
This commit is contained in:
197
tests/ScadaLink.Host.Tests/ActorPathTests.cs
Normal file
197
tests/ScadaLink.Host.Tests/ActorPathTests.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using Akka.Actor;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using ScadaLink.ConfigurationDatabase;
|
||||
using ScadaLink.Host;
|
||||
using ScadaLink.Host.Actors;
|
||||
|
||||
namespace ScadaLink.Host.Tests;
|
||||
|
||||
[CollectionDefinition("ActorSystem")]
|
||||
public class ActorSystemCollection : ICollectionFixture<object> { }
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that all expected Central-role actors are created at the correct paths
|
||||
/// when AkkaHostedService starts.
|
||||
/// </summary>
|
||||
[Collection("ActorSystem")]
|
||||
public class CentralActorPathTests : IAsyncLifetime
|
||||
{
|
||||
private WebApplicationFactory<Program>? _factory;
|
||||
private ActorSystem? _actorSystem;
|
||||
private string? _previousEnv;
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
_previousEnv = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
|
||||
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", "Central");
|
||||
|
||||
_factory = new WebApplicationFactory<Program>()
|
||||
.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.ConfigureAppConfiguration((_, config) =>
|
||||
{
|
||||
config.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["ScadaLink:Node:NodeHostname"] = "localhost",
|
||||
["ScadaLink:Node:RemotingPort"] = "0",
|
||||
["ScadaLink:Cluster:SeedNodes:0"] = "akka.tcp://scadalink@localhost:25510",
|
||||
["ScadaLink:Cluster:SeedNodes:1"] = "akka.tcp://scadalink@localhost:25520",
|
||||
["ScadaLink:Cluster:MinNrOfMembers"] = "1",
|
||||
["ScadaLink:Database:SkipMigrations"] = "true",
|
||||
["ScadaLink:Security:JwtSigningKey"] = "test-signing-key-must-be-at-least-32-chars-long!",
|
||||
["ScadaLink:Security:LdapServer"] = "localhost",
|
||||
["ScadaLink:Security:LdapPort"] = "3893",
|
||||
["ScadaLink:Security:LdapUseTls"] = "false",
|
||||
["ScadaLink:Security:AllowInsecureLdap"] = "true",
|
||||
["ScadaLink:Security:LdapSearchBase"] = "dc=scadalink,dc=local",
|
||||
});
|
||||
});
|
||||
builder.UseSetting("ScadaLink:Node:Role", "Central");
|
||||
builder.UseSetting("ScadaLink:Database:SkipMigrations", "true");
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
// Replace SQL Server with in-memory database
|
||||
var descriptorsToRemove = services
|
||||
.Where(d =>
|
||||
d.ServiceType == typeof(DbContextOptions<ScadaLinkDbContext>) ||
|
||||
d.ServiceType == typeof(DbContextOptions) ||
|
||||
d.ServiceType == typeof(ScadaLinkDbContext) ||
|
||||
d.ServiceType.FullName?.Contains("EntityFrameworkCore") == true)
|
||||
.ToList();
|
||||
foreach (var d in descriptorsToRemove)
|
||||
services.Remove(d);
|
||||
|
||||
services.AddDbContext<ScadaLinkDbContext>(options =>
|
||||
options.UseInMemoryDatabase($"ActorPathTests_{Guid.NewGuid()}"));
|
||||
});
|
||||
});
|
||||
|
||||
// CreateClient triggers host startup including AkkaHostedService
|
||||
_ = _factory.CreateClient();
|
||||
|
||||
var akkaService = _factory.Services.GetRequiredService<AkkaHostedService>();
|
||||
_actorSystem = akkaService.ActorSystem;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
_factory?.Dispose();
|
||||
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", _previousEnv);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CentralActors_DeadLetterMonitor_Exists()
|
||||
=> await AssertActorExists("/user/dead-letter-monitor");
|
||||
|
||||
[Fact]
|
||||
public async Task CentralActors_CentralCommunication_Exists()
|
||||
=> await AssertActorExists("/user/central-communication");
|
||||
|
||||
[Fact]
|
||||
public async Task CentralActors_Management_Exists()
|
||||
=> await AssertActorExists("/user/management");
|
||||
|
||||
private async Task AssertActorExists(string path)
|
||||
{
|
||||
Assert.NotNull(_actorSystem);
|
||||
var selection = _actorSystem!.ActorSelection(path);
|
||||
var identity = await selection.Ask<ActorIdentity>(
|
||||
new Identify(path), TimeSpan.FromSeconds(5));
|
||||
Assert.NotNull(identity.Subject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that all expected Site-role actors are created at the correct paths
|
||||
/// when AkkaHostedService starts.
|
||||
/// </summary>
|
||||
[Collection("ActorSystem")]
|
||||
public class SiteActorPathTests : IAsyncLifetime
|
||||
{
|
||||
private IHost? _host;
|
||||
private ActorSystem? _actorSystem;
|
||||
private string _tempDbPath = null!;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_tempDbPath = Path.Combine(Path.GetTempPath(), $"scadalink_actor_test_{Guid.NewGuid()}.db");
|
||||
|
||||
var builder = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder();
|
||||
builder.ConfigureAppConfiguration(config =>
|
||||
{
|
||||
config.Sources.Clear();
|
||||
config.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["ScadaLink:Node:Role"] = "Site",
|
||||
["ScadaLink:Node:NodeHostname"] = "localhost",
|
||||
["ScadaLink:Node:SiteId"] = "TestSite",
|
||||
["ScadaLink:Node:RemotingPort"] = "0",
|
||||
["ScadaLink:Cluster:SeedNodes:0"] = "akka.tcp://scadalink@localhost:25510",
|
||||
["ScadaLink:Cluster:SeedNodes:1"] = "akka.tcp://scadalink@localhost:25520",
|
||||
["ScadaLink:Cluster:MinNrOfMembers"] = "1",
|
||||
["ScadaLink:Database:SiteDbPath"] = _tempDbPath,
|
||||
// Configure a dummy central contact point to trigger ClusterClient creation
|
||||
["ScadaLink:Communication:CentralContactPoints:0"] = "akka.tcp://scadalink@localhost:25510",
|
||||
});
|
||||
});
|
||||
builder.ConfigureServices((context, services) =>
|
||||
{
|
||||
SiteServiceRegistration.Configure(services, context.Configuration);
|
||||
});
|
||||
|
||||
_host = builder.Build();
|
||||
await _host.StartAsync();
|
||||
|
||||
var akkaService = _host.Services.GetRequiredService<AkkaHostedService>();
|
||||
_actorSystem = akkaService.ActorSystem;
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
if (_host != null)
|
||||
{
|
||||
await _host.StopAsync();
|
||||
_host.Dispose();
|
||||
}
|
||||
try { File.Delete(_tempDbPath); } catch { /* best effort */ }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SiteActors_DeadLetterMonitor_Exists()
|
||||
=> await AssertActorExists("/user/dead-letter-monitor");
|
||||
|
||||
[Fact]
|
||||
public async Task SiteActors_DclManager_Exists()
|
||||
=> await AssertActorExists("/user/dcl-manager");
|
||||
|
||||
[Fact]
|
||||
public async Task SiteActors_DeploymentManagerSingleton_Exists()
|
||||
=> await AssertActorExists("/user/deployment-manager-singleton");
|
||||
|
||||
[Fact]
|
||||
public async Task SiteActors_DeploymentManagerProxy_Exists()
|
||||
=> await AssertActorExists("/user/deployment-manager-proxy");
|
||||
|
||||
[Fact]
|
||||
public async Task SiteActors_SiteCommunication_Exists()
|
||||
=> await AssertActorExists("/user/site-communication");
|
||||
|
||||
[Fact]
|
||||
public async Task SiteActors_CentralClusterClient_Exists()
|
||||
=> await AssertActorExists("/user/central-cluster-client");
|
||||
|
||||
private async Task AssertActorExists(string path)
|
||||
{
|
||||
Assert.NotNull(_actorSystem);
|
||||
var selection = _actorSystem!.ActorSelection(path);
|
||||
var identity = await selection.Ask<ActorIdentity>(
|
||||
new Identify(path), TimeSpan.FromSeconds(5));
|
||||
Assert.NotNull(identity.Subject);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user