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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Security.Cryptography;
using System.Text.Json;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Automation.Provider;
using XFEEDToolBox.Controls;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Views;

namespace XFEEDToolBox;

public partial class App
{
    private static async Task<object> RunMarketCacheSettingsProbeAsync(string directory)
    {
        var exercise=Environment.GetCommandLineArgs().Contains("--exercise-fixture-cache-clear");
        var fixturePath=Path.GetFullPath(Path.Combine(directory,"isolated-data"));
        if(exercise&&(!ViewModel.OfflinePreview||!Path.GetFullPath(Runtime.DataDirectory).Equals(fixturePath,StringComparison.OrdinalIgnoreCase)))
            throw new InvalidOperationException("Cache cleanup acceptance may mutate only its explicit offline report-directory fixture.");
        await File.WriteAllTextAsync(Path.Combine(directory,"requirements.json"),JsonSerializer.Serialize(new{requirements=new[]{
            new{id="identity",kind="behavior",description="清楚区分隔离清理夹具与真实数据目录,只在显式隔离夹具执行删除。"},
            new{id="scope",kind="text",description="显示真实占用、默认500ly与可选中心的规则、旧存量与个人数据保留说明。"},
            new{id="clear",kind="behavior",description="实际按钮清理夹具行情及旧备份,并显示结果;生产只读取图不据此声称大库已清理。"},
            new{id="guard",kind="behavior",description="运行状态禁用清理,恢复可用后仅清除行情数据,设置、事件与交易记录字节保持。"},
            new{id="layout",kind="layout",description="宽窗和窄窗中说明、占用、按钮及状态无重叠与不可访问裁切。"}
        }},new JsonSerializerOptions{WriteIndented=true}));
        var checks=new List<object>();var captures=new List<object>();var protectedFiles=new Dictionary<string,string>();
        void Check(string item,bool passed,string evidence)
        {checks.Add(new{item,passed,evidence});if(!passed)throw new InvalidOperationException(item+": "+evidence);}
        static string Hash(string path)
        {
            using var bytes=new FileStream(path,FileMode.Open,FileAccess.Read,FileShare.ReadWrite|FileShare.Delete);
            return Convert.ToHexStringLower(SHA256.HashData(bytes));
        }
        var legacy=Path.Combine(Runtime.DataDirectory,"markets.db.verified.db");
        const long fixtureStation=7000000000001;
        if(exercise)
        {
            Runtime.Settings.Save(Runtime.SettingsPath);
            var history=Path.Combine(Runtime.DataDirectory,"trade-ledgers");Directory.CreateDirectory(history);
            var sentinel=Path.Combine(history,"cache-probe-preserve.json");await File.WriteAllTextAsync(sentinel,"{\"fixture\":true}");
            foreach(var file in new[]{Runtime.SettingsPath,Path.Combine(Runtime.DataDirectory,"game-events.db"),sentinel})
                if(File.Exists(file))protectedFiles[file]=Hash(file);
            var now=DateTimeOffset.UtcNow;
            var station=new Station{MarketId=fixtureStation,Name="Cache fixture",SystemName="Isolated fixture",Position=new(0,0,0),UpdatedAt=now};
            var seeded=await Runtime.Markets.SaveSnapshotAsync(new(station,[new CommodityQuote{MarketId=fixtureStation,Commodity="fixture",DisplayName="Cache fixture",BuyPrice=1,SellPrice=2,CollectedAt=now,Source=DataSource.Journal}],now,DataSource.Journal));
            Check("the explicit local-market fixture was seeded",seeded&&Runtime.Markets.GetStation(fixtureStation) is not null,"This is synthetic offline cache data using the local-observation bypass, never a live Journal or game transaction.");
            await File.WriteAllBytesAsync(legacy,new byte[1024*1024]);
        }
        AppShellPage.Current!.Navigate(typeof(SettingPage));await Task.Delay(180);
        var page=(SettingPage)AppShellPage.Current.CurrentPage!;
        var data=(CommunityToolkit.WinUI.Controls.SettingsExpander)page.FindName("DataSettings");data.IsExpanded=true;
        await ViewModel.RefreshMarketCacheUsageCommand.ExecuteAsync(null);await Task.Delay(140);page.UpdateLayout();
        var card=(FrameworkElement)page.FindName("MarketCacheCard");
        var scroll=(ScrollViewer)page.FindName("SettingsScrollViewer");
        var button=Descendants(page).OfType<Button>().Single(b=>ReferenceEquals(b.Command,ViewModel.ClearMarketCacheCommand)||ReferenceEquals(AsyncAction.GetCommand(b),ViewModel.ClearMarketCacheCommand));
        Check("cache card and exact cleanup command are present",card.ActualWidth>100&&button.IsLoaded,"The rendered Settings card is bound to the production cleanup command.");
        var beforeText=ViewModel.MarketCacheUsageText;
        Check("actual usage is displayed",!string.IsNullOrWhiteSpace(beforeText),beforeText);
        async Task Capture(string name)
        {
            page.UpdateLayout();var point=card.TransformToVisual(scroll).TransformPoint(new(0,0));
            scroll.ChangeView(null,Math.Max(0,scroll.VerticalOffset+point.Y-16),null,true);await Task.Delay(140);page.UpdateLayout();
            var path=Path.Combine(directory,name+".png");await CaptureRefinementElementAsync((FrameworkElement)MainWindow.Content,path);
            var bounds=card.TransformToVisual(scroll).TransformPoint(new(0,0));
            captures.Add(new{name,path,sha256=Hash(path),window=new{MainWindow.AppWindow.Size.Width,MainWindow.AppWindow.Size.Height},card=new{bounds.X,bounds.Y,card.ActualWidth,card.ActualHeight},scroll=new{scroll.ActualWidth,scroll.ActualHeight,scroll.VerticalOffset}});
        }
        await Capture("cache-settings-wide");
        MainWindow.AppWindow.Resize(new(1280,1040));await Task.Delay(180);await Capture("cache-settings-compact");
        if(exercise)
        {
            var previous=ViewModel.RunActive;
            try
            {
                ViewModel.RunActive=true;await Task.Delay(100);
                Check("cleanup is blocked by the running UI state",!ViewModel.ClearMarketCacheCommand.CanExecute(null)&&!button.IsEnabled,"This is an isolated UI-state fixture; no trade engine or game action was started.");
                await Capture("cache-settings-blocked");
            }
            finally{ViewModel.RunActive=previous;}
            await Task.Delay(100);Check("cleanup is available after the fixture stops",button.IsEnabled&&ViewModel.ClearMarketCacheCommand.CanExecute(null),ViewModel.MarketCacheBlockReason);
            ((IInvokeProvider)new ButtonAutomationPeer(button).GetPattern(PatternInterface.Invoke)).Invoke();
            await Task.Delay(20);if(ViewModel.ClearMarketCacheCommand.ExecutionTask is{} task)await task;
            await ViewModel.RefreshMarketCacheUsageCommand.ExecuteAsync(null);
            Check("the original cache backup was removed",!File.Exists(legacy),"Only the explicit isolated 1 MiB market backup was seeded for this action.");
            Check("active market data was reset",Runtime.Markets.GetStation(fixtureStation) is null,"The seeded station and quote are absent after the actual UI cleanup action.");
            Check("settings event index and trade ledger were preserved",protectedFiles.All(pair=>File.Exists(pair.Key)&&Hash(pair.Key)==pair.Value),"The original bytes of every protected fixture file agree.");
            await Capture("cache-settings-cleared");
        }
        var result=new{checks,captures,fixture=exercise,productionCacheCleared=false,Runtime.DataDirectory,beforeText,afterText=ViewModel.MarketCacheUsageText,status=ViewModel.MarketCacheStatus,legacyNotice=ViewModel.MarketCacheLegacyNotice,protectedFiles,gameCommandsInvoked=false,tradeOrdersSubmitted=false,osInputsSent=false,
            visualReview="Actual pixel/ROI and text inspection is still required; a successful capture is not a visual pass."};
        await File.WriteAllTextAsync(Path.Combine(directory,"cache-settings-evidence.json"),JsonSerializer.Serialize(result,new JsonSerializerOptions{WriteIndented=true}));
        return result;
    }
}