"use strict"; const test = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const path = require("node:path"); const workflow = require("../../Web/named-playlist-workflow.js"); const repositoryRoot = path.resolve(__dirname, "..", ".."); const mainWindow = fs.readFileSync(path.join(repositoryRoot, "MainWindow.xaml.cs"), "utf8"); const namedBridge = fs.readFileSync(path.join(repositoryRoot, "MainWindow.NamedPlaylists.cs"), "utf8"); const pagePlanBridge = fs.readFileSync(path.join(repositoryRoot, "MainWindow.PlaylistPagePlans.cs"), "utf8"); const appScript = fs.readFileSync(path.join(repositoryRoot, "Web", "app.js"), "utf8"); test("native switch and JavaScript contract use the same five requests and four responses", () => { for (const requestType of Object.values(workflow.bridgeContract.requests)) { assert.match(mainWindow, new RegExp(`case "${requestType}"`)); } for (const responseType of Object.values(workflow.bridgeContract.responses)) { assert.match(namedBridge, new RegExp(`"${responseType}"`)); } assert.match(mainWindow, /new LegacyNamedPlaylistPersistenceService\(/); assert.match(mainWindow, /new OracleNamedPlaylistMutationExecutor\(/); }); test("named reads use latest-request cancellation and the shared playout-priority database gate", () => { assert.match(namedBridge, /Interlocked\.Exchange\(\s*ref _namedPlaylistReadCancellation,\s*requestCancellation\)/s); assert.match(namedBridge, /CancelRequest\(previousCancellation\)/); assert.ok((namedBridge.match(/await _databaseActivityGate\.WaitAsync\(requestToken\)/g) || []).length >= 2); assert.ok((namedBridge.match(/Volatile\.Read\(ref _playoutCommandInFlight\) != 0/g) || []).length >= 4); assert.match(mainWindow, /CancelRequest\(Volatile\.Read\(ref _namedPlaylistReadCancellation\)\)/); assert.match(mainWindow, /CancelRequest\(Volatile\.Read\(ref _namedPlaylistWriteCancellation\)\)/); }); test("writes are single-flight, never superseded, and permanently quarantine OutcomeUnknown", () => { assert.match(namedBridge, /_namedPlaylistWriteGate\.WaitAsync\(0\)/); assert.match(namedBridge, /catch \(NamedPlaylistMutationException exception\)/); assert.match(namedBridge, /if \(exception\.OutcomeUnknown\)/); assert.match(namedBridge, /Interlocked\.CompareExchange\(ref _namedPlaylistWriteQuarantined, 1, 0\)/); assert.match(namedBridge, /IsNamedPlaylistWriteQuarantined\(\)/); assert.match(namedBridge, /do not retry|was not retried/i); assert.doesNotMatch(namedBridge, /for\s*\([^)]*retry|while\s*\([^)]*retry/i); }); test("browser correlation loss cancels active requests and latches an in-flight write", () => { assert.match(mainWindow, /private void QuarantineIfBrowserCorrelationCanBeLost\(\)\s*\{\s*InvalidateNamedPlaylistBrowserRequests\(\)/s); assert.match(namedBridge, /Interlocked\.Increment\(ref _namedPlaylistBrowserGeneration\)/); assert.match(namedBridge, /Volatile\.Read\(ref _namedPlaylistWriteInFlight\) != 0/); assert.match(namedBridge, /Interlocked\.Exchange\(\s*ref _namedPlaylistWriteCancellation,\s*null\)/s); }); test("native replacement accepts only the exact eight raw-row properties and 1000 rows", () => { for (const property of [ "itemIndex", "enabled", "groupCode", "subject", "graphicType", "subtype", "pageText", "dataCode" ]) { assert.match(namedBridge, new RegExp(`"${property}"`)); } assert.match(namedBridge, /itemsElement\.GetArrayLength\(\) > LegacyNamedPlaylistPersistenceService\.MaximumItems/); assert.match(namedBridge, /itemIndex != expectedIndex/); assert.match(namedBridge, /value\.Contains\('\^'\)/); assert.match(namedBridge, /return string\.Equals\(page\.ToString\(\), value, StringComparison\.Ordinal\)/); }); test("page preflight uses one correlated read-only bridge with playout priority", () => { assert.match(mainWindow, /case "request-playlist-page-plans"/); assert.match(mainWindow, /CancelRequest\(Volatile\.Read\(ref _playlistPagePlanCancellation\)\)/); assert.match(pagePlanBridge, /Interlocked\.Exchange\(\s*ref _playlistPagePlanCancellation,\s*requestCancellation\)/s); assert.match(pagePlanBridge, /await _databaseActivityGate\.WaitAsync\(requestToken\)/); assert.ok((pagePlanBridge.match(/Volatile\.Read\(ref _playoutCommandInFlight\) != 0/g) || []).length >= 2); assert.match(pagePlanBridge, /PreflightPagePlansAsync\(/); assert.match(pagePlanBridge, /PostMessage\("playlist-page-plans-results"/); assert.match(pagePlanBridge, /PostMessage\("playlist-page-plans-error"/); assert.doesNotMatch(pagePlanBridge, /PrepareAsync\(|PlayAsync\(|ClearAsync\(|UnloadAsync\(/); }); test("browser page-plan handling requires exact correlation and exposes explicit retry", () => { assert.match(appScript, /normalizePagePlanResponse\(payload, pending\.request\)/); assert.match(appScript, /payload\?\.requestId !== pending\.requestId/); assert.match(appScript, /applyPagePlanResponse\(/); assert.match(appScript, /page-result-empty/); assert.match(appScript, /namedPlaylistPageRetryButton/); assert.match(appScript, /case "playlist-page-plans-results"/); assert.match(appScript, /case "playlist-page-plans-error"/); assert.doesNotMatch(appScript, /markPageRecalculated\(/); });