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
105 changes: 105 additions & 0 deletions tests/CoreTest/Download/DownloadPlanBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System.Linq;
using GeneralUpdate.Core.Download;
using GeneralUpdate.Core.Download.Models;
using Xunit;

namespace CoreTest.Download;

public class DownloadPlanBuilderTests
{
[Fact]
public void Build_EmptyAssets_ReturnsEmptyPlan()
{
var plan = DownloadPlanBuilder.Build(Array.Empty<DownloadAsset>(), "1.0.0");
Assert.False(plan.HasAssets);
}

[Fact]
public void Build_SingleAsset_ReturnsIt()
{
var assets = new[] { new DownloadAsset("pkg", "http://x", 100, "sha", "1.0.1") };
var plan = DownloadPlanBuilder.Build(assets, "1.0.0");
Assert.True(plan.HasAssets);
Assert.Single(plan.Assets);
Assert.Equal("1.0.1", plan.Assets[0].Version);
}

[Fact]
public void Build_CrossVersionPackage_DirectJump()
{
var assets = new[]
{
new DownloadAsset("chain", "http://x", 100, "sha", "1.0.1"),
new DownloadAsset("jump", "http://y", 500, "sha2", "2.0.0",
IsCrossVersion: true, FromVersion: "1.0.0")
};

var plan = DownloadPlanBuilder.Build(assets, "1.0.0");
Assert.Single(plan.Assets); // Only the cross-version jump package
Assert.Equal("2.0.0", plan.Assets[0].Version);
}

[Fact]
public void Build_FrozenPackagesExcluded()
{
var assets = new[]
{
new DownloadAsset("bad", "http://x", 100, "sha", "1.0.1", IsFreeze: true),
new DownloadAsset("good", "http://y", 100, "sha2", "1.0.2")
};

var plan = DownloadPlanBuilder.Build(assets, "1.0.0");
Assert.Single(plan.Assets);
Assert.Equal("1.0.2", plan.Assets[0].Version);
}

[Fact]
public void Build_ForcedUpdate_MarksPlan()
{
var assets = new[]
{
new DownloadAsset("forced", "http://x", 100, "sha", "1.0.1", IsForcibly: true)
};

var plan = DownloadPlanBuilder.Build(assets, "1.0.0");
Assert.True(plan.IsForcibly);
}

[Fact]
public void Build_VersionChain_MultipleSteps()
{
var assets = new[]
{
new DownloadAsset("v101", "http://x", 100, "sha1", "1.0.1"),
new DownloadAsset("v102", "http://y", 100, "sha2", "1.0.2"),
new DownloadAsset("v103", "http://z", 100, "sha3", "1.0.3")
};

var plan = DownloadPlanBuilder.Build(assets, "1.0.0");
Assert.Equal(3, plan.Assets.Count);
Assert.Equal("1.0.1", plan.Assets[0].Version);
Assert.Equal("1.0.3", plan.Assets[^1].Version);
}

[Fact]
public void Build_SameVersion_ReturnsEmpty()
{
var assets = new[] { new DownloadAsset("same", "http://x", 100, "sha", "1.0.0") };
var plan = DownloadPlanBuilder.Build(assets, "1.0.0");
Assert.False(plan.HasAssets);
}

[Fact]
public void Build_MinClientVersion_FiltersOut()
{
var assets = new[]
{
new DownloadAsset("compat", "http://x", 100, "sha1", "1.0.1", MinClientVersion: "1.0.0"),
new DownloadAsset("incompat", "http://y", 100, "sha2", "1.0.2", MinClientVersion: "2.0.0")
};

var plan = DownloadPlanBuilder.Build(assets, "1.0.0");
Assert.Single(plan.Assets);
Assert.Equal("1.0.1", plan.Assets[0].Version);
}
}
87 changes: 87 additions & 0 deletions tests/CoreTest/Event/EventManagerConcurrencyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using GeneralUpdate.Core.Event;
using Xunit;

namespace CoreTest.Event;

public class EventManagerConcurrencyTests
{
[Fact]
public async Task ConcurrentAddRemoveDispatch_NoExceptions()
{
var completed = new TaskCompletionSource<bool>();
var errors = 0;

// Concurrent writers
Parallel.For(0, 100, i =>
{
try
{
Action<object, ExceptionEventArgs> handler = (s, e) => { };
EventManager.Instance.AddListener(handler);
EventManager.Instance.RemoveListener(handler);
}
catch { Interlocked.Increment(ref errors); }
});

// Concurrent dispatchers
Parallel.For(0, 50, i =>
{
try
{
EventManager.Instance.Dispatch(this, new ExceptionEventArgs(new Exception("test"), "test"));
}
catch { Interlocked.Increment(ref errors); }
});

// Concurrent readers (dispatch + add)
Parallel.For(0, 50, i =>
{
try
{
Action<object, ProgressEventArgs> handler = (s, e) => { };
EventManager.Instance.AddListener(handler);
EventManager.Instance.RemoveListener(handler);
}
catch { Interlocked.Increment(ref errors); }
});

await Task.Delay(100); // Let all tasks finish
Assert.Equal(0, errors);
}

[Fact]
public void Dispatch_HandlerException_DoesNotBlockOthers()
{
var handler2Called = false;
Action<object, ExceptionEventArgs> handler1 = (s, e) => throw new InvalidOperationException("boom");
Action<object, ExceptionEventArgs> handler2 = (s, e) => handler2Called = true;

EventManager.Instance.AddListener(handler1);
EventManager.Instance.AddListener(handler2);

EventManager.Instance.Dispatch(this, new ExceptionEventArgs(null, "test"));

Assert.True(handler2Called);

// Cleanup
EventManager.Instance.RemoveListener(handler1);
EventManager.Instance.RemoveListener(handler2);
}

[Fact]
public void AddRemove_Dispatch_DoesNotThrow()
{
var callCount = 0;
Action<object, ExceptionEventArgs> handler = (s, e) => Interlocked.Increment(ref callCount);

EventManager.Instance.AddListener(handler);
EventManager.Instance.Dispatch(this, new ExceptionEventArgs(null, "test"));
EventManager.Instance.RemoveListener(handler);
EventManager.Instance.Dispatch(this, new ExceptionEventArgs(null, "test"));

Assert.Equal(1, callCount); // Only first dispatch should trigger
}
}
78 changes: 78 additions & 0 deletions tests/CoreTest/FileSystem/FileTreeEnumeratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System.IO;
using GeneralUpdate.Core.Configuration;
using GeneralUpdate.Core.FileSystem;
using Xunit;

namespace CoreTest.FileSystem;

public class FileTreeEnumeratorTests
{
[Fact]
public void EnumerateFiles_ReturnsAllFilesInFlatDirectory()
{
var tmpDir = Path.Combine(Path.GetTempPath(), $"ft_test_{System.Guid.NewGuid():N}");
try
{
Directory.CreateDirectory(tmpDir);
File.WriteAllText(Path.Combine(tmpDir, "a.txt"), "a");
File.WriteAllText(Path.Combine(tmpDir, "b.dll"), "b");

var enumerator = FileTreeEnumerator.FromConfig(BlackListConfig.Empty);
var files = enumerator.EnumerateFiles(tmpDir).ToList();

Assert.Equal(2, files.Count);
}
finally { Directory.Delete(tmpDir, true); }
}

[Fact]
public void EnumerateFiles_BlacklistedFormat_Excluded()
{
var tmpDir = Path.Combine(Path.GetTempPath(), $"ft_test_{System.Guid.NewGuid():N}");
try
{
Directory.CreateDirectory(tmpDir);
File.WriteAllText(Path.Combine(tmpDir, "app.exe"), "a");
File.WriteAllText(Path.Combine(tmpDir, "data.pdb"), "b");
File.WriteAllText(Path.Combine(tmpDir, "config.xml"), "c");

var config = new BlackListConfig(BlackFormats: new[] { ".pdb", ".xml" });
var enumerator = FileTreeEnumerator.FromConfig(config);
var files = enumerator.EnumerateFiles(tmpDir).ToList();

Assert.Single(files);
Assert.EndsWith(".exe", files[0]);
}
finally { Directory.Delete(tmpDir, true); }
}

[Fact]
public void EnumerateFiles_BlacklistedDirectory_Skipped()
{
var tmpDir = Path.Combine(Path.GetTempPath(), $"ft_test_{System.Guid.NewGuid():N}");
try
{
Directory.CreateDirectory(tmpDir);
var logDir = Path.Combine(tmpDir, "logs");
Directory.CreateDirectory(logDir);
File.WriteAllText(Path.Combine(tmpDir, "main.exe"), "a");
File.WriteAllText(Path.Combine(logDir, "app.log"), "b");

var config = new BlackListConfig(SkipDirectorys: new[] { "logs" });
var enumerator = FileTreeEnumerator.FromConfig(config);
var files = enumerator.EnumerateFiles(tmpDir).ToList();

Assert.Single(files);
Assert.EndsWith("main.exe", files[0]);
}
finally { Directory.Delete(tmpDir, true); }
}

[Fact]
public void EnumerateFiles_NonExistentDirectory_ReturnsEmpty()
{
var enumerator = FileTreeEnumerator.FromConfig(BlackListConfig.Empty);
var files = enumerator.EnumerateFiles("C:\\does\\not\\exist").ToList();
Assert.Empty(files);
}
}
Loading