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 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.UtcNowbefore.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();}