From 0300768ad4de781cde7da7612d39c07aefc47d4e Mon Sep 17 00:00:00 2001
From: mleem97 <52848568+mleem97@users.noreply.github.com>
Date: Thu, 21 May 2026 13:41:35 +0000
Subject: [PATCH 1/2] Implement BetaPluginSource to fetch plugins from server
---
.../Services/BetaPluginSource.cs | 103 +++++++++---------
.../BetaPluginSourceTests.cs | 50 +++++++++
2 files changed, 102 insertions(+), 51 deletions(-)
create mode 100644 tests/GregModmanager.Tests/BetaPluginSourceTests.cs
diff --git a/src/GregModmanager.Core/Services/BetaPluginSource.cs b/src/GregModmanager.Core/Services/BetaPluginSource.cs
index 3970159..b45d023 100644
--- a/src/GregModmanager.Core/Services/BetaPluginSource.cs
+++ b/src/GregModmanager.Core/Services/BetaPluginSource.cs
@@ -1,51 +1,52 @@
-using System.Text.Json;
-using GregModmanager.Models;
-
-namespace GregModmanager.Services;
-
-///
-/// Beta distribution channel served from a custom backend.
-/// Configure base URL via Preferences.
-///
-public sealed class BetaPluginSource : IgregPluginChannelSource
-{
- /// Preferences key for the beta server base URL.
- public const string PrefKeyBetaServerUrl = "greg_beta_server_url";
-
- private static readonly HttpClient _http = new();
-
- public string ChannelName => "beta";
-
- public IReadOnlyList ListPlugins()
- {
-#if WINDOWS || ANDROID || IOS || MACCATALYST
- var url = Preferences.Default.Get(PrefKeyBetaServerUrl, string.Empty);
-#else
- var url = "";
-#endif
- if (string.IsNullOrWhiteSpace(url))
- {
- throw new InvalidOperationException(
- "Beta-Kanal: Server-URL ist noch nicht konfiguriert. " +
- "Setze die URL unter Einstellungen (Preferences-Key: greg_beta_server_url).");
- }
-
- var endpoint = url.TrimEnd('/') + "/api/plugins";
-
- try
- {
- var response = _http.GetAsync(endpoint).GetAwaiter().GetResult();
- response.EnsureSuccessStatusCode();
-
- var json = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
-
- var plugins = JsonSerializer.Deserialize(json, AppJsonContext.Default.ListPluginPackageInfo);
-
- return plugins ?? new List();
- }
- catch (Exception ex)
- {
- throw new InvalidOperationException($"Beta-Kanal: Fehler beim Abrufen der Plugins (URL: {endpoint}). Details: {ex.Message}", ex);
- }
- }
-}
+using System.Text.Json;
+using GregModmanager.Models;
+using GregModmanager.Localization;
+using System.Collections.Generic;
+using System.Net.Http;
+using System;
+
+namespace GregModmanager.Services;
+
+///
+/// Beta distribution channel served from a custom backend.
+/// Configure base URL via Preferences.
+///
+public sealed class BetaPluginSource : IgregPluginChannelSource
+{
+ /// Preferences key for the beta server base URL.
+ public const string PrefKeyBetaServerUrl = "greg_beta_server_url";
+
+ private static readonly HttpClient _http = new();
+
+ public string ChannelName => "beta";
+
+ public IReadOnlyList ListPlugins()
+ {
+ var url = S.Preferences.GetString(PrefKeyBetaServerUrl, string.Empty);
+ if (string.IsNullOrWhiteSpace(url))
+ {
+ throw new InvalidOperationException(
+ "Beta-Kanal: Server-URL ist noch nicht konfiguriert. " +
+ "Setze die URL unter Einstellungen (Preferences-Key: greg_beta_server_url).");
+ }
+
+ var endpoint = url.TrimEnd('/') + "/api/plugins";
+
+ try
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
+ using var response = _http.Send(request);
+ response.EnsureSuccessStatusCode();
+
+ using var stream = response.Content.ReadAsStream();
+
+ var plugins = JsonSerializer.Deserialize(stream, AppJsonContext.Default.ListPluginPackageInfo);
+
+ return plugins ?? new List();
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException($"Beta-Kanal: Fehler beim Abrufen der Plugins (URL: {endpoint}). Details: {ex.Message}", ex);
+ }
+ }
+}
diff --git a/tests/GregModmanager.Tests/BetaPluginSourceTests.cs b/tests/GregModmanager.Tests/BetaPluginSourceTests.cs
new file mode 100644
index 0000000..944d745
--- /dev/null
+++ b/tests/GregModmanager.Tests/BetaPluginSourceTests.cs
@@ -0,0 +1,50 @@
+using System;
+using Xunit;
+using GregModmanager.Services;
+using GregModmanager.Localization;
+using System.Collections.Generic;
+
+namespace GregModmanager.Tests;
+
+public class BetaPluginSourceTests
+{
+ private class MockPreferences : IPreferences
+ {
+ private readonly Dictionary _data = new();
+
+ public string GetString(string key, string defaultValue) => _data.TryGetValue(key, out var val) && val is string s ? s : defaultValue;
+ public bool GetBool(string key, bool defaultValue) => _data.TryGetValue(key, out var val) && val is bool b ? b : defaultValue;
+ public int GetInt(string key, int defaultValue) => _data.TryGetValue(key, out var val) && val is int i ? i : defaultValue;
+
+ public void SetString(string key, string value) => _data[key] = value;
+ public void SetBool(string key, bool value) => _data[key] = value;
+ public void SetInt(string key, int value) => _data[key] = value;
+ public void Remove(string key) => _data.Remove(key);
+ }
+
+ [Fact]
+ public void ListPlugins_ThrowsWhenUrlNotConfigured()
+ {
+ var mockPrefs = new MockPreferences();
+ S.Preferences = mockPrefs;
+
+ var source = new BetaPluginSource();
+
+ var ex = Assert.Throws(() => source.ListPlugins());
+ Assert.Contains("Server-URL ist noch nicht konfiguriert", ex.Message);
+ }
+
+ [Fact]
+ public void ListPlugins_ThrowsWhenUrlIsInvalidOrEndpointFails()
+ {
+ var mockPrefs = new MockPreferences();
+ mockPrefs.SetString(BetaPluginSource.PrefKeyBetaServerUrl, "http://localhost:12345/invalid");
+ S.Preferences = mockPrefs;
+
+ var source = new BetaPluginSource();
+
+ var ex = Assert.Throws(() => source.ListPlugins());
+ Assert.Contains("Fehler beim Abrufen der Plugins", ex.Message);
+ Assert.Contains("http://localhost:12345/invalid/api/plugins", ex.Message);
+ }
+}
From e96612d95cc084097a7e11689772fac7da8f6c74 Mon Sep 17 00:00:00 2001
From: mleem97 <52848568+mleem97@users.noreply.github.com>
Date: Thu, 21 May 2026 13:57:41 +0000
Subject: [PATCH 2/2] Fix CI failure in TelemetryService by using
AppJsonContext.Default.Object instead of reflection-based overload that
throws trim warnings
---
src/GregModmanager.Core/Models/AppJsonContext.cs | 1 +
src/GregModmanager.Core/Services/TelemetryService.cs | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/GregModmanager.Core/Models/AppJsonContext.cs b/src/GregModmanager.Core/Models/AppJsonContext.cs
index d7fe96d..8a3cc3c 100644
--- a/src/GregModmanager.Core/Models/AppJsonContext.cs
+++ b/src/GregModmanager.Core/Models/AppJsonContext.cs
@@ -34,6 +34,7 @@ namespace GregModmanager.Models;
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(RalphTaskStatus))]
[JsonSerializable(typeof(AssetModMetadata))]
+[JsonSerializable(typeof(object))]
public partial class AppJsonContext : JsonSerializerContext
{
}
diff --git a/src/GregModmanager.Core/Services/TelemetryService.cs b/src/GregModmanager.Core/Services/TelemetryService.cs
index 8531b66..0c3f72c 100644
--- a/src/GregModmanager.Core/Services/TelemetryService.cs
+++ b/src/GregModmanager.Core/Services/TelemetryService.cs
@@ -99,7 +99,7 @@ public async Task TrackEventAsync(string eventName, object payload, Dictionary JsonSerializer.Serialize(sync, AppJsonContext.Default.SyncCollectionEvent),
- _ => JsonSerializer.Serialize(payload, payload.GetType(), AppJsonContext.Default.Options)
+ _ => JsonSerializer.Serialize(payload, AppJsonContext.Default.Object)
};
await PushToLokiAsync(eventName, message, labels);