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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

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

namespace XFEEDToolBox.Test;

[TestFixture]
public class NativeUnsubmittedTradeIntentsTests
{
    private sealed class Channel:IOriginalCommerceChannel
    {
        public string Identity=>"100:200";
        public DateTimeOffset? ProcessStartedAt{get;set;}
        public bool Known;
        public int Calls;
        public long Credits=10000;
        public Task<JsonElement> CallAsync(object request,CancellationToken token)
        {
            Calls++;var op=JsonSerializer.SerializeToElement(request).GetProperty("op").GetString();
            return Task.FromResult(op switch{
                "commerce.operation.status"=>JsonSerializer.SerializeToElement(new{known=Known,mutationsSent=Known?1:0}),
                "commerce.inventory"=>JsonSerializer.SerializeToElement(new{verified=true,count=0,credits=Credits}),
                _=>throw new InvalidOperationException("Only read-only reconciliation is allowed")});
        }
    }
    private static PlayerState Player()=>new(){InGame=true,Docked=true,CommanderId="test-commander",CurrentShipId=7,
        LoadGameAt=DateTimeOffset.UtcNow.AddMinutes(-1),Credits=10000,CreditsReliable=true,CargoReliable=true};
    private static string Pending(string directory,Channel channel,PlayerState player)
    {
        var file=Path.Combine(directory,new string('a',32)+".pending.json");
        File.WriteAllText(file,JsonSerializer.Serialize(new{operationKey=new string('a',32),channel=channel.Identity,player.CommanderId,player.CurrentShipId,player.LoadGameAt,
            intent=new{before=new{verified=true,count=0,credits=10000}}}));return file;
    }
    [Test] public async Task SameLiveSessionUnacceptedKeyIsMarkedUnsubmittedWithoutDeletingTheIntent()
    {
        using var directory=new TemporaryDirectory();var channel=new Channel();var player=Player();var pending=Pending(directory.Path,channel,player);var original=File.ReadAllText(pending);
        await NativeUnsubmittedTradeIntents.ReconcileAsync(directory.Path,channel,player,default);
        using var proof=JsonDocument.Parse(File.ReadAllText(pending.Replace(".pending.json",".confirmed.json")));
        Check.That(!proof.RootElement.GetProperty("submitted").GetBoolean());Check.That(!proof.RootElement.TryGetProperty("receipt",out _));
        Check.Equal(original,File.ReadAllText(pending));Check.Equal(2,channel.Calls);
    }
    [Test] public async Task NewSessionCannotUseTheOldOperationMapAsProof()
    {
        using var directory=new TemporaryDirectory();var channel=new Channel();var player=Player();Pending(directory.Path,channel,player);
        await NativeUnsubmittedTradeIntents.ReconcileAsync(directory.Path,channel,player with{LoadGameAt=DateTimeOffset.UtcNow},default);
        Check.Equal(0,channel.Calls);Check.Equal(0,Directory.GetFiles(directory.Path,"*.confirmed.json").Length);
    }
    [Test] public async Task AcceptedKeyRemainsUnresolved()
    {
        using var directory=new TemporaryDirectory();var channel=new Channel{Known=true};var player=Player();Pending(directory.Path,channel,player);
        await NativeUnsubmittedTradeIntents.ReconcileAsync(directory.Path,channel,player,default);
        Check.Equal(1,channel.Calls);Check.Equal(0,Directory.GetFiles(directory.Path,"*.confirmed.json").Length);
    }
    [Test] public async Task ChangedWalletCannotBeUsedAsUnsubmittedProof()
    {
        using var directory=new TemporaryDirectory();var channel=new Channel{Credits=9999};var player=Player();Pending(directory.Path,channel,player);
        await NativeUnsubmittedTradeIntents.ReconcileAsync(directory.Path,channel,player,default);
        Check.Equal(0,Directory.GetFiles(directory.Path,"*.confirmed.json").Length);
    }
    [Test] public async Task EmptyLegacyIntentCanOnlyResolveInsideItsOriginalProcessAndSession()
    {
        using var directory=new TemporaryDirectory();var channel=new Channel{ProcessStartedAt=DateTimeOffset.UtcNow.AddMinutes(-2)};var player=Player();
        var pending=Pending(directory.Path,channel,player);File.WriteAllBytes(pending,[]);
        await NativeUnsubmittedTradeIntents.ReconcileAsync(directory.Path,channel,player,default);
        using var proof=JsonDocument.Parse(File.ReadAllText(pending.Replace(".pending.json",".confirmed.json")));
        Check.That(!proof.RootElement.GetProperty("submitted").GetBoolean()&&proof.RootElement.GetProperty("malformedIntent").GetBoolean());
        Check.Equal(0L,new FileInfo(pending).Length);Check.Equal(3,channel.Calls);
    }
    [Test] public async Task EmptyIntentFromAnOlderProcessOrSessionCannotUseAbsenceAsProof()
    {
        foreach(var oldSession in new[]{false,true})
        {
            using var directory=new TemporaryDirectory();var channel=new Channel{ProcessStartedAt=DateTimeOffset.UtcNow.AddMinutes(-2)};var player=Player();
            var pending=Pending(directory.Path,channel,player);File.WriteAllBytes(pending,[]);var future=DateTimeOffset.UtcNow.AddSeconds(1);
            if(oldSession)player=player with{LoadGameAt=future};else channel.ProcessStartedAt=future;
            await NativeUnsubmittedTradeIntents.ReconcileAsync(directory.Path,channel,player,default);
            Check.Equal(0,channel.Calls);Check.Equal(0,Directory.GetFiles(directory.Path,"*.confirmed.json").Length);
        }
    }
    [Test] public async Task AcceptedEmptyIntentAlwaysRemainsUnresolved()
    {
        using var directory=new TemporaryDirectory();var channel=new Channel{ProcessStartedAt=DateTimeOffset.UtcNow.AddMinutes(-2),Known=true};var player=Player();
        var pending=Pending(directory.Path,channel,player);File.WriteAllBytes(pending,[]);
        await NativeUnsubmittedTradeIntents.ReconcileAsync(directory.Path,channel,player,default);
        Check.Equal(1,channel.Calls);Check.Equal(0,Directory.GetFiles(directory.Path,"*.confirmed.json").Length);
    }
}