Phase 7: Integration surfaces — Inbound API, External System Gateway, Notification Service

Inbound API (WP-1–5):
- POST /api/{methodName} with X-API-Key auth (401/403)
- Parameter validation with extended type system (Object, List)
- Central script execution with configurable timeout
- Route.To() cross-site calls (Call, GetAttribute/SetAttribute batch)
- Failures-only logging

External System Gateway (WP-6–10):
- HTTP/REST client with JSON, API Key + Basic Auth
- Dual call modes: Call() synchronous, CachedCall() with S&F
- Error classification (transient: 5xx/408/429, permanent: 4xx)
- Database.Connection() (ADO.NET pooling) + Database.CachedWrite() (S&F)

Notification Service (WP-11–13):
- SMTP with OAuth2 Client Credentials + Basic Auth
- BCC delivery, plain text, token lifecycle
- Transient → S&F, permanent → returned to script
- ScriptRuntimeContext wired with ExternalSystem/Database/Notify APIs

Repository implementations: ExternalSystem, Notification, InboundApi, InstanceLocator
781 tests pass, zero warnings.
This commit is contained in:
Joseph Doherty
2026-03-16 22:19:12 -04:00
parent b659978764
commit 2ae807df37
4 changed files with 16 additions and 7 deletions

View File

@@ -21,7 +21,13 @@ public static class ServiceCollectionExtensions
services.AddScoped<ISecurityRepository, SecurityRepository>();
services.AddScoped<ICentralUiRepository, CentralUiRepository>();
services.AddScoped<ITemplateEngineRepository, TemplateEngineRepository>();
services.AddScoped<IDeploymentManagerRepository, DeploymentManagerRepository>();
services.AddScoped<ISiteRepository, SiteRepository>();
services.AddScoped<IExternalSystemRepository, ExternalSystemRepository>();
services.AddScoped<INotificationRepository, NotificationRepository>();
services.AddScoped<IInboundApiRepository, InboundApiRepository>();
services.AddScoped<IAuditService, AuditService>();
services.AddScoped<IInstanceLocator, InstanceLocator>();
services.AddDataProtection()
.PersistKeysToDbContext<ScadaLinkDbContext>();

View File

@@ -11,10 +11,10 @@ public static class ServiceCollectionExtensions
.BindConfiguration("ScadaLink:ExternalSystemGateway");
services.AddHttpClient();
services.AddSingleton<ExternalSystemClient>();
services.AddSingleton<IExternalSystemClient>(sp => sp.GetRequiredService<ExternalSystemClient>());
services.AddSingleton<DatabaseGateway>();
services.AddSingleton<IDatabaseGateway>(sp => sp.GetRequiredService<DatabaseGateway>());
services.AddScoped<ExternalSystemClient>();
services.AddScoped<IExternalSystemClient>(sp => sp.GetRequiredService<ExternalSystemClient>());
services.AddScoped<DatabaseGateway>();
services.AddScoped<IDatabaseGateway>(sp => sp.GetRequiredService<DatabaseGateway>());
return services;
}

View File

@@ -13,8 +13,8 @@ public static class ServiceCollectionExtensions
services.AddHttpClient();
services.AddSingleton<OAuth2TokenService>();
services.AddSingleton<Func<ISmtpClientWrapper>>(_ => () => new MailKitSmtpClientWrapper());
services.AddSingleton<NotificationDeliveryService>();
services.AddSingleton<INotificationDeliveryService>(sp => sp.GetRequiredService<NotificationDeliveryService>());
services.AddScoped<NotificationDeliveryService>();
services.AddScoped<INotificationDeliveryService>(sp => sp.GetRequiredService<NotificationDeliveryService>());
return services;
}

View File

@@ -200,7 +200,10 @@ public class IntegrationSurfaceTests
// Create a minimal context — we use Substitute.For<IActorRef> which is fine since
// we won't exercise Akka functionality in these tests.
var actorRef = Substitute.For<Akka.Actor.IActorRef>();
var sharedLibrary = Substitute.For<SiteRuntime.Scripts.SharedScriptLibrary>(
var compilationService = new SiteRuntime.Scripts.ScriptCompilationService(
Microsoft.Extensions.Logging.Abstractions.NullLogger<SiteRuntime.Scripts.ScriptCompilationService>.Instance);
var sharedLibrary = new SiteRuntime.Scripts.SharedScriptLibrary(
compilationService,
Microsoft.Extensions.Logging.Abstractions.NullLogger<SiteRuntime.Scripts.SharedScriptLibrary>.Instance);
return new SiteRuntime.Scripts.ScriptRuntimeContext(