using LANCommander.SDK.Enums; using LANCommander.SDK.Helpers; using LANCommander.SDK.Models; namespace LANCommander.SDK.Tests.Helpers; public class ScriptHelperTests : IDisposable { private readonly string _tempDir; public ScriptHelperTests() { _tempDir = Path.Combine(Path.GetTempPath(), $"lc-script-tests-{Guid.NewGuid()}"); Directory.CreateDirectory(_tempDir); } public void Dispose() { if (Directory.Exists(_tempDir)) Directory.Delete(_tempDir, true); } private Game MakeGame(params Script[] scripts) => new() { Id = Guid.NewGuid(), Title = "Test Game", InstallDirectory = _tempDir, Scripts = scripts }; private static Script MakeScript(ScriptType type, string contents = "Write-Host 'test'", bool requiresAdmin = false) => new() { Type = type, Name = type.ToString(), Contents = contents, RequiresAdmin = requiresAdmin }; // ── GetScriptFileName ───────────────────────────────────────────────────── [Theory] [InlineData(ScriptType.Install, "Install.ps1")] [InlineData(ScriptType.Uninstall, "Uninstall.ps1")] [InlineData(ScriptType.NameChange, "ChangeName.ps1")] [InlineData(ScriptType.KeyChange, "ChangeKey.ps1")] [InlineData(ScriptType.DetectInstall, "DetectInstall.ps1")] [InlineData(ScriptType.BeforeStart, "BeforeStart.ps1")] [InlineData(ScriptType.AfterStop, "AfterStop.ps1")] public void GetScriptFileName_ReturnsExpectedFilename(ScriptType type, string expected) { var result = ScriptHelper.GetScriptFileName(type); Assert.Equal(expected, result); } // ── GetScriptFilePath ───────────────────────────────────────────────────── [Fact] public void GetScriptFilePath_WithGuidId_ReturnsPathUnderLanCommanderMetadata() { var id = Guid.NewGuid(); var result = ScriptHelper.GetScriptFilePath(_tempDir, id, ScriptType.Install); var expected = Path.Combine(_tempDir, ".lancommander", id.ToString(), "Install.ps1"); Assert.Equal(expected, result); } [Fact] public void GetScriptFilePath_WithStringId_ReturnsCorrectPath() { var result = ScriptHelper.GetScriptFilePath(_tempDir, "my-tool-id", ScriptType.BeforeStart); var expected = Path.Combine(_tempDir, ".lancommander", "my-tool-id", "BeforeStart.ps1"); Assert.Equal(expected, result); } [Theory] [InlineData(ScriptType.Install, "Install.ps1")] [InlineData(ScriptType.Uninstall, "Uninstall.ps1")] [InlineData(ScriptType.AfterStop, "AfterStop.ps1")] public void GetScriptFilePath_FilenameMatchesGetScriptFileName(ScriptType type, string expectedFile) { var result = ScriptHelper.GetScriptFilePath(_tempDir, Guid.NewGuid(), type); Assert.Equal(expectedFile, Path.GetFileName(result)); } // ── GetScriptContents (Game) ────────────────────────────────────────────── [Fact] public void GetScriptContents_WhenRequiresAdmin_PrependsAdminHeader() { var script = MakeScript(ScriptType.Install, "Write-Host 'install'", requiresAdmin: true); var result = ScriptHelper.GetScriptContents(script); Assert.StartsWith("#Requires -RunAsAdministrator", result); Assert.Contains("Write-Host 'install'", result); } [Fact] public void GetScriptContents_WhenDoesNotRequireAdmin_DoesNotPrependHeader() { var script = MakeScript(ScriptType.Install, "Write-Host 'install'", requiresAdmin: false); var result = ScriptHelper.GetScriptContents(script); Assert.DoesNotContain("#Requires -RunAsAdministrator", result); } [Fact] public void GetScriptContents_WhenMultipleScripts_ReturnsCorrectOne() { var installScript = MakeScript(ScriptType.Install, "install contents"); var uninstallScript = MakeScript(ScriptType.Uninstall, "uninstall contents"); Assert.Equal("install contents", ScriptHelper.GetScriptContents(installScript)); Assert.Equal("uninstall contents", ScriptHelper.GetScriptContents(uninstallScript)); } // ── SaveTempScriptAsync (string) ────────────────────────────────────────── [Fact] public async Task SaveTempScriptAsync_String_CreatesPs1File() { var tempPath = await ScriptHelper.SaveTempScriptAsync("Write-Host 'test'"); try { Assert.True(File.Exists(tempPath)); Assert.EndsWith(".ps1", tempPath); } finally { if (File.Exists(tempPath)) File.Delete(tempPath); } } [Fact] public async Task SaveTempScriptAsync_String_WritesExpectedContent() { var contents = "Write-Host 'hello world'"; var tempPath = await ScriptHelper.SaveTempScriptAsync(contents); try { Assert.Equal(contents, await File.ReadAllTextAsync(tempPath)); } finally { if (File.Exists(tempPath)) File.Delete(tempPath); } } [Fact] public async Task SaveTempScriptAsync_String_EachCallCreatesDistinctFile() { var path1 = await ScriptHelper.SaveTempScriptAsync("script 1"); var path2 = await ScriptHelper.SaveTempScriptAsync("script 2"); try { Assert.NotEqual(path1, path2); } finally { if (File.Exists(path1)) File.Delete(path1); if (File.Exists(path2)) File.Delete(path2); } } // ── SaveTempScriptAsync (Script model) ──────────────────────────────────── [Fact] public async Task SaveTempScriptAsync_Script_CreatesPs1FileWithContents() { var script = MakeScript(ScriptType.Install, "Write-Host 'from model'"); var tempPath = await ScriptHelper.SaveTempScriptAsync(script); try { Assert.True(File.Exists(tempPath)); Assert.EndsWith(".ps1", tempPath); Assert.Equal(script.Contents, await File.ReadAllTextAsync(tempPath)); } finally { if (File.Exists(tempPath)) File.Delete(tempPath); } } // ── SaveScriptAsync (Game) ──────────────────────────────────────────────── [Fact] public async Task SaveScriptAsync_Game_WhenScriptExists_WritesFileAtExpectedPath() { var script = MakeScript(ScriptType.Install, "Write-Host 'install'"); var game = MakeGame(script); await ScriptHelper.SaveScriptAsync(game, script, _tempDir); var expectedPath = ScriptHelper.GetScriptFilePath(_tempDir, game.Id, ScriptType.Install); Assert.True(File.Exists(expectedPath)); Assert.Equal("Write-Host 'install'", await File.ReadAllTextAsync(expectedPath)); } [Fact] public async Task SaveScriptAsync_Game_WhenScriptDoesNotExist_DoesNotCreateFile() { var game = MakeGame(); // no scripts await ScriptHelper.SaveScriptAsync(game, null, _tempDir); var path = ScriptHelper.GetScriptFilePath(_tempDir, game.Id, ScriptType.Install); Assert.False(File.Exists(path)); } [Fact] public async Task SaveScriptAsync_Game_CreatesParentDirectoriesAsNeeded() { var script = MakeScript(ScriptType.Uninstall, "Write-Host 'uninstall'"); var game = MakeGame(script); var nestedInstallDir = Path.Combine(_tempDir, "nested", "install"); await ScriptHelper.SaveScriptAsync(game, script, nestedInstallDir); var expectedPath = ScriptHelper.GetScriptFilePath(nestedInstallDir, game.Id, ScriptType.Uninstall); Assert.True(File.Exists(expectedPath)); } [Fact] public async Task SaveScriptAsync_Game_OverwritesExistingFile() { var script = MakeScript(ScriptType.Install, "first"); var game = MakeGame(script); await ScriptHelper.SaveScriptAsync(game, script, _tempDir); // Replace the script on the game object and save again var secondScript = MakeScript(ScriptType.Install, "second"); await ScriptHelper.SaveScriptAsync(game, secondScript, _tempDir); var path = ScriptHelper.GetScriptFilePath(_tempDir, game.Id, ScriptType.Install); Assert.Equal("second", await File.ReadAllTextAsync(path)); } }