feat: initialize existing development playout PCs
This commit is contained in:
@@ -189,7 +189,7 @@ public sealed class DevelopmentLiveLaunchBootstrapTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnvironmentFailure_RestoresEveryPreviousProcessValue()
|
||||
public void EnvironmentFailure_DisarmsEveryPreviousProcessValue()
|
||||
{
|
||||
var platform = FakePlatform.Valid();
|
||||
platform.Environment[
|
||||
@@ -209,20 +209,32 @@ public sealed class DevelopmentLiveLaunchBootstrapTests
|
||||
|
||||
Assert.Equal(DevelopmentLiveBootstrapStatus.Rejected, result.Status);
|
||||
Assert.Equal("development-live-environment-failed", result.Code);
|
||||
Assert.Equal(
|
||||
"old-native",
|
||||
platform.Environment[
|
||||
InstalledK3dInteropMetadata.ApprovedNativeSha256EnvironmentVariable]);
|
||||
Assert.Equal(
|
||||
"old-interop",
|
||||
platform.Environment[
|
||||
InstalledK3dInteropMetadata.ApprovedSha256EnvironmentVariable]);
|
||||
Assert.Equal(
|
||||
"DryRun",
|
||||
platform.Environment[DevelopmentLiveLaunchBootstrap.ModeEnvironmentVariable]);
|
||||
Assert.Equal(
|
||||
"old-authorization",
|
||||
platform.Environment[PlayoutOptionsLoader.LiveAuthorizationEnvironmentVariable]);
|
||||
Assert.Empty(platform.Environment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RejectedExactDebugRequest_DisarmsInheritedLiveGates()
|
||||
{
|
||||
var platform = new FakePlatform(Encoding.UTF8.GetBytes("{}"));
|
||||
platform.Environment[
|
||||
InstalledK3dInteropMetadata.ApprovedNativeSha256EnvironmentVariable] =
|
||||
new string('A', 64);
|
||||
platform.Environment[
|
||||
InstalledK3dInteropMetadata.ApprovedSha256EnvironmentVariable] =
|
||||
new string('B', 64);
|
||||
platform.Environment[DevelopmentLiveLaunchBootstrap.ModeEnvironmentVariable] =
|
||||
DevelopmentLiveLaunchBootstrap.RequiredMode;
|
||||
platform.Environment[PlayoutOptionsLoader.LiveAuthorizationEnvironmentVariable] =
|
||||
PlayoutOptionsLoader.RequiredLiveAuthorizationValue;
|
||||
|
||||
var result = DevelopmentLiveLaunchBootstrap.TryApply(
|
||||
DevelopmentLiveLaunchBootstrap.ExactLaunchArgument,
|
||||
isDebugBuild: true,
|
||||
"authorization.json",
|
||||
platform);
|
||||
|
||||
Assert.Equal(DevelopmentLiveBootstrapStatus.Rejected, result.Status);
|
||||
Assert.Empty(platform.Environment);
|
||||
}
|
||||
|
||||
public static TheoryData<string> InvalidAuthorizationJson() => new()
|
||||
|
||||
@@ -88,6 +88,210 @@ public sealed class PlayoutOptionsLoaderTests
|
||||
Assert.Empty(options.TestSceneAllowlist);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_RejectedDevelopmentLiveRequestCannotInheritLiveEnvironment()
|
||||
{
|
||||
using var environment = ClearedEnvironment()
|
||||
.Set("MBN_STOCK_PLAYOUT_MODE", "Live")
|
||||
.Set("MBN_STOCK_PLAYOUT_HOST", "127.0.0.1")
|
||||
.Set(
|
||||
PlayoutOptionsLoader.LiveAuthorizationEnvironmentVariable,
|
||||
PlayoutOptionsLoader.RequiredLiveAuthorizationValue);
|
||||
using var file = TemporaryJsonFile.Create(DevelopmentLiveBaseJson());
|
||||
|
||||
var options = PlayoutOptionsLoader.Load(
|
||||
file.Path,
|
||||
operatorSceneDirectory: "C:\\operator-scenes",
|
||||
operatorBackgroundDirectory: "C:\\operator-backgrounds",
|
||||
forceSafeDryRun: true);
|
||||
var validated = ValidatedPlayoutOptions.Create(options);
|
||||
|
||||
Assert.Equal(PlayoutMode.DryRun, validated.Mode);
|
||||
Assert.False(validated.TrustedLiveOutputEnabled);
|
||||
Assert.Null(validated.SceneDirectory);
|
||||
Assert.Empty(validated.TestSceneAllowlist);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadDevelopmentLive_IgnoresEnvironmentAndPinsExecutableCuts()
|
||||
{
|
||||
using var environment = ClearedEnvironment()
|
||||
.Set("MBN_STOCK_PLAYOUT_MODE", "Disabled")
|
||||
.Set("MBN_STOCK_PLAYOUT_HOST", "stale-remote-host.invalid")
|
||||
.Set("MBN_STOCK_PLAYOUT_PORT", "32001")
|
||||
.Set("MBN_STOCK_PLAYOUT_OUTPUT_CHANNEL", "99")
|
||||
.Set("MBN_STOCK_PLAYOUT_SCENE_DIRECTORY", "C:\\stale-scenes")
|
||||
.Set(
|
||||
"MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_DIRECTORY",
|
||||
"C:\\stale-backgrounds")
|
||||
.Set("MBN_STOCK_PLAYOUT_RECONNECT_ENABLED", "true")
|
||||
.Set("MBN_STOCK_PLAYOUT_MAXIMUM_RECONNECT_ATTEMPTS", "9");
|
||||
using var file = TemporaryJsonFile.Create(DevelopmentLiveBaseJson());
|
||||
var executableRoot = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
Guid.NewGuid().ToString("N"));
|
||||
var cuts = Path.Combine(executableRoot, "Cuts");
|
||||
Directory.CreateDirectory(cuts);
|
||||
try
|
||||
{
|
||||
var options = PlayoutOptionsLoader.LoadDevelopmentLive(
|
||||
file.Path,
|
||||
executableRoot);
|
||||
|
||||
Assert.True(options.DevelopmentLiveBootstrapApplied);
|
||||
Assert.Equal(PlayoutMode.Live, options.Mode);
|
||||
Assert.Equal("127.0.0.1", options.Host);
|
||||
Assert.Equal(31001, options.Port);
|
||||
Assert.Equal(7, options.OutputChannel);
|
||||
Assert.Equal(Path.GetFullPath(cuts), options.SceneDirectory);
|
||||
Assert.Equal(
|
||||
Path.GetFullPath(cuts),
|
||||
options.DevelopmentLiveExecutableSceneDirectory);
|
||||
Assert.Null(options.LegacyBackgroundDirectory);
|
||||
Assert.False(options.ReconnectEnabled);
|
||||
Assert.Equal(0, options.MaximumReconnectAttempts);
|
||||
|
||||
var validated = ValidatedPlayoutOptions.Create(options);
|
||||
Assert.Equal("127.0.0.1", validated.Host);
|
||||
Assert.Equal(Path.GetFullPath(cuts), validated.SceneDirectory);
|
||||
Assert.False(validated.ReconnectEnabled);
|
||||
Assert.Equal(0, validated.MaximumReconnectAttempts);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(executableRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadDevelopmentLive_RejectsBaseProfileThatCanReconnect()
|
||||
{
|
||||
using var environment = ClearedEnvironment();
|
||||
using var file = TemporaryJsonFile.Create(
|
||||
DevelopmentLiveBaseJson().Replace(
|
||||
"\"reconnectEnabled\": false",
|
||||
"\"reconnectEnabled\": true",
|
||||
StringComparison.Ordinal));
|
||||
|
||||
var exception = Assert.Throws<PlayoutConfigurationException>(
|
||||
() => PlayoutOptionsLoader.LoadDevelopmentLive(file.Path, "C:\\runtime"));
|
||||
|
||||
Assert.Contains(
|
||||
"local configuration is invalid",
|
||||
exception.Message,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_DevelopmentLiveRequiresLiteralLoopbackAtFinalBoundary()
|
||||
{
|
||||
using var environment = ClearedEnvironment();
|
||||
using var file = TemporaryJsonFile.Create(DevelopmentLiveBaseJson());
|
||||
var executableRoot = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(Path.Combine(executableRoot, "Cuts"));
|
||||
try
|
||||
{
|
||||
var options = PlayoutOptionsLoader.LoadDevelopmentLive(
|
||||
file.Path,
|
||||
executableRoot);
|
||||
options.Host = "playout.dev.invalid";
|
||||
|
||||
var exception = Assert.Throws<PlayoutConfigurationException>(
|
||||
() => ValidatedPlayoutOptions.Create(options));
|
||||
|
||||
Assert.Contains(
|
||||
nameof(PlayoutOptions.Host),
|
||||
exception.Message,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(executableRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadDevelopmentLive_RejectsNonCanonicalJsonAndAllowlistExpansion()
|
||||
{
|
||||
using var environment = ClearedEnvironment();
|
||||
var canonical = DevelopmentLiveBaseJson();
|
||||
var closingBrace = canonical.LastIndexOf('}');
|
||||
Assert.True(closingBrace > 0);
|
||||
string[] invalidConfigurations =
|
||||
[
|
||||
canonical.Replace(
|
||||
"\"mode\": \"DryRun\"",
|
||||
"\"Mode\": \"DryRun\"",
|
||||
StringComparison.Ordinal),
|
||||
canonical.Replace(
|
||||
"\"mode\": \"DryRun\"",
|
||||
"\"mode\": \"DryRun\", \"mode\": \"DryRun\"",
|
||||
StringComparison.Ordinal),
|
||||
canonical.Replace(
|
||||
"\"port\": 31001",
|
||||
"\"port\": \"31001\"",
|
||||
StringComparison.Ordinal),
|
||||
canonical[..closingBrace] + ",\n}",
|
||||
canonical.Replace("{", "{/* comment */", StringComparison.Ordinal),
|
||||
canonical[..closingBrace] + ",\n \"unexpected\": true\n}",
|
||||
canonical.Replace(
|
||||
"\"N5001\"",
|
||||
"\"N5001\", \"9999\"",
|
||||
StringComparison.Ordinal)
|
||||
];
|
||||
|
||||
foreach (var json in invalidConfigurations)
|
||||
{
|
||||
using var file = TemporaryJsonFile.Create(json);
|
||||
|
||||
var exception = Assert.Throws<PlayoutConfigurationException>(
|
||||
() => PlayoutOptionsLoader.LoadDevelopmentLive(
|
||||
file.Path,
|
||||
"C:\\runtime"));
|
||||
|
||||
Assert.Equal(
|
||||
"Development Live local configuration is invalid.",
|
||||
exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_DevelopmentLiveRejectsSceneRootReplacementAfterLoading()
|
||||
{
|
||||
using var environment = ClearedEnvironment();
|
||||
using var file = TemporaryJsonFile.Create(DevelopmentLiveBaseJson());
|
||||
var executableRoot = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
Guid.NewGuid().ToString("N"));
|
||||
var replacementRoot = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(Path.Combine(executableRoot, "Cuts"));
|
||||
Directory.CreateDirectory(replacementRoot);
|
||||
try
|
||||
{
|
||||
var options = PlayoutOptionsLoader.LoadDevelopmentLive(
|
||||
file.Path,
|
||||
executableRoot);
|
||||
options.SceneDirectory = replacementRoot;
|
||||
|
||||
var exception = Assert.Throws<PlayoutConfigurationException>(
|
||||
() => ValidatedPlayoutOptions.Create(options));
|
||||
|
||||
Assert.Contains(
|
||||
"protected startup contract",
|
||||
exception.Message,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(executableRoot, recursive: true);
|
||||
Directory.Delete(replacementRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyExecutableAssetDefaults_UsesCutsBesideExecutable()
|
||||
{
|
||||
@@ -335,4 +539,43 @@ public sealed class PlayoutOptionsLoaderTests
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string DevelopmentLiveBaseJson() =>
|
||||
"""
|
||||
{
|
||||
"mode": "DryRun",
|
||||
"host": "127.0.0.1",
|
||||
"port": 31001,
|
||||
"tcpMode": 1,
|
||||
"clientPort": 0,
|
||||
"sceneDirectory": null,
|
||||
"outputChannel": 7,
|
||||
"layoutIndex": 10,
|
||||
"legacySceneFadeDuration": 6,
|
||||
"legacySceneBackgroundKind": "None",
|
||||
"legacySceneBackgroundAssetPath": null,
|
||||
"legacySceneBackgroundVideoLoopCount": 2004,
|
||||
"legacySceneBackgroundVideoLoopInfinite": true,
|
||||
"legacyBackgroundDirectory": null,
|
||||
"testProcessWindowTitlePattern": null,
|
||||
"testSceneAllowlist": [
|
||||
"5001", "5006", "5011", "5016", "50160", "5023", "5024", "5025",
|
||||
"5026", "5029", "5032", "5037", "5068", "5070", "5072", "5074",
|
||||
"5076", "5077", "5078", "5079", "5080", "5081", "5082", "5083",
|
||||
"5084", "5085", "5086", "50860", "5087", "5088", "6001", "6067",
|
||||
"8001", "8002", "8003", "8018", "8032", "8035", "8040", "8046",
|
||||
"8051", "8056", "8061", "8067", "N5001"
|
||||
],
|
||||
"trustedLiveOutputEnabled": true,
|
||||
"queueCapacity": 64,
|
||||
"connectTimeoutMilliseconds": 5000,
|
||||
"operationTimeoutMilliseconds": 5000,
|
||||
"disconnectTimeoutMilliseconds": 3000,
|
||||
"processPollIntervalMilliseconds": 1000,
|
||||
"reconnectDelayMilliseconds": 1000,
|
||||
"maximumReconnectAttempts": 0,
|
||||
"reconnectEnabled": false,
|
||||
"maximumAutomaticRefreshesPerTakeIn": 0
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
@@ -258,6 +258,21 @@ public sealed class PlayoutSafetyValidationTests : IDisposable
|
||||
Assert.Contains(nameof(PlayoutOptions.Host), exception.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_OrdinaryLiveModeRetainsConfiguredRemoteEndpointBehavior()
|
||||
{
|
||||
var options = ValidTestOptions();
|
||||
options.Mode = PlayoutMode.Live;
|
||||
options.Host = "playout.dev.invalid";
|
||||
options.OutputChannel = null;
|
||||
options.TestProcessWindowTitlePattern = null;
|
||||
options.TrustedLiveOutputEnabled = true;
|
||||
|
||||
var validated = ValidatedPlayoutOptions.Create(options);
|
||||
|
||||
Assert.Equal("playout.dev.invalid", validated.Host);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(".*")]
|
||||
[InlineData("PGM")]
|
||||
|
||||
Reference in New Issue
Block a user