From 0f0176e1ed4dc31258b30cbd8a4ebb16cfe838b1 Mon Sep 17 00:00:00 2001 From: mleem97 <52848568+mleem97@users.noreply.github.com> Date: Thu, 21 May 2026 13:10:20 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=A7=AA=20Add=20SubDirectoryFixerInsta?= =?UTF-8?q?llerService=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GregModmanager.Tests.csproj | 3 + .../SubDirectoryFixerInstallerServiceTests.cs | 150 ++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 tests/GregModmanager.Tests/SubDirectoryFixerInstallerServiceTests.cs diff --git a/tests/GregModmanager.Tests/GregModmanager.Tests.csproj b/tests/GregModmanager.Tests/GregModmanager.Tests.csproj index 29869cb..c7c27ec 100644 --- a/tests/GregModmanager.Tests/GregModmanager.Tests.csproj +++ b/tests/GregModmanager.Tests/GregModmanager.Tests.csproj @@ -17,6 +17,9 @@ + + + diff --git a/tests/GregModmanager.Tests/SubDirectoryFixerInstallerServiceTests.cs b/tests/GregModmanager.Tests/SubDirectoryFixerInstallerServiceTests.cs new file mode 100644 index 0000000..a3233fc --- /dev/null +++ b/tests/GregModmanager.Tests/SubDirectoryFixerInstallerServiceTests.cs @@ -0,0 +1,150 @@ +using GregModmanager.Avalonia.Services; +using System.Security.Cryptography; + +namespace GregModmanager.Tests; + +public class SubDirectoryFixerInstallerServiceTests : IDisposable +{ + private readonly string _tempGameRoot; + + public SubDirectoryFixerInstallerServiceTests() + { + _tempGameRoot = Path.Combine(Path.GetTempPath(), "GregModmanager_Tests_" + Guid.NewGuid().ToString()); + + // Note: The service uses Path.Combine(AppContext.BaseDirectory, "SubDirectoryFixer\\SubDirectoryFixer.dll"). + // On Linux, Path.Combine does not translate '\' to '/', so it attempts to find a file literally named + // "SubDirectoryFixer\SubDirectoryFixer.dll" in the BaseDirectory. + // We create it here to ensure the tests pass on Linux when run in the CI/headless environment. + var payloadPath = Path.Combine(AppContext.BaseDirectory, "SubDirectoryFixer\\SubDirectoryFixer.dll"); + var dirPath = Path.GetDirectoryName(payloadPath); + if (!string.IsNullOrEmpty(dirPath)) + { + Directory.CreateDirectory(dirPath); + } + if (!File.Exists(payloadPath)) + { + File.WriteAllText(payloadPath, "dummy-payload-content"); + } + } + + public void Dispose() + { + if (Directory.Exists(_tempGameRoot)) + { + try + { + Directory.Delete(_tempGameRoot, true); + } + catch + { + // Ignore cleanup errors + } + } + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public async Task EnsureInstalledAsync_WithInvalidGameRoot_ReturnsSkippedNoGameRoot(string? gameRoot) + { + var result = await SubDirectoryFixerInstallerService.EnsureInstalledAsync(gameRoot); + + Assert.Equal(SubDirectoryFixerInstallStatus.SkippedNoGameRoot, result.Status); + } + + [Fact] + public async Task EnsureInstalledAsync_WithNonExistentGameRoot_ReturnsSkippedNoGameRoot() + { + var result = await SubDirectoryFixerInstallerService.EnsureInstalledAsync(_tempGameRoot); + + Assert.Equal(SubDirectoryFixerInstallStatus.SkippedNoGameRoot, result.Status); + } + + [Fact] + public async Task EnsureInstalledAsync_WithValidGameRoot_InstallsSuccessfully() + { + // Arrange + Directory.CreateDirectory(_tempGameRoot); + + // Act + var result = await SubDirectoryFixerInstallerService.EnsureInstalledAsync(_tempGameRoot); + + // Assert + Assert.Equal(SubDirectoryFixerInstallStatus.Installed, result.Status); + + var pluginsDir = Path.Combine(_tempGameRoot, "Plugins"); + var targetFile = Path.Combine(pluginsDir, "SubDirectoryFixer.dll"); + var markerFile = Path.Combine(pluginsDir, ".gregmodmanager-subdirfixer.sha256"); + + Assert.True(File.Exists(targetFile)); + Assert.True(File.Exists(markerFile)); + } + + [Fact] + public async Task EnsureInstalledAsync_AlreadyInstalled_ReturnsAlreadyInstalled() + { + // Arrange + Directory.CreateDirectory(_tempGameRoot); + + // First install + await SubDirectoryFixerInstallerService.EnsureInstalledAsync(_tempGameRoot); + + // Act - Second install + var result = await SubDirectoryFixerInstallerService.EnsureInstalledAsync(_tempGameRoot); + + // Assert + Assert.Equal(SubDirectoryFixerInstallStatus.AlreadyInstalled, result.Status); + } + + [Fact] + public async Task EnsureInstalledAsync_MarkerHashMismatch_UpdatesSuccessfully() + { + // Arrange + Directory.CreateDirectory(_tempGameRoot); + + // First install + await SubDirectoryFixerInstallerService.EnsureInstalledAsync(_tempGameRoot); + + // Tamper with marker file + var pluginsDir = Path.Combine(_tempGameRoot, "Plugins"); + var markerFile = Path.Combine(pluginsDir, ".gregmodmanager-subdirfixer.sha256"); + + // Make sure Plugins exists in case the first call failed + Directory.CreateDirectory(pluginsDir); + await File.WriteAllTextAsync(markerFile, "invalid-hash"); + + // Act - Update + var result = await SubDirectoryFixerInstallerService.EnsureInstalledAsync(_tempGameRoot); + + // Assert + Assert.Equal(SubDirectoryFixerInstallStatus.Installed, result.Status); + var newMarkerHash = await File.ReadAllTextAsync(markerFile); + Assert.NotEqual("invalid-hash", newMarkerHash); + } + + [Fact] + public async Task EnsureInstalledAsync_FileLocked_ReturnsFailedStatus() + { + // Arrange + Directory.CreateDirectory(_tempGameRoot); + + // Create the directory structure where it will be installed + var pluginsDir = Path.Combine(_tempGameRoot, "Plugins"); + Directory.CreateDirectory(pluginsDir); + + // Create a dummy file and lock it + var targetFile = Path.Combine(pluginsDir, "SubDirectoryFixer.dll"); + await File.WriteAllTextAsync(targetFile, "dummy"); + + // Lock the file for exclusive access + using var stream = new FileStream(targetFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None); + + // Act + var result = await SubDirectoryFixerInstallerService.EnsureInstalledAsync(_tempGameRoot); + + // Assert + Assert.Equal(SubDirectoryFixerInstallStatus.Failed, result.Status); + Assert.Contains("install failed", result.Message, StringComparison.OrdinalIgnoreCase); + } +} From 2a4a634c1405b5c92351083702eac18e69155282 Mon Sep 17 00:00:00 2001 From: mleem97 <52848568+mleem97@users.noreply.github.com> Date: Thu, 21 May 2026 13:36:37 +0000 Subject: [PATCH 2/2] Fix CI build and serialization warnings --- build/scripts/linux/build-avalonia-packages.sh | 2 +- src/GregModmanager.Core/Models/AppJsonContext.cs | 1 + src/GregModmanager.Core/Services/TelemetryService.cs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) mode change 100644 => 100755 build/scripts/linux/build-avalonia-packages.sh diff --git a/build/scripts/linux/build-avalonia-packages.sh b/build/scripts/linux/build-avalonia-packages.sh old mode 100644 new mode 100755 index 6a35814..8a3b868 --- a/build/scripts/linux/build-avalonia-packages.sh +++ b/build/scripts/linux/build-avalonia-packages.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" PROJECT_PATH="$REPO_ROOT/src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj" OUTPUT_ROOT="${1:-$REPO_ROOT/artifacts/avalonia-linux}" VERSION="${2:-1.1.0}" diff --git a/src/GregModmanager.Core/Models/AppJsonContext.cs b/src/GregModmanager.Core/Models/AppJsonContext.cs index d7fe96d..8a3cc3c 100644 --- a/src/GregModmanager.Core/Models/AppJsonContext.cs +++ b/src/GregModmanager.Core/Models/AppJsonContext.cs @@ -34,6 +34,7 @@ namespace GregModmanager.Models; [JsonSerializable(typeof(int))] [JsonSerializable(typeof(RalphTaskStatus))] [JsonSerializable(typeof(AssetModMetadata))] +[JsonSerializable(typeof(object))] public partial class AppJsonContext : JsonSerializerContext { } diff --git a/src/GregModmanager.Core/Services/TelemetryService.cs b/src/GregModmanager.Core/Services/TelemetryService.cs index 8531b66..dc74657 100644 --- a/src/GregModmanager.Core/Services/TelemetryService.cs +++ b/src/GregModmanager.Core/Services/TelemetryService.cs @@ -99,7 +99,7 @@ public async Task TrackEventAsync(string eventName, object payload, Dictionary JsonSerializer.Serialize(sync, AppJsonContext.Default.SyncCollectionEvent), - _ => JsonSerializer.Serialize(payload, payload.GetType(), AppJsonContext.Default.Options) + _ => JsonSerializer.Serialize((object)payload, AppJsonContext.Default.Object) }; await PushToLokiAsync(eventName, message, labels);