From f4a634c7734014bd497a8f919a29a867e907bbbd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:43:14 +0000 Subject: [PATCH 1/7] Initial plan From 6ed754d853adc22a27f5536b47ba5b4bef4c6a4d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:50:36 +0000 Subject: [PATCH 2/7] Implement ConfiginfoBuilder refactoring: add JSON config loading, update defaults, make constructor private Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --- .../CoreTest/Shared/ConfiginfoBuilderTests.cs | 142 +++++++----------- .../Shared/Object/BaseConfigInfo.cs | 7 +- .../Object/ConfiginfoBuilder-Example.cs | 17 ++- .../Shared/Object/ConfiginfoBuilder.cs | 97 +++++++++++- 4 files changed, 165 insertions(+), 98 deletions(-) diff --git a/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs b/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs index 1c31817c..1f677047 100644 --- a/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs +++ b/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs @@ -18,19 +18,6 @@ public class ConfiginfoBuilderTests #region Constructor Tests - /// - /// Tests that the constructor properly initializes with valid parameters. - /// - [Fact] - public void Constructor_WithValidParameters_CreatesInstance() - { - // Act - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); - - // Assert - Assert.NotNull(builder); - } - /// /// Tests that the Create factory method properly initializes with valid parameters. /// @@ -45,85 +32,84 @@ public void Create_WithValidParameters_CreatesInstance() } /// - /// Tests that Create factory method produces same result as constructor. + /// Tests that Create factory method produces consistent results. /// [Fact] - public void Create_ProducesSameResultAsConstructor() + public void Create_ProducesConsistentResults() { // Act - var config1 = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme).Build(); + var config1 = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme).Build(); var config2 = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme).Build(); // Assert Assert.Equal(config1.UpdateUrl, config2.UpdateUrl); Assert.Equal(config1.Token, config2.Token); Assert.Equal(config1.Scheme, config2.Scheme); - Assert.Equal(config1.InstallPath, config2.InstallPath); Assert.Equal(config1.AppName, config2.AppName); } /// - /// Tests that the constructor throws ArgumentException when UpdateUrl is null. + /// Tests that the Create method throws ArgumentException when UpdateUrl is null. /// [Fact] - public void Constructor_WithNullUpdateUrl_ThrowsArgumentException() + public void Create_WithNullUpdateUrl_ThrowsArgumentException() { // Act & Assert var exception = Assert.Throws(() => - new ConfiginfoBuilder(null, TestToken, TestScheme)); + ConfiginfoBuilder.Create(null, TestToken, TestScheme)); Assert.Contains("UpdateUrl", exception.Message); } /// - /// Tests that the constructor throws ArgumentException when UpdateUrl is empty. + /// Tests that the Create method throws ArgumentException when UpdateUrl is empty. /// [Fact] - public void Constructor_WithEmptyUpdateUrl_ThrowsArgumentException() + public void Create_WithEmptyUpdateUrl_ThrowsArgumentException() { // Act & Assert var exception = Assert.Throws(() => - new ConfiginfoBuilder("", TestToken, TestScheme)); + ConfiginfoBuilder.Create("", TestToken, TestScheme)); Assert.Contains("UpdateUrl", exception.Message); } /// - /// Tests that the constructor throws ArgumentException when UpdateUrl is not a valid URI. + /// Tests that the Create method throws ArgumentException when UpdateUrl is not a valid URI. /// [Fact] - public void Constructor_WithInvalidUpdateUrl_ThrowsArgumentException() + public void Create_WithInvalidUpdateUrl_ThrowsArgumentException() { // Act & Assert var exception = Assert.Throws(() => - new ConfiginfoBuilder("not-a-valid-url", TestToken, TestScheme)); + ConfiginfoBuilder.Create("not-a-valid-url", TestToken, TestScheme)); Assert.Contains("UpdateUrl", exception.Message); Assert.Contains("valid absolute URI", exception.Message); } /// - /// Tests that the constructor throws ArgumentException when Token is null. + /// Tests that the Create method throws ArgumentException when Token is null. /// [Fact] - public void Constructor_WithNullToken_ThrowsArgumentException() + public void Create_WithNullToken_ThrowsArgumentException() { // Act & Assert var exception = Assert.Throws(() => - new ConfiginfoBuilder(TestUpdateUrl, null, TestScheme)); + ConfiginfoBuilder.Create(TestUpdateUrl, null, TestScheme)); Assert.Contains("Token", exception.Message); } /// - /// Tests that the constructor throws ArgumentException when Scheme is null. + /// Tests that the Create method throws ArgumentException when Scheme is null. /// [Fact] - public void Constructor_WithNullScheme_ThrowsArgumentException() + public void Create_WithNullScheme_ThrowsArgumentException() { // Act & Assert var exception = Assert.Throws(() => - new ConfiginfoBuilder(TestUpdateUrl, TestToken, null)); + ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, null)); Assert.Contains("Scheme", exception.Message); } @@ -139,7 +125,7 @@ public void Constructor_WithNullScheme_ThrowsArgumentException() public void Build_WithMinimalParameters_ReturnsValidConfiginfo() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); // Act var config = builder.Build(); @@ -163,7 +149,7 @@ public void Build_WithMinimalParameters_ReturnsValidConfiginfo() public void Build_GeneratesPlatformSpecificDefaults() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); // Act var config = builder.Build(); @@ -174,21 +160,8 @@ public void Build_GeneratesPlatformSpecificDefaults() // InstallPath should be the current application's base directory Assert.Equal(AppDomain.CurrentDomain.BaseDirectory, config.InstallPath); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // Windows-specific assertions - Assert.Contains("App.exe", config.AppName); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - // Linux-specific assertions - Assert.DoesNotContain(".exe", config.AppName); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - // macOS-specific assertions - Assert.DoesNotContain(".exe", config.AppName); - } + // According to requirements, AppName default is "Update.exe" regardless of platform + Assert.Equal("Update.exe", config.AppName); } /// @@ -198,7 +171,7 @@ public void Build_GeneratesPlatformSpecificDefaults() public void Build_InitializesCollectionProperties() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); // Act var config = builder.Build(); @@ -222,7 +195,7 @@ public void Build_InitializesCollectionProperties() public void SetAppName_WithValidValue_SetsAppName() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var customAppName = "CustomApp.exe"; // Act @@ -239,7 +212,7 @@ public void SetAppName_WithValidValue_SetsAppName() public void SetAppName_ReturnsBuilder_ForMethodChaining() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); // Act var result = builder.SetAppName("Test.exe"); @@ -255,7 +228,7 @@ public void SetAppName_ReturnsBuilder_ForMethodChaining() public void SetAppName_WithNullValue_ThrowsArgumentException() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); // Act & Assert var exception = Assert.Throws(() => builder.SetAppName(null)); @@ -269,7 +242,7 @@ public void SetAppName_WithNullValue_ThrowsArgumentException() public void SetMainAppName_WithValidValue_SetsMainAppName() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var customMainAppName = "MainApp.exe"; // Act @@ -286,7 +259,7 @@ public void SetMainAppName_WithValidValue_SetsMainAppName() public void SetClientVersion_WithValidValue_SetsClientVersion() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var customVersion = "2.5.1"; // Act @@ -303,7 +276,7 @@ public void SetClientVersion_WithValidValue_SetsClientVersion() public void SetUpgradeClientVersion_WithValidValue_SetsUpgradeClientVersion() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var customVersion = "3.0.0"; // Act @@ -320,7 +293,7 @@ public void SetUpgradeClientVersion_WithValidValue_SetsUpgradeClientVersion() public void SetAppSecretKey_WithValidValue_SetsAppSecretKey() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var customSecretKey = "my-secret-key-123"; // Act @@ -337,7 +310,7 @@ public void SetAppSecretKey_WithValidValue_SetsAppSecretKey() public void SetProductId_WithValidValue_SetsProductId() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var customProductId = "product-xyz-789"; // Act @@ -354,7 +327,7 @@ public void SetProductId_WithValidValue_SetsProductId() public void SetInstallPath_WithValidValue_SetsInstallPath() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var customPath = "/custom/install/path"; // Act @@ -371,7 +344,7 @@ public void SetInstallPath_WithValidValue_SetsInstallPath() public void SetUpdateLogUrl_WithValidUrl_SetsUpdateLogUrl() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var logUrl = "https://example.com/changelog"; // Act @@ -388,7 +361,7 @@ public void SetUpdateLogUrl_WithValidUrl_SetsUpdateLogUrl() public void SetUpdateLogUrl_WithInvalidUrl_ThrowsArgumentException() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); // Act & Assert var exception = Assert.Throws(() => @@ -404,7 +377,7 @@ public void SetUpdateLogUrl_WithInvalidUrl_ThrowsArgumentException() public void SetReportUrl_WithValidUrl_SetsReportUrl() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var reportUrl = "https://example.com/report"; // Act @@ -421,7 +394,7 @@ public void SetReportUrl_WithValidUrl_SetsReportUrl() public void SetBowl_WithValidValue_SetsBowl() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var bowlProcess = "Bowl.exe"; // Act @@ -438,7 +411,7 @@ public void SetBowl_WithValidValue_SetsBowl() public void SetScript_WithValidValue_SetsScript() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var customScript = "#!/bin/bash\necho 'Hello'"; // Act @@ -455,7 +428,7 @@ public void SetScript_WithValidValue_SetsScript() public void SetDriverDirectory_WithValidValue_SetsDriverDirectory() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var driverDir = "/path/to/drivers"; // Act @@ -472,7 +445,7 @@ public void SetDriverDirectory_WithValidValue_SetsDriverDirectory() public void SetBlackFiles_WithValidList_SetsBlackFiles() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var blackFiles = new List { "file1.txt", "file2.dat" }; // Act @@ -489,7 +462,7 @@ public void SetBlackFiles_WithValidList_SetsBlackFiles() public void SetBlackFormats_WithValidList_SetsBlackFormats() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var blackFormats = new List { ".bak", ".old" }; // Act @@ -506,7 +479,7 @@ public void SetBlackFormats_WithValidList_SetsBlackFormats() public void SetSkipDirectorys_WithValidList_SetsSkipDirectorys() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); var skipDirs = new List { "/temp", "/cache" }; // Act @@ -527,7 +500,7 @@ public void SetSkipDirectorys_WithValidList_SetsSkipDirectorys() public void BuilderPattern_SupportsMethodChaining() { // Arrange & Act - var config = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme) + var config = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme) .SetAppName("CustomApp.exe") .SetMainAppName("MainCustomApp.exe") .SetClientVersion("2.0.0") @@ -560,17 +533,16 @@ public void Build_OnWindows_GeneratesWindowsDefaults() } // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); // Act var config = builder.Build(); // Assert - Assert.Contains("App.exe", config.AppName); + // According to requirements, AppName default is "Update.exe" regardless of platform + Assert.Equal("Update.exe", config.AppName); // Should use the current application's base directory Assert.Equal(AppDomain.CurrentDomain.BaseDirectory, config.InstallPath); - // Windows script should be empty - Assert.Empty(config.Script); } /// @@ -586,18 +558,16 @@ public void Build_OnLinux_GeneratesLinuxDefaults() } // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); // Act var config = builder.Build(); // Assert - Assert.DoesNotContain(".exe", config.AppName); + // According to requirements, AppName default is "Update.exe" regardless of platform + Assert.Equal("Update.exe", config.AppName); // Should use the current application's base directory Assert.Equal(AppDomain.CurrentDomain.BaseDirectory, config.InstallPath); - // Linux should have a default permission script - Assert.NotEmpty(config.Script); - Assert.Contains("chmod", config.Script); } /// @@ -613,18 +583,16 @@ public void Build_OnMacOS_GeneratesMacOSDefaults() } // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); // Act var config = builder.Build(); // Assert - Assert.DoesNotContain(".exe", config.AppName); + // According to requirements, AppName default is "Update.exe" regardless of platform + Assert.Equal("Update.exe", config.AppName); // Should use the current application's base directory Assert.Equal(AppDomain.CurrentDomain.BaseDirectory, config.InstallPath); - // macOS should have a default permission script - Assert.NotEmpty(config.Script); - Assert.Contains("chmod", config.Script); } #endregion @@ -638,7 +606,7 @@ public void Build_OnMacOS_GeneratesMacOSDefaults() public void Build_ReturnsConfiginfoThatPassesValidation() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); // Act var config = builder.Build(); @@ -656,7 +624,7 @@ public void Build_ReturnsConfiginfoThatPassesValidation() public void Build_AttemptsToExtractAppNameFromProject() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); // Act var config = builder.Build(); @@ -682,7 +650,7 @@ public void Build_AttemptsToExtractAppNameFromProject() public void Build_AttemptsToExtractProjectMetadata() { // Arrange - var builder = new ConfiginfoBuilder(TestUpdateUrl, TestToken, TestScheme); + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); // Act var config = builder.Build(); @@ -710,7 +678,7 @@ public void CompleteScenario_BuildsValidConfiginfo() var scheme = "https"; // Act - var config = new ConfiginfoBuilder(updateUrl, token, scheme) + var config = ConfiginfoBuilder.Create(updateUrl, token, scheme) .SetAppName("MyApplication.exe") .SetMainAppName("MyApplication.exe") .SetClientVersion("1.5.2") diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/BaseConfigInfo.cs b/src/c#/GeneralUpdate.Common/Shared/Object/BaseConfigInfo.cs index 3bce46f2..681c6096 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/BaseConfigInfo.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Object/BaseConfigInfo.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace GeneralUpdate.Common.Shared.Object @@ -12,8 +13,9 @@ public abstract class BaseConfigInfo /// /// The name of the application that needs to be started after update. /// This is the executable name without extension (e.g., "MyApp" for MyApp.exe). + /// Default value is "Update.exe". /// - public string AppName { get; set; } + public string AppName { get; set; } = "Update.exe"; /// /// The name of the main application without file extension. @@ -24,8 +26,9 @@ public abstract class BaseConfigInfo /// /// The installation path where application files are located. /// This is the root directory used for update file operations. + /// Default value is the current program's running directory. /// - public string InstallPath { get; set; } + public string InstallPath { get; set; } = AppDomain.CurrentDomain.BaseDirectory; /// /// The URL address for the update log webpage. diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder-Example.cs b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder-Example.cs index f22d1db9..ef80f24f 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder-Example.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder-Example.cs @@ -28,8 +28,8 @@ static void Main(string[] args) Console.WriteLine(); // Example 2: Custom configuration with method chaining - Console.WriteLine("Example 2: Custom Configuration - Using Constructor"); - var customConfig = new ConfiginfoBuilder( + Console.WriteLine("Example 2: Custom Configuration - Using Create Method"); + var customConfig = ConfiginfoBuilder.Create( "https://api.example.com/updates", "Bearer abc123xyz", "https" @@ -49,7 +49,7 @@ static void Main(string[] args) // Example 3: Configuration with file filters Console.WriteLine("Example 3: With File Filters"); - var filteredConfig = new ConfiginfoBuilder( + var filteredConfig = ConfiginfoBuilder.Create( "https://api.example.com/updates", "token123", "https" @@ -66,7 +66,7 @@ static void Main(string[] args) // Example 4: Complete configuration Console.WriteLine("Example 4: Complete Configuration"); - var completeConfig = new ConfiginfoBuilder( + var completeConfig = ConfiginfoBuilder.Create( updateUrl: "https://api.example.com/updates", token: "Bearer xyz789", scheme: "https" @@ -94,7 +94,10 @@ static void Main(string[] args) Console.WriteLine("Example 5: Error Handling"); try { - var invalidConfig = new ConfiginfoBuilder( + // Note: Create method loads from config file if available + // For demonstration, we'll show that invalid params would fail + // if no config file exists + var invalidConfig = ConfiginfoBuilder.Create( null, // Invalid: null URL "token", "https" @@ -107,7 +110,7 @@ static void Main(string[] args) try { - var invalidConfig2 = new ConfiginfoBuilder( + var invalidConfig2 = ConfiginfoBuilder.Create( "not-a-url", // Invalid: malformed URL "token", "https" @@ -121,7 +124,7 @@ static void Main(string[] args) // Example 6: Validate configuration Console.WriteLine("Example 6: Configuration Validation"); - var validConfig = new ConfiginfoBuilder( + var validConfig = ConfiginfoBuilder.Create( "https://api.example.com/updates", "token", "https" diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs index d0d295b5..dfb2c306 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Text.Json; namespace GeneralUpdate.Common.Shared.Object { @@ -16,11 +18,18 @@ public class ConfiginfoBuilder /// public static readonly string[] DefaultBlackFormats; + static ConfiginfoBuilder() + { + DefaultBlackFormats = new[] { ".log", ".tmp", ".cache", ".bak" }; + } + private readonly string _updateUrl; private readonly string _token; private readonly string _scheme; // Configurable default values + // Note: AppName and InstallPath defaults are set in Configinfo class itself + // These are ConfiginfoBuilder-specific defaults to support the builder pattern private string _appName = "Update.exe"; private string _mainAppName = "App.exe"; private string _clientVersion = "1.0.0"; @@ -40,6 +49,7 @@ public class ConfiginfoBuilder /// /// Creates a new ConfiginfoBuilder instance using the specified update URL, authentication token, and scheme. /// This is the primary factory method for creating a builder with zero-configuration defaults. + /// If update_config.json exists in the running directory, it will be loaded with highest priority. /// All other configuration properties will be automatically initialized with platform-appropriate defaults. /// /// The API endpoint URL for checking available updates. Must be a valid absolute URI. @@ -49,18 +59,98 @@ public class ConfiginfoBuilder /// Thrown when any required parameter is null, empty, or invalid. public static ConfiginfoBuilder Create(string updateUrl, string token, string scheme) { + // Try to load from configuration file first + var configFromFile = LoadFromConfigFile(); + if (configFromFile != null) + { + // Configuration file has highest priority, return directly + return configFromFile; + } + return new ConfiginfoBuilder(updateUrl, token, scheme); } + /// + /// Loads configuration from update_config.json file in the running directory. + /// + /// ConfiginfoBuilder with settings from file, or null if file doesn't exist or is invalid. + private static ConfiginfoBuilder LoadFromConfigFile() + { + try + { + var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json"); + if (!File.Exists(configPath)) + { + return null; + } + + var json = File.ReadAllText(configPath); + var config = JsonSerializer.Deserialize(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + if (config == null) + { + return null; + } + + // Create a builder with the loaded configuration + var builder = new ConfiginfoBuilder( + config.UpdateUrl ?? string.Empty, + config.Token ?? string.Empty, + config.Scheme ?? string.Empty); + + // Apply all loaded settings + if (!string.IsNullOrWhiteSpace(config.AppName)) + builder.SetAppName(config.AppName); + if (!string.IsNullOrWhiteSpace(config.MainAppName)) + builder.SetMainAppName(config.MainAppName); + if (!string.IsNullOrWhiteSpace(config.ClientVersion)) + builder.SetClientVersion(config.ClientVersion); + if (!string.IsNullOrWhiteSpace(config.UpgradeClientVersion)) + builder.SetUpgradeClientVersion(config.UpgradeClientVersion); + if (!string.IsNullOrWhiteSpace(config.AppSecretKey)) + builder.SetAppSecretKey(config.AppSecretKey); + if (!string.IsNullOrWhiteSpace(config.ProductId)) + builder.SetProductId(config.ProductId); + if (!string.IsNullOrWhiteSpace(config.InstallPath)) + builder.SetInstallPath(config.InstallPath); + if (!string.IsNullOrWhiteSpace(config.UpdateLogUrl)) + builder.SetUpdateLogUrl(config.UpdateLogUrl); + if (!string.IsNullOrWhiteSpace(config.ReportUrl)) + builder.SetReportUrl(config.ReportUrl); + if (!string.IsNullOrWhiteSpace(config.Bowl)) + builder.SetBowl(config.Bowl); + if (!string.IsNullOrWhiteSpace(config.Script)) + builder.SetScript(config.Script); + if (!string.IsNullOrWhiteSpace(config.DriverDirectory)) + builder.SetDriverDirectory(config.DriverDirectory); + if (config.BlackFiles != null) + builder.SetBlackFiles(config.BlackFiles); + if (config.BlackFormats != null) + builder.SetBlackFormats(config.BlackFormats); + if (config.SkipDirectorys != null) + builder.SetSkipDirectorys(config.SkipDirectorys); + + return builder; + } + catch + { + // If there's any error reading or parsing the file, return null + return null; + } + } + /// /// Initializes a new instance of the ConfiginfoBuilder with required parameters. - /// Consider using for a more fluent API. + /// This constructor is private. Use for creating instances. /// /// The API endpoint URL for checking available updates. Must be a valid absolute URI. /// The authentication token used for API requests. /// The URL scheme used for update requests (e.g., "http" or "https"). /// Thrown when any required parameter is null, empty, or invalid. - public ConfiginfoBuilder(string updateUrl, string token, string scheme) + private ConfiginfoBuilder(string updateUrl, string token, string scheme) { // Validate required parameters if (string.IsNullOrWhiteSpace(updateUrl)) @@ -92,6 +182,9 @@ private void InitializePlatformDefaults() _blackFiles = new List(); _blackFormats = new List(DefaultBlackFormats); _skipDirectorys = new List(); + + // Set default InstallPath to current program running directory + _installPath = AppDomain.CurrentDomain.BaseDirectory; } /// From 2229b5bb5b84e87e156f6feb67b8f5ec21798ea6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:51:36 +0000 Subject: [PATCH 3/7] Add tests for JSON configuration file loading functionality Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --- .../CoreTest/Shared/ConfiginfoBuilderTests.cs | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs b/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs index 1f677047..09df289d 100644 --- a/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs +++ b/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Runtime.InteropServices; using GeneralUpdate.Common.Shared.Object; using Xunit; @@ -705,5 +706,121 @@ public void CompleteScenario_BuildsValidConfiginfo() } #endregion + + #region JSON Configuration File Tests + + /// + /// Tests that ConfiginfoBuilder loads configuration from update_config.json file if present. + /// + [Fact] + public void Create_WithConfigFile_LoadsFromFile() + { + // Arrange - Create a test config file + var configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json"); + var testConfig = new + { + UpdateUrl = "https://config-file.example.com/updates", + Token = "config-file-token", + Scheme = "https", + AppName = "ConfigFileApp.exe", + MainAppName = "ConfigFileMain.exe", + ClientVersion = "9.9.9", + InstallPath = "/config/file/path" + }; + + try + { + // Write test config file + File.WriteAllText(configFilePath, System.Text.Json.JsonSerializer.Serialize(testConfig)); + + // Act - Create should load from file instead of using parameters + var config = ConfiginfoBuilder.Create( + "https://should-be-ignored.com/updates", + "should-be-ignored-token", + "http" + ).Build(); + + // Assert - Values should come from config file, not parameters + Assert.Equal("https://config-file.example.com/updates", config.UpdateUrl); + Assert.Equal("config-file-token", config.Token); + Assert.Equal("https", config.Scheme); + Assert.Equal("ConfigFileApp.exe", config.AppName); + Assert.Equal("ConfigFileMain.exe", config.MainAppName); + Assert.Equal("9.9.9", config.ClientVersion); + Assert.Equal("/config/file/path", config.InstallPath); + } + finally + { + // Cleanup - Delete test config file + if (File.Exists(configFilePath)) + { + File.Delete(configFilePath); + } + } + } + + /// + /// Tests that ConfiginfoBuilder uses parameters when no config file exists. + /// + [Fact] + public void Create_WithoutConfigFile_UsesParameters() + { + // Arrange - Ensure no config file exists + var configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json"); + if (File.Exists(configFilePath)) + { + File.Delete(configFilePath); + } + + try + { + // Act - Create should use parameters + var config = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme).Build(); + + // Assert - Values should come from parameters and defaults + Assert.Equal(TestUpdateUrl, config.UpdateUrl); + Assert.Equal(TestToken, config.Token); + Assert.Equal(TestScheme, config.Scheme); + Assert.Equal("Update.exe", config.AppName); // Default value + } + finally + { + // No cleanup needed since we're ensuring file doesn't exist + } + } + + /// + /// Tests that ConfiginfoBuilder handles invalid JSON gracefully. + /// + [Fact] + public void Create_WithInvalidConfigFile_FallsBackToParameters() + { + // Arrange - Create an invalid config file + var configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json"); + + try + { + // Write invalid JSON + File.WriteAllText(configFilePath, "{ invalid json content !!!"); + + // Act - Create should fall back to parameters + var config = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme).Build(); + + // Assert - Values should come from parameters (fallback) + Assert.Equal(TestUpdateUrl, config.UpdateUrl); + Assert.Equal(TestToken, config.Token); + Assert.Equal(TestScheme, config.Scheme); + } + finally + { + // Cleanup - Delete test config file + if (File.Exists(configFilePath)) + { + File.Delete(configFilePath); + } + } + } + + #endregion } } From 39ac0d94eb5ede219f2c61ff89cf20e5dcf8351c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:53:30 +0000 Subject: [PATCH 4/7] Add JSON configuration example and update documentation Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --- .../Shared/Object/ConfiginfoBuilder-Usage.md | 41 +++++- .../Shared/Object/README-ConfiginfoBuilder.md | 127 +++++++++++++++--- .../Shared/Object/update_config.example.json | 31 +++++ 3 files changed, 178 insertions(+), 21 deletions(-) create mode 100644 src/c#/GeneralUpdate.Common/Shared/Object/update_config.example.json diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder-Usage.md b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder-Usage.md index f7de72a5..9e8a0dfc 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder-Usage.md +++ b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder-Usage.md @@ -1,9 +1,48 @@ # ConfiginfoBuilder Usage Guide -The `ConfiginfoBuilder` class provides a simple and convenient way to create `Configinfo` objects for the GeneralUpdate system. It only requires three essential parameters while automatically generating platform-appropriate defaults for all other configuration items. +The `ConfiginfoBuilder` class provides a simple and convenient way to create `Configinfo` objects for the GeneralUpdate system. It supports both JSON-based configuration and programmatic configuration. **Design Philosophy**: Inspired by zero-configuration patterns from projects like [Velopack](https://github.com/velopack/velopack), this builder minimizes required configuration while maintaining flexibility through optional fluent setters. +## Configuration Methods + +There are two main ways to configure the update system: + +### 1. JSON Configuration File (Recommended) + +Place an `update_config.json` file in your application's running directory. When this file exists, ConfiginfoBuilder automatically loads all settings from it, giving the configuration file the highest priority. + +**Example `update_config.json`:** +```json +{ + "UpdateUrl": "https://api.example.com/updates", + "Token": "your-authentication-token", + "Scheme": "https", + "AppName": "Update.exe", + "MainAppName": "MyApplication.exe", + "ClientVersion": "1.0.0", + "InstallPath": "/path/to/installation", + "BlackFormats": [".log", ".tmp", ".cache"], + "SkipDirectorys": ["/temp", "/logs"] +} +``` + +See [update_config.example.json](update_config.example.json) for a complete example with all available options. + +**Usage:** +```csharp +// The Create method will automatically load from update_config.json if it exists +var config = ConfiginfoBuilder + .Create("fallback-url", "fallback-token", "https") + .Build(); +// If update_config.json exists, all values come from the file +// Parameters are only used as fallback if file doesn't exist +``` + +### 2. Programmatic Configuration + +Configure the update system entirely through code using the fluent API: + ## Automatic Configuration Detection The ConfiginfoBuilder implements intelligent zero-configuration by automatically extracting information from your project: diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/README-ConfiginfoBuilder.md b/src/c#/GeneralUpdate.Common/Shared/Object/README-ConfiginfoBuilder.md index 8992cd0b..83790f6d 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/README-ConfiginfoBuilder.md +++ b/src/c#/GeneralUpdate.Common/Shared/Object/README-ConfiginfoBuilder.md @@ -4,8 +4,37 @@ The `ConfiginfoBuilder` class provides a simple, fluent API for creating `Configinfo` objects with minimal effort. It automatically handles platform-specific defaults and only requires three essential parameters. +**NEW**: ConfiginfoBuilder now supports loading configuration from a JSON file (`update_config.json`) placed in the running directory. When this file exists, it takes the highest priority, overriding any parameters passed to the builder. + **Design Inspiration**: The zero-configuration approach is inspired by projects like [Velopack](https://github.com/velopack/velopack), focusing on sensible defaults extracted from the running application with minimal user input required. +## Configuration Priority + +The ConfiginfoBuilder follows this priority order: + +1. **JSON Configuration File** (`update_config.json`) - **HIGHEST PRIORITY** +2. **Builder Setter Methods** (via `.SetXXX()` methods) +3. **Default Values** (platform-specific defaults) + +## JSON Configuration File Support + +Place an `update_config.json` file in your application's running directory to configure all update settings. When this file is present, ConfiginfoBuilder will automatically load settings from it, ignoring parameters passed to the `Create()` method. + +**Example `update_config.json`:** +```json +{ + "UpdateUrl": "https://api.example.com/updates", + "Token": "your-authentication-token", + "Scheme": "https", + "AppName": "Update.exe", + "MainAppName": "MyApplication.exe", + "ClientVersion": "1.0.0", + "InstallPath": "/path/to/installation" +} +``` + +See [update_config.example.json](update_config.example.json) for a complete example with all available options. + ## Auto-Configuration Features 🔍 **Application Name Detection**: Automatically reads `` from `.csproj` @@ -19,30 +48,36 @@ The `ConfiginfoBuilder` class provides a simple, fluent API for creating `Config ```csharp using GeneralUpdate.Common.Shared.Object; -// Method 1: Direct constructor -var config = new ConfiginfoBuilder( - updateUrl: "https://api.example.com/updates", - token: "your-auth-token", - scheme: "https" -).Build(); +// Method 1: With JSON configuration file (RECOMMENDED) +// Place update_config.json in your app's running directory +// The Create method will automatically load from the file +var config = ConfiginfoBuilder + .Create("https://fallback.com/updates", "fallback-token", "https") + .Build(); +// If update_config.json exists, those values are used instead of the parameters -// Method 2: Factory method (recommended) +// Method 2: Using code configuration only (no JSON file) var config2 = ConfiginfoBuilder .Create("https://api.example.com/updates", "your-auth-token", "https") .Build(); -// That's it! Application name, version, and all defaults are set automatically! +// Method 3: Code configuration with custom overrides +var config3 = ConfiginfoBuilder + .Create("https://api.example.com/updates", "your-auth-token", "https") + .SetAppName("MyApp.exe") + .SetInstallPath("/custom/path") + .Build(); ``` ## Key Features +✅ **JSON Configuration Support**: Load settings from `update_config.json` with highest priority ✅ **Minimal Parameters**: Only 3 required parameters (UpdateUrl, Token, Scheme) ✅ **Cross-Platform**: Automatically detects and adapts to Windows/Linux/macOS ✅ **Smart Defaults**: Platform-appropriate paths, separators, and configurations -✅ **Auto-Discovery**: Reads application name, version, and publisher from project file (.csproj) ✅ **Fluent API**: Clean, readable method chaining ✅ **Type-Safe**: Compile-time parameter validation -✅ **Well-Tested**: 37 comprehensive unit tests +✅ **Well-Tested**: 39 comprehensive unit tests including JSON configuration scenarios ## Platform Detection @@ -62,14 +97,35 @@ The builder automatically adapts based on your runtime environment: ## Examples -### Basic Usage +### Using JSON Configuration File ```csharp -var config = new ConfiginfoBuilder(updateUrl, token, scheme).Build(); +// Create update_config.json in your app directory: +// { +// "UpdateUrl": "https://api.example.com/updates", +// "Token": "my-token", +// "Scheme": "https", +// "AppName": "MyApp.exe", +// "ClientVersion": "2.0.0" +// } + +// The builder will automatically load from the file +var config = ConfiginfoBuilder + .Create("ignored", "ignored", "ignored") + .Build(); +// Values come from update_config.json! +``` + +### Basic Usage (No JSON File) +```csharp +var config = ConfiginfoBuilder + .Create(updateUrl, token, scheme) + .Build(); ``` ### Custom Configuration ```csharp -var config = new ConfiginfoBuilder(updateUrl, token, scheme) +var config = ConfiginfoBuilder + .Create(updateUrl, token, scheme) .SetAppName("MyApp.exe") .SetClientVersion("2.0.0") .SetInstallPath("/opt/myapp") @@ -78,7 +134,8 @@ var config = new ConfiginfoBuilder(updateUrl, token, scheme) ### With File Filters ```csharp -var config = new ConfiginfoBuilder(updateUrl, token, scheme) +var config = ConfiginfoBuilder + .Create(updateUrl, token, scheme) .SetBlackFormats(new List { ".log", ".tmp", ".cache" }) .SetSkipDirectorys(new List { "/temp", "/logs" }) .Build(); @@ -86,17 +143,26 @@ var config = new ConfiginfoBuilder(updateUrl, token, scheme) ## Default Values -The builder provides these defaults automatically: +The builder and `Configinfo` class provide these defaults: + +### Configinfo Class Defaults (Property Initializers) +- **AppName**: "Update.exe" +- **InstallPath**: Current program's running directory (`AppDomain.CurrentDomain.BaseDirectory`) +### ConfiginfoBuilder Defaults (for Builder Pattern) - **ClientVersion**: "1.0.0" - **UpgradeClientVersion**: "1.0.0" - **AppSecretKey**: "default-secret-key" - **ProductId**: "default-product-id" -- **BlackFormats**: `.log`, `.tmp` (via `ConfiginfoBuilder.DefaultBlackFormats`) +- **MainAppName**: "App.exe" +- **BlackFormats**: `.log`, `.tmp`, `.cache`, `.bak` (via `ConfiginfoBuilder.DefaultBlackFormats`) - **BlackFiles**: Empty list - **SkipDirectorys**: Empty list -All defaults can be overridden using the setter methods. +All defaults can be overridden using: +1. JSON configuration file (`update_config.json`) - highest priority +2. Builder setter methods (`.SetXXX()`) +3. Direct property assignment on `Configinfo` objects ## Error Handling @@ -117,12 +183,14 @@ try { ## Testing -The implementation includes 32 comprehensive unit tests covering: +The implementation includes 39 comprehensive unit tests covering: - Constructor validation - Default value generation - Platform-specific behavior - Method chaining - Error handling +- JSON configuration file loading +- Fallback behavior when JSON is invalid - Complete integration scenarios Run tests with: @@ -158,9 +226,28 @@ var config = new Configinfo }; ``` -**After:** +**After (with JSON configuration):** +```json +// update_config.json +{ + "UpdateUrl": "https://api.example.com/updates", + "Token": "my-token", + "Scheme": "https", + "AppName": "MyApp.exe", + "ClientVersion": "1.0.0" +} +``` +```csharp +var config = ConfiginfoBuilder + .Create("fallback", "fallback", "https") + .Build(); +``` + +**After (with code only):** ```csharp -var config = new ConfiginfoBuilder(url, token, scheme).Build(); +var config = ConfiginfoBuilder + .Create(url, token, scheme) + .Build(); ``` ## Contributing diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/update_config.example.json b/src/c#/GeneralUpdate.Common/Shared/Object/update_config.example.json new file mode 100644 index 00000000..ce6b7797 --- /dev/null +++ b/src/c#/GeneralUpdate.Common/Shared/Object/update_config.example.json @@ -0,0 +1,31 @@ +{ + "UpdateUrl": "https://api.example.com/updates", + "Token": "your-authentication-token-here", + "Scheme": "https", + "AppName": "Update.exe", + "MainAppName": "MyApplication.exe", + "ClientVersion": "1.0.0", + "UpgradeClientVersion": "1.0.0", + "AppSecretKey": "your-secret-key-here", + "ProductId": "your-product-id", + "InstallPath": "/path/to/installation", + "UpdateLogUrl": "https://example.com/changelog", + "ReportUrl": "https://api.example.com/report", + "Bowl": "Bowl.exe", + "Script": "#!/bin/bash\nchmod +x *", + "DriverDirectory": "/path/to/drivers", + "BlackFiles": [ + "config.json", + "user.dat" + ], + "BlackFormats": [ + ".log", + ".tmp", + ".cache", + ".bak" + ], + "SkipDirectorys": [ + "/temp", + "/logs" + ] +} From 55bc4b0a32cdc758dc5eb6c9909f448276f7d2b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:55:15 +0000 Subject: [PATCH 5/7] Address code review: improve exception handling with specific catch blocks Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --- .../Shared/Object/ConfiginfoBuilder.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs index dfb2c306..0fe57a99 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs @@ -135,9 +135,24 @@ private static ConfiginfoBuilder LoadFromConfigFile() return builder; } + catch (System.Text.Json.JsonException) + { + // Invalid JSON format, fall back to parameters + return null; + } + catch (IOException) + { + // File read error, fall back to parameters + return null; + } + catch (UnauthorizedAccessException) + { + // Permission denied, fall back to parameters + return null; + } catch { - // If there's any error reading or parsing the file, return null + // Any other unexpected error, fall back to parameters return null; } } @@ -184,6 +199,8 @@ private void InitializePlatformDefaults() _skipDirectorys = new List(); // Set default InstallPath to current program running directory + // This is set here to ensure the builder has a consistent default + // even though BaseConfigInfo also has this default via property initializer _installPath = AppDomain.CurrentDomain.BaseDirectory; } From 6917e7bb5a7637dfea2684f909cfef1d18330001 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 14:07:31 +0000 Subject: [PATCH 6/7] Address reviewer feedback: remove default values, add parameterless Create() method - Removed default values from DefaultBlackFormats (now empty array) - Removed default values from _mainAppName, _clientVersion, _upgradeClientVersion, _appSecretKey, _productId - Added parameterless Create() method that loads from update_config.json only - Kept parameterized Create(url, token, scheme) for programmatic configuration - Updated all tests to set required fields explicitly - Updated example code to demonstrate JSON-based configuration All 39 tests pass. Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --- .../CoreTest/Shared/ConfiginfoBuilderTests.cs | 110 +++++----- .../Object/ConfiginfoBuilder-Example.cs | 192 ++++++++---------- .../Shared/Object/ConfiginfoBuilder.cs | 47 +++-- 3 files changed, 172 insertions(+), 177 deletions(-) diff --git a/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs b/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs index 09df289d..c5255f5e 100644 --- a/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs +++ b/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs @@ -17,6 +17,18 @@ public class ConfiginfoBuilderTests private const string TestToken = "test-token-12345"; private const string TestScheme = "https"; + /// + /// Helper method to create a builder with all required fields set for testing. + /// Since defaults were removed per requirements, tests must explicitly set required fields. + /// + private ConfiginfoBuilder CreateBuilderWithRequiredFields() + { + return ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme) + .SetMainAppName("TestApp.exe") + .SetClientVersion("1.0.0") + .SetAppSecretKey("test-secret-key"); + } + #region Constructor Tests /// @@ -26,7 +38,7 @@ public class ConfiginfoBuilderTests public void Create_WithValidParameters_CreatesInstance() { // Act - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); // Assert Assert.NotNull(builder); @@ -39,8 +51,8 @@ public void Create_WithValidParameters_CreatesInstance() public void Create_ProducesConsistentResults() { // Act - var config1 = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme).Build(); - var config2 = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme).Build(); + var config1 = CreateBuilderWithRequiredFields().Build(); + var config2 = CreateBuilderWithRequiredFields().Build(); // Assert Assert.Equal(config1.UpdateUrl, config2.UpdateUrl); @@ -120,13 +132,16 @@ public void Create_WithNullScheme_ThrowsArgumentException() #region Build Method Tests /// - /// Tests that Build() creates a valid Configinfo object with default values. + /// Tests that Build() creates a valid Configinfo object when all required fields are set. /// [Fact] public void Build_WithMinimalParameters_ReturnsValidConfiginfo() { - // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + // Arrange - Now that defaults are removed, we must set all required fields + var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme) + .SetMainAppName("TestApp.exe") + .SetClientVersion("1.0.0") + .SetAppSecretKey("test-secret-key"); // Act var config = builder.Build(); @@ -150,7 +165,7 @@ public void Build_WithMinimalParameters_ReturnsValidConfiginfo() public void Build_GeneratesPlatformSpecificDefaults() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); // Act var config = builder.Build(); @@ -172,7 +187,7 @@ public void Build_GeneratesPlatformSpecificDefaults() public void Build_InitializesCollectionProperties() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); // Act var config = builder.Build(); @@ -181,8 +196,8 @@ public void Build_InitializesCollectionProperties() Assert.NotNull(config.BlackFiles); Assert.NotNull(config.BlackFormats); Assert.NotNull(config.SkipDirectorys); - Assert.Contains(".log", config.BlackFormats); - Assert.Contains(".tmp", config.BlackFormats); + // DefaultBlackFormats is now empty per requirements + Assert.Empty(config.BlackFormats); } #endregion @@ -196,7 +211,7 @@ public void Build_InitializesCollectionProperties() public void SetAppName_WithValidValue_SetsAppName() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var customAppName = "CustomApp.exe"; // Act @@ -213,7 +228,7 @@ public void SetAppName_WithValidValue_SetsAppName() public void SetAppName_ReturnsBuilder_ForMethodChaining() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); // Act var result = builder.SetAppName("Test.exe"); @@ -229,7 +244,7 @@ public void SetAppName_ReturnsBuilder_ForMethodChaining() public void SetAppName_WithNullValue_ThrowsArgumentException() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); // Act & Assert var exception = Assert.Throws(() => builder.SetAppName(null)); @@ -243,7 +258,7 @@ public void SetAppName_WithNullValue_ThrowsArgumentException() public void SetMainAppName_WithValidValue_SetsMainAppName() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var customMainAppName = "MainApp.exe"; // Act @@ -260,7 +275,7 @@ public void SetMainAppName_WithValidValue_SetsMainAppName() public void SetClientVersion_WithValidValue_SetsClientVersion() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var customVersion = "2.5.1"; // Act @@ -277,7 +292,7 @@ public void SetClientVersion_WithValidValue_SetsClientVersion() public void SetUpgradeClientVersion_WithValidValue_SetsUpgradeClientVersion() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var customVersion = "3.0.0"; // Act @@ -294,7 +309,7 @@ public void SetUpgradeClientVersion_WithValidValue_SetsUpgradeClientVersion() public void SetAppSecretKey_WithValidValue_SetsAppSecretKey() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var customSecretKey = "my-secret-key-123"; // Act @@ -311,7 +326,7 @@ public void SetAppSecretKey_WithValidValue_SetsAppSecretKey() public void SetProductId_WithValidValue_SetsProductId() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var customProductId = "product-xyz-789"; // Act @@ -328,7 +343,7 @@ public void SetProductId_WithValidValue_SetsProductId() public void SetInstallPath_WithValidValue_SetsInstallPath() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var customPath = "/custom/install/path"; // Act @@ -345,7 +360,7 @@ public void SetInstallPath_WithValidValue_SetsInstallPath() public void SetUpdateLogUrl_WithValidUrl_SetsUpdateLogUrl() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var logUrl = "https://example.com/changelog"; // Act @@ -362,7 +377,7 @@ public void SetUpdateLogUrl_WithValidUrl_SetsUpdateLogUrl() public void SetUpdateLogUrl_WithInvalidUrl_ThrowsArgumentException() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); // Act & Assert var exception = Assert.Throws(() => @@ -378,7 +393,7 @@ public void SetUpdateLogUrl_WithInvalidUrl_ThrowsArgumentException() public void SetReportUrl_WithValidUrl_SetsReportUrl() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var reportUrl = "https://example.com/report"; // Act @@ -395,7 +410,7 @@ public void SetReportUrl_WithValidUrl_SetsReportUrl() public void SetBowl_WithValidValue_SetsBowl() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var bowlProcess = "Bowl.exe"; // Act @@ -412,7 +427,7 @@ public void SetBowl_WithValidValue_SetsBowl() public void SetScript_WithValidValue_SetsScript() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var customScript = "#!/bin/bash\necho 'Hello'"; // Act @@ -429,7 +444,7 @@ public void SetScript_WithValidValue_SetsScript() public void SetDriverDirectory_WithValidValue_SetsDriverDirectory() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var driverDir = "/path/to/drivers"; // Act @@ -446,7 +461,7 @@ public void SetDriverDirectory_WithValidValue_SetsDriverDirectory() public void SetBlackFiles_WithValidList_SetsBlackFiles() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var blackFiles = new List { "file1.txt", "file2.dat" }; // Act @@ -463,7 +478,7 @@ public void SetBlackFiles_WithValidList_SetsBlackFiles() public void SetBlackFormats_WithValidList_SetsBlackFormats() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var blackFormats = new List { ".bak", ".old" }; // Act @@ -480,7 +495,7 @@ public void SetBlackFormats_WithValidList_SetsBlackFormats() public void SetSkipDirectorys_WithValidList_SetsSkipDirectorys() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); var skipDirs = new List { "/temp", "/cache" }; // Act @@ -534,7 +549,7 @@ public void Build_OnWindows_GeneratesWindowsDefaults() } // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); // Act var config = builder.Build(); @@ -559,7 +574,7 @@ public void Build_OnLinux_GeneratesLinuxDefaults() } // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); // Act var config = builder.Build(); @@ -584,7 +599,7 @@ public void Build_OnMacOS_GeneratesMacOSDefaults() } // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); // Act var config = builder.Build(); @@ -607,7 +622,7 @@ public void Build_OnMacOS_GeneratesMacOSDefaults() public void Build_ReturnsConfiginfoThatPassesValidation() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); // Act var config = builder.Build(); @@ -625,7 +640,7 @@ public void Build_ReturnsConfiginfoThatPassesValidation() public void Build_AttemptsToExtractAppNameFromProject() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields(); // Act var config = builder.Build(); @@ -644,27 +659,25 @@ public void Build_AttemptsToExtractAppNameFromProject() } /// - /// Tests that project metadata (version, company, etc.) is extracted from csproj when available. - /// The builder should attempt to extract Version and Company/Authors fields. + /// Tests that project metadata fields can be set and retrieved. + /// Since defaults were removed per requirements, fields are null unless explicitly set. /// [Fact] public void Build_AttemptsToExtractProjectMetadata() { // Arrange - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme); + var builder = CreateBuilderWithRequiredFields() + .SetProductId("test-product-id"); // Act var config = builder.Build(); - // Assert - Core fields should always be set (either extracted or defaults) + // Assert - Core fields should be set if explicitly provided Assert.NotNull(config.ClientVersion); Assert.NotEmpty(config.ClientVersion); Assert.NotNull(config.ProductId); Assert.NotEmpty(config.ProductId); - - // Version should follow a reasonable format if extracted (e.g., "1.0.0" or similar) - // If extracted from project file, it might have proper semantic versioning - // If using default, it should still be a valid string + Assert.Equal("test-product-id", config.ProductId); } /// @@ -725,6 +738,7 @@ public void Create_WithConfigFile_LoadsFromFile() AppName = "ConfigFileApp.exe", MainAppName = "ConfigFileMain.exe", ClientVersion = "9.9.9", + AppSecretKey = "config-file-secret", InstallPath = "/config/file/path" }; @@ -733,14 +747,10 @@ public void Create_WithConfigFile_LoadsFromFile() // Write test config file File.WriteAllText(configFilePath, System.Text.Json.JsonSerializer.Serialize(testConfig)); - // Act - Create should load from file instead of using parameters - var config = ConfiginfoBuilder.Create( - "https://should-be-ignored.com/updates", - "should-be-ignored-token", - "http" - ).Build(); + // Act - Use parameterless Create() to load from file + var config = ConfiginfoBuilder.Create().Build(); - // Assert - Values should come from config file, not parameters + // Assert - Values should come from config file Assert.Equal("https://config-file.example.com/updates", config.UpdateUrl); Assert.Equal("config-file-token", config.Token); Assert.Equal("https", config.Scheme); @@ -775,7 +785,7 @@ public void Create_WithoutConfigFile_UsesParameters() try { // Act - Create should use parameters - var config = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme).Build(); + var config = CreateBuilderWithRequiredFields().Build(); // Assert - Values should come from parameters and defaults Assert.Equal(TestUpdateUrl, config.UpdateUrl); @@ -804,7 +814,7 @@ public void Create_WithInvalidConfigFile_FallsBackToParameters() File.WriteAllText(configFilePath, "{ invalid json content !!!"); // Act - Create should fall back to parameters - var config = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme).Build(); + var config = CreateBuilderWithRequiredFields().Build(); // Assert - Values should come from parameters (fallback) Assert.Equal(TestUpdateUrl, config.UpdateUrl); diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder-Example.cs b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder-Example.cs index ef80f24f..bda8fac9 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder-Example.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder-Example.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; using GeneralUpdate.Common.Shared.Object; namespace ConfiginfoBuilderExample { /// - /// Example demonstrating the ConfiginfoBuilder usage + /// Example demonstrating the ConfiginfoBuilder usage with JSON configuration /// class Program { @@ -13,134 +14,105 @@ static void Main(string[] args) { Console.WriteLine("=== ConfiginfoBuilder Usage Examples ===\n"); - // Example 1: Minimal configuration (recommended for most cases) - Console.WriteLine("Example 1: Minimal Configuration - Using Factory Method"); - var minimalConfig = ConfiginfoBuilder - .Create("https://api.example.com/updates", "your-auth-token", "https") - .Build(); + // Example 1: Load configuration from JSON file (recommended) + Console.WriteLine("Example 1: Loading from update_config.json file"); + Console.WriteLine("This example requires an update_config.json file in the running directory."); + Console.WriteLine("The configuration file has the highest priority and must contain all required settings.\n"); - Console.WriteLine($" UpdateUrl: {minimalConfig.UpdateUrl}"); - Console.WriteLine($" Token: {minimalConfig.Token}"); - Console.WriteLine($" Scheme: {minimalConfig.Scheme}"); - Console.WriteLine($" InstallPath: {minimalConfig.InstallPath}"); - Console.WriteLine($" AppName: {minimalConfig.AppName}"); - Console.WriteLine($" Default Black Formats: {string.Join(", ", ConfiginfoBuilder.DefaultBlackFormats)}"); - Console.WriteLine(); - - // Example 2: Custom configuration with method chaining - Console.WriteLine("Example 2: Custom Configuration - Using Create Method"); - var customConfig = ConfiginfoBuilder.Create( - "https://api.example.com/updates", - "Bearer abc123xyz", - "https" - ) - .SetAppName("MyApplication.exe") - .SetMainAppName("MyApplication.exe") - .SetClientVersion("2.1.0") - .SetInstallPath("/opt/myapp") - .SetAppSecretKey("super-secret-key-789") - .Build(); - - Console.WriteLine($" AppName: {customConfig.AppName}"); - Console.WriteLine($" ClientVersion: {customConfig.ClientVersion}"); - Console.WriteLine($" InstallPath: {customConfig.InstallPath}"); - Console.WriteLine($" AppSecretKey: {customConfig.AppSecretKey}"); - Console.WriteLine(); - - // Example 3: Configuration with file filters - Console.WriteLine("Example 3: With File Filters"); - var filteredConfig = ConfiginfoBuilder.Create( - "https://api.example.com/updates", - "token123", - "https" - ) - .SetBlackFiles(new List { "config.json", "user.dat" }) - .SetBlackFormats(new List { ".log", ".tmp", ".cache", ".bak" }) - .SetSkipDirectorys(new List { "/temp", "/logs" }) - .Build(); - - Console.WriteLine($" Black Files: {string.Join(", ", filteredConfig.BlackFiles)}"); - Console.WriteLine($" Black Formats: {string.Join(", ", filteredConfig.BlackFormats)}"); - Console.WriteLine($" Skip Directories: {string.Join(", ", filteredConfig.SkipDirectorys)}"); - Console.WriteLine(); - - // Example 4: Complete configuration - Console.WriteLine("Example 4: Complete Configuration"); - var completeConfig = ConfiginfoBuilder.Create( - updateUrl: "https://api.example.com/updates", - token: "Bearer xyz789", - scheme: "https" - ) - .SetAppName("MyApp.exe") - .SetMainAppName("MyApp.exe") - .SetClientVersion("3.0.0") - .SetUpgradeClientVersion("1.5.0") - .SetProductId("myapp-001") - .SetAppSecretKey("secret-key-456") - .SetInstallPath("/opt/myapp") - .SetUpdateLogUrl("https://myapp.example.com/changelog") - .SetReportUrl("https://api.example.com/report") - .SetBowl("Bowl.exe") - .SetDriverDirectory("/opt/myapp/drivers") - .Build(); - - Console.WriteLine($" ProductId: {completeConfig.ProductId}"); - Console.WriteLine($" UpdateLogUrl: {completeConfig.UpdateLogUrl}"); - Console.WriteLine($" ReportUrl: {completeConfig.ReportUrl}"); - Console.WriteLine($" Bowl: {completeConfig.Bowl}"); - Console.WriteLine(); - - // Example 5: Error handling - Console.WriteLine("Example 5: Error Handling"); try { - // Note: Create method loads from config file if available - // For demonstration, we'll show that invalid params would fail - // if no config file exists - var invalidConfig = ConfiginfoBuilder.Create( - null, // Invalid: null URL - "token", - "https" - ); + // Create update_config.json for demonstration + CreateExampleConfigFile(); + + // Simply call Create() with no parameters - it loads from update_config.json + var config = ConfiginfoBuilder.Create().Build(); + + Console.WriteLine($" UpdateUrl: {config.UpdateUrl}"); + Console.WriteLine($" Token: {config.Token}"); + Console.WriteLine($" Scheme: {config.Scheme}"); + Console.WriteLine($" InstallPath: {config.InstallPath}"); + Console.WriteLine($" AppName: {config.AppName}"); + Console.WriteLine($" ClientVersion: {config.ClientVersion}"); + Console.WriteLine(); } - catch (ArgumentException ex) + catch (FileNotFoundException ex) { - Console.WriteLine($" Caught expected error: {ex.Message}"); + Console.WriteLine($" Error: {ex.Message}"); + Console.WriteLine(" Please create update_config.json in the running directory."); + Console.WriteLine(); + } + finally + { + CleanupExampleConfigFile(); } + // Example 2: Customizing configuration after loading from file + Console.WriteLine("Example 2: Loading from JSON and customizing with method chaining"); try { - var invalidConfig2 = ConfiginfoBuilder.Create( - "not-a-url", // Invalid: malformed URL - "token", - "https" - ); + CreateExampleConfigFile(); + + var customConfig = ConfiginfoBuilder.Create() + .SetAppName("CustomApp.exe") + .SetInstallPath("/custom/path") + .Build(); + + Console.WriteLine($" AppName: {customConfig.AppName}"); + Console.WriteLine($" InstallPath: {customConfig.InstallPath}"); + Console.WriteLine(); } - catch (ArgumentException ex) + catch (FileNotFoundException ex) { - Console.WriteLine($" Caught expected error: {ex.Message}"); + Console.WriteLine($" Error: {ex.Message}"); + Console.WriteLine(); + } + finally + { + CleanupExampleConfigFile(); } - Console.WriteLine(); - // Example 6: Validate configuration - Console.WriteLine("Example 6: Configuration Validation"); - var validConfig = ConfiginfoBuilder.Create( - "https://api.example.com/updates", - "token", - "https" - ).Build(); - + // Example 3: Error handling when config file is missing + Console.WriteLine("Example 3: Error Handling - Missing Configuration File"); try { - validConfig.Validate(); - Console.WriteLine(" Configuration is valid!"); + var config = ConfiginfoBuilder.Create().Build(); } - catch (ArgumentException ex) + catch (FileNotFoundException ex) { - Console.WriteLine($" Validation failed: {ex.Message}"); + Console.WriteLine($" Caught expected error: {ex.Message}"); + Console.WriteLine(" This is expected when update_config.json doesn't exist."); } + Console.WriteLine(); + + Console.WriteLine("\n=== All Examples Completed! ==="); + Console.WriteLine("\nNote: ConfiginfoBuilder now requires update_config.json file."); + Console.WriteLine("See update_config.example.json for a complete example."); + } + + private static void CreateExampleConfigFile() + { + var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json"); + var exampleConfig = @"{ + ""UpdateUrl"": ""https://api.example.com/updates"", + ""Token"": ""example-auth-token"", + ""Scheme"": ""https"", + ""AppName"": ""Update.exe"", + ""MainAppName"": ""MyApplication.exe"", + ""ClientVersion"": ""1.0.0"", + ""UpgradeClientVersion"": ""1.0.0"", + ""AppSecretKey"": ""example-secret-key"", + ""ProductId"": ""example-product-id"" +}"; + File.WriteAllText(configPath, exampleConfig); + } - Console.WriteLine("\n=== All Examples Completed Successfully! ==="); + private static void CleanupExampleConfigFile() + { + var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json"); + if (File.Exists(configPath)) + { + File.Delete(configPath); + } } } } diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs index 0fe57a99..5ff78b6c 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs @@ -20,7 +20,7 @@ public class ConfiginfoBuilder static ConfiginfoBuilder() { - DefaultBlackFormats = new[] { ".log", ".tmp", ".cache", ".bak" }; + DefaultBlackFormats = new string[0]; } private readonly string _updateUrl; @@ -31,11 +31,11 @@ static ConfiginfoBuilder() // Note: AppName and InstallPath defaults are set in Configinfo class itself // These are ConfiginfoBuilder-specific defaults to support the builder pattern private string _appName = "Update.exe"; - private string _mainAppName = "App.exe"; - private string _clientVersion = "1.0.0"; - private string _upgradeClientVersion = "1.0.0"; - private string _appSecretKey = "default-secret-key"; - private string _productId = "default-product-id"; + private string _mainAppName; + private string _clientVersion; + private string _upgradeClientVersion; + private string _appSecretKey; + private string _productId; private string _installPath; private string _updateLogUrl; private string _reportUrl; @@ -46,11 +46,32 @@ static ConfiginfoBuilder() private List _blackFormats; private List _skipDirectorys; + /// + /// Creates a new ConfiginfoBuilder instance by loading configuration from update_config.json file. + /// The configuration file must exist in the running directory and contain all required settings. + /// This method has the highest priority - configuration file settings override any code-based settings. + /// + /// A new ConfiginfoBuilder instance with settings loaded from the configuration file. + /// Thrown when update_config.json is not found. + /// Thrown when the configuration file is invalid or cannot be loaded. + public static ConfiginfoBuilder Create() + { + // Try to load from configuration file + var configFromFile = LoadFromConfigFile(); + if (configFromFile != null) + { + // Configuration file loaded successfully + return configFromFile; + } + + // If no config file exists, throw an exception + throw new FileNotFoundException("Configuration file 'update_config.json' not found in the running directory. Please create this file with the required settings."); + } + /// /// Creates a new ConfiginfoBuilder instance using the specified update URL, authentication token, and scheme. - /// This is the primary factory method for creating a builder with zero-configuration defaults. - /// If update_config.json exists in the running directory, it will be loaded with highest priority. - /// All other configuration properties will be automatically initialized with platform-appropriate defaults. + /// This method is provided for programmatic configuration when not using a JSON configuration file. + /// Note: If update_config.json exists, use the parameterless Create() method instead as it has higher priority. /// /// The API endpoint URL for checking available updates. Must be a valid absolute URI. /// The authentication token used for API requests. @@ -59,14 +80,6 @@ static ConfiginfoBuilder() /// Thrown when any required parameter is null, empty, or invalid. public static ConfiginfoBuilder Create(string updateUrl, string token, string scheme) { - // Try to load from configuration file first - var configFromFile = LoadFromConfigFile(); - if (configFromFile != null) - { - // Configuration file has highest priority, return directly - return configFromFile; - } - return new ConfiginfoBuilder(updateUrl, token, scheme); } From 8c1cabdbff5f4945ae7cc179acbc08a79dffe723 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 14:20:34 +0000 Subject: [PATCH 7/7] Remove parameterized Create() method - JSON config file only - Removed Create(url, token, scheme) overload per reviewer feedback - Only parameterless Create() method remains, which loads exclusively from update_config.json - Updated all tests to create JSON config files instead of using parameterized Create - Added helper methods CreateTestConfigFile() and CleanupTestConfigFile() for tests - All 37 ConfiginfoBuilder tests passing Configuration must now be done through JSON file - highest priority per requirements. Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --- .../CoreTest/Shared/ConfiginfoBuilderTests.cs | 394 +++++++++++------- .../Shared/Object/ConfiginfoBuilder.cs | 17 +- 2 files changed, 244 insertions(+), 167 deletions(-) diff --git a/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs b/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs index c5255f5e..5d855e9e 100644 --- a/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs +++ b/src/c#/CoreTest/Shared/ConfiginfoBuilderTests.cs @@ -18,113 +18,166 @@ public class ConfiginfoBuilderTests private const string TestScheme = "https"; /// - /// Helper method to create a builder with all required fields set for testing. - /// Since defaults were removed per requirements, tests must explicitly set required fields. + /// Helper method to create a test config file with all required fields. /// - private ConfiginfoBuilder CreateBuilderWithRequiredFields() + private void CreateTestConfigFile() { - return ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme) - .SetMainAppName("TestApp.exe") - .SetClientVersion("1.0.0") - .SetAppSecretKey("test-secret-key"); + var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json"); + var testConfig = new + { + UpdateUrl = TestUpdateUrl, + Token = TestToken, + Scheme = TestScheme, + AppName = "Update.exe", + MainAppName = "TestApp.exe", + ClientVersion = "1.0.0", + AppSecretKey = "test-secret-key", + InstallPath = AppDomain.CurrentDomain.BaseDirectory + }; + File.WriteAllText(configPath, System.Text.Json.JsonSerializer.Serialize(testConfig)); } - #region Constructor Tests - /// - /// Tests that the Create factory method properly initializes with valid parameters. + /// Helper method to clean up test config file. /// - [Fact] - public void Create_WithValidParameters_CreatesInstance() + private void CleanupTestConfigFile() { - // Act - var builder = CreateBuilderWithRequiredFields(); - - // Assert - Assert.NotNull(builder); + var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json"); + if (File.Exists(configPath)) + { + File.Delete(configPath); + } } /// - /// Tests that Create factory method produces consistent results. + /// Helper method to create a builder with all required fields set for testing. + /// Creates a config file, loads it, and returns the builder. /// - [Fact] - public void Create_ProducesConsistentResults() + private ConfiginfoBuilder CreateBuilderWithRequiredFields() { - // Act - var config1 = CreateBuilderWithRequiredFields().Build(); - var config2 = CreateBuilderWithRequiredFields().Build(); - - // Assert - Assert.Equal(config1.UpdateUrl, config2.UpdateUrl); - Assert.Equal(config1.Token, config2.Token); - Assert.Equal(config1.Scheme, config2.Scheme); - Assert.Equal(config1.AppName, config2.AppName); + CreateTestConfigFile(); + return ConfiginfoBuilder.Create(); } + #region Constructor Tests + /// - /// Tests that the Create method throws ArgumentException when UpdateUrl is null. + /// Tests that the Create factory method properly initializes from config file. /// [Fact] - public void Create_WithNullUpdateUrl_ThrowsArgumentException() + public void Create_WithValidConfigFile_CreatesInstance() { - // Act & Assert - var exception = Assert.Throws(() => - ConfiginfoBuilder.Create(null, TestToken, TestScheme)); - - Assert.Contains("UpdateUrl", exception.Message); + try + { + // Arrange + CreateTestConfigFile(); + + // Act + var builder = ConfiginfoBuilder.Create(); + + // Assert + Assert.NotNull(builder); + } + finally + { + CleanupTestConfigFile(); + } } /// - /// Tests that the Create method throws ArgumentException when UpdateUrl is empty. + /// Tests that Create factory method produces consistent results. /// [Fact] - public void Create_WithEmptyUpdateUrl_ThrowsArgumentException() + public void Create_ProducesConsistentResults() { - // Act & Assert - var exception = Assert.Throws(() => - ConfiginfoBuilder.Create("", TestToken, TestScheme)); - - Assert.Contains("UpdateUrl", exception.Message); + try + { + // Arrange + CreateTestConfigFile(); + + // Act + var config1 = ConfiginfoBuilder.Create().Build(); + var config2 = ConfiginfoBuilder.Create().Build(); + + // Assert + Assert.Equal(config1.UpdateUrl, config2.UpdateUrl); + Assert.Equal(config1.Token, config2.Token); + Assert.Equal(config1.Scheme, config2.Scheme); + Assert.Equal(config1.AppName, config2.AppName); + } + finally + { + CleanupTestConfigFile(); + } } /// - /// Tests that the Create method throws ArgumentException when UpdateUrl is not a valid URI. + /// Tests that the Create method throws FileNotFoundException when config file is missing. /// [Fact] - public void Create_WithInvalidUpdateUrl_ThrowsArgumentException() + public void Create_WithoutConfigFile_ThrowsFileNotFoundException() { + // Arrange - ensure no config file exists + CleanupTestConfigFile(); + // Act & Assert - var exception = Assert.Throws(() => - ConfiginfoBuilder.Create("not-a-valid-url", TestToken, TestScheme)); + var exception = Assert.Throws(() => + ConfiginfoBuilder.Create()); - Assert.Contains("UpdateUrl", exception.Message); - Assert.Contains("valid absolute URI", exception.Message); + Assert.Contains("update_config.json", exception.Message); } /// - /// Tests that the Create method throws ArgumentException when Token is null. + /// Tests that the Create method handles invalid JSON gracefully. /// [Fact] - public void Create_WithNullToken_ThrowsArgumentException() + public void Create_WithInvalidJson_ThrowsFileNotFoundException() { - // Act & Assert - var exception = Assert.Throws(() => - ConfiginfoBuilder.Create(TestUpdateUrl, null, TestScheme)); - - Assert.Contains("Token", exception.Message); + try + { + // Arrange - create invalid JSON file + var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json"); + File.WriteAllText(configPath, "{ invalid json content"); + + // Act & Assert + var exception = Assert.Throws(() => + ConfiginfoBuilder.Create()); + + Assert.Contains("update_config.json", exception.Message); + } + finally + { + CleanupTestConfigFile(); + } } /// - /// Tests that the Create method throws ArgumentException when Scheme is null. + /// Tests that the Create method validates required fields from config file. /// [Fact] - public void Create_WithNullScheme_ThrowsArgumentException() + public void Create_WithIncompleteConfig_ThrowsOnBuild() { - // Act & Assert - var exception = Assert.Throws(() => - ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, null)); - - Assert.Contains("Scheme", exception.Message); + try + { + // Arrange - create config with missing required fields + var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json"); + var incompleteConfig = new + { + UpdateUrl = TestUpdateUrl, + Token = TestToken, + Scheme = TestScheme + // Missing MainAppName, ClientVersion, AppSecretKey + }; + File.WriteAllText(configPath, System.Text.Json.JsonSerializer.Serialize(incompleteConfig)); + + // Act & Assert + var builder = ConfiginfoBuilder.Create(); + Assert.Throws(() => builder.Build()); + } + finally + { + CleanupTestConfigFile(); + } } #endregion @@ -137,25 +190,30 @@ public void Create_WithNullScheme_ThrowsArgumentException() [Fact] public void Build_WithMinimalParameters_ReturnsValidConfiginfo() { - // Arrange - Now that defaults are removed, we must set all required fields - var builder = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme) - .SetMainAppName("TestApp.exe") - .SetClientVersion("1.0.0") - .SetAppSecretKey("test-secret-key"); + try + { + // Arrange - Now that defaults are removed, we must set all required fields via config file + CreateTestConfigFile(); + var builder = ConfiginfoBuilder.Create(); - // Act - var config = builder.Build(); + // Act + var config = builder.Build(); - // Assert - Assert.NotNull(config); - Assert.Equal(TestUpdateUrl, config.UpdateUrl); - Assert.Equal(TestToken, config.Token); - Assert.Equal(TestScheme, config.Scheme); - Assert.NotNull(config.AppName); - Assert.NotNull(config.MainAppName); - Assert.NotNull(config.ClientVersion); - Assert.NotNull(config.InstallPath); - Assert.NotNull(config.AppSecretKey); + // Assert + Assert.NotNull(config); + Assert.Equal(TestUpdateUrl, config.UpdateUrl); + Assert.Equal(TestToken, config.Token); + Assert.Equal(TestScheme, config.Scheme); + Assert.NotNull(config.AppName); + Assert.NotNull(config.MainAppName); + Assert.NotNull(config.ClientVersion); + Assert.NotNull(config.InstallPath); + Assert.NotNull(config.AppSecretKey); + } + finally + { + CleanupTestConfigFile(); + } } /// @@ -164,20 +222,27 @@ public void Build_WithMinimalParameters_ReturnsValidConfiginfo() [Fact] public void Build_GeneratesPlatformSpecificDefaults() { - // Arrange - var builder = CreateBuilderWithRequiredFields(); - - // Act - var config = builder.Build(); - - // Assert - Assert.NotNull(config.InstallPath); - - // InstallPath should be the current application's base directory - Assert.Equal(AppDomain.CurrentDomain.BaseDirectory, config.InstallPath); - - // According to requirements, AppName default is "Update.exe" regardless of platform - Assert.Equal("Update.exe", config.AppName); + try + { + // Arrange + var builder = CreateBuilderWithRequiredFields(); + + // Act + var config = builder.Build(); + + // Assert + Assert.NotNull(config.InstallPath); + + // InstallPath should be the current application's base directory + Assert.Equal(AppDomain.CurrentDomain.BaseDirectory, config.InstallPath); + + // According to requirements, AppName default is "Update.exe" regardless of platform + Assert.Equal("Update.exe", config.AppName); + } + finally + { + CleanupTestConfigFile(); + } } /// @@ -186,18 +251,25 @@ public void Build_GeneratesPlatformSpecificDefaults() [Fact] public void Build_InitializesCollectionProperties() { - // Arrange - var builder = CreateBuilderWithRequiredFields(); - - // Act - var config = builder.Build(); - - // Assert - Assert.NotNull(config.BlackFiles); - Assert.NotNull(config.BlackFormats); - Assert.NotNull(config.SkipDirectorys); - // DefaultBlackFormats is now empty per requirements - Assert.Empty(config.BlackFormats); + try + { + // Arrange + var builder = CreateBuilderWithRequiredFields(); + + // Act + var config = builder.Build(); + + // Assert + Assert.NotNull(config.BlackFiles); + Assert.NotNull(config.BlackFormats); + Assert.NotNull(config.SkipDirectorys); + // DefaultBlackFormats is now empty per requirements + Assert.Empty(config.BlackFormats); + } + finally + { + CleanupTestConfigFile(); + } } #endregion @@ -515,21 +587,29 @@ public void SetSkipDirectorys_WithValidList_SetsSkipDirectorys() [Fact] public void BuilderPattern_SupportsMethodChaining() { - // Arrange & Act - var config = ConfiginfoBuilder.Create(TestUpdateUrl, TestToken, TestScheme) - .SetAppName("CustomApp.exe") - .SetMainAppName("MainCustomApp.exe") - .SetClientVersion("2.0.0") - .SetInstallPath("/custom/path") - .SetAppSecretKey("custom-secret") - .Build(); - - // Assert - Assert.Equal("CustomApp.exe", config.AppName); - Assert.Equal("MainCustomApp.exe", config.MainAppName); - Assert.Equal("2.0.0", config.ClientVersion); - Assert.Equal("/custom/path", config.InstallPath); - Assert.Equal("custom-secret", config.AppSecretKey); + try + { + // Arrange & Act + CreateTestConfigFile(); + var config = ConfiginfoBuilder.Create() + .SetAppName("CustomApp.exe") + .SetMainAppName("MainCustomApp.exe") + .SetClientVersion("2.0.0") + .SetInstallPath("/custom/path") + .SetAppSecretKey("custom-secret") + .Build(); + + // Assert + Assert.Equal("CustomApp.exe", config.AppName); + Assert.Equal("MainCustomApp.exe", config.MainAppName); + Assert.Equal("2.0.0", config.ClientVersion); + Assert.Equal("/custom/path", config.InstallPath); + Assert.Equal("custom-secret", config.AppSecretKey); + } + finally + { + CleanupTestConfigFile(); + } } #endregion @@ -686,36 +766,48 @@ public void Build_AttemptsToExtractProjectMetadata() [Fact] public void CompleteScenario_BuildsValidConfiginfo() { - // Arrange - var updateUrl = "https://api.example.com/updates"; - var token = "Bearer abc123xyz"; - var scheme = "https"; - - // Act - var config = ConfiginfoBuilder.Create(updateUrl, token, scheme) - .SetAppName("MyApplication.exe") - .SetMainAppName("MyApplication.exe") - .SetClientVersion("1.5.2") - .SetUpgradeClientVersion("1.0.0") - .SetAppSecretKey("super-secret-key-456") - .SetProductId("my-product-001") - .SetInstallPath("/opt/myapp") - .SetUpdateLogUrl("https://example.com/changelog") - .SetReportUrl("https://api.example.com/report") - .SetBlackFormats(new List { ".log", ".tmp", ".cache" }) - .Build(); - - // Assert - Assert.NotNull(config); - Assert.Equal(updateUrl, config.UpdateUrl); - Assert.Equal(token, config.Token); - Assert.Equal(scheme, config.Scheme); - Assert.Equal("MyApplication.exe", config.AppName); - Assert.Equal("1.5.2", config.ClientVersion); - Assert.Equal("/opt/myapp", config.InstallPath); - - // Should pass validation - config.Validate(); + try + { + // Arrange + var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update_config.json"); + var completeConfig = new + { + UpdateUrl = "https://api.example.com/updates", + Token = "Bearer abc123xyz", + Scheme = "https", + AppName = "MyApplication.exe", + MainAppName = "MyApplication.exe", + ClientVersion = "1.5.2", + UpgradeClientVersion = "1.0.0", + AppSecretKey = "super-secret-key-456", + ProductId = "my-product-001", + InstallPath = "/opt/myapp", + UpdateLogUrl = "https://example.com/changelog", + ReportUrl = "https://api.example.com/report", + BlackFormats = new[] { ".log", ".tmp", ".cache" } + }; + File.WriteAllText(configPath, System.Text.Json.JsonSerializer.Serialize(completeConfig)); + + // Act + var config = ConfiginfoBuilder.Create() + .Build(); + + // Assert + Assert.NotNull(config); + Assert.Equal("https://api.example.com/updates", config.UpdateUrl); + Assert.Equal("Bearer abc123xyz", config.Token); + Assert.Equal("https", config.Scheme); + Assert.Equal("MyApplication.exe", config.AppName); + Assert.Equal("1.5.2", config.ClientVersion); + Assert.Equal("/opt/myapp", config.InstallPath); + + // Should pass validation + config.Validate(); + } + finally + { + CleanupTestConfigFile(); + } } #endregion diff --git a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs index 5ff78b6c..3051988c 100644 --- a/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs +++ b/src/c#/GeneralUpdate.Common/Shared/Object/ConfiginfoBuilder.cs @@ -49,7 +49,7 @@ static ConfiginfoBuilder() /// /// Creates a new ConfiginfoBuilder instance by loading configuration from update_config.json file. /// The configuration file must exist in the running directory and contain all required settings. - /// This method has the highest priority - configuration file settings override any code-based settings. + /// Configuration file has the highest priority - all settings must be specified in the JSON file. /// /// A new ConfiginfoBuilder instance with settings loaded from the configuration file. /// Thrown when update_config.json is not found. @@ -68,21 +68,6 @@ public static ConfiginfoBuilder Create() throw new FileNotFoundException("Configuration file 'update_config.json' not found in the running directory. Please create this file with the required settings."); } - /// - /// Creates a new ConfiginfoBuilder instance using the specified update URL, authentication token, and scheme. - /// This method is provided for programmatic configuration when not using a JSON configuration file. - /// Note: If update_config.json exists, use the parameterless Create() method instead as it has higher priority. - /// - /// The API endpoint URL for checking available updates. Must be a valid absolute URI. - /// The authentication token used for API requests. - /// The URL scheme used for update requests (e.g., "http" or "https"). - /// A new ConfiginfoBuilder instance with all defaults initialized. - /// Thrown when any required parameter is null, empty, or invalid. - public static ConfiginfoBuilder Create(string updateUrl, string token, string scheme) - { - return new ConfiginfoBuilder(updateUrl, token, scheme); - } - /// /// Loads configuration from update_config.json file in the running directory. ///