using System.Collections.Concurrent; using Microsoft.Playwright; namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests; /// Exercises the deployed activity listener with browser-trusted input and real antiforgery. [Collection("Playwright")] public class SessionActivityTrustedInputTests(PlaywrightFixture fixture) { /// Verifies that synthetic input and pings stay passive while a trusted key press renews the session with antiforgery. /// A task that represents the asynchronous operation. [Fact] public async Task UI2_SyntheticInputAndPingStayPassive_TrustedKeyRenewsWithAntiforgery() { // The fixture logs in using fetch, so this fresh context has no prior trusted input // and no activity request consuming the listener's 15-second throttle window. var page = await fixture.NewAuthenticatedPageAsync(); try { await page.WaitForFunctionAsync(""" () => performance.getEntriesByType('resource').some( r => r.name.includes('/js/session-expiry.js')) """, null, new() { Timeout = 15000 }); var token = page.Locator("input[name='__RequestVerificationToken']").First; await Assertions.Expect(token).ToHaveAttributeAsync("value", new System.Text.RegularExpressions.Regex(".+")); var activityRequests = new ConcurrentQueue(); page.Request += (_, request) => { if (new Uri(request.Url).AbsolutePath == "/auth/activity") activityRequests.Enqueue(request); }; var pingStatus = await page.EvaluateAsync(""" async () => { for (const type of ['pointerdown', 'keydown', 'wheel', 'touchstart']) document.dispatchEvent(new Event(type, { bubbles: true })); return (await fetch('/auth/ping', { cache: 'no-store' })).status; } """); Assert.Equal(200, pingStatus); // This bounded observation catches fetch scheduled by synthetic events without // waiting for the real 30-minute idle expiry (covered by fake-clock HTTP tests). await page.WaitForTimeoutAsync(500); Assert.Empty(activityRequests); var responseTask = page.WaitForResponseAsync( response => new Uri(response.Url).AbsolutePath == "/auth/activity", new() { Timeout = 10000 }); await page.Keyboard.PressAsync("Shift"); // Browser-generated keydown has isTrusted=true. var response = await responseTask; Assert.Equal(204, response.Status); var request = Assert.Single(activityRequests); Assert.Equal("POST", request.Method); var headers = await request.AllHeadersAsync(); Assert.True(headers.TryGetValue("requestverificationtoken", out var sentToken)); Assert.Equal(await token.InputValueAsync(), sentToken); // A second trusted key inside 15 seconds must not flood the activity endpoint. await page.Keyboard.PressAsync("Shift"); await page.WaitForTimeoutAsync(500); Assert.Single(activityRequests); } finally { await page.Context.CloseAsync(); } } }