using MBN_STOCK_WEBVIEW.Playout.Diagnostics; using MBN_STOCK_WEBVIEW.Playout.Interop; using MBN_STOCK_WEBVIEW.Playout.Registration; using System.Security.Cryptography; namespace MBN_STOCK_WEBVIEW.Playout.Tests; public sealed class InstalledK3dInteropMetadataTests { [Fact] public void ApprovedSha256_RequiresAndDecodesExactly64HexCharacters() { var parsed = InstalledK3dInteropMetadata.ParseApprovedSha256( "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF"); Assert.Equal(32, parsed.Length); Assert.Equal(0x01, parsed[0]); Assert.Equal(0xEF, parsed[^1]); } [Theory] [InlineData(null)] [InlineData("")] [InlineData("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde")] [InlineData(" 123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")] [InlineData("g123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")] public void ApprovedSha256_RejectsMissingMalformedOrWhitespaceValues(string? value) { Assert.Throws( () => InstalledK3dInteropMetadata.ParseApprovedSha256(value)); } [Fact] public void ApprovedSha256_RequiresExactContentForEitherBinaryPin() { byte[] content = [1, 2, 3, 4]; var approved = Convert.ToHexString(SHA256.HashData(content)); using var matching = new MemoryStream(content); using var different = new MemoryStream([1, 2, 3, 5]); InstalledK3dInteropMetadata.ValidateApprovedSha256(matching, approved); Assert.Throws( () => InstalledK3dInteropMetadata.ValidateApprovedSha256(different, approved)); Assert.Equal( "MBN_STOCK_K3D_NATIVE_SHA256", InstalledK3dInteropMetadata.ApprovedNativeSha256EnvironmentVariable); Assert.Equal( "MBN_STOCK_K3D_INTEROP_SHA256", InstalledK3dInteropMetadata.ApprovedSha256EnvironmentVariable); } [Fact] public void FileLease_KeepsNativeAndInteropFilesWriteLocked() { var directory = Path.Combine(Path.GetTempPath(), $"k3d-lease-{Guid.NewGuid():N}"); Directory.CreateDirectory(directory); var nativePath = Path.Combine(directory, "native.dll"); var interopPath = Path.Combine(directory, "interop.dll"); File.WriteAllBytes(nativePath, [1]); File.WriteAllBytes(interopPath, [2]); InstalledK3dInteropFileLease? lease = null; try { lease = new InstalledK3dInteropFileLease( new FileStream(nativePath, FileMode.Open, FileAccess.Read, FileShare.Read), new FileStream(interopPath, FileMode.Open, FileAccess.Read, FileShare.Read)); Assert.Throws(() => OpenForWrite(nativePath).Dispose()); Assert.Throws(() => OpenForWrite(interopPath).Dispose()); } finally { lease?.InteropStream.Dispose(); lease?.NativeStream.Dispose(); Directory.Delete(directory, recursive: true); } } [Fact] public void RegisteredPaths_MapOnlyExactX64VendorLayout() { const string native = @"C:\Vendor\K3DAsyncEngine\DLL\x64\Release\K3DAsyncEngine.dll"; var paths = InstalledK3dInteropMetadata.ResolveRegisteredPaths(ReadySnapshot(native)); Assert.Equal(Path.GetFullPath(native), paths.NativeBinaryPath); Assert.Equal( Path.GetFullPath( @"C:\Vendor\K3DAsyncEngine\Bin\x64\C#\Interop.K3DAsyncEngineLib.dll"), paths.InteropAssemblyPath); } [Fact] public void RegisteredPaths_RejectDivergentClassBinary() { const string native = @"C:\Vendor\K3DAsyncEngine\DLL\x64\Release\K3DAsyncEngine.dll"; var snapshot = ReadySnapshot(native) with { EventHandler = ReadySnapshot(native).EventHandler with { InprocServerPath = @"C:\Other\K3DAsyncEngine\DLL\x64\Release\K3DAsyncEngine.dll" } }; Assert.Throws( () => InstalledK3dInteropMetadata.ResolveRegisteredPaths(snapshot)); } [Fact] public void RegisteredPaths_RejectUnexpectedNativeLayout() { const string native = @"C:\Vendor\K3DAsyncEngine\bin\K3DAsyncEngine.dll"; Assert.Throws( () => InstalledK3dInteropMetadata.ResolveRegisteredPaths(ReadySnapshot(native))); } private static K3dRegistrySnapshot ReadySnapshot(string nativePath) => new( true, nativePath, new K3dClassRegistrationSnapshot( true, K3dComConstants.KaEngineProgId, K3dComConstants.KaEngineClassId, nativePath, K3dComConstants.ApartmentThreadingModel, K3dComConstants.TypeLibraryId), new K3dClassRegistrationSnapshot( true, K3dComConstants.KaEventHandlerProgId, K3dComConstants.KaEventHandlerClassId, nativePath, K3dComConstants.ApartmentThreadingModel, K3dComConstants.TypeLibraryId), false); private static FileStream OpenForWrite(string path) => new( path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete); }