feat: add verified development live handoff

This commit is contained in:
2026-07-24 14:54:57 +09:00
parent 68a41194c9
commit 8b628908f0
11 changed files with 2355 additions and 105 deletions

View File

@@ -187,7 +187,7 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
}
[Fact]
public void ActiveDatabaseIniIsPostBuildOutputOnlyAndNeverAPackagingItem()
public void DatabaseCredentialsStayInUserLocalConfigurationAndNeverEnterBuildOutputs()
{
var project = XDocument.Load(ProjectPath);
Assert.DoesNotContain(
@@ -196,59 +196,49 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
element => (element.Attribute("Include")?.Value ?? string.Empty)
.Contains("MmoneyCoder.ini", StringComparison.OrdinalIgnoreCase));
var upToDateInput = Assert.Single(project.Descendants("UpToDateCheckInput"));
Assert.Equal(
"$(LegacyResSourceRoot)\\MmoneyCoder.ini",
upToDateInput.Attribute("Include")?.Value);
var upToDateBuilt = Assert.Single(project.Descendants("UpToDateCheckBuilt"));
Assert.Equal(
"$(TargetDir)Res\\MmoneyCoder.ini",
upToDateBuilt.Attribute("Include")?.Value);
var copyTarget = Assert.Single(
Assert.DoesNotContain(
project.Descendants("UpToDateCheckInput"),
element => (element.Attribute("Include")?.Value ?? string.Empty)
.Contains("MmoneyCoder.ini", StringComparison.OrdinalIgnoreCase));
Assert.DoesNotContain(
project.Descendants("UpToDateCheckBuilt"),
element => (element.Attribute("Include")?.Value ?? string.Empty)
.Contains("MmoneyCoder.ini", StringComparison.OrdinalIgnoreCase));
Assert.DoesNotContain(
project.Descendants("Target"),
element => string.Equals(
element.Attribute("Name")?.Value,
"CopyLegacyDatabaseIniToLocalOutput",
StringComparison.Ordinal));
Assert.Equal("Build", copyTarget.Attribute("AfterTargets")?.Value);
Assert.Contains(
"GenerateAppxPackageOnBuild",
copyTarget.Attribute("Condition")?.Value,
StringComparison.Ordinal);
Assert.Contains(
"PublishAppxPackage",
copyTarget.Attribute("Condition")?.Value,
StringComparison.Ordinal);
Assert.Equal(
"$(LegacyResSourceRoot)\\MmoneyCoder.ini",
copyTarget.Attribute("Inputs")?.Value);
Assert.Equal(
"$(TargetDir)Res\\MmoneyCoder.ini",
copyTarget.Attribute("Outputs")?.Value);
var copy = Assert.Single(copyTarget.Elements("Copy"));
Assert.Equal(
"$(LegacyResSourceRoot)\\MmoneyCoder.ini",
copy.Attribute("SourceFiles")?.Value);
Assert.Equal(
"$(TargetDir)Res\\MmoneyCoder.ini",
copy.Attribute("DestinationFiles")?.Value);
var fileWrite = Assert.Single(copyTarget.Descendants("FileWrites"));
Assert.Equal(
"$(TargetDir)Res\\MmoneyCoder.ini",
fileWrite.Attribute("Include")?.Value);
var cleanTarget = Assert.Single(
Assert.DoesNotContain(
project.Descendants("Target"),
element => string.Equals(
element.Attribute("Name")?.Value,
"RemoveLegacyDatabaseIniFromLocalOutput",
StringComparison.Ordinal));
Assert.Equal("Clean", cleanTarget.Attribute("BeforeTargets")?.Value);
var delete = Assert.Single(cleanTarget.Elements("Delete"));
var scrubTarget = Assert.Single(
project.Descendants("Target"),
element => string.Equals(
element.Attribute("Name")?.Value,
"ScrubLegacyDatabaseIniFromBuildOutput",
StringComparison.Ordinal));
Assert.Equal("PrepareForBuild", scrubTarget.Attribute("BeforeTargets")?.Value);
var scrubDelete = Assert.Single(scrubTarget.Elements("Delete"));
Assert.Equal(
"$(TargetDir)Res\\MmoneyCoder.ini",
delete.Attribute("Files")?.Value);
scrubDelete.Attribute("Files")?.Value);
var validationTarget = Assert.Single(
project.Descendants("Target"),
element => string.Equals(
element.Attribute("Name")?.Value,
"ValidateLegacyRuntimeAssets",
StringComparison.Ordinal));
Assert.DoesNotContain(
validationTarget.Elements("Error"),
element => (element.Attribute("Condition")?.Value ?? string.Empty)
.Contains("MmoneyCoder.ini", StringComparison.OrdinalIgnoreCase));
var rejectionTarget = Assert.Single(
project.Descendants("Target"),
@@ -385,16 +375,12 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
StringComparison.Ordinal);
});
var databaseCopy = Assert.Single(
Assert.DoesNotContain(
project.Descendants("Target"),
element => string.Equals(
element.Attribute("Name")?.Value,
"CopyLegacyDatabaseIniToLocalOutput",
StringComparison.Ordinal));
Assert.Contains(
"$(LegacyRuntimeAssetsEnabled)",
databaseCopy.Attribute("Condition")?.Value,
StringComparison.Ordinal);
var mainWindow = File.ReadAllText(Path.Combine(
RepositoryRoot,
@@ -447,7 +433,6 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
"$(LegacyResSourceRoot)",
"$(LegacyCutsSourceRoot)\\%(LegacyRequiredScene.Identity).t2s",
"$(LegacyCutsSourceRoot)\\%(LegacyRequiredBuiltInAsset.Identity)",
"$(LegacyResSourceRoot)\\MmoneyCoder.ini",
"$(LegacyResSourceRoot)\\logo.png",
"$(LegacyResSourceRoot)\\종목.ini",
"$(LegacyResSourceRoot)\\업종_코스피.ini",
@@ -482,17 +467,138 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
Assert.Contains(@"Images\KRX.png", requiredAssets);
Assert.Contains(@"Images\NXT.png", requiredAssets);
var databaseIniError = Assert.Single(
Assert.DoesNotContain(
target.Elements("Error"),
error => (error.Attribute("Condition")?.Value ?? string.Empty)
.Contains("$(LegacyResSourceRoot)\\MmoneyCoder.ini", StringComparison.Ordinal));
.Contains("MmoneyCoder.ini", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void DevelopmentLiveHandoffUsesAnIgnoredVerifiedRuntimeRootAndSafeLocalFiles()
{
var directoryPropsPath = Path.Combine(RepositoryRoot, "Directory.Build.props");
var directoryProps = XDocument.Load(directoryPropsPath);
var localImport = Assert.Single(directoryProps.Descendants("Import"));
Assert.Equal(
"$(MSBuildThisFileDirectory)Directory.Build.local.props",
localImport.Attribute("Project")?.Value);
Assert.Contains(
"GenerateAppxPackageOnBuild",
databaseIniError.Attribute("Condition")?.Value,
"Exists(",
localImport.Attribute("Condition")?.Value,
StringComparison.Ordinal);
var gitIgnore = File.ReadAllLines(Path.Combine(RepositoryRoot, ".gitignore"));
Assert.Contains("Directory.Build.local.props", gitIgnore);
var initializer = File.ReadAllText(Path.Combine(
RepositoryRoot,
"scripts",
"Initialize-DevelopmentLiveConfig.ps1"));
Assert.Contains(
"[ValidateSet('127.0.0.1', '::1')]",
initializer,
StringComparison.Ordinal);
Assert.Contains("mode = 'DryRun'", initializer, StringComparison.Ordinal);
Assert.Contains(
"trustedLiveOutputEnabled = $true",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"PublishAppxPackage",
databaseIniError.Attribute("Condition")?.Value,
"maximumReconnectAttempts = 0",
initializer,
StringComparison.Ordinal);
Assert.Contains("reconnectEnabled = $false", initializer, StringComparison.Ordinal);
Assert.Contains(
"maximumAutomaticRefreshesPerTakeIn = 0",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"I_AUTHORIZE_LIVE_PROGRAM_OUTPUT_FOR_THIS_LAUNCH",
initializer,
StringComparison.Ordinal);
Assert.Contains(
"SetAccessRuleProtection($true, $false)",
initializer,
StringComparison.Ordinal);
var authorizationInvalidation = initializer.IndexOf(
"Invalidate-ExistingAuthorization -Path $authorizationPath",
StringComparison.Ordinal);
var baseConfigurationWrite = initializer.IndexOf(
"Write-ProtectedJson $playoutPath",
StringComparison.Ordinal);
Assert.True(authorizationInvalidation >= 0);
Assert.True(baseConfigurationWrite > authorizationInvalidation);
Assert.Contains(
"Write-ProtectedJson $authorizationPath",
initializer,
StringComparison.Ordinal);
}
[Fact]
public void DevelopmentRuntimeBundleRequiresIndependentHashAndClosedPayloadValidation()
{
var bundleCreator = File.ReadAllText(Path.Combine(
RepositoryRoot,
"scripts",
"New-LegacyRuntimeBundle.ps1"));
var bundleInitializer = File.ReadAllText(Path.Combine(
RepositoryRoot,
"scripts",
"Initialize-LegacyRuntimeBundle.ps1"));
Assert.Contains("$expectedResAssetCount = 34", bundleCreator, StringComparison.Ordinal);
Assert.Contains(
"Get-GitRepositoryAncestor -Path $sourceRoot",
bundleCreator,
StringComparison.Ordinal);
Assert.Contains(
"MmoneyCoder material is forbidden",
bundleCreator,
StringComparison.Ordinal);
Assert.Contains(
"LegacyRuntimeBundle.zip.sha256",
bundleCreator,
StringComparison.Ordinal);
Assert.Contains(
"[string] $ExpectedSha256",
bundleInitializer,
StringComparison.Ordinal);
Assert.Contains(
"ExpectedSha256 must be a separately delivered",
bundleInitializer,
StringComparison.Ordinal);
var archiveHashValidation = bundleInitializer.IndexOf(
"$actualArchiveHash.Equals(",
StringComparison.Ordinal);
var archiveOpen = bundleInitializer.IndexOf(
"New-Object IO.Compression.ZipArchive(",
StringComparison.Ordinal);
Assert.True(archiveHashValidation >= 0);
Assert.True(archiveOpen > archiveHashValidation);
Assert.Contains(
"Archive entry expanded beyond its declared length",
bundleInitializer,
StringComparison.Ordinal);
Assert.Contains(
"$actualCutsFileCount -le 0",
bundleInitializer,
StringComparison.Ordinal);
Assert.Contains(
"[Environment+SpecialFolder]::LocalApplicationData",
bundleInitializer,
StringComparison.Ordinal);
Assert.DoesNotContain(
"Get-NormalizedFullPath -Path $env:LOCALAPPDATA",
bundleInitializer,
StringComparison.Ordinal);
Assert.Contains(
"Existing Directory.Build.local.props is conditional, duplicated",
bundleInitializer,
StringComparison.Ordinal);
Assert.Contains(
"Assert-NoReparsePointInTree",
bundleInitializer,
StringComparison.Ordinal);
}