diff --git a/scripts/agents/k2_ingested_merge_golden_smoke.sh b/scripts/agents/k2_ingested_merge_golden_smoke.sh new file mode 100755 index 00000000..5d4883fa --- /dev/null +++ b/scripts/agents/k2_ingested_merge_golden_smoke.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Merge neocities K2 Full fixture with golden mod-builds/KOTOR2_Full.toml for download URLs. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT" + +FIXTURE="${FIXTURE:-$ROOT/src/ModSync.Tests/Fixtures/k2_full_guide.md}" +GOLDEN="${GOLDEN:-$ROOT/mod-builds/TOMLs/KOTOR2_Full.toml}" +OUT_DIR="${OUT_DIR:-$ROOT/tmp/k2_merge_golden_smoke}" +MERGED="${MERGED:-$OUT_DIR/k2_merged.toml}" +KOTOR_DIR="${KOTOR_DIR:-$OUT_DIR/kotor}" +MOD_DIR="${MOD_DIR:-$OUT_DIR/mods}" + +usage() { + cat <<'EOF' +Usage: k2_ingested_merge_golden_smoke.sh [options] + +Merges the neocities K2 Full markdown fixture with golden KOTOR2_Full.toml +(existing order; keep golden instructions, options, and download URLs). + +Options: + --fixture PATH Neocities markdown fixture (default: k2_full_guide.md) + --golden PATH Golden TOML (default: ./mod-builds/TOMLs/KOTOR2_Full.toml) + --out-dir PATH Output directory (default: ./tmp/k2_merge_golden_smoke) + -h, --help Show this help +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --fixture) FIXTURE="$2"; shift 2 ;; + --golden) GOLDEN="$2"; shift 2 ;; + --out-dir) OUT_DIR="$2"; MERGED="$OUT_DIR/k2_merged.toml"; KOTOR_DIR="$OUT_DIR/kotor"; MOD_DIR="$OUT_DIR/mods"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;; + esac +done + +if [[ ! -f "$FIXTURE" ]]; then + echo "Fixture not found: $FIXTURE" >&2 + exit 1 +fi + +if [[ ! -f "$GOLDEN" ]]; then + echo "Golden TOML not found: $GOLDEN" >&2 + echo "Clone mod-builds: git clone https://github.com/th3w1zard1/mod-builds ./mod-builds" >&2 + exit 1 +fi + +mkdir -p "$OUT_DIR" +"$ROOT/scripts/agents/create_template_kotor_install.sh" "$KOTOR_DIR" "$MOD_DIR" >/dev/null + +dotnet build "$ROOT/src/ModSync.Tests/ModSync.Tests.csproj" -c Debug -f net9.0 -v q + +echo "==> merge (golden existing + neocities incoming)" +dotnet run --project "$ROOT/src/ModSync.Tests/ModSync.Tests.csproj" -f net9.0 --no-build -- \ + merge --existing "$GOLDEN" --incoming "$FIXTURE" \ + --use-existing-order --prefer-existing-instructions --prefer-existing-options --prefer-existing-modlinks \ + -f toml -o "$MERGED" --plaintext + +echo "==> validate --dry-run-only (structure + VFS; archives may be missing locally)" +dotnet run --project "$ROOT/src/ModSync.Tests/ModSync.Tests.csproj" -f net9.0 --no-build -- \ + validate --input "$MERGED" --game-dir "$KOTOR_DIR" --source-dir "$MOD_DIR" \ + --dry-run-only --errors-only --best-effort || true + +echo "Merge smoke complete." +echo " Merged TOML: $MERGED" +echo " Next: stage/download mods, then install with --select mod:NAME" diff --git a/src/ModSync.Tests/K2FullGuideMergeTests.cs b/src/ModSync.Tests/K2FullGuideMergeTests.cs new file mode 100644 index 00000000..886b1160 --- /dev/null +++ b/src/ModSync.Tests/K2FullGuideMergeTests.cs @@ -0,0 +1,146 @@ +// Copyright 2021-2025 ModSync +// Licensed under the Business Source License 1.1 (BSL 1.1). +// See LICENSE.txt file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using ModSync.Core; +using ModSync.Core.CLI; +using ModSync.Core.Services; + +using NUnit.Framework; + +namespace ModSync.Tests +{ + /// + /// Neocities K2 Full fixture merged with golden mod-builds/KOTOR2_Full.toml should + /// inherit download URLs and authored instructions for round-trip download workflows. + /// + [TestFixture] + public sealed class K2FullGuideMergeTests + { + private static readonly string GoldenTomlRelative = Path.Combine("mod-builds", "TOMLs", "KOTOR2_Full.toml"); + + private static string ResolveRepoRoot() + { + string[] candidates = + { + Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "..", "..")), + Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "..", "..", "..")), + Path.GetFullPath(Environment.CurrentDirectory), + }; + + foreach (string candidate in candidates.Distinct(StringComparer.Ordinal)) + { + if (File.Exists(Path.Combine(candidate, "ModSync.sln"))) + { + return candidate; + } + } + + throw new DirectoryNotFoundException("Could not locate repository root containing ModSync.sln"); + } + + private static (string fixturePath, string goldenTomlPath) ResolveInputs() + { + string repoRoot = ResolveRepoRoot(); + string fixturePath = Path.Combine(repoRoot, "src", "ModSync.Tests", "Fixtures", "k2_full_guide.md"); + string goldenTomlPath = Path.Combine(repoRoot, GoldenTomlRelative); + + if (!File.Exists(fixturePath)) + { + Assert.Fail($"Expected neocities fixture at {fixturePath}"); + } + + if (!File.Exists(goldenTomlPath)) + { + Assert.Ignore($"Golden TOML not found at {goldenTomlPath} (clone mod-builds at repo root for this test)."); + } + + return (fixturePath, goldenTomlPath); + } + + private static List MergeNeocitiesWithGolden(string fixturePath, string goldenTomlPath) + { + var mergeOptions = new MergeOptions + { + UseExistingOrder = true, + PreferExistingInstructions = true, + PreferExistingOptions = true, + PreferExistingResourceRegistry = true, + }; + + return ComponentMergeService.MergeInstructionSets( + goldenTomlPath, + fixturePath, + mergeOptions); + } + + [Test] + public void K2FullGuideFixture_MergeWithGoldenToml_InheritsDownloadUrls() + { + (string fixturePath, string goldenTomlPath) = ResolveInputs(); + + List merged = MergeNeocitiesWithGolden(fixturePath, goldenTomlPath); + + Assert.That(merged.Count, Is.GreaterThanOrEqualTo(100), + "Merged set should retain the golden build component count"); + + ModComponent silentSion = merged.FirstOrDefault(c => + c.Name.IndexOf("Silent Sion Restoration", StringComparison.OrdinalIgnoreCase) >= 0); + Assert.That(silentSion, Is.Not.Null, "Silent Sion should match between neocities fixture and golden TOML"); + + bool hasDownloadLink = silentSion.ResourceRegistry?.Count > 0; + Assert.That(hasDownloadLink, Is.True, + "Merged Silent Sion should keep golden download URLs in ResourceRegistry"); + + int withUrls = merged.Count(c => c.ResourceRegistry?.Count > 0); + Assert.That(withUrls, Is.GreaterThanOrEqualTo(100), + "Most golden K2 components should retain download URLs after neocities merge"); + } + + [Test] + public void K2FullGuideFixture_CliMergeWithGolden_ProducesMergedToml() + { + (string fixturePath, string goldenTomlPath) = ResolveInputs(); + + string outputToml = Path.Combine(Path.GetTempPath(), "ModSync_K2Merge_" + Guid.NewGuid() + ".toml"); + try + { + int exit = ModBuildConverter.Run(new[] + { + "merge", + "--existing", goldenTomlPath, + "--incoming", fixturePath, + "--use-existing-order", + "--prefer-existing-instructions", + "--prefer-existing-options", + "--prefer-existing-modlinks", + "-f", "toml", + "-o", outputToml, + "--plaintext", + }); + + Assert.That(exit, Is.EqualTo(0), "CLI merge neocities fixture + golden TOML should succeed"); + Assert.That(File.Exists(outputToml), Is.True); + + List merged = FileLoadingService.LoadFromFile(outputToml).ToList(); + ModComponent silentSion = merged.First(c => + c.Name.IndexOf("Silent Sion Restoration", StringComparison.OrdinalIgnoreCase) >= 0); + + Assert.That(silentSion.ResourceRegistry?.Count, Is.GreaterThan(0), + "CLI-merged Silent Sion should retain golden download metadata"); + } + finally + { + if (File.Exists(outputToml)) + { + File.Delete(outputToml); + } + } + } + } +}