diff --git a/build/scripts/linux/build-avalonia-packages.sh b/build/scripts/linux/build-avalonia-packages.sh index 6a35814..8a3b868 100644 --- 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/Services/TelemetryService.cs b/src/GregModmanager.Core/Services/TelemetryService.cs index 8531b66..648dded 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(payload, typeof(object), AppJsonContext.Default) }; await PushToLokiAsync(eventName, message, labels); diff --git a/tests/GregModmanager.Tests/Services/UploadDependencyCheckerTests.cs b/tests/GregModmanager.Tests/Services/UploadDependencyCheckerTests.cs new file mode 100644 index 0000000..680f581 --- /dev/null +++ b/tests/GregModmanager.Tests/Services/UploadDependencyCheckerTests.cs @@ -0,0 +1,502 @@ +using GregModmanager.Models; +using GregModmanager.Services; +using GregModmanager.Steam; + +namespace GregModmanager.Tests.Services; + +public class UploadDependencyCheckerTests : IDisposable +{ + private readonly string _tempPath; + + public UploadDependencyCheckerTests() + { + _tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(_tempPath); + } + + public void Dispose() + { + if (Directory.Exists(_tempPath)) + { + try + { + Directory.Delete(_tempPath, true); + } + catch + { + // Ignore cleanup errors in tests + } + } + } + + private WorkshopMetadata CreateValidMetadata() + { + return new WorkshopMetadata + { + Title = "Valid Title", + Description = "Valid Description", + Visibility = "Public", + Tags = ["Tag1"], + PreviewImageRelativePath = "preview.png" + }; + } + + [Fact] + public void Check_MissingContentFolder_ReturnsError() + { + // Arrange + var metadata = CreateValidMetadata(); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Content folder" && r.Severity == UploadCheckSeverity.Error); + Assert.False(UploadDependencyChecker.IsReadyToUpload(results)); + } + + [Fact] + public void Check_EmptyContentFolder_ReturnsError() + { + // Arrange + var metadata = CreateValidMetadata(); + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Content folder" && r.Severity == UploadCheckSeverity.Error); + Assert.False(UploadDependencyChecker.IsReadyToUpload(results)); + } + + [Fact] + public void Check_MissingNativeConfigJson_ReturnsWarning() + { + // Arrange + var metadata = CreateValidMetadata(); + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "config.json" && r.Severity == UploadCheckSeverity.Warning); + } + + [Fact] + public void Check_PresentNativeConfigJson_ReturnsOk() + { + // Arrange + var metadata = CreateValidMetadata(); + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "config.json"), "{}"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "config.json" && r.Severity == UploadCheckSeverity.Ok); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Check_EmptyTitle_ReturnsError(string? emptyTitle) + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.Title = emptyTitle!; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Title" && r.Severity == UploadCheckSeverity.Error); + Assert.False(UploadDependencyChecker.IsReadyToUpload(results)); + } + + [Fact] + public void Check_LongTitle_ReturnsError() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.Title = new string('A', SteamConstants.MaxTitleLength + 1); + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Title" && r.Severity == UploadCheckSeverity.Error); + Assert.False(UploadDependencyChecker.IsReadyToUpload(results)); + } + + [Fact] + public void Check_ValidTitle_ReturnsOk() + { + // Arrange + var metadata = CreateValidMetadata(); + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Title" && r.Severity == UploadCheckSeverity.Ok); + } + + [Fact] + public void Check_EmptyDescription_ReturnsWarning() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.Description = ""; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Description" && r.Severity == UploadCheckSeverity.Warning); + } + + [Fact] + public void Check_LongDescription_ReturnsError() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.Description = new string('A', SteamConstants.MaxDescriptionLength + 1); + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Description" && r.Severity == UploadCheckSeverity.Error); + } + + [Theory] + [InlineData("Public")] + [InlineData("FriendsOnly")] + [InlineData("Private")] + public void Check_ValidVisibility_ReturnsOk(string visibility) + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.Visibility = visibility; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Visibility" && r.Severity == UploadCheckSeverity.Ok); + } + + [Fact] + public void Check_InvalidVisibility_ReturnsWarning() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.Visibility = "InvalidVisibility"; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Visibility" && r.Severity == UploadCheckSeverity.Warning); + } + + [Fact] + public void Check_MissingPreviewImage_ReturnsWarning() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.PreviewImageRelativePath = "preview.png"; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Preview image" && r.Severity == UploadCheckSeverity.Warning); + } + + [Fact] + public void Check_LargePreviewImage_ReturnsWarning() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.PreviewImageRelativePath = "preview.png"; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + var previewPath = Path.Combine(_tempPath, "preview.png"); + // Create a fake large file using sparse file if possible, or just write some data. + using (var fs = new FileStream(previewPath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + fs.SetLength(1_048_576 + 10); + } + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Preview image" && r.Severity == UploadCheckSeverity.Warning); + } + + [Fact] + public void Check_ValidPreviewImage_ReturnsOk() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.PreviewImageRelativePath = "preview.png"; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + var previewPath = Path.Combine(_tempPath, "preview.png"); + File.WriteAllText(previewPath, "fake image content"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Preview image" && r.Severity == UploadCheckSeverity.Ok); + } + + [Fact] + public void Check_EmptyTags_ReturnsWarning() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.Tags = new List(); + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Tags" && r.Severity == UploadCheckSeverity.Warning); + } + + [Fact] + public void Check_ValidTags_ReturnsOk() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.Tags = ["Tag1", "Tag2"]; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "Tags" && r.Severity == UploadCheckSeverity.Ok); + } + + [Fact] + public void Check_FirstPublishEmptyChangelog_ReturnsError() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.PublishedFileId = 0; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata, changeLog: ""); + + // Assert + Assert.Contains(results, r => r.Label == "Changelog" && r.Severity == UploadCheckSeverity.Error); + } + + [Fact] + public void Check_UpdateEmptyChangelog_ReturnsWarning() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.PublishedFileId = 12345; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata, changeLog: ""); + + // Assert + Assert.Contains(results, r => r.Label == "Changelog" && r.Severity == UploadCheckSeverity.Warning); + } + + [Fact] + public void Check_ValidChangelog_ReturnsOk() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.PublishedFileId = 12345; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata, changeLog: "Initial release"); + + // Assert + Assert.Contains(results, r => r.Label == "Changelog" && r.Severity == UploadCheckSeverity.Ok); + } + + [Fact] + public void Check_GregFrameworkDependency_NeedsGregWithoutDescription_ReturnsWarning() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.Needsgreg = true; + metadata.Description = "Some standard mod without mentioning the framework"; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "GregFramework (greg)" && r.Severity == UploadCheckSeverity.Warning); + } + + [Fact] + public void Check_GregFrameworkDependency_NeedsGregWithDescription_ReturnsOk() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.Needsgreg = true; + metadata.Description = "This mod requires gregCoreModFramework to work."; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "GregFramework (greg)" && r.Severity == UploadCheckSeverity.Ok); + } + + [Fact] + public void Check_GregFrameworkDependency_DoesNotNeedGregButMentionsIt_ReturnsWarning() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.Needsgreg = false; + metadata.Description = "This mod is similar to gregCoreModFramework."; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "GregFramework (greg)" && r.Severity == UploadCheckSeverity.Warning); + } + + [Fact] + public void Check_GregFrameworkDependency_DoesNotNeedGregAndNoMentions_ReturnsOk() + { + // Arrange + var metadata = CreateValidMetadata(); + metadata.Needsgreg = false; + metadata.Description = "Standard mod without any mentions."; + + var contentDir = Path.Combine(_tempPath, "content"); + Directory.CreateDirectory(contentDir); + File.WriteAllText(Path.Combine(contentDir, "somefile.txt"), "test"); + + // Act + var results = UploadDependencyChecker.Check(_tempPath, metadata); + + // Assert + Assert.Contains(results, r => r.Label == "GregFramework (greg)" && r.Severity == UploadCheckSeverity.Ok); + } + + [Fact] + public void IsReadyToUpload_NoErrors_ReturnsTrue() + { + // Arrange + var results = new List + { + new UploadCheckResult { Label = "Test", Severity = UploadCheckSeverity.Ok, Detail = "" }, + new UploadCheckResult { Label = "Test", Severity = UploadCheckSeverity.Warning, Detail = "" } + }; + + // Act + var isReady = UploadDependencyChecker.IsReadyToUpload(results); + + // Assert + Assert.True(isReady); + } + + [Fact] + public void IsReadyToUpload_WithErrors_ReturnsFalse() + { + // Arrange + var results = new List + { + new UploadCheckResult { Label = "Test", Severity = UploadCheckSeverity.Ok, Detail = "" }, + new UploadCheckResult { Label = "Test", Severity = UploadCheckSeverity.Error, Detail = "" } + }; + + // Act + var isReady = UploadDependencyChecker.IsReadyToUpload(results); + + // Assert + Assert.False(isReady); + } +}