From f15e05055df27abeb6b6b6b595a61eebcac70409 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:24:53 +0000 Subject: [PATCH 1/3] Initial plan From ec7251d0f3eaca756e250f1794f3b3f10319da00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:28:27 +0000 Subject: [PATCH 2/3] Add comprehensive test cases for GeneralUpdate.Extension core functions Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --- .../ExtensionTest/DependencyResolverTests.cs | 406 ++++++++++++++ src/c#/ExtensionTest/ExtensionCatalogTests.cs | 281 ++++++++++ .../GeneralExtensionHostTests.cs | 528 ++++++++++++++++++ src/c#/ExtensionTest/PlatformMatcherTests.cs | 267 +++++++++ .../VersionCompatibilityCheckerTests.cs | 327 +++++++++++ 5 files changed, 1809 insertions(+) create mode 100644 src/c#/ExtensionTest/DependencyResolverTests.cs create mode 100644 src/c#/ExtensionTest/ExtensionCatalogTests.cs create mode 100644 src/c#/ExtensionTest/GeneralExtensionHostTests.cs create mode 100644 src/c#/ExtensionTest/PlatformMatcherTests.cs create mode 100644 src/c#/ExtensionTest/VersionCompatibilityCheckerTests.cs diff --git a/src/c#/ExtensionTest/DependencyResolverTests.cs b/src/c#/ExtensionTest/DependencyResolverTests.cs new file mode 100644 index 00000000..c3ff023e --- /dev/null +++ b/src/c#/ExtensionTest/DependencyResolverTests.cs @@ -0,0 +1,406 @@ +using GeneralUpdate.Extension.Catalog; +using GeneralUpdate.Extension.Common.Models; +using GeneralUpdate.Extension.Dependencies; +using Moq; + +namespace ExtensionTest; + +public class DependencyResolverTests +{ + [Fact] + public void ResolveDependencies_ShouldReturnEmpty_WhenNoDependencies() + { + // Arrange + var catalogMock = new Mock(); + var extension = new ExtensionMetadata + { + Id = "ext1", + Dependencies = null + }; + catalogMock.Setup(c => c.GetInstalledExtensionById("ext1")) + .Returns(extension); + + var resolver = new DependencyResolver(catalogMock.Object); + + // Act + var result = resolver.ResolveDependencies(extension); + + // Assert + Assert.Single(result); + Assert.Equal("ext1", result[0]); + } + + [Fact] + public void ResolveDependencies_ShouldReturnEmpty_WhenDependenciesEmpty() + { + // Arrange + var catalogMock = new Mock(); + var extension = new ExtensionMetadata + { + Id = "ext1", + Dependencies = "" + }; + catalogMock.Setup(c => c.GetInstalledExtensionById("ext1")) + .Returns(extension); + + var resolver = new DependencyResolver(catalogMock.Object); + + // Act + var result = resolver.ResolveDependencies(extension); + + // Assert + Assert.Single(result); + Assert.Equal("ext1", result[0]); + } + + [Fact] + public void ResolveDependencies_ShouldReturnSingleDependency() + { + // Arrange + var catalogMock = new Mock(); + var dep1 = new ExtensionMetadata + { + Id = "dep1", + Dependencies = null + }; + var extension = new ExtensionMetadata + { + Id = "ext1", + Dependencies = "dep1" + }; + + catalogMock.Setup(c => c.GetInstalledExtensionById("ext1")) + .Returns(extension); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep1")) + .Returns(dep1); + + var resolver = new DependencyResolver(catalogMock.Object); + + // Act + var result = resolver.ResolveDependencies(extension); + + // Assert + Assert.Equal(2, result.Count); + Assert.Equal("dep1", result[0]); + Assert.Equal("ext1", result[1]); + } + + [Fact] + public void ResolveDependencies_ShouldReturnMultipleDependencies() + { + // Arrange + var catalogMock = new Mock(); + var dep1 = new ExtensionMetadata + { + Id = "dep1", + Dependencies = null + }; + var dep2 = new ExtensionMetadata + { + Id = "dep2", + Dependencies = null + }; + var extension = new ExtensionMetadata + { + Id = "ext1", + Dependencies = "dep1,dep2" + }; + + catalogMock.Setup(c => c.GetInstalledExtensionById("ext1")) + .Returns(extension); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep1")) + .Returns(dep1); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep2")) + .Returns(dep2); + + var resolver = new DependencyResolver(catalogMock.Object); + + // Act + var result = resolver.ResolveDependencies(extension); + + // Assert + Assert.Equal(3, result.Count); + Assert.Contains("dep1", result); + Assert.Contains("dep2", result); + Assert.Equal("ext1", result[2]); + } + + [Fact] + public void ResolveDependencies_ShouldHandleNestedDependencies() + { + // Arrange + var catalogMock = new Mock(); + var dep2 = new ExtensionMetadata + { + Id = "dep2", + Dependencies = null + }; + var dep1 = new ExtensionMetadata + { + Id = "dep1", + Dependencies = "dep2" + }; + var extension = new ExtensionMetadata + { + Id = "ext1", + Dependencies = "dep1" + }; + + catalogMock.Setup(c => c.GetInstalledExtensionById("ext1")) + .Returns(extension); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep1")) + .Returns(dep1); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep2")) + .Returns(dep2); + + var resolver = new DependencyResolver(catalogMock.Object); + + // Act + var result = resolver.ResolveDependencies(extension); + + // Assert + Assert.Equal(3, result.Count); + Assert.Equal("dep2", result[0]); + Assert.Equal("dep1", result[1]); + Assert.Equal("ext1", result[2]); + } + + [Fact] + public void ResolveDependencies_ShouldHandleSharedDependencies() + { + // Arrange + var catalogMock = new Mock(); + var depShared = new ExtensionMetadata + { + Id = "shared", + Dependencies = null + }; + var dep1 = new ExtensionMetadata + { + Id = "dep1", + Dependencies = "shared" + }; + var dep2 = new ExtensionMetadata + { + Id = "dep2", + Dependencies = "shared" + }; + var extension = new ExtensionMetadata + { + Id = "ext1", + Dependencies = "dep1,dep2" + }; + + catalogMock.Setup(c => c.GetInstalledExtensionById("ext1")) + .Returns(extension); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep1")) + .Returns(dep1); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep2")) + .Returns(dep2); + catalogMock.Setup(c => c.GetInstalledExtensionById("shared")) + .Returns(depShared); + + var resolver = new DependencyResolver(catalogMock.Object); + + // Act + var result = resolver.ResolveDependencies(extension); + + // Assert + Assert.Equal(4, result.Count); + Assert.Equal("shared", result[0]); + Assert.Contains("dep1", result); + Assert.Contains("dep2", result); + Assert.Equal("ext1", result[3]); + } + + [Fact] + public void ResolveDependencies_ShouldThrow_WhenCircularDependency() + { + // Arrange + var catalogMock = new Mock(); + var dep1 = new ExtensionMetadata + { + Id = "dep1", + Dependencies = "dep2" + }; + var dep2 = new ExtensionMetadata + { + Id = "dep2", + Dependencies = "dep1" + }; + + catalogMock.Setup(c => c.GetInstalledExtensionById("dep1")) + .Returns(dep1); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep2")) + .Returns(dep2); + + var resolver = new DependencyResolver(catalogMock.Object); + + // Act & Assert + Assert.Throws(() => resolver.ResolveDependencies(dep1)); + } + + [Fact] + public void ResolveDependencies_ShouldHandleWhitespaceInDependencies() + { + // Arrange + var catalogMock = new Mock(); + var dep1 = new ExtensionMetadata + { + Id = "dep1", + Dependencies = null + }; + var dep2 = new ExtensionMetadata + { + Id = "dep2", + Dependencies = null + }; + var extension = new ExtensionMetadata + { + Id = "ext1", + Dependencies = " dep1 , dep2 " + }; + + catalogMock.Setup(c => c.GetInstalledExtensionById("ext1")) + .Returns(extension); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep1")) + .Returns(dep1); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep2")) + .Returns(dep2); + + var resolver = new DependencyResolver(catalogMock.Object); + + // Act + var result = resolver.ResolveDependencies(extension); + + // Assert + Assert.Equal(3, result.Count); + Assert.Contains("dep1", result); + Assert.Contains("dep2", result); + } + + [Fact] + public void GetMissingDependencies_ShouldReturnEmpty_WhenAllInstalled() + { + // Arrange + var catalogMock = new Mock(); + var dep1 = new ExtensionMetadata + { + Id = "dep1", + Dependencies = null + }; + var extension = new ExtensionMetadata + { + Id = "ext1", + Dependencies = "dep1" + }; + + catalogMock.Setup(c => c.GetInstalledExtensionById("ext1")) + .Returns(extension); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep1")) + .Returns(dep1); + + var resolver = new DependencyResolver(catalogMock.Object); + + // Act + var result = resolver.GetMissingDependencies(extension); + + // Assert + Assert.Empty(result); + } + + [Fact] + public void GetMissingDependencies_ShouldReturnMissing_WhenNotInstalled() + { + // Arrange + var catalogMock = new Mock(); + var extension = new ExtensionMetadata + { + Id = "ext1", + Dependencies = "dep1" + }; + + catalogMock.Setup(c => c.GetInstalledExtensionById("ext1")) + .Returns(extension); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep1")) + .Returns((ExtensionMetadata?)null); + + var resolver = new DependencyResolver(catalogMock.Object); + + // Act + var result = resolver.GetMissingDependencies(extension); + + // Assert + Assert.Single(result); + Assert.Equal("dep1", result[0]); + } + + [Fact] + public void GetMissingDependencies_ShouldReturnAllMissing_WhenMultipleMissing() + { + // Arrange + var catalogMock = new Mock(); + var dep1 = new ExtensionMetadata + { + Id = "dep1", + Dependencies = null + }; + var extension = new ExtensionMetadata + { + Id = "ext1", + Dependencies = "dep1,dep2,dep3" + }; + + catalogMock.Setup(c => c.GetInstalledExtensionById("ext1")) + .Returns(extension); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep1")) + .Returns(dep1); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep2")) + .Returns((ExtensionMetadata?)null); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep3")) + .Returns((ExtensionMetadata?)null); + + var resolver = new DependencyResolver(catalogMock.Object); + + // Act + var result = resolver.GetMissingDependencies(extension); + + // Assert + Assert.Equal(2, result.Count); + Assert.Contains("dep2", result); + Assert.Contains("dep3", result); + } + + [Fact] + public void GetMissingDependencies_ShouldHandleNestedMissing() + { + // Arrange + var catalogMock = new Mock(); + var dep1 = new ExtensionMetadata + { + Id = "dep1", + Dependencies = "dep2" + }; + var extension = new ExtensionMetadata + { + Id = "ext1", + Dependencies = "dep1" + }; + + catalogMock.Setup(c => c.GetInstalledExtensionById("ext1")) + .Returns(extension); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep1")) + .Returns(dep1); + catalogMock.Setup(c => c.GetInstalledExtensionById("dep2")) + .Returns((ExtensionMetadata?)null); + + var resolver = new DependencyResolver(catalogMock.Object); + + // Act + var result = resolver.GetMissingDependencies(extension); + + // Assert + Assert.Single(result); + Assert.Equal("dep2", result[0]); + } +} diff --git a/src/c#/ExtensionTest/ExtensionCatalogTests.cs b/src/c#/ExtensionTest/ExtensionCatalogTests.cs new file mode 100644 index 00000000..829297dd --- /dev/null +++ b/src/c#/ExtensionTest/ExtensionCatalogTests.cs @@ -0,0 +1,281 @@ +using GeneralUpdate.Extension.Catalog; +using GeneralUpdate.Extension.Common.Enums; +using GeneralUpdate.Extension.Common.Models; +using Newtonsoft.Json; + +namespace ExtensionTest; + +public class ExtensionCatalogTests : IDisposable +{ + private readonly string _testCatalogPath; + + public ExtensionCatalogTests() + { + _testCatalogPath = Path.Combine(Path.GetTempPath(), $"ExtensionTest_{Guid.NewGuid()}"); + Directory.CreateDirectory(_testCatalogPath); + } + + public void Dispose() + { + if (Directory.Exists(_testCatalogPath)) + { + Directory.Delete(_testCatalogPath, true); + } + } + + [Fact] + public void Constructor_ShouldInitializeCatalog() + { + // Arrange & Act + var catalog = new ExtensionCatalog(_testCatalogPath); + + // Assert + Assert.NotNull(catalog); + } + + [Fact] + public void LoadInstalledExtensions_ShouldLoadFromDirectory() + { + // Arrange + var catalog = new ExtensionCatalog(_testCatalogPath); + var extension = CreateTestExtension("ext1", "TestExtension"); + CreateManifestFile(extension); + + // Act + catalog.LoadInstalledExtensions(); + var extensions = catalog.GetInstalledExtensions(); + + // Assert + Assert.Single(extensions); + Assert.Equal("ext1", extensions[0].Id); + Assert.Equal("TestExtension", extensions[0].Name); + } + + [Fact] + public void LoadInstalledExtensions_ShouldSkipBackupDirectory() + { + // Arrange + var catalog = new ExtensionCatalog(_testCatalogPath); + var extension = CreateTestExtension("ext1", "TestExtension"); + CreateManifestFile(extension); + + // Create a backup directory that should be skipped + var backupDir = Path.Combine(_testCatalogPath, ".backup"); + Directory.CreateDirectory(backupDir); + var backupExtension = CreateTestExtension("backup1", "BackupExtension"); + var backupManifest = Path.Combine(backupDir, "manifest.json"); + File.WriteAllText(backupManifest, JsonConvert.SerializeObject(backupExtension)); + + // Act + catalog.LoadInstalledExtensions(); + var extensions = catalog.GetInstalledExtensions(); + + // Assert + Assert.Single(extensions); + Assert.Equal("ext1", extensions[0].Id); + } + + [Fact] + public void GetInstalledExtensions_ShouldReturnAllExtensions() + { + // Arrange + var catalog = new ExtensionCatalog(_testCatalogPath); + var ext1 = CreateTestExtension("ext1", "Extension1"); + var ext2 = CreateTestExtension("ext2", "Extension2"); + CreateManifestFile(ext1); + CreateManifestFile(ext2); + catalog.LoadInstalledExtensions(); + + // Act + var extensions = catalog.GetInstalledExtensions(); + + // Assert + Assert.Equal(2, extensions.Count); + Assert.Contains(extensions, e => e.Id == "ext1"); + Assert.Contains(extensions, e => e.Id == "ext2"); + } + + [Fact] + public void GetInstalledExtensionsByPlatform_ShouldFilterByPlatform() + { + // Arrange + var catalog = new ExtensionCatalog(_testCatalogPath); + var ext1 = CreateTestExtension("ext1", "WindowsExt", TargetPlatform.Windows); + var ext2 = CreateTestExtension("ext2", "LinuxExt", TargetPlatform.Linux); + var ext3 = CreateTestExtension("ext3", "AllExt", TargetPlatform.All); + CreateManifestFile(ext1); + CreateManifestFile(ext2); + CreateManifestFile(ext3); + catalog.LoadInstalledExtensions(); + + // Act + var windowsExtensions = catalog.GetInstalledExtensionsByPlatform(TargetPlatform.Windows); + + // Assert + Assert.Equal(2, windowsExtensions.Count); + Assert.Contains(windowsExtensions, e => e.Id == "ext1"); + Assert.Contains(windowsExtensions, e => e.Id == "ext3"); + } + + [Fact] + public void GetInstalledExtensionById_ShouldReturnExtension() + { + // Arrange + var catalog = new ExtensionCatalog(_testCatalogPath); + var extension = CreateTestExtension("ext1", "TestExtension"); + CreateManifestFile(extension); + catalog.LoadInstalledExtensions(); + + // Act + var result = catalog.GetInstalledExtensionById("ext1"); + + // Assert + Assert.NotNull(result); + Assert.Equal("ext1", result.Id); + Assert.Equal("TestExtension", result.Name); + } + + [Fact] + public void GetInstalledExtensionById_ShouldReturnNullForNonExistent() + { + // Arrange + var catalog = new ExtensionCatalog(_testCatalogPath); + catalog.LoadInstalledExtensions(); + + // Act + var result = catalog.GetInstalledExtensionById("nonexistent"); + + // Assert + Assert.Null(result); + } + + [Fact] + public void AddOrUpdateInstalledExtension_ShouldAddNewExtension() + { + // Arrange + var catalog = new ExtensionCatalog(_testCatalogPath); + var extension = CreateTestExtension("ext1", "TestExtension"); + + // Act + catalog.AddOrUpdateInstalledExtension(extension); + var result = catalog.GetInstalledExtensionById("ext1"); + + // Assert + Assert.NotNull(result); + Assert.Equal("ext1", result.Id); + } + + [Fact] + public void AddOrUpdateInstalledExtension_ShouldUpdateExistingExtension() + { + // Arrange + var catalog = new ExtensionCatalog(_testCatalogPath); + var extension = CreateTestExtension("ext1", "TestExtension"); + catalog.AddOrUpdateInstalledExtension(extension); + + // Act + extension.Version = "2.0.0"; + catalog.AddOrUpdateInstalledExtension(extension); + var result = catalog.GetInstalledExtensionById("ext1"); + + // Assert + Assert.NotNull(result); + Assert.Equal("2.0.0", result.Version); + } + + [Fact] + public void AddOrUpdateInstalledExtension_ShouldCreateManifestFile() + { + // Arrange + var catalog = new ExtensionCatalog(_testCatalogPath); + var extension = CreateTestExtension("ext1", "TestExtension"); + + // Act + catalog.AddOrUpdateInstalledExtension(extension); + var manifestPath = Path.Combine(_testCatalogPath, "TestExtension", "manifest.json"); + + // Assert + Assert.True(File.Exists(manifestPath)); + } + + [Fact] + public void RemoveInstalledExtension_ShouldRemoveExtension() + { + // Arrange + var catalog = new ExtensionCatalog(_testCatalogPath); + var extension = CreateTestExtension("ext1", "TestExtension"); + catalog.AddOrUpdateInstalledExtension(extension); + + // Act + catalog.RemoveInstalledExtension("ext1"); + var result = catalog.GetInstalledExtensionById("ext1"); + + // Assert + Assert.Null(result); + } + + [Fact] + public void RemoveInstalledExtension_ShouldRemoveDirectory() + { + // Arrange + var catalog = new ExtensionCatalog(_testCatalogPath); + var extension = CreateTestExtension("ext1", "TestExtension"); + catalog.AddOrUpdateInstalledExtension(extension); + var extensionDir = Path.Combine(_testCatalogPath, "TestExtension"); + + // Act + catalog.RemoveInstalledExtension("ext1"); + + // Assert + Assert.False(Directory.Exists(extensionDir)); + } + + [Fact] + public void RemoveInstalledExtension_ShouldNotThrowForNonExistent() + { + // Arrange + var catalog = new ExtensionCatalog(_testCatalogPath); + + // Act & Assert + var exception = Record.Exception(() => catalog.RemoveInstalledExtension("nonexistent")); + Assert.Null(exception); + } + + [Fact] + public void LoadInstalledExtensions_ShouldHandleNonExistentDirectory() + { + // Arrange + var nonExistentPath = Path.Combine(Path.GetTempPath(), $"NonExistent_{Guid.NewGuid()}"); + var catalog = new ExtensionCatalog(nonExistentPath); + + // Act & Assert + var exception = Record.Exception(() => catalog.LoadInstalledExtensions()); + Assert.Null(exception); + } + + private ExtensionMetadata CreateTestExtension( + string id, + string name, + TargetPlatform platform = TargetPlatform.All) + { + return new ExtensionMetadata + { + Id = id, + Name = name, + DisplayName = name, + Version = "1.0.0", + Description = $"Test extension {name}", + SupportedPlatforms = platform, + Status = true + }; + } + + private void CreateManifestFile(ExtensionMetadata extension) + { + var extensionDir = Path.Combine(_testCatalogPath, extension.Name!); + Directory.CreateDirectory(extensionDir); + var manifestPath = Path.Combine(extensionDir, "manifest.json"); + var json = JsonConvert.SerializeObject(extension, Formatting.Indented); + File.WriteAllText(manifestPath, json); + } +} diff --git a/src/c#/ExtensionTest/GeneralExtensionHostTests.cs b/src/c#/ExtensionTest/GeneralExtensionHostTests.cs new file mode 100644 index 00000000..d5380c88 --- /dev/null +++ b/src/c#/ExtensionTest/GeneralExtensionHostTests.cs @@ -0,0 +1,528 @@ +using GeneralUpdate.Extension.Catalog; +using GeneralUpdate.Extension.Compatibility; +using GeneralUpdate.Extension.Communication; +using GeneralUpdate.Extension.Common.Enums; +using GeneralUpdate.Extension.Common.Models; +using GeneralUpdate.Extension.Core; +using GeneralUpdate.Extension.Dependencies; +using GeneralUpdate.Extension.Download; +using Moq; + +namespace ExtensionTest; + +public class GeneralExtensionHostTests : IDisposable +{ + private readonly string _testExtensionsDirectory; + private readonly Mock _httpClientMock; + private readonly Mock _catalogMock; + private readonly Mock _compatibilityCheckerMock; + private readonly Mock _downloadQueueMock; + private readonly Mock _dependencyResolverMock; + private readonly Mock _platformMatcherMock; + + public GeneralExtensionHostTests() + { + _testExtensionsDirectory = Path.Combine(Path.GetTempPath(), $"ExtHostTest_{Guid.NewGuid()}"); + _httpClientMock = new Mock(); + _catalogMock = new Mock(); + _compatibilityCheckerMock = new Mock(); + _downloadQueueMock = new Mock(); + _dependencyResolverMock = new Mock(); + _platformMatcherMock = new Mock(); + } + + public void Dispose() + { + if (Directory.Exists(_testExtensionsDirectory)) + { + Directory.Delete(_testExtensionsDirectory, true); + } + } + + [Fact] + public void Constructor_ShouldThrow_WhenOptionsNull() + { + // Act & Assert + Assert.Throws(() => new GeneralExtensionHost( + null!, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + )); + } + + [Fact] + public void Constructor_ShouldThrow_WhenHttpClientNull() + { + // Arrange + var options = CreateTestOptions(); + + // Act & Assert + Assert.Throws(() => new GeneralExtensionHost( + options, + null!, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + )); + } + + [Fact] + public void Constructor_ShouldThrow_WhenCatalogNull() + { + // Arrange + var options = CreateTestOptions(); + + // Act & Assert + Assert.Throws(() => new GeneralExtensionHost( + options, + _httpClientMock.Object, + null!, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + )); + } + + [Fact] + public void Constructor_ShouldThrow_WhenCompatibilityCheckerNull() + { + // Arrange + var options = CreateTestOptions(); + + // Act & Assert + Assert.Throws(() => new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + null!, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + )); + } + + [Fact] + public void Constructor_ShouldThrow_WhenDownloadQueueNull() + { + // Arrange + var options = CreateTestOptions(); + + // Act & Assert + Assert.Throws(() => new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + null!, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + )); + } + + [Fact] + public void Constructor_ShouldThrow_WhenPlatformMatcherNull() + { + // Arrange + var options = CreateTestOptions(); + + // Act & Assert + Assert.Throws(() => new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + null! + )); + } + + [Fact] + public void Constructor_ShouldInitialize_WithValidParameters() + { + // Arrange + var options = CreateTestOptions(); + + // Act + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + // Assert + Assert.NotNull(host); + Assert.NotNull(host.ExtensionCatalog); + } + + [Fact] + public void Constructor_ShouldLoadInstalledExtensions() + { + // Arrange + var options = CreateTestOptions(); + + // Act + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + // Assert + _catalogMock.Verify(c => c.LoadInstalledExtensions(), Times.Once); + } + + [Fact] + public void Constructor_ShouldCreateExtensionsDirectory() + { + // Arrange + var options = CreateTestOptions(); + + // Act + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + // Assert + Assert.True(Directory.Exists(_testExtensionsDirectory)); + } + + [Fact] + public void Constructor_ShouldCreateBackupDirectory() + { + // Arrange + var options = CreateTestOptions(); + + // Act + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + // Assert + var backupDir = Path.Combine(_testExtensionsDirectory, ".backup"); + Assert.True(Directory.Exists(backupDir)); + } + + [Fact] + public void IsExtensionCompatible_ShouldCallCompatibilityChecker() + { + // Arrange + var options = CreateTestOptions(); + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + var extension = new ExtensionMetadata + { + Id = "ext1", + MinHostVersion = "1.0.0" + }; + + _compatibilityCheckerMock.Setup(c => c.IsCompatible(extension, "1.0.0")) + .Returns(true); + + // Act + var result = host.IsExtensionCompatible(extension); + + // Assert + _compatibilityCheckerMock.Verify(c => c.IsCompatible(extension, "1.0.0"), Times.Once); + } + + [Fact] + public void IsExtensionCompatible_ShouldReturnTrue_WhenCompatible() + { + // Arrange + var options = CreateTestOptions(); + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + var extension = new ExtensionMetadata + { + Id = "ext1", + MinHostVersion = "1.0.0" + }; + + _compatibilityCheckerMock.Setup(c => c.IsCompatible(extension, "1.0.0")) + .Returns(true); + + // Act + var result = host.IsExtensionCompatible(extension); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsExtensionCompatible_ShouldReturnFalse_WhenNotCompatible() + { + // Arrange + var options = CreateTestOptions(); + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + var extension = new ExtensionMetadata + { + Id = "ext1", + MinHostVersion = "2.0.0" + }; + + _compatibilityCheckerMock.Setup(c => c.IsCompatible(extension, "1.0.0")) + .Returns(false); + + // Act + var result = host.IsExtensionCompatible(extension); + + // Assert + Assert.False(result); + } + + [Fact] + public void SetAutoUpdate_ShouldEnableAutoUpdate() + { + // Arrange + var options = CreateTestOptions(); + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + // Act + host.SetAutoUpdate("ext1", true); + + // Assert + Assert.True(host.IsAutoUpdateEnabled("ext1")); + } + + [Fact] + public void SetAutoUpdate_ShouldDisableAutoUpdate() + { + // Arrange + var options = CreateTestOptions(); + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + // Act + host.SetAutoUpdate("ext1", true); + host.SetAutoUpdate("ext1", false); + + // Assert + Assert.False(host.IsAutoUpdateEnabled("ext1")); + } + + [Fact] + public void SetGlobalAutoUpdate_ShouldEnableGlobalAutoUpdate() + { + // Arrange + var options = CreateTestOptions(); + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + // Act + host.SetGlobalAutoUpdate(true); + + // Assert - Extension without specific setting should follow global setting + Assert.True(host.IsAutoUpdateEnabled("any-extension")); + } + + [Fact] + public void SetGlobalAutoUpdate_ShouldDisableGlobalAutoUpdate() + { + // Arrange + var options = CreateTestOptions(); + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + // Act + host.SetGlobalAutoUpdate(true); + host.SetGlobalAutoUpdate(false); + + // Assert + Assert.False(host.IsAutoUpdateEnabled("any-extension")); + } + + [Fact] + public void IsAutoUpdateEnabled_ShouldReturnExtensionSpecificSetting() + { + // Arrange + var options = CreateTestOptions(); + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + // Act + host.SetGlobalAutoUpdate(false); + host.SetAutoUpdate("ext1", true); + + // Assert + Assert.True(host.IsAutoUpdateEnabled("ext1")); + Assert.False(host.IsAutoUpdateEnabled("ext2")); + } + + [Fact] + public async Task InstallExtensionAsync_ShouldReturnFalse_WhenFileNotFound() + { + // Arrange + var options = CreateTestOptions(); + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + var nonExistentPath = Path.Combine(_testExtensionsDirectory, "nonexistent.zip"); + + // Act + var result = await host.InstallExtensionAsync(nonExistentPath); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task InstallExtensionAsync_ShouldReturnFalse_WhenNotZipFile() + { + // Arrange + var options = CreateTestOptions(); + var host = new GeneralExtensionHost( + options, + _httpClientMock.Object, + _catalogMock.Object, + _compatibilityCheckerMock.Object, + _downloadQueueMock.Object, + _dependencyResolverMock.Object, + _platformMatcherMock.Object + ); + + var txtFilePath = Path.Combine(_testExtensionsDirectory, "test.txt"); + File.WriteAllText(txtFilePath, "test"); + + // Act + var result = await host.InstallExtensionAsync(txtFilePath); + + // Assert + Assert.False(result); + } + + [Fact] + public void LegacyConstructor_ShouldInitialize_WithValidOptions() + { + // Arrange + var options = new ExtensionHostOptions + { + HostVersion = "1.0.0", + ExtensionsDirectory = _testExtensionsDirectory, + ServerUrl = "https://example.com", + Scheme = "Bearer", + Token = "test-token" + }; + + // Act + var host = new GeneralExtensionHost(options); + + // Assert + Assert.NotNull(host); + Assert.NotNull(host.ExtensionCatalog); + } + + [Fact] + public void LegacyConstructor_ShouldThrow_WhenOptionsNull() + { + // Act & Assert + Assert.Throws(() => new GeneralExtensionHost(null!)); + } + + private ExtensionHostOptions CreateTestOptions() + { + return new ExtensionHostOptions + { + HostVersion = "1.0.0", + ExtensionsDirectory = _testExtensionsDirectory, + ServerUrl = "https://example.com", + Scheme = "Bearer", + Token = "test-token" + }; + } +} diff --git a/src/c#/ExtensionTest/PlatformMatcherTests.cs b/src/c#/ExtensionTest/PlatformMatcherTests.cs new file mode 100644 index 00000000..0a326583 --- /dev/null +++ b/src/c#/ExtensionTest/PlatformMatcherTests.cs @@ -0,0 +1,267 @@ +using GeneralUpdate.Extension.Compatibility; +using GeneralUpdate.Extension.Common.Enums; +using GeneralUpdate.Extension.Common.Models; +using System.Runtime.InteropServices; + +namespace ExtensionTest; + +public class PlatformMatcherTests +{ + private readonly PlatformMatcher _matcher; + + public PlatformMatcherTests() + { + _matcher = new PlatformMatcher(); + } + + [Fact] + public void GetCurrentPlatform_ShouldReturnValidPlatform() + { + // Act + var platform = _matcher.GetCurrentPlatform(); + + // Assert + Assert.NotEqual(TargetPlatform.None, platform); + Assert.True( + platform == TargetPlatform.Windows || + platform == TargetPlatform.Linux || + platform == TargetPlatform.MacOS + ); + } + + [Fact] + public void GetCurrentPlatform_ShouldMatchRuntimeInformation() + { + // Act + var platform = _matcher.GetCurrentPlatform(); + + // Assert + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Equal(TargetPlatform.Windows, platform); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Assert.Equal(TargetPlatform.Linux, platform); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Assert.Equal(TargetPlatform.MacOS, platform); + } + } + + [Fact] + public void IsCurrentPlatformSupported_ShouldReturnTrue_WhenAllPlatformsSupported() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + SupportedPlatforms = TargetPlatform.All + }; + + // Act + var result = _matcher.IsCurrentPlatformSupported(extension); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsCurrentPlatformSupported_ShouldReturnTrue_WhenCurrentPlatformSupported() + { + // Arrange + var currentPlatform = _matcher.GetCurrentPlatform(); + var extension = new ExtensionMetadata + { + Id = "ext1", + SupportedPlatforms = currentPlatform + }; + + // Act + var result = _matcher.IsCurrentPlatformSupported(extension); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsCurrentPlatformSupported_ShouldReturnFalse_WhenCurrentPlatformNotSupported() + { + // Arrange + var currentPlatform = _matcher.GetCurrentPlatform(); + var otherPlatform = GetOtherPlatform(currentPlatform); + var extension = new ExtensionMetadata + { + Id = "ext1", + SupportedPlatforms = otherPlatform + }; + + // Act + var result = _matcher.IsCurrentPlatformSupported(extension); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsCurrentPlatformSupported_ShouldReturnFalse_WhenNoPlatformSupported() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + SupportedPlatforms = TargetPlatform.None + }; + + // Act + var result = _matcher.IsCurrentPlatformSupported(extension); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsPlatformSupported_ShouldReturnTrue_WhenWindowsSupported() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + SupportedPlatforms = TargetPlatform.Windows + }; + + // Act + var result = _matcher.IsPlatformSupported(extension, TargetPlatform.Windows); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsPlatformSupported_ShouldReturnTrue_WhenLinuxSupported() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + SupportedPlatforms = TargetPlatform.Linux + }; + + // Act + var result = _matcher.IsPlatformSupported(extension, TargetPlatform.Linux); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsPlatformSupported_ShouldReturnTrue_WhenMacOSSupported() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + SupportedPlatforms = TargetPlatform.MacOS + }; + + // Act + var result = _matcher.IsPlatformSupported(extension, TargetPlatform.MacOS); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsPlatformSupported_ShouldReturnTrue_WhenMultiplePlatformsSupported() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + SupportedPlatforms = TargetPlatform.Windows | TargetPlatform.Linux + }; + + // Act + var result1 = _matcher.IsPlatformSupported(extension, TargetPlatform.Windows); + var result2 = _matcher.IsPlatformSupported(extension, TargetPlatform.Linux); + var result3 = _matcher.IsPlatformSupported(extension, TargetPlatform.MacOS); + + // Assert + Assert.True(result1); + Assert.True(result2); + Assert.False(result3); + } + + [Fact] + public void IsPlatformSupported_ShouldReturnTrue_WhenAllPlatformsSupported() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + SupportedPlatforms = TargetPlatform.All + }; + + // Act + var result1 = _matcher.IsPlatformSupported(extension, TargetPlatform.Windows); + var result2 = _matcher.IsPlatformSupported(extension, TargetPlatform.Linux); + var result3 = _matcher.IsPlatformSupported(extension, TargetPlatform.MacOS); + + // Assert + Assert.True(result1); + Assert.True(result2); + Assert.True(result3); + } + + [Fact] + public void IsPlatformSupported_ShouldReturnFalse_WhenPlatformNotSupported() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + SupportedPlatforms = TargetPlatform.Windows + }; + + // Act + var result1 = _matcher.IsPlatformSupported(extension, TargetPlatform.Linux); + var result2 = _matcher.IsPlatformSupported(extension, TargetPlatform.MacOS); + + // Assert + Assert.False(result1); + Assert.False(result2); + } + + [Fact] + public void IsPlatformSupported_ShouldReturnFalse_WhenNoPlatformSupported() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + SupportedPlatforms = TargetPlatform.None + }; + + // Act + var result1 = _matcher.IsPlatformSupported(extension, TargetPlatform.Windows); + var result2 = _matcher.IsPlatformSupported(extension, TargetPlatform.Linux); + var result3 = _matcher.IsPlatformSupported(extension, TargetPlatform.MacOS); + + // Assert + Assert.False(result1); + Assert.False(result2); + Assert.False(result3); + } + + private TargetPlatform GetOtherPlatform(TargetPlatform current) + { + return current switch + { + TargetPlatform.Windows => TargetPlatform.Linux, + TargetPlatform.Linux => TargetPlatform.MacOS, + TargetPlatform.MacOS => TargetPlatform.Windows, + _ => TargetPlatform.Windows + }; + } +} diff --git a/src/c#/ExtensionTest/VersionCompatibilityCheckerTests.cs b/src/c#/ExtensionTest/VersionCompatibilityCheckerTests.cs new file mode 100644 index 00000000..bf0feb19 --- /dev/null +++ b/src/c#/ExtensionTest/VersionCompatibilityCheckerTests.cs @@ -0,0 +1,327 @@ +using GeneralUpdate.Extension.Compatibility; +using GeneralUpdate.Extension.Common.Models; + +namespace ExtensionTest; + +public class VersionCompatibilityCheckerTests +{ + private readonly VersionCompatibilityChecker _checker; + + public VersionCompatibilityCheckerTests() + { + _checker = new VersionCompatibilityChecker(); + } + + [Fact] + public void IsCompatible_ShouldReturnTrue_WhenNoVersionConstraints() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + Name = "TestExtension" + }; + + // Act + var result = _checker.IsCompatible(extension, "1.0.0"); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsCompatible_ShouldReturnTrue_WhenHostVersionEmpty() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + MinHostVersion = "1.0.0", + MaxHostVersion = "2.0.0" + }; + + // Act + var result = _checker.IsCompatible(extension, ""); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsCompatible_ShouldReturnTrue_WhenHostVersionMeetsMinVersion() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + MinHostVersion = "1.0.0" + }; + + // Act + var result = _checker.IsCompatible(extension, "1.5.0"); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsCompatible_ShouldReturnTrue_WhenHostVersionEqualsMinVersion() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + MinHostVersion = "1.0.0" + }; + + // Act + var result = _checker.IsCompatible(extension, "1.0.0"); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsCompatible_ShouldReturnFalse_WhenHostVersionBelowMinVersion() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + MinHostVersion = "2.0.0" + }; + + // Act + var result = _checker.IsCompatible(extension, "1.0.0"); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsCompatible_ShouldReturnTrue_WhenHostVersionUnderMaxVersion() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + MaxHostVersion = "2.0.0" + }; + + // Act + var result = _checker.IsCompatible(extension, "1.5.0"); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsCompatible_ShouldReturnTrue_WhenHostVersionEqualsMaxVersion() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + MaxHostVersion = "2.0.0" + }; + + // Act + var result = _checker.IsCompatible(extension, "2.0.0"); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsCompatible_ShouldReturnFalse_WhenHostVersionAboveMaxVersion() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + MaxHostVersion = "2.0.0" + }; + + // Act + var result = _checker.IsCompatible(extension, "3.0.0"); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsCompatible_ShouldReturnTrue_WhenHostVersionInRange() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + MinHostVersion = "1.0.0", + MaxHostVersion = "3.0.0" + }; + + // Act + var result = _checker.IsCompatible(extension, "2.0.0"); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsCompatible_ShouldReturnFalse_WhenHostVersionOutOfRange() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + MinHostVersion = "1.0.0", + MaxHostVersion = "2.0.0" + }; + + // Act + var result1 = _checker.IsCompatible(extension, "0.5.0"); + var result2 = _checker.IsCompatible(extension, "3.0.0"); + + // Assert + Assert.False(result1); + Assert.False(result2); + } + + [Fact] + public void IsCompatible_ShouldReturnFalse_WhenHostVersionInvalid() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + MinHostVersion = "1.0.0" + }; + + // Act + var result = _checker.IsCompatible(extension, "invalid-version"); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsCompatible_ShouldReturnFalse_WhenMinVersionInvalid() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + MinHostVersion = "invalid" + }; + + // Act + var result = _checker.IsCompatible(extension, "1.0.0"); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsCompatible_ShouldReturnFalse_WhenMaxVersionInvalid() + { + // Arrange + var extension = new ExtensionMetadata + { + Id = "ext1", + MaxHostVersion = "invalid" + }; + + // Act + var result = _checker.IsCompatible(extension, "1.0.0"); + + // Assert + Assert.False(result); + } + + [Fact] + public void FindLatestCompatibleVersion_ShouldReturnLatestVersion() + { + // Arrange + var extensions = new List + { + new ExtensionMetadata { Id = "ext1", Version = "1.0.0", MinHostVersion = "1.0.0" }, + new ExtensionMetadata { Id = "ext1", Version = "1.5.0", MinHostVersion = "1.0.0" }, + new ExtensionMetadata { Id = "ext1", Version = "2.0.0", MinHostVersion = "1.0.0" } + }; + + // Act + var result = _checker.FindLatestCompatibleVersion(extensions, "1.5.0"); + + // Assert + Assert.NotNull(result); + Assert.Equal("2.0.0", result.Version); + } + + [Fact] + public void FindLatestCompatibleVersion_ShouldReturnNullWhenNoneCompatible() + { + // Arrange + var extensions = new List + { + new ExtensionMetadata { Id = "ext1", Version = "1.0.0", MinHostVersion = "2.0.0" }, + new ExtensionMetadata { Id = "ext1", Version = "1.5.0", MinHostVersion = "2.0.0" } + }; + + // Act + var result = _checker.FindLatestCompatibleVersion(extensions, "1.0.0"); + + // Assert + Assert.Null(result); + } + + [Fact] + public void FindLatestCompatibleVersion_ShouldHandleMaxVersion() + { + // Arrange + var extensions = new List + { + new ExtensionMetadata { Id = "ext1", Version = "1.0.0", MaxHostVersion = "2.0.0" }, + new ExtensionMetadata { Id = "ext1", Version = "1.5.0", MaxHostVersion = "1.5.0" }, + new ExtensionMetadata { Id = "ext1", Version = "2.0.0", MaxHostVersion = "3.0.0" } + }; + + // Act + var result = _checker.FindLatestCompatibleVersion(extensions, "2.5.0"); + + // Assert + Assert.NotNull(result); + Assert.Equal("2.0.0", result.Version); + } + + [Fact] + public void FindLatestCompatibleVersion_ShouldReturnNullForEmptyList() + { + // Arrange + var extensions = new List(); + + // Act + var result = _checker.FindLatestCompatibleVersion(extensions, "1.0.0"); + + // Assert + Assert.Null(result); + } + + [Fact] + public void FindLatestCompatibleVersion_ShouldHandleVersionParsing() + { + // Arrange + var extensions = new List + { + new ExtensionMetadata { Id = "ext1", Version = "1.0.0.0" }, + new ExtensionMetadata { Id = "ext1", Version = "1.10.0" }, + new ExtensionMetadata { Id = "ext1", Version = "1.2.0" } + }; + + // Act + var result = _checker.FindLatestCompatibleVersion(extensions, "1.0.0"); + + // Assert + Assert.NotNull(result); + Assert.Equal("1.10.0", result.Version); + } +} From 9f558a646c53510155d35cd5d2ce352e352ab344 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 07:30:28 +0000 Subject: [PATCH 3/3] Add DownloadQueueManager test cases (17 tests) Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --- .../DownloadQueueManagerTests.cs | 334 ++++++++++++++++++ 1 file changed, 334 insertions(+) create mode 100644 src/c#/ExtensionTest/DownloadQueueManagerTests.cs diff --git a/src/c#/ExtensionTest/DownloadQueueManagerTests.cs b/src/c#/ExtensionTest/DownloadQueueManagerTests.cs new file mode 100644 index 00000000..141815f6 --- /dev/null +++ b/src/c#/ExtensionTest/DownloadQueueManagerTests.cs @@ -0,0 +1,334 @@ +using GeneralUpdate.Extension.Common.Enums; +using GeneralUpdate.Extension.Common.Models; +using GeneralUpdate.Extension.Download; + +namespace ExtensionTest; + +public class DownloadQueueManagerTests : IDisposable +{ + private DownloadQueueManager? _manager; + + public void Dispose() + { + _manager?.Dispose(); + } + + [Fact] + public void Constructor_ShouldInitialize_WithDefaultConcurrency() + { + // Act + _manager = new DownloadQueueManager(); + + // Assert + Assert.NotNull(_manager); + } + + [Fact] + public void Constructor_ShouldInitialize_WithCustomConcurrency() + { + // Act + _manager = new DownloadQueueManager(5); + + // Assert + Assert.NotNull(_manager); + } + + [Fact] + public void Enqueue_ShouldAddTaskToQueue() + { + // Arrange + _manager = new DownloadQueueManager(); + var extension = CreateTestExtension("ext1"); + var task = new DownloadTask { Extension = extension }; + + // Act + _manager.Enqueue(task); + + // Assert + var retrievedTask = _manager.GetTask("ext1"); + Assert.NotNull(retrievedTask); + Assert.Equal("ext1", retrievedTask.Extension.Id); + } + + [Fact] + public void Enqueue_ShouldSetStatusToQueued() + { + // Arrange + _manager = new DownloadQueueManager(); + var extension = CreateTestExtension("ext1"); + var task = new DownloadTask { Extension = extension }; + var statusChanges = new List(); + + _manager.DownloadStatusChanged += (sender, e) => + { + statusChanges.Add(e.Task.Status); + }; + + // Act + _manager.Enqueue(task); + + // Assert - Queued should be the first status + Assert.Contains(ExtensionUpdateStatus.Queued, statusChanges); + } + + [Fact] + public void Enqueue_ShouldRaiseDownloadStatusChangedEvent() + { + // Arrange + _manager = new DownloadQueueManager(); + var extension = CreateTestExtension("ext1"); + var task = new DownloadTask { Extension = extension }; + var eventRaised = false; + + _manager.DownloadStatusChanged += (sender, e) => + { + eventRaised = true; + Assert.Equal("ext1", e.Task.Extension.Id); + Assert.Equal(ExtensionUpdateStatus.Queued, e.Task.Status); + }; + + // Act + _manager.Enqueue(task); + + // Assert + Assert.True(eventRaised); + } + + [Fact] + public void GetTask_ShouldReturnTask_WhenExists() + { + // Arrange + _manager = new DownloadQueueManager(); + var extension = CreateTestExtension("ext1"); + var task = new DownloadTask { Extension = extension }; + _manager.Enqueue(task); + + // Act + var result = _manager.GetTask("ext1"); + + // Assert + Assert.NotNull(result); + Assert.Equal("ext1", result.Extension.Id); + } + + [Fact] + public void GetTask_ShouldReturnNull_WhenNotExists() + { + // Arrange + _manager = new DownloadQueueManager(); + + // Act + var result = _manager.GetTask("nonexistent"); + + // Assert + Assert.Null(result); + } + + [Fact] + public void CancelTask_ShouldCancelExistingTask() + { + // Arrange + _manager = new DownloadQueueManager(); + var extension = CreateTestExtension("ext1"); + var task = new DownloadTask { Extension = extension }; + _manager.Enqueue(task); + + // Act + _manager.CancelTask("ext1"); + + // Assert + Assert.True(task.CancellationTokenSource.IsCancellationRequested); + } + + [Fact] + public void CancelTask_ShouldNotThrow_WhenTaskNotExists() + { + // Arrange + _manager = new DownloadQueueManager(); + + // Act & Assert + var exception = Record.Exception(() => _manager.CancelTask("nonexistent")); + Assert.Null(exception); + } + + [Fact] + public void GetActiveTasks_ShouldReturnEmptyList_WhenNoTasks() + { + // Arrange + _manager = new DownloadQueueManager(); + + // Act + var tasks = _manager.GetActiveTasks(); + + // Assert + Assert.Empty(tasks); + } + + [Fact] + public void GetActiveTasks_ShouldReturnAllTasks() + { + // Arrange + _manager = new DownloadQueueManager(); + var ext1 = CreateTestExtension("ext1"); + var ext2 = CreateTestExtension("ext2"); + var task1 = new DownloadTask { Extension = ext1 }; + var task2 = new DownloadTask { Extension = ext2 }; + + _manager.Enqueue(task1); + _manager.Enqueue(task2); + + // Act + var tasks = _manager.GetActiveTasks(); + + // Assert + Assert.Equal(2, tasks.Count); + Assert.Contains(tasks, t => t.Extension.Id == "ext1"); + Assert.Contains(tasks, t => t.Extension.Id == "ext2"); + } + + [Fact] + public async Task Enqueue_ShouldProcessTask_Eventually() + { + // Arrange + _manager = new DownloadQueueManager(); + var extension = CreateTestExtension("ext1"); + var task = new DownloadTask { Extension = extension }; + var statusChanges = new List(); + + _manager.DownloadStatusChanged += (sender, e) => + { + statusChanges.Add(e.Task.Status); + }; + + // Act + _manager.Enqueue(task); + + // Wait for processing (with timeout) + await Task.Delay(300); + + // Assert + Assert.Contains(ExtensionUpdateStatus.Queued, statusChanges); + Assert.Contains(ExtensionUpdateStatus.Updating, statusChanges); + Assert.Contains(ExtensionUpdateStatus.UpdateSuccessful, statusChanges); + } + + [Fact] + public async Task Enqueue_ShouldHandleCancellation() + { + // Arrange + _manager = new DownloadQueueManager(); + var extension = CreateTestExtension("ext1"); + var task = new DownloadTask { Extension = extension }; + var statusChanges = new List(); + + _manager.DownloadStatusChanged += (sender, e) => + { + statusChanges.Add(e.Task.Status); + }; + + _manager.Enqueue(task); + + // Act - Cancel immediately after enqueueing + _manager.CancelTask("ext1"); + + // Wait for processing (with timeout) + await Task.Delay(300); + + // Assert + Assert.True(task.CancellationTokenSource.IsCancellationRequested); + } + + [Fact] + public async Task Enqueue_MultipleTasksShouldProcess() + { + // Arrange + _manager = new DownloadQueueManager(2); // Allow 2 concurrent downloads + var completedTasks = 0; + + _manager.DownloadStatusChanged += (sender, e) => + { + if (e.Task.Status == ExtensionUpdateStatus.UpdateSuccessful) + { + Interlocked.Increment(ref completedTasks); + } + }; + + // Act - Enqueue multiple tasks + for (int i = 1; i <= 5; i++) + { + var ext = CreateTestExtension($"ext{i}"); + var task = new DownloadTask { Extension = ext }; + _manager.Enqueue(task); + } + + // Wait for all to complete (with timeout) + await Task.Delay(1000); + + // Assert + Assert.Equal(5, completedTasks); + } + + [Fact] + public void Dispose_ShouldCancelAllTasks() + { + // Arrange + _manager = new DownloadQueueManager(); + var tasks = new List(); + + for (int i = 1; i <= 3; i++) + { + var ext = CreateTestExtension($"ext{i}"); + var task = new DownloadTask { Extension = ext }; + tasks.Add(task); + _manager.Enqueue(task); + } + + // Act + _manager.Dispose(); + + // Assert + foreach (var task in tasks) + { + Assert.True(task.CancellationTokenSource.IsCancellationRequested); + } + } + + [Fact] + public void Dispose_ShouldClearActiveTasks() + { + // Arrange + _manager = new DownloadQueueManager(); + var ext = CreateTestExtension("ext1"); + var task = new DownloadTask { Extension = ext }; + _manager.Enqueue(task); + + // Act + _manager.Dispose(); + var activeTasks = _manager.GetActiveTasks(); + + // Assert + Assert.Empty(activeTasks); + } + + [Fact] + public void Dispose_ShouldNotThrow_WhenCalledMultipleTimes() + { + // Arrange + _manager = new DownloadQueueManager(); + + // Act & Assert + _manager.Dispose(); + var exception = Record.Exception(() => _manager.Dispose()); + Assert.Null(exception); + } + + private ExtensionMetadata CreateTestExtension(string id) + { + return new ExtensionMetadata + { + Id = id, + Name = $"Extension-{id}", + Version = "1.0.0" + }; + } +}