1f8c5810ca
An ephemeral inbound method (created and deleted through the CLI, with a never-taken Route.To branch so prediction resolves a route target) is run live in a real browser through the fixture's /auth/login; the reload test drives the OnAfterRenderAsync localStorage restore and then proves the page is still interactive. Both fail on the pre-fix image at the 'Predicted' wait (the circuit deadlock) and pass on the fixed one. Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
76 lines
3.0 KiB
C#
76 lines
3.0 KiB
C#
using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment;
|
|
|
|
/// <summary>
|
|
/// <see cref="IAsyncLifetime"/> fixture for the Flow Debugger E2E tests: provisions ONE ephemeral
|
|
/// inbound API method through the CLI and deletes it on dispose, so the tests never depend on
|
|
/// rig-seeded methods (the <c>FlowOutboundDemo</c> / <c>DelmiaRecipeDownload</c> stand-ins) and
|
|
/// never need the login-disabled override — the Playwright fixture logs in through
|
|
/// <c>/auth/login</c> like every other page test.
|
|
///
|
|
/// <para>
|
|
/// The method takes one <c>Text</c> parameter and echoes it. Its script also carries a
|
|
/// <c>Route.To(...).Call(...)</c> on a branch that never executes (the target instance does not
|
|
/// exist): the Flow Debugger's <em>prediction</em> walks that branch anyway and resolves the route
|
|
/// target through the repository, which is exactly the path that used to deadlock the Blazor
|
|
/// circuit (sync-over-async in <c>FlowDebugService.PredictMethodAsync</c>, fixed 2026-09-06) — a
|
|
/// method with no route target would not exercise it.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Gated on <see cref="ClusterAvailability.IsAvailableAsync"/>: when the dev cluster is down,
|
|
/// <see cref="Available"/> is <see langword="false"/>, nothing is provisioned and the consuming
|
|
/// tests skip.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class FlowDebuggerFixture : IAsyncLifetime
|
|
{
|
|
/// <summary>The parameter-definitions JSON of the ephemeral method (one required string).</summary>
|
|
private const string ParametersJson =
|
|
"""{"type":"object","properties":{"Text":{"type":"string"}},"required":["Text"]}""";
|
|
|
|
/// <summary>Whether the cluster was reachable at setup and the method exists.</summary>
|
|
public bool Available { get; private set; }
|
|
|
|
/// <summary>Id of the ephemeral inbound method (0 when unavailable).</summary>
|
|
public int MethodId { get; private set; }
|
|
|
|
/// <summary>Name of the ephemeral inbound method (its <c>POST /api/{name}</c> segment).</summary>
|
|
public string MethodName { get; private set; } = string.Empty;
|
|
|
|
/// <inheritdoc/>
|
|
public async Task InitializeAsync()
|
|
{
|
|
Available = await ClusterAvailability.IsAvailableAsync();
|
|
if (!Available)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MethodName = CliRunner.UniqueName("flow");
|
|
const string script =
|
|
"""
|
|
var text = Parameters.ContainsKey("Text") ? Parameters["Text"]?.ToString() : null;
|
|
if (text == "route-me")
|
|
{
|
|
return await Route.To("zztest-no-such-instance").Call("Process", new { });
|
|
}
|
|
return new { ok = true, echo = text };
|
|
""";
|
|
try
|
|
{
|
|
MethodId = await CliRunner.CreateApiMethodAsync(MethodName, script, ParametersJson);
|
|
}
|
|
catch
|
|
{
|
|
Available = false;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public Task DisposeAsync() =>
|
|
MethodId > 0 ? CliRunner.DeleteApiMethodAsync(MethodId) : Task.CompletedTask;
|
|
}
|