Files
MBN_STOCK_WEBVIEW/tests/MBN_STOCK_WEBVIEW.Playout.Tests/PlayoutOptionsLoaderTests.cs

183 lines
7.4 KiB
C#

namespace MBN_STOCK_WEBVIEW.Playout.Tests;
public sealed class PlayoutOptionsLoaderTests
{
private static readonly string[] EnvironmentNames =
[
"MBN_STOCK_PLAYOUT_MODE",
"MBN_STOCK_PLAYOUT_HOST",
"MBN_STOCK_PLAYOUT_PORT",
"MBN_STOCK_PLAYOUT_TCP_MODE",
"MBN_STOCK_PLAYOUT_CLIENT_PORT",
"MBN_STOCK_PLAYOUT_OUTPUT_CHANNEL",
"MBN_STOCK_PLAYOUT_LAYOUT_INDEX",
"MBN_STOCK_PLAYOUT_SCENE_DIRECTORY",
"MBN_STOCK_PLAYOUT_TEST_WINDOW_TITLE_PATTERN",
"MBN_STOCK_PLAYOUT_QUEUE_CAPACITY",
"MBN_STOCK_PLAYOUT_CONNECT_TIMEOUT_MS",
"MBN_STOCK_PLAYOUT_OPERATION_TIMEOUT_MS",
"MBN_STOCK_PLAYOUT_DISCONNECT_TIMEOUT_MS",
"MBN_STOCK_PLAYOUT_PROCESS_POLL_INTERVAL_MS",
"MBN_STOCK_PLAYOUT_RECONNECT_DELAY_MS",
"MBN_STOCK_PLAYOUT_MAXIMUM_RECONNECT_ATTEMPTS",
"MBN_STOCK_PLAYOUT_RECONNECT_ENABLED",
PlayoutOptionsLoader.LiveAuthorizationEnvironmentVariable,
"MBN_STOCK_PLAYOUT_TEST_SCENE_ALLOWLIST",
"MBN_STOCK_PLAYOUT_TRUSTED_LIVE_OUTPUT_ENABLED"
];
[Fact]
public void Load_WhenFileIsMissing_UsesSafeDryRunDefaults()
{
using var environment = ClearedEnvironment();
using var file = TemporaryJsonFile.Missing();
var options = PlayoutOptionsLoader.Load(file.Path);
Assert.Equal(PlayoutMode.DryRun, options.Mode);
Assert.Equal("127.0.0.1", options.Host);
Assert.Equal(30001, options.Port);
Assert.Equal(1, options.TcpMode);
Assert.Equal(0, options.ClientPort);
Assert.Null(options.OutputChannel);
Assert.Null(options.SceneDirectory);
Assert.Empty(options.TestSceneAllowlist);
Assert.False(options.TrustedLiveOutputEnabled);
Assert.True(options.ReconnectEnabled);
}
[Fact]
public void Load_EnvironmentOverridesJson_ExceptFileOnlySafetyGates()
{
using var environment = ClearedEnvironment()
.Set("MBN_STOCK_PLAYOUT_MODE", "DryRun")
.Set("MBN_STOCK_PLAYOUT_HOST", "env-host")
.Set("MBN_STOCK_PLAYOUT_PORT", "32001")
.Set("MBN_STOCK_PLAYOUT_TCP_MODE", "2")
.Set("MBN_STOCK_PLAYOUT_CLIENT_PORT", "32002")
.Set("MBN_STOCK_PLAYOUT_OUTPUT_CHANNEL", "12")
.Set("MBN_STOCK_PLAYOUT_LAYOUT_INDEX", "13")
.Set("MBN_STOCK_PLAYOUT_SCENE_DIRECTORY", "C:\\env-scenes")
.Set("MBN_STOCK_PLAYOUT_TEST_WINDOW_TITLE_PATTERN", "ENV TEST")
.Set("MBN_STOCK_PLAYOUT_QUEUE_CAPACITY", "14")
.Set("MBN_STOCK_PLAYOUT_CONNECT_TIMEOUT_MS", "1501")
.Set("MBN_STOCK_PLAYOUT_OPERATION_TIMEOUT_MS", "1502")
.Set("MBN_STOCK_PLAYOUT_DISCONNECT_TIMEOUT_MS", "1503")
.Set("MBN_STOCK_PLAYOUT_PROCESS_POLL_INTERVAL_MS", "1504")
.Set("MBN_STOCK_PLAYOUT_RECONNECT_DELAY_MS", "1505")
.Set("MBN_STOCK_PLAYOUT_MAXIMUM_RECONNECT_ATTEMPTS", "4")
.Set("MBN_STOCK_PLAYOUT_RECONNECT_ENABLED", "false")
.Set("MBN_STOCK_PLAYOUT_TEST_SCENE_ALLOWLIST", "C:\\env\\must-not-apply.t2s")
.Set("MBN_STOCK_PLAYOUT_TRUSTED_LIVE_OUTPUT_ENABLED", "false");
using var file = TemporaryJsonFile.Create(
"""
{
"mode": "Test",
"host": "json-host",
"port": 31001,
"tcpMode": 1,
"clientPort": 31002,
"outputChannel": 3,
"layoutIndex": 10,
"sceneDirectory": "C:\\json-scenes",
"testProcessWindowTitlePattern": "JSON TEST",
"testSceneAllowlist": ["C:\\test-scenes\\allowed.t2s"],
"trustedLiveOutputEnabled": true,
"queueCapacity": 64,
"connectTimeoutMilliseconds": 5000,
"operationTimeoutMilliseconds": 5000,
"disconnectTimeoutMilliseconds": 3000,
"processPollIntervalMilliseconds": 1000,
"reconnectDelayMilliseconds": 1000,
"maximumReconnectAttempts": 3,
"reconnectEnabled": true
}
""");
var options = PlayoutOptionsLoader.Load(file.Path);
Assert.Equal(PlayoutMode.DryRun, options.Mode);
Assert.Equal("env-host", options.Host);
Assert.Equal(32001, options.Port);
Assert.Equal(2, options.TcpMode);
Assert.Equal(32002, options.ClientPort);
Assert.Equal(12, options.OutputChannel);
Assert.Equal(13, options.LayoutIndex);
Assert.Equal("C:\\env-scenes", options.SceneDirectory);
Assert.Equal("ENV TEST", options.TestProcessWindowTitlePattern);
Assert.Equal(14, options.QueueCapacity);
Assert.Equal(1501, options.ConnectTimeoutMilliseconds);
Assert.Equal(1502, options.OperationTimeoutMilliseconds);
Assert.Equal(1503, options.DisconnectTimeoutMilliseconds);
Assert.Equal(1504, options.ProcessPollIntervalMilliseconds);
Assert.Equal(1505, options.ReconnectDelayMilliseconds);
Assert.Equal(4, options.MaximumReconnectAttempts);
Assert.False(options.ReconnectEnabled);
Assert.Equal(["C:\\test-scenes\\allowed.t2s"], options.TestSceneAllowlist);
Assert.True(options.TrustedLiveOutputEnabled);
}
[Fact]
public void LoadFileOnly_IgnoresEnvironmentForReproducibleExplicitDiagnostics()
{
using var environment = ClearedEnvironment()
.Set("MBN_STOCK_PLAYOUT_MODE", "Live")
.Set("MBN_STOCK_PLAYOUT_HOST", "sensitive-host");
using var file = TemporaryJsonFile.Create(
"""
{
"mode": "Test",
"host": "127.0.0.1"
}
""");
var options = PlayoutOptionsLoader.LoadFileOnly(file.Path);
Assert.Equal(PlayoutMode.Test, options.Mode);
Assert.Equal("127.0.0.1", options.Host);
}
[Fact]
public void Load_InvalidJson_ReturnsOnlySafeConfigurationMessage()
{
using var environment = ClearedEnvironment();
using var file = TemporaryJsonFile.Create("{ invalid-json: true }");
var exception = Assert.Throws<PlayoutConfigurationException>(
() => PlayoutOptionsLoader.Load(file.Path));
Assert.Equal("송출 설정 파일 형식이 올바르지 않습니다.", exception.Message);
Assert.DoesNotContain(file.Path, exception.Message, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("invalid-json", exception.Message, StringComparison.OrdinalIgnoreCase);
}
[Theory]
[InlineData("MBN_STOCK_PLAYOUT_MODE", "not-a-mode")]
[InlineData("MBN_STOCK_PLAYOUT_PORT", "not-a-number")]
[InlineData("MBN_STOCK_PLAYOUT_RECONNECT_ENABLED", "not-a-bool")]
public void Load_InvalidEnvironmentValue_IdentifiesVariableWithoutLeakingValue(
string name,
string invalidValue)
{
using var environment = ClearedEnvironment().Set(name, invalidValue);
using var file = TemporaryJsonFile.Missing();
var exception = Assert.Throws<PlayoutConfigurationException>(
() => PlayoutOptionsLoader.Load(file.Path));
Assert.Contains(name, exception.Message, StringComparison.Ordinal);
Assert.DoesNotContain(invalidValue, exception.Message, StringComparison.Ordinal);
}
private static EnvironmentScope ClearedEnvironment()
{
var result = new EnvironmentScope();
foreach (var name in EnvironmentNames)
{
result.Set(name, null);
}
return result;
}
}