Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions src/c#/GeneralUpdate.sln
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExtensionTest", "ExtensionT
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GeneralUpdate.Ext", "GeneralUpdate.Ext\GeneralUpdate.Ext.csproj", "{27028918-925E-45D4-BD72-199349B6E6AA}"
EndProject
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The solution file shows that the BowlTest project is replacing ClientCoreTest project (GUID 18E96D5E-9D34-4047-B75E-7D832A055FD2 is being removed and GUID 46165893-12E1-4131-8D2A-BD18676B1CEE is being added). However, the ClientCoreTest directory still exists in the tests folder. This appears to be an error - the change should add BowlTest without removing ClientCoreTest. The ClientCoreTest project should remain in the solution alongside the new BowlTest project.

Suggested change
EndProject
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClientCoreTest", "..\..\tests\ClientCoreTest\ClientCoreTest.csproj", "{18E96D5E-9D34-4047-B75E-7D832A055FD2}"
EndProject

Copilot uses AI. Check for mistakes.
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClientCoreTest", "..\..\tests\ClientCoreTest\ClientCoreTest.csproj", "{18E96D5E-9D34-4047-B75E-7D832A055FD2}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BowlTest", "..\..\tests\BowlTest\BowlTest.csproj", "{46165893-12E1-4131-8D2A-BD18676B1CEE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -175,18 +175,18 @@ Global
{27028918-925E-45D4-BD72-199349B6E6AA}.Release|x64.Build.0 = Release|Any CPU
{27028918-925E-45D4-BD72-199349B6E6AA}.Release|x86.ActiveCfg = Release|Any CPU
{27028918-925E-45D4-BD72-199349B6E6AA}.Release|x86.Build.0 = Release|Any CPU
{18E96D5E-9D34-4047-B75E-7D832A055FD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{18E96D5E-9D34-4047-B75E-7D832A055FD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18E96D5E-9D34-4047-B75E-7D832A055FD2}.Debug|x64.ActiveCfg = Debug|Any CPU
{18E96D5E-9D34-4047-B75E-7D832A055FD2}.Debug|x64.Build.0 = Debug|Any CPU
{18E96D5E-9D34-4047-B75E-7D832A055FD2}.Debug|x86.ActiveCfg = Debug|Any CPU
{18E96D5E-9D34-4047-B75E-7D832A055FD2}.Debug|x86.Build.0 = Debug|Any CPU
{18E96D5E-9D34-4047-B75E-7D832A055FD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18E96D5E-9D34-4047-B75E-7D832A055FD2}.Release|Any CPU.Build.0 = Release|Any CPU
{18E96D5E-9D34-4047-B75E-7D832A055FD2}.Release|x64.ActiveCfg = Release|Any CPU
{18E96D5E-9D34-4047-B75E-7D832A055FD2}.Release|x64.Build.0 = Release|Any CPU
{18E96D5E-9D34-4047-B75E-7D832A055FD2}.Release|x86.ActiveCfg = Release|Any CPU
{18E96D5E-9D34-4047-B75E-7D832A055FD2}.Release|x86.Build.0 = Release|Any CPU
{46165893-12E1-4131-8D2A-BD18676B1CEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{46165893-12E1-4131-8D2A-BD18676B1CEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{46165893-12E1-4131-8D2A-BD18676B1CEE}.Debug|x64.ActiveCfg = Debug|Any CPU
{46165893-12E1-4131-8D2A-BD18676B1CEE}.Debug|x64.Build.0 = Debug|Any CPU
{46165893-12E1-4131-8D2A-BD18676B1CEE}.Debug|x86.ActiveCfg = Debug|Any CPU
{46165893-12E1-4131-8D2A-BD18676B1CEE}.Debug|x86.Build.0 = Debug|Any CPU
{46165893-12E1-4131-8D2A-BD18676B1CEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{46165893-12E1-4131-8D2A-BD18676B1CEE}.Release|Any CPU.Build.0 = Release|Any CPU
{46165893-12E1-4131-8D2A-BD18676B1CEE}.Release|x64.ActiveCfg = Release|Any CPU
{46165893-12E1-4131-8D2A-BD18676B1CEE}.Release|x64.Build.0 = Release|Any CPU
{46165893-12E1-4131-8D2A-BD18676B1CEE}.Release|x86.ActiveCfg = Release|Any CPU
{46165893-12E1-4131-8D2A-BD18676B1CEE}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -203,6 +203,7 @@ Global
{7B8E164F-C2D8-4C5F-8E20-450FB56AEB34} = {91F059E6-7AD3-4FB7-9604-30A7849C6EFF}
{3FA78F9A-A942-4F7E-A30D-70DF42E8A83D} = {245C36B9-612E-4AB0-979A-4D83A0D8117E}
{27028918-925E-45D4-BD72-199349B6E6AA} = {74BE0282-A10D-4A81-A0F0-FAA79A6152B7}
{46165893-12E1-4131-8D2A-BD18676B1CEE} = {245C36B9-612E-4AB0-979A-4D83A0D8117E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A7B2D0AD-E000-4749-BAC0-FF21B9872805}
Expand Down
26 changes: 26 additions & 0 deletions tests/BowlTest/BowlTest.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="xunit" Version="2.9.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4"/>
<PackageReference Include="Moq" Version="4.20.72"/>
</ItemGroup>

<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\c#\GeneralUpdate.Bowl\GeneralUpdate.Bowl.csproj" />
</ItemGroup>

</Project>
241 changes: 241 additions & 0 deletions tests/BowlTest/BowlTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
using System;
using System.Runtime.InteropServices;
using GeneralUpdate.Bowl;
using GeneralUpdate.Bowl.Strategys;

namespace BowlTest
{
/// <summary>
/// Contains test cases for the Bowl class.
/// Tests the main entry point and parameter creation logic.
/// </summary>
public class BowlTests
{
/// <summary>
/// Tests that Launch with valid MonitorParameter doesn't throw on Windows.
/// This test can only run on Windows as Linux is not fully implemented.
/// </summary>
[Fact]
public void Launch_WithValidParameter_DoesNotThrow_OnWindows()
{
// This test validates the code path but requires Windows platform
// and valid file paths to fully execute. We verify it doesn't throw
// during initialization phase.

if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Skip on non-Windows platforms
return;
}

// Arrange
var tempPath = System.IO.Path.GetTempPath();
var testPath = System.IO.Path.Combine(tempPath, $"BowlTest_{Guid.NewGuid()}");
System.IO.Directory.CreateDirectory(testPath);

try
{
var parameter = new MonitorParameter
{
TargetPath = testPath,
FailDirectory = System.IO.Path.Combine(testPath, "fail"),
BackupDirectory = System.IO.Path.Combine(testPath, "backup"),
ProcessNameOrId = "notepad.exe",
DumpFileName = "test.dmp",
FailFileName = "test.json",
WorkModel = "Normal",
ExtendedField = "1.0.0"
};

// Act & Assert
// Note: This will fail when it tries to launch procdump as it won't exist
// But we're testing that the initialization doesn't throw
var exception = Record.Exception(() => Bowl.Launch(parameter));

// We expect some kind of exception since procdump won't exist
// but not ArgumentNullException or PlatformNotSupportedException
Assert.True(exception == null ||
(exception is not ArgumentNullException &&
exception is not PlatformNotSupportedException));
}
finally
{
// Cleanup
if (System.IO.Directory.Exists(testPath))
{
System.IO.Directory.Delete(testPath, true);
}
}
}

/// <summary>
/// Tests that Launch throws PlatformNotSupportedException on unsupported platforms.
/// This test verifies the platform detection logic.
/// </summary>
[Fact]
public void Launch_OnUnsupportedPlatform_ThrowsPlatformNotSupportedException()
{
// This test verifies platform detection
// On Windows, it should work; on other platforms, it should throw

if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Arrange
var parameter = new MonitorParameter
{
TargetPath = "/tmp/test",
FailDirectory = "/tmp/test/fail",
BackupDirectory = "/tmp/test/backup",
ProcessNameOrId = "test",
DumpFileName = "test.dmp",
FailFileName = "test.json"
};

// Act & Assert
Assert.Throws<PlatformNotSupportedException>(() => Bowl.Launch(parameter));
}
}

/// <summary>
/// Tests that CreateParameter throws when ProcessInfo environment variable is null.
/// This test uses reflection to test the private CreateParameter method.
/// </summary>
[Fact]
public void CreateParameter_WithNullEnvironmentVariable_ThrowsArgumentNullException()
{
// Arrange
var originalValue = Environment.GetEnvironmentVariable("ProcessInfo");
Environment.SetEnvironmentVariable("ProcessInfo", null);

try
{
// Act & Assert
var exception = Assert.Throws<ArgumentNullException>(() => Bowl.Launch());
Assert.Contains("ProcessInfo", exception.Message);
}
finally
{
// Restore original value
Environment.SetEnvironmentVariable("ProcessInfo", originalValue);
}
}

/// <summary>
/// Tests that CreateParameter throws when ProcessInfo environment variable is empty.
/// </summary>
[Fact]
public void CreateParameter_WithEmptyEnvironmentVariable_ThrowsArgumentNullException()
{
// Arrange
var originalValue = Environment.GetEnvironmentVariable("ProcessInfo");
Environment.SetEnvironmentVariable("ProcessInfo", "");

try
{
// Act & Assert
var exception = Assert.Throws<ArgumentNullException>(() => Bowl.Launch());
Assert.Contains("ProcessInfo", exception.Message);
}
finally
{
// Restore original value
Environment.SetEnvironmentVariable("ProcessInfo", originalValue);
}
}

/// <summary>
/// Tests that CreateParameter throws when ProcessInfo JSON is invalid.
/// </summary>
[Fact]
public void CreateParameter_WithInvalidJson_ThrowsException()
{
// Arrange
var originalValue = Environment.GetEnvironmentVariable("ProcessInfo");
Environment.SetEnvironmentVariable("ProcessInfo", "{ invalid json }");

try
{
// Act & Assert
Assert.ThrowsAny<Exception>(() => Bowl.Launch());
}
finally
{
// Restore original value
Environment.SetEnvironmentVariable("ProcessInfo", originalValue);
}
}

/// <summary>
/// Tests that CreateParameter throws when ProcessInfo deserializes to null.
/// </summary>
[Fact]
public void CreateParameter_WithNullDeserialization_ThrowsArgumentNullException()
{
// Arrange
var originalValue = Environment.GetEnvironmentVariable("ProcessInfo");
Environment.SetEnvironmentVariable("ProcessInfo", "null");

try
{
// Act & Assert
var exception = Assert.Throws<ArgumentNullException>(() => Bowl.Launch());
Assert.Contains("ProcessInfo", exception.Message);
}
finally
{
// Restore original value
Environment.SetEnvironmentVariable("ProcessInfo", originalValue);
}
}

/// <summary>
/// Tests that Launch with valid ProcessInfo environment variable creates correct parameter.
/// This test verifies the environment variable parsing logic.
/// </summary>
[Fact]
public void Launch_WithValidProcessInfoEnvironment_ParsesCorrectly()
{
// Skip on non-Windows as platform check will fail first
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}

// Arrange
var originalValue = Environment.GetEnvironmentVariable("ProcessInfo");
var tempPath = System.IO.Path.GetTempPath();
var testPath = System.IO.Path.Combine(tempPath, $"BowlTest_{Guid.NewGuid()}");

var processInfoJson = @"{
""AppName"": ""TestApp.exe"",
""InstallPath"": """ + testPath.Replace("\\", "\\\\") + @""",
""LastVersion"": ""1.2.3""
}";

Environment.SetEnvironmentVariable("ProcessInfo", processInfoJson);

try
{
System.IO.Directory.CreateDirectory(testPath);

// Act & Assert
// Should not throw ArgumentNullException for ProcessInfo
var exception = Record.Exception(() => Bowl.Launch());

// Should not be ArgumentNullException related to ProcessInfo
Assert.True(exception == null ||
exception is not ArgumentNullException ||
!exception.Message.Contains("ProcessInfo"));
}
finally
{
// Cleanup
Environment.SetEnvironmentVariable("ProcessInfo", originalValue);
if (System.IO.Directory.Exists(testPath))
{
try { System.IO.Directory.Delete(testPath, true); } catch { }
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Poor error handling: empty catch block.

Suggested change
try { System.IO.Directory.Delete(testPath, true); } catch { }
try
{
System.IO.Directory.Delete(testPath, true);
}
catch (Exception ex)
{
// Best-effort cleanup: do not fail the test on delete errors, but log for diagnostics
Console.WriteLine($"[BowlTests] Failed to delete test directory '{testPath}': {ex}");
}

Copilot uses AI. Check for mistakes.
}
}
}
}
}
Loading
Loading