更新 update 门户站点界面和后台功能
build-winui / winui (push) Has been cancelled

This commit is contained in:
QWQLwToo
2026-06-27 18:09:11 +08:00
parent 2513eb2903
commit 962a2f2143
56 changed files with 4564 additions and 714 deletions
+133 -5
View File
@@ -18,15 +18,19 @@ public sealed class RemoteMediaCatalogTests
var image = catalog.Categories.Single(category => category.Id == "image");
Assert.IsTrue(image.Enabled);
Assert.AreEqual("随机图片", image.DisplayName);
Assert.AreEqual(RemoteMediaKind.Image, image.Kind);
Assert.IsTrue(image.Layout.ShowPreview);
CollectionAssert.Contains(image.Sources.First(source => source.Id == "xjj").SupportedFormats.ToArray(), "jpg");
Assert.AreEqual(30, image.Sources.First(source => source.Id == "xjj").RefreshIntervalSeconds);
Assert.AreEqual("image", image.Sources.First(source => source.Id == "xjj").MediaType);
var video = catalog.Categories.Single(category => category.Id == "video");
Assert.AreEqual("随机视频", video.DisplayName);
Assert.AreEqual(RemoteMediaKind.Video, video.Kind);
Assert.IsFalse(video.Layout.AutoPlay);
CollectionAssert.Contains(video.Sources.First().SupportedFormats.ToArray(), "mp4");
Assert.AreEqual("video", video.Sources.First().MediaType);
}
[TestMethod]
@@ -75,6 +79,74 @@ public sealed class RemoteMediaCatalogTests
Assert.AreEqual("https://example.test/media", source.ThumbnailUrl);
}
[TestMethod]
public void ParsesUnifiedResolvedMediaFields()
{
const string content = """
{
"categories": [
{
"id": "image",
"subcategories": [
{
"id": "demo",
"api_url": "https://api.example.test/random",
"resolvedUrl": "https://cdn.example.test/media/demo.webp",
"resolvedKey": "data.cover",
"mediaType": "image",
"supported_formats": ["json", "webp"]
}
]
}
]
}
""";
var source = RemoteMediaCatalogParser.Parse(content).Categories.Single().Sources.Single();
Assert.AreEqual("https://api.example.test/random", source.ApiUrl);
Assert.AreEqual("https://cdn.example.test/media/demo.webp", source.ResolvedUrl);
Assert.AreEqual("data.cover", source.ResolvedKey);
Assert.AreEqual("image", source.MediaType);
Assert.AreEqual(source.ResolvedUrl, source.EffectiveApiUrl);
Assert.IsTrue(source.IsAvailable);
}
[TestMethod]
public void ExplicitMediaTypeWinsOverCategoryAndFormats()
{
const string content = """
{
"categories": [
{
"id": "mixed",
"type": "image",
"subcategories": [
{
"id": "json_picture",
"api_url": "https://api.example.test/random",
"mediaType": "image",
"supported_formats": ["json", "mp4"]
},
{
"id": "clip",
"api_url": "https://api.example.test/clip",
"type": "video",
"supported_formats": ["jpg"]
}
]
}
]
}
""";
var category = RemoteMediaCatalogParser.Parse(content).Categories.Single();
Assert.AreEqual(RemoteMediaKind.Image, category.Kind);
Assert.AreEqual(RemoteMediaKind.Image, category.Sources[0].Kind);
Assert.AreEqual(RemoteMediaKind.Video, category.Sources[1].Kind);
}
[TestMethod]
public async Task ServiceWritesReadsFallsBackAndClearsCache()
{
@@ -112,6 +184,58 @@ public sealed class RemoteMediaCatalogTests
}
}
[TestMethod]
public async Task ServicePrefersUnifiedBootstrapSources()
{
var root = Path.Combine(Path.GetTempPath(), "ymhut-remote-media-" + Guid.NewGuid().ToString("N"));
try
{
var paths = new AppPaths(root);
paths.EnsureCreated();
const string bootstrap = """
{
"ok": true,
"sources": {
"layout_version": "2.0.0",
"categories": [
{
"id": "image",
"subcategories": [
{
"id": "demo",
"api_url": "https://api.example.test/random",
"resolvedUrl": "https://cdn.example.test/media/demo.webp",
"supported_formats": ["json", "webp"]
}
]
}
]
}
}
""";
var api = new FakeApiManager(
string.Empty,
uriResponses: new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
[RemoteMediaCatalogService.BootstrapUri.AbsolutePath] = bootstrap
});
var service = new RemoteMediaCatalogService(paths, api);
var result = await service.LoadAsync(forceRefresh: false);
Assert.AreEqual(RemoteMediaCatalogLoadSource.Remote, result.Source);
Assert.AreEqual(RemoteMediaCatalogService.BootstrapUri.AbsolutePath, api.LastUri?.AbsolutePath);
Assert.AreEqual("https://cdn.example.test/media/demo.webp", result.Catalog.Categories.Single().Sources.Single().EffectiveApiUrl);
}
finally
{
if (Directory.Exists(root))
{
Directory.Delete(root, recursive: true);
}
}
}
private static string ReadRepoFile(params string[] segments)
{
var directory = new DirectoryInfo(Directory.GetCurrentDirectory());
@@ -129,7 +253,7 @@ public sealed class RemoteMediaCatalogTests
throw new DirectoryNotFoundException("Unable to locate repository sample file.");
}
private sealed class FakeApiManager(string content, bool success = true) : IApiManager
private sealed class FakeApiManager(string content, bool success = true, IReadOnlyDictionary<string, string>? uriResponses = null) : IApiManager
{
public Uri? LastUri { get; private set; }
@@ -149,14 +273,18 @@ public sealed class RemoteMediaCatalogTests
public Task<ApiResponse> FetchUriAsync(string endpointId, Uri uri, string input = "", CancellationToken cancellationToken = default)
{
LastUri = uri;
var responseContent = uriResponses is not null && uriResponses.TryGetValue(uri.AbsolutePath, out var match)
? match
: content;
var responseSuccess = success || !string.IsNullOrWhiteSpace(responseContent);
return Task.FromResult(new ApiResponse(
endpointId,
uri,
success,
success ? content : string.Empty,
success ? null : "offline",
responseSuccess,
responseSuccess ? responseContent : string.Empty,
responseSuccess ? null : "offline",
DateTimeOffset.Now,
success ? 200 : 0));
responseSuccess ? 200 : 0));
}
public Task<ApiHealthStatus> CheckHealthAsync(string endpointId, string input = "", CancellationToken cancellationToken = default)