using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.OtOpcUa.Configuration.LocalCache;
namespace ZB.MOM.WW.OtOpcUa.Server;
///
/// DI registration helpers consumed by Program.cs. Extracted so tests can assert
/// the production wire-up actually composes without spinning up the full Host.
///
public static class ServerWiring
{
///
/// Server-014 — registers the Phase 6.1 Stream D generation-sealed bootstrap chain:
/// , ,
/// , and . Without these
/// registrations OpcUaServerService cannot consume the sealed bootstrap and the
/// stays inert — /healthz's usingStaleConfig
/// never flips on a DB outage with a warm cache.
///
///
/// The cache root is sourced from — same path
/// the legacy uses for its LiteDB cache, so both bootstrap
/// paths persist alongside each other while the migration completes.
///
public static IServiceCollection AddSealedBootstrap(this IServiceCollection services, NodeOptions options)
{
// Use a sibling directory off LocalCachePath so the LiteDB file and the
// GenerationSealedCache snapshots don't clash. The cache root is a directory;
// LocalCachePath is canonically the LiteDB file path.
var cacheRoot = ResolveCacheRoot(options.LocalCachePath);
// Register NodeOptions only if the caller hasn't already done so — Program.cs
// registers it earlier in its DI chain, but the wiring helper supports standalone
// unit tests that want to compose just the SealedBootstrap chain.
if (!services.Any(d => d.ServiceType == typeof(NodeOptions)))
services.AddSingleton(options);
services.AddSingleton(new GenerationSealedCache(cacheRoot));
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
return services;
}
private static string ResolveCacheRoot(string localCachePath)
{
// LocalCachePath is the LiteDB file (e.g. "config_cache.db"); the sealed cache is a
// directory. Pick a sibling folder so the two don't share a path.
if (string.IsNullOrWhiteSpace(localCachePath))
return Path.Combine(Path.GetTempPath(), "otopcua-sealed-cache");
var dir = Path.GetDirectoryName(localCachePath);
var name = Path.GetFileNameWithoutExtension(localCachePath);
var root = string.IsNullOrEmpty(dir)
? $"{name}.sealed"
: Path.Combine(dir, $"{name}.sealed");
return root;
}
}