更新 update 门户站点界面和后台功能
build-winui / winui (push) Has been cancelled
build-winui / winui (push) Has been cancelled
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -85,6 +85,30 @@ public sealed class RemoteMediaResolverTests
|
||||
Assert.AreEqual("text/html", result.ContentType);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ProbeFailureDoesNotPretendUrlIsDirectMedia()
|
||||
{
|
||||
var resolver = CreateResolver(_ => throw new HttpRequestException("SSL connection failed"));
|
||||
|
||||
var result = await resolver.ResolveMediaAsync("https://cdn.test/broken/video.mp4", RemoteMediaKind.Video, cacheBust: false);
|
||||
|
||||
Assert.AreEqual("https://cdn.test/broken/video.mp4", result.Uri.AbsoluteUri);
|
||||
Assert.IsFalse(result.IsDirectMedia);
|
||||
Assert.AreEqual("application/x-ymhut-probe-failed", result.ContentType);
|
||||
Assert.AreEqual(".mp4", result.SuggestedExtension);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task RejectsWrongKindMediaAsNonDirectForExpectedKind()
|
||||
{
|
||||
var resolver = CreateResolver(_ => Text(HttpStatusCode.OK, string.Empty, "image/jpeg"));
|
||||
|
||||
var result = await resolver.ResolveMediaAsync("https://example.test/random", RemoteMediaKind.Video, cacheBust: false);
|
||||
|
||||
Assert.IsFalse(result.IsDirectMedia);
|
||||
Assert.AreEqual("image/jpeg", result.ContentType);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TreatsDirectMediaContentTypeAsPlayable()
|
||||
{
|
||||
|
||||
@@ -708,26 +708,26 @@ public sealed class ToolExecutorTests
|
||||
public void UpdateNoticeJsonKeepsPlainTextAndAddsMarkdown()
|
||||
{
|
||||
var repoRoot = Directory.GetParent(FindAssetsRoot())!.FullName;
|
||||
var noticePath = Path.Combine(repoRoot, "update-notice", "2.0.6.3.json");
|
||||
var noticePath = Path.Combine(repoRoot, "update-notice", "2.0.7.5.json");
|
||||
var totalPath = Path.Combine(repoRoot, "update-notice", "total.json");
|
||||
|
||||
using var notice = JsonDocument.Parse(File.ReadAllText(noticePath));
|
||||
var noticeRoot = notice.RootElement;
|
||||
Assert.AreEqual("2.0.6.3", noticeRoot.GetProperty("app_version").GetString());
|
||||
Assert.AreEqual("2.0.7.5", noticeRoot.GetProperty("app_version").GetString());
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(noticeRoot.GetProperty("message").GetString()));
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(noticeRoot.GetProperty("release_notes").GetString()));
|
||||
StringAssert.Contains(noticeRoot.GetProperty("message_md").GetString(), "YMhut Box 2.0.6.3");
|
||||
StringAssert.Contains(noticeRoot.GetProperty("release_notes_md").GetString(), "QQ 信息");
|
||||
StringAssert.Contains(noticeRoot.GetProperty("release_notes_md").GetString(), "安全浏览器");
|
||||
StringAssert.Contains(noticeRoot.GetProperty("message_md").GetString(), "YMhut Box 2.0.7.5");
|
||||
StringAssert.Contains(noticeRoot.GetProperty("release_notes_md").GetString(), "随机放映室");
|
||||
StringAssert.Contains(noticeRoot.GetProperty("release_notes_md").GetString(), "音量控制");
|
||||
|
||||
using var total = JsonDocument.Parse(File.ReadAllText(totalPath));
|
||||
var totalRoot = total.RootElement;
|
||||
Assert.AreEqual("2.0.6.3", totalRoot.GetProperty("latest_version").GetString());
|
||||
Assert.AreEqual("2.0.7.5", totalRoot.GetProperty("latest_version").GetString());
|
||||
var latest = totalRoot.GetProperty("latest");
|
||||
Assert.AreEqual("2.0.6.3", latest.GetProperty("version").GetString());
|
||||
StringAssert.Contains(latest.GetProperty("release_notes_md").GetString(), "QQ 信息");
|
||||
Assert.AreEqual("2.0.6.3", totalRoot.GetProperty("versions")[0].GetProperty("version").GetString());
|
||||
StringAssert.Contains(totalRoot.GetProperty("versions")[0].GetProperty("summary").GetString(), "QQ 信息");
|
||||
Assert.AreEqual("2.0.7.5", latest.GetProperty("version").GetString());
|
||||
StringAssert.Contains(latest.GetProperty("release_notes_md").GetString(), "随机放映室");
|
||||
Assert.AreEqual("2.0.7.5", totalRoot.GetProperty("versions")[0].GetProperty("version").GetString());
|
||||
StringAssert.Contains(totalRoot.GetProperty("versions")[0].GetProperty("summary").GetString(), "随机放映室");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
Reference in New Issue
Block a user