feat: add safe Tornado K3D playout adapter
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
using MBN_STOCK_WEBVIEW.Playout.Interop;
|
||||
using MBN_STOCK_WEBVIEW.Playout.Registration;
|
||||
|
||||
namespace MBN_STOCK_WEBVIEW.Playout.Tests;
|
||||
|
||||
public sealed class K3dRegistrationProbeTests
|
||||
{
|
||||
private const string TypeLibraryPath = "C:\\vendor-secret\\typelib-x64.dll";
|
||||
private const string EnginePath = "C:\\vendor-secret\\engine-x64.dll";
|
||||
private const string EventHandlerPath = "C:\\vendor-secret\\events-x64.dll";
|
||||
|
||||
[Fact]
|
||||
public void Probe_WithReciprocalAmd64MachineRegistration_IsReady()
|
||||
{
|
||||
var binaries = ReadyBinaries();
|
||||
var probe = CreateProbe(ReadySnapshot(), binaries);
|
||||
|
||||
var report = probe.Probe();
|
||||
|
||||
Assert.True(report.IsReady);
|
||||
Assert.Equal(K3dRegistrationIssue.None, report.Issues);
|
||||
Assert.True(report.IsEngineClassReady);
|
||||
Assert.True(report.IsEventHandlerClassReady);
|
||||
Assert.Equal(
|
||||
new[] { TypeLibraryPath, EnginePath, EventHandlerPath },
|
||||
binaries.InspectedPaths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Probe_WhenEitherDirectionOfReciprocalMappingDiffers_FailsClosed()
|
||||
{
|
||||
var snapshot = ReadySnapshot() with
|
||||
{
|
||||
Engine = ReadySnapshot().Engine with
|
||||
{
|
||||
ClassProgId = "Unexpected.Engine.1",
|
||||
ProgIdClassId = K3dComConstants.KaEventHandlerClassId
|
||||
},
|
||||
EventHandler = ReadySnapshot().EventHandler with
|
||||
{
|
||||
ProgIdClassId = K3dComConstants.KaEngineClassId
|
||||
}
|
||||
};
|
||||
var probe = CreateProbe(snapshot, ReadyBinaries());
|
||||
|
||||
var report = probe.Probe();
|
||||
|
||||
Assert.False(report.IsReady);
|
||||
Assert.True(report.Issues.HasFlag(K3dRegistrationIssue.EngineProgIdMismatch));
|
||||
Assert.True(report.Issues.HasFlag(K3dRegistrationIssue.EngineProgIdClassIdMismatch));
|
||||
Assert.True(report.Issues.HasFlag(K3dRegistrationIssue.EventHandlerProgIdClassIdMismatch));
|
||||
Assert.False(report.IsEngineClassReady);
|
||||
Assert.False(report.IsEventHandlerClassReady);
|
||||
AssertSafe(report);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Probe_WhenEitherClassServerIsNotAmd64_FailsClosed()
|
||||
{
|
||||
var binaries = ReadyBinaries();
|
||||
binaries.Set(EnginePath, exists: true, isAmd64: false);
|
||||
binaries.Set(EventHandlerPath, exists: true, isAmd64: false);
|
||||
var probe = CreateProbe(ReadySnapshot(), binaries);
|
||||
|
||||
var report = probe.Probe();
|
||||
|
||||
Assert.False(report.IsReady);
|
||||
Assert.True(report.Issues.HasFlag(K3dRegistrationIssue.EngineServerIsNotAmd64));
|
||||
Assert.True(report.Issues.HasFlag(K3dRegistrationIssue.EventHandlerServerIsNotAmd64));
|
||||
Assert.False(report.IsEngineClassReady);
|
||||
Assert.False(report.IsEventHandlerClassReady);
|
||||
AssertSafe(report);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Probe_WhenCurrentUserOverrideExists_FailsClosedWithoutDetails()
|
||||
{
|
||||
var probe = CreateProbe(
|
||||
ReadySnapshot() with { CurrentUserOverridePresent = true },
|
||||
ReadyBinaries());
|
||||
|
||||
var report = probe.Probe();
|
||||
|
||||
Assert.False(report.IsReady);
|
||||
Assert.True(report.Issues.HasFlag(K3dRegistrationIssue.CurrentUserOverridePresent));
|
||||
Assert.False(report.IsEngineClassReady);
|
||||
Assert.False(report.IsEventHandlerClassReady);
|
||||
Assert.Contains("재정의", report.Message, StringComparison.Ordinal);
|
||||
AssertSafe(report);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CurrentUserOverrideBoundary_CoversBothClassesProgIdsAndTypeLibrary()
|
||||
{
|
||||
Assert.Equal(
|
||||
new[]
|
||||
{
|
||||
$"CLSID\\{K3dComConstants.KaEngineClassId}",
|
||||
$"CLSID\\{K3dComConstants.KaEventHandlerClassId}",
|
||||
K3dComConstants.KaEngineProgId,
|
||||
K3dComConstants.KaEventHandlerProgId,
|
||||
$"TypeLib\\{K3dComConstants.TypeLibraryId}"
|
||||
},
|
||||
WindowsK3dRegistrySnapshotSource.CurrentUserOverridePaths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Probe_WhenRegistrySnapshotCannotBeRead_ReturnsSafeFailure()
|
||||
{
|
||||
var probe = new K3dRegistrationProbe(
|
||||
new ThrowingSnapshotSource(),
|
||||
ReadyBinaries());
|
||||
|
||||
var report = probe.Probe();
|
||||
|
||||
Assert.False(report.IsReady);
|
||||
Assert.Equal(K3dRegistrationIssue.RegistryReadFailed, report.Issues);
|
||||
AssertSafe(report);
|
||||
}
|
||||
|
||||
private static K3dRegistrationProbe CreateProbe(
|
||||
K3dRegistrySnapshot snapshot,
|
||||
FakeBinaryInspector binaries) =>
|
||||
new(new FakeSnapshotSource(snapshot), binaries);
|
||||
|
||||
private static K3dRegistrySnapshot ReadySnapshot() => new(
|
||||
Is64BitProcess: true,
|
||||
Win64TypeLibraryPath: TypeLibraryPath,
|
||||
Engine: new K3dClassRegistrationSnapshot(
|
||||
ClassKeyPresent: true,
|
||||
ClassProgId: K3dComConstants.KaEngineProgId,
|
||||
ProgIdClassId: K3dComConstants.KaEngineClassId,
|
||||
InprocServerPath: EnginePath,
|
||||
ThreadingModel: K3dComConstants.ApartmentThreadingModel,
|
||||
TypeLibraryId: K3dComConstants.TypeLibraryId),
|
||||
EventHandler: new K3dClassRegistrationSnapshot(
|
||||
ClassKeyPresent: true,
|
||||
ClassProgId: K3dComConstants.KaEventHandlerProgId,
|
||||
ProgIdClassId: K3dComConstants.KaEventHandlerClassId,
|
||||
InprocServerPath: EventHandlerPath,
|
||||
ThreadingModel: K3dComConstants.ApartmentThreadingModel,
|
||||
TypeLibraryId: K3dComConstants.TypeLibraryId),
|
||||
CurrentUserOverridePresent: false);
|
||||
|
||||
private static FakeBinaryInspector ReadyBinaries() => new FakeBinaryInspector()
|
||||
.Set(TypeLibraryPath, exists: true, isAmd64: true)
|
||||
.Set(EnginePath, exists: true, isAmd64: true)
|
||||
.Set(EventHandlerPath, exists: true, isAmd64: true);
|
||||
|
||||
private static void AssertSafe(K3dRegistrationReport report)
|
||||
{
|
||||
Assert.DoesNotContain(TypeLibraryPath, report.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain(EnginePath, report.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain(EventHandlerPath, report.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("HKEY", report.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("CLSID\\", report.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private sealed class FakeSnapshotSource : IK3dRegistrySnapshotSource
|
||||
{
|
||||
private readonly K3dRegistrySnapshot _snapshot;
|
||||
|
||||
public FakeSnapshotSource(K3dRegistrySnapshot snapshot)
|
||||
{
|
||||
_snapshot = snapshot;
|
||||
}
|
||||
|
||||
public K3dRegistrySnapshot Capture() => _snapshot;
|
||||
}
|
||||
|
||||
private sealed class ThrowingSnapshotSource : IK3dRegistrySnapshotSource
|
||||
{
|
||||
public K3dRegistrySnapshot Capture() =>
|
||||
throw new UnauthorizedAccessException("HKEY secret registry detail");
|
||||
}
|
||||
|
||||
private sealed class FakeBinaryInspector : IPeBinaryInspector
|
||||
{
|
||||
private readonly Dictionary<string, PeBinaryInspection> _inspections =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly List<string> _inspectedPaths = [];
|
||||
|
||||
public IReadOnlyList<string> InspectedPaths => _inspectedPaths;
|
||||
|
||||
public FakeBinaryInspector Set(string path, bool exists, bool isAmd64)
|
||||
{
|
||||
_inspections[path] = new PeBinaryInspection(exists, isAmd64);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PeBinaryInspection Inspect(string? path)
|
||||
{
|
||||
if (path is null)
|
||||
{
|
||||
return new PeBinaryInspection(false, false);
|
||||
}
|
||||
|
||||
_inspectedPaths.Add(path);
|
||||
return _inspections.TryGetValue(path, out var inspection)
|
||||
? inspection
|
||||
: new PeBinaryInspection(false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user