test(upserver): 归档存档迁移与玩法闭环回归工具

This commit is contained in:
Liubing\LB
2026-09-28 18:16:14 +08:00
parent c4ae02def9
commit a6f5e107f8
232 changed files with 12903 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
using asap.core;
namespace asap.core { public interface IConfig { T Get<T>(string key, T fallback); } public interface IEventAggregator {void Publish<T>(T x);Stream<T> GetEvent<T>();} public class EventAggregator:IEventAggregator { readonly Dictionary<Type,object> events=new();public Stream<T> GetEvent<T>(){if(!events.ContainsKey(typeof(T)))events[typeof(T)]=new Stream<T>();return (Stream<T>)events[typeof(T)];} public void Publish<T>(T x)=>GetEvent<T>().Emit(x); } public interface IStageService{Task SwitchStageAsync(string s);} }
public class Stream<T> {public event Action<T> Event;public IDisposable Subscribe(Action<T> action){Event+=action;return new Subscription(()=>Event-=action);}public void Emit(T x)=>Event?.Invoke(x);}
public class Subscription:IDisposable {Action end;public Subscription(Action a){end=a;}public void Dispose(){end?.Invoke();end=null;}}
public static class GContext {static EventAggregator events=new();public static Stream<T> OnEvent<T>()=>events.GetEvent<T>();public static void Publish<T>(T x)=>events.Publish(x);public static Container container=new();}
public class Container {public T Resolve<T>()=>default;}
namespace game {public class EvtAPISvrLoginSuccess{} public static class QuitStage{public static bool Logout;}}
namespace UniRx {} namespace GameCore {}
namespace UnityEngine {public static class Debug {public static void Log(object x){Console.WriteLine(x); } public static void LogError(object x){Console.WriteLine("PRODUCTION ERROR: "+x);}public static void LogException(Exception x){if(x is not OperationCanceledException)throw x;}}public class GameObject{public T GetComponent<T>() where T:new()=>new();}}
public static class Awaiters { public static Task NextFrame=>Task.CompletedTask; }
public class UIManager {public static UIManager Instance=new();public Task<UnityEngine.GameObject> ShowUI(object x)=>Task.FromResult(new UnityEngine.GameObject());}
public static class UITypes {public static object NoticeConfirmPopupPanel=new();}
public class NoticeConfirmPopupPanel {public void Init(int i,string title,string info,Action onClickLeft,Action onClose,string btn_left_text){}}
public static class LocalizationMgr {public static string GetText(string s)=>s;}
public static class LoadingScreen {public static Task Show()=>Task.CompletedTask;}
namespace LitJson {}
public static class GConstant {public const string K_Event_API_URL="EventApiURL";}
namespace game {public class EvtAPISvrUnauthorized{public int SessionVersion{get;set;}}}
class Config(string url):asap.core.IConfig {public T Get<T>(string key,T fallback)=>(T)(object)url;}

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>disable</Nullable><RestoreConfigFile>$(MSBuildThisFileDirectory)../MinimalLoginTests/NuGet.Config</RestoreConfigFile></PropertyGroup><ItemGroup>
<Compile Include="../../../Client/Assets/Scripts/Services/RTService.cs"/>
<Compile Include="../../../Client/Assets/Scripts/Services/Account/S101AccountSession.cs"/>
<Compile Include="../../../Client/Assets/Scripts/Core/CustomServerMgr.cs"/>
<Reference Include="Newtonsoft.Json"><HintPath>../../../Client/Library/PackageCache/com.unity.nuget.newtonsoft-json@3.2.1/Runtime/Newtonsoft.Json.dll</HintPath></Reference>
<Reference Include="Microsoft.AspNetCore.Connections.Abstractions"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.AspNetCore.Connections.Abstractions.dll</HintPath></Reference>
<Reference Include="Microsoft.AspNetCore.Http.Connections.Client"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.AspNetCore.Http.Connections.Client.dll</HintPath></Reference>
<Reference Include="Microsoft.AspNetCore.Http.Connections.Common"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.AspNetCore.Http.Connections.Common.dll</HintPath></Reference>
<Reference Include="Microsoft.AspNetCore.SignalR.Client.Core"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.AspNetCore.SignalR.Client.Core.dll</HintPath></Reference>
<Reference Include="Microsoft.AspNetCore.SignalR.Client"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.AspNetCore.SignalR.Client.dll</HintPath></Reference>
<Reference Include="Microsoft.AspNetCore.SignalR.Common"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.AspNetCore.SignalR.Common.dll</HintPath></Reference>
<Reference Include="Microsoft.AspNetCore.SignalR.Protocols.Json"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.AspNetCore.SignalR.Protocols.Json.dll</HintPath></Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.Bcl.AsyncInterfaces.dll</HintPath></Reference>
<Reference Include="Microsoft.Bcl.TimeProvider"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.Bcl.TimeProvider.dll</HintPath></Reference>
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath></Reference>
<Reference Include="Microsoft.Extensions.DependencyInjection"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.Extensions.DependencyInjection.dll</HintPath></Reference>
<Reference Include="Microsoft.Extensions.Features"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.Extensions.Features.dll</HintPath></Reference>
<Reference Include="Microsoft.Extensions.Logging.Abstractions"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.Extensions.Logging.Abstractions.dll</HintPath></Reference>
<Reference Include="Microsoft.Extensions.Logging"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.Extensions.Logging.dll</HintPath></Reference>
<Reference Include="Microsoft.Extensions.Options"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.Extensions.Options.dll</HintPath></Reference>
<Reference Include="Microsoft.Extensions.Primitives"><HintPath>../../../Client/Assets/Plugins/SignalR/Microsoft.Extensions.Primitives.dll</HintPath></Reference>
<Reference Include="System.IO.Pipelines"><HintPath>../../../Client/Assets/Plugins/SignalR/System.IO.Pipelines.dll</HintPath></Reference></ItemGroup></Project>

View File

@@ -0,0 +1,59 @@
using System.Text.Json;
using game.Account;
using Microsoft.AspNetCore.SignalR.Client;
var endpoint = new Uri(JsonDocument.Parse(File.ReadAllText("CodexOutput/upserver/network-proxy-address.json")).RootElement.GetProperty("url").GetString());
if(endpoint.Host!="127.0.0.1")throw new Exception("Non-loopback fixture");
using var control = new HttpClient { Timeout=TimeSpan.FromSeconds(10) };
void Check(bool ok,string text){if(!ok)throw new Exception(text);}
async Task<JsonElement> Stats()=>JsonDocument.Parse(await control.GetStringAsync(new Uri(endpoint,"__status"))).RootElement;
using (var session = new S101AccountSession(endpoint))
{
foreach(var mode in new[]{"401","reset","timeout"})
{
await control.GetStringAsync(new Uri(endpoint,"__control?mode="+mode));
var started=DateTime.UtcNow;
var failed=await session.LoginAsync("102");
var elapsed=(DateTime.UtcNow-started).TotalSeconds;
var expected=mode=="401"?AccountLoginError.Http:mode=="reset"?AccountLoginError.Network:AccountLoginError.Timeout;
Check(failed.Error==expected&&!session.IsAuthenticated&&session.PlayerId==null&&session.InternalId==0,"Failure isolation "+mode+"/"+failed.Error);
Check(mode!="401"||failed.StatusCode==401,"Expected status401");
Check(mode!="timeout"||elapsed>=14,"Production timeout not reached");
var recovered=await session.LoginAsync("102");
Check(recovered.Success&&session.IsAuthenticated&&session.PlayerId=="102","Login recovery "+recovered.Error);
Check((await session.CheckAuthorizationAsync()).Success,"Recovery cookie rejected");
Console.WriteLine($"PASS real HTTP {mode}: {failed.Error}; seconds={elapsed:F1}; retry+authorized readonly Get=OK");
}
}
using var account=new S101AccountSession(endpoint);
var mgr=new CustomServerMgr(new Config(endpoint.AbsoluteUri));
var rt=new SignalRService(mgr,null);
try{
Check((await account.LoginAsync("102")).Success,"Hub account login");
mgr.UseAccountSession(account);
await rt.Connect();
Check(rt.IsConnected&&await rt.Publish("Test","acceptance","before")=="OK","Initial hub RPC");
var before=await Stats();
await control.GetStringAsync(new Uri(endpoint,"__control?mode=drop"));
bool observed=false;
var deadline=DateTime.UtcNow.AddSeconds(35);
while(DateTime.UtcNow<deadline){
if(!rt.IsConnected)observed=true;
if(observed&&rt.IsConnected)break;
await Task.Delay(50);
}
Check(observed&&rt.IsConnected,"Real socket automatic reconnect");
Check(await rt.Publish("Test","acceptance","after")=="OK","Post-reconnect RPC");
var after=await Stats();
Check(after.GetProperty("upgrades").GetInt32()>before.GetProperty("upgrades").GetInt32(),"No new real websocket upgrade");
Check(after.GetProperty("negotiate").GetInt32()==0,"Unexpected negotiate request");
Console.WriteLine("PASS real WebSocket dropped, disconnected observed, automatically recovered, Test=OK, negotiate=0");
await rt.ReleaseAsync();
await Task.Delay(500);
var released=await Stats();
await Task.Delay(2500);
var stable=await Stats();
Check(!rt.IsConnected&&stable.GetProperty("active").GetInt32()==0&&stable.GetProperty("upgrades").GetInt32()==released.GetProperty("upgrades").GetInt32(),"Release leaked or reconnected");
Console.WriteLine("PASS ReleaseAsync: active sockets=0, no late reconnect");
Console.WriteLine("SignalR DLL="+typeof(HubConnection).Assembly.GetName().Version);
Console.WriteLine("Fixture counters="+stable.GetRawText());
}finally{await rt.ReleaseAsync();mgr.ClearAccountSession();}