XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Net;
using System.Text;
using System.Text.Json;
using XFEEDToolBox.Core.News;
using XFEExtension.NetCore.XUnit.Attributes;

namespace XFEEDToolBox.Test;

[TestFixture]
public class GalnetPresentationTests
{
    [Test] public void OfficialMetadataRetainsDifferentPicturesAndRejectsInjectedHosts()
    {
        const string html = """
            <meta content="https://image-service.zaonce.net/current" property="og:image">
            <article class="c-galnet-latest__article" data-v-123>
              <a href="/news/galnet/previous"><figure><img srcset="ignored" src="https://static.zaonce.net/previous.png?x=1&amp;y=2"></figure></a>
            </article>
            <article class="c-galnet-latest__article"><a href="https://evil.example/news"><img src="https://image-service.zaonce.net/no"></a></article>
            <article class="c-galnet-latest__article"><a href="/news/galnet/bad"><img src="https://www.elitedangerous.com.evil.example/image"></a></article>
            <article class="c-galnet-listing__item"><a href="/news/galnet/listed"><img src="https://image-service.zaonce.net/listed"></a></article>
            """;
        var media = GalnetMediaCatalog.Parse(html, new("https://www.elitedangerous.com/news/galnet/current"));
        Check.Equal(3, media.Count);
        Check.Equal("https://image-service.zaonce.net/current", media["https://www.elitedangerous.com/news/galnet/current"]);
        Check.Equal("https://static.zaonce.net/previous.png?x=1&y=2", media["https://www.elitedangerous.com/news/galnet/previous"]);
        Check.That(!GalnetMediaCatalog.AllowedImage("https://user@static.zaonce.net/image"));
        Check.That(!GalnetMediaCatalog.AllowedImage("file:///C:/local"));
    }

    [Test] public void TranslationQueriesPreserveUnicodeAndStayBelowTheByteLimit()
    {
        var text = string.Concat(Enumerable.Repeat("Anaconda Mk II, 飞船 🚀: a long description. ", 80));
        var chunks = GalnetNewsTranslator.Chunks(text).ToArray();
        Check.That(chunks.Length > 1);
        Check.Equal(text, string.Concat(chunks));
        Check.That(chunks.All(c => Encoding.UTF8.GetByteCount(c) <= 450));
        Check.That(chunks.All(c => !char.IsLowSurrogate(c[0]) && !char.IsHighSurrogate(c[^1])));
    }

    private sealed class TranslationHandler : HttpMessageHandler
    {
        public int Requests;
        public bool Offline, Quota, Wait;
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token)
        {
            Requests++;
            Check.Equal("api.mymemory.translated.net", request.RequestUri!.Host);
            Check.That(request.RequestUri.Query.Contains("langpair=en%7Czh-CN", StringComparison.OrdinalIgnoreCase));
            if (Wait) await Task.Delay(Timeout.Infinite, token);
            if (Offline) throw new HttpRequestException("offline");
            return new(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(new { responseStatus = Quota ? 429 : 200, responseData = new { translatedText = Quota ? "MYMEMORY WARNING: quota" : "巨蟒飞船的测试新闻。" } }), Encoding.UTF8, "application/json") };
        }
    }

    [Test] public async Task TranslationIsPersistedAndOriginalContentChangesInvalidateItsCache()
    {
        using var folder = new TemporaryDirectory();
        using var handler = new TranslationHandler(); using var http = new HttpClient(handler);
        var path = folder.File("translated.json");
        var service = new GalnetNewsTranslator(path, http);
        var translated = await service.TranslateAsync("Anaconda field trials", default);
        Check.Equal("巨蟒飞船的测试新闻。", translated);
        Check.Equal(1, handler.Requests);
        handler.Offline = true;
        var restarted = new GalnetNewsTranslator(path, http);
        Check.Equal(translated, await restarted.TranslateAsync("Anaconda field trials", default));
        Check.Equal(1, handler.Requests);
        Check.That(await restarted.CachedAsync("Anaconda field trials UPDATED", default) is null);
    }

    [Test] public async Task FailedTranslationsAreNotCachedOrPresentedAsChineseAndQuotaBacksOff()
    {
        using var folder = new TemporaryDirectory();
        using var handler = new TranslationHandler { Quota = true }; using var http = new HttpClient(handler);
        var service = new GalnetNewsTranslator(folder.File("translated.json"), http);
        for (var i = 0; i < 2; i++)
        {
            try { await service.TranslateAsync("Anaconda field trials", default); throw new InvalidOperationException("Expected quota failure"); }
            catch (HttpRequestException) { }
        }
        Check.Equal(1, handler.Requests);
        Check.That(await service.CachedAsync("Anaconda field trials", default) is null);
        Check.That(!File.Exists(folder.File("translated.json")));
    }

    [Test] public async Task CancellationDoesNotTurnTheOriginalArticleIntoACachedTranslation()
    {
        using var folder = new TemporaryDirectory();
        using var handler = new TranslationHandler { Wait = true }; using var http = new HttpClient(handler);
        var service = new GalnetNewsTranslator(folder.File("translated.json"), http);
        using var stop = new CancellationTokenSource(50);
        try { await service.TranslateAsync("Anaconda field trials", stop.Token); throw new InvalidOperationException("Expected cancellation"); }
        catch (OperationCanceledException) { }
        Check.That(await service.CachedAsync("Anaconda field trials", default) is null);
    }
}

[TestFixture, Explicit("Downloads public Galnet news/images and translates one public article.")]
public class GalnetNewsCarouselLiveTests
{
    [Test] public async Task OfficialPicturesAndAutomaticChineseTranslationProduceARealCarouselEdition()
    {
        var directory = Path.GetFullPath("artifacts/news-carousel"); Directory.CreateDirectory(directory);
        var news = new GalnetNewsService(Path.Combine(directory, "live-feed.json"));
        var snapshot = (await news.RefreshAsync()).Snapshot ?? throw new InvalidOperationException("Official Galnet feed unavailable");
        var media = new GalnetMediaCatalog(Path.Combine(directory, "live-media.json"));
        await media.RefreshAsync(snapshot, default);
        var translator = new GalnetNewsTranslator(Path.Combine(directory, "live-translations.json"));
        var first = snapshot.Articles[0];
        var title = await translator.TranslateAsync(first.Title, default);
        var teaser = await translator.TranslateAsync(GalnetSlide.Preview(first.Body), default);
        var body = await translator.TranslateAsync(first.Body, default);
        Check.That(title.Any(c => c is >= '\u3400' and <= '\u9fff'));
        Check.That(body.Any(c => c is >= '\u3400' and <= '\u9fff'));
        var slides = snapshot.Articles.Take(6).Select((a, i) => new GalnetSlide(a, media.Image(a), i == 0 ? title : a.Title,
            i == 0 ? teaser : GalnetSlide.Preview(a.Body), i == 0 ? body : a.Body, i == 0, i == 0)).ToArray();
        Check.That(slides.Length >= 3 && slides.All(a => GalnetMediaCatalog.AllowedImage(a.Image)), "Official images are missing");
        Check.That(slides.Select(a => a.Image).Distinct().Count() > 1);
        await File.WriteAllTextAsync(Path.Combine(directory, "live-slides.json"), JsonSerializer.Serialize(slides, new JsonSerializerOptions(JsonSerializerDefaults.Web)));
        await File.WriteAllTextAsync(Path.Combine(directory, "live-summary.json"), JsonSerializer.Serialize(new
        {
            source = GalnetRss.FeedUrl, count = slides.Length, officialImages = slides.Length,
            differentImages = slides.Select(s => s.Image).Distinct().Count(), translatedTitle = title,
            translatedParagraphs = body.Split('\n', StringSplitOptions.RemoveEmptyEntries).Length,
            restoredChinese = await new GalnetNewsTranslator(Path.Combine(directory, "live-translations.json")).CachedAsync(first.Body, default) == body,
            gameCallsInvoked = false
        }, new JsonSerializerOptions { WriteIndented = true }));
    }
}