feat: add read-only bank operations console

This commit is contained in:
golem
2026-08-21 03:35:45 -06:00
parent 9ce8843279
commit 10507e769d
18 changed files with 967 additions and 1 deletions
+191
View File
@@ -0,0 +1,191 @@
import { render, screen } from "@testing-library/react";
import type { Address, Hex } from "viem";
import { describe, expect, it } from "vitest";
import shellHtml from "../index.html?raw";
import { App } from "./App";
import type { DashboardState } from "./hooks/useBankDashboard";
import type { DashboardSnapshot, DeploymentManifest } from "./types/dashboard";
const address = (digit: string) => `0x${digit.repeat(40)}` as Address;
const transactionHash = (digit: string) => `0x${digit.repeat(64)}` as Hex;
const localManifest: DeploymentManifest = {
schemaVersion: 1,
network: "anvil",
chainId: 31337,
deploymentBlock: 3n,
rpcUrl: "http://127.0.0.1:8545",
token: address("1"),
proxy: address("2"),
implementation: address("3"),
owner: address("4"),
actors: [
{ label: "Alice", address: address("5") },
{ label: "Bob", address: address("6") },
],
};
const snapshot: DashboardSnapshot = {
blockNumber: 18n,
synchronizedAt: new Date("2026-08-21T10:00:00.000Z"),
version: 1,
paused: true,
asset: localManifest.token,
owner: localManifest.owner,
proxy: localManifest.proxy,
implementation: localManifest.implementation,
reserves: 1_500_000_000n,
liabilities: 1_400_000_000n,
surplus: 100_000_000n,
actors: [
{ label: "Alice", address: address("5"), balance: 900_000_000n },
{ label: "Bob", address: address("6"), balance: 500_000_000n },
],
activity: [
{ kind: "deposit", account: address("5"), amount: 1_000_000_000n, blockNumber: 4n, logIndex: 0, transactionHash: transactionHash("a") },
{ kind: "withdrawal", account: address("5"), amount: 100_000_000n, blockNumber: 5n, logIndex: 0, transactionHash: transactionHash("b") },
{ kind: "paused", account: address("4"), blockNumber: 6n, logIndex: 0, transactionHash: transactionHash("c") },
{ kind: "ownershipTransferred", previousOwner: address("7"), newOwner: address("4"), blockNumber: 3n, logIndex: 1, transactionHash: transactionHash("d") },
{ kind: "upgraded", implementation: address("3"), blockNumber: 3n, logIndex: 0, transactionHash: transactionHash("d") },
],
diagnostics: [{ blockNumber: 7n, logIndex: 2, transactionHash: transactionHash("e"), message: "Could not decode a proxy event log." }],
};
function ready(manifest = localManifest, value = snapshot): DashboardState {
return { status: "ready", manifest, snapshot: value };
}
describe("read-only operations console", () => {
it("loads the console entry point from the browser document", () => {
const document = new DOMParser().parseFromString(shellHtml, "text/html");
expect(document.querySelector('script[type="module"]')?.getAttribute("src")).toBe("/src/main.tsx");
});
it("keeps the exact educational funds warning and complete trust disclosure visible", () => {
render(<App dashboard={ready()} />);
expect(screen.getByText("Educational demo — mock token — never use real funds.")).toBeTruthy();
expect(screen.getByText(/educational and unaudited/i)).toBeTruthy();
expect(screen.getByText(/owner can pause customer actions and install arbitrary future logic/i)).toBeTruthy();
expect(screen.getByText(/UUPS mistakes can corrupt state or permanently brick upgradeability/i)).toBeTruthy();
expect(screen.getByText(/professional audits, operational key controls, multisig or timelocked governance, incident procedures, legal advice, and jurisdiction-specific compliance/i)).toBeTruthy();
});
it("renders network, synchronization, lifecycle, pause, and V1 status", () => {
render(<App dashboard={ready()} />);
expect(screen.getByText("Local Anvil")).toBeTruthy();
expect(screen.getByText("Ready")).toBeTruthy();
expect(screen.getByText("Block 18")).toBeTruthy();
expect(screen.getByText("Paused")).toBeTruthy();
expect(screen.getByText("Version 1")).toBeTruthy();
expect(screen.getByText(/Aug 21, 2026/)).toBeTruthy();
});
it("renders accounting, contract identity, and local tracked accounts", () => {
render(<App dashboard={ready()} />);
expect(screen.getByText("1,500.00 mUSDC")).toBeTruthy();
expect(screen.getByText("1,400.00 mUSDC")).toBeTruthy();
expect(screen.getByText("100.00 mUSDC")).toBeTruthy();
expect(screen.getByText("107.14%")).toBeTruthy();
expect(screen.getByText("Proxy")).toBeTruthy();
expect(screen.getByText("Implementation")).toBeTruthy();
expect(screen.getByText("Token")).toBeTruthy();
expect(screen.getByText("Owner")).toBeTruthy();
expect(screen.getByText("Deployment block")).toBeTruthy();
expect(screen.getByText("Alice")).toBeTruthy();
expect(screen.getByText("Bob")).toBeTruthy();
expect(screen.getByText("900.00 mUSDC")).toBeTruthy();
expect(screen.getByText("500.00 mUSDC")).toBeTruthy();
});
it("renders valid V1 activity newest first beside an isolated decode warning", () => {
render(<App dashboard={ready()} />);
const timeline = screen.getByLabelText("Proxy activity");
expect(timeline.textContent).toContain("Deposited");
expect(timeline.textContent).toContain("Withdrawn");
expect(timeline.textContent).toContain("Paused by");
expect(timeline.textContent).toContain("Ownership transferred");
expect(timeline.textContent).toContain("Implementation upgraded");
expect(timeline.textContent).toContain("Block 7");
expect(timeline.textContent).toContain("Could not decode a proxy event log.");
expect(timeline.textContent?.indexOf("Paused by")).toBeLessThan(timeline.textContent?.indexOf("Withdrawn") ?? 0);
expect(timeline.textContent).toContain("0xcccc…cccc");
});
it("uses validated Base Sepolia explorer links and public shortened account labels", () => {
const baseManifest: DeploymentManifest = {
...localManifest,
network: "baseSepolia",
chainId: 84532,
rpcUrl: "https://sepolia.base.org",
explorerBaseUrl: "https://sepolia.basescan.org",
};
render(<App dashboard={ready(baseManifest)} />);
expect(screen.getByText("Base Sepolia")).toBeTruthy();
expect(screen.queryByText("Alice")).toBeNull();
expect(screen.getAllByText("0x5555…5555").length).toBeGreaterThan(0);
const proxyLink = screen.getByRole("link", { name: /proxy.*0x2222…2222/i });
expect(proxyLink.getAttribute("href")).toBe(`https://sepolia.basescan.org/address/${snapshot.proxy}`);
const transactionLink = screen.getByRole("link", { name: /transaction 0xcccc…cccc/i });
expect(transactionLink.getAttribute("href")).toBe(`https://sepolia.basescan.org/tx/${transactionHash("c")}`);
});
it("does not create explorer links for local Anvil", () => {
render(<App dashboard={ready()} />);
expect(screen.queryAllByRole("link")).toHaveLength(0);
});
it("shows No deposits instead of dividing zero liabilities", () => {
render(<App dashboard={ready(localManifest, { ...snapshot, reserves: 0n, liabilities: 0n, surplus: 0n })} />);
expect(screen.getByText("No deposits")).toBeTruthy();
});
it("distinguishes stale and disconnected conditions without hiding the persistent warnings", () => {
const { rerender } = render(<App dashboard={{
status: "stale",
manifest: localManifest,
snapshot,
failedAt: new Date("2026-08-21T10:05:00.000Z"),
error: "RPC timeout",
}} />);
expect(screen.getByText("Stale")).toBeTruthy();
expect(screen.getByText(/RPC timeout/)).toBeTruthy();
expect(screen.getByText("1,500.00 mUSDC")).toBeTruthy();
rerender(<App dashboard={{
status: "disconnected",
manifest: localManifest,
failedAt: new Date("2026-08-21T10:06:00.000Z"),
error: "connection refused",
}} />);
expect(screen.getAllByText("Disconnected")).toHaveLength(2);
expect(screen.getByText(/connection refused/)).toBeTruthy();
expect(screen.getByText("Educational demo — mock token — never use real funds.")).toBeTruthy();
expect(screen.getByText(/educational and unaudited/i)).toBeTruthy();
});
it("renders explanatory invalid-manifest and chain-mismatch terminal screens", () => {
const { rerender } = render(<App dashboard={{ status: "invalid-manifest", error: "manifest is missing required field proxy" }} />);
expect(screen.getAllByText("Invalid manifest")).toHaveLength(2);
expect(screen.getByText(/missing required field proxy/)).toBeTruthy();
rerender(<App dashboard={{
status: "chain-mismatch",
manifest: localManifest,
error: "endpoint chain ID 84532 does not match manifest chain ID 31337",
}} />);
expect(screen.getAllByText("Chain mismatch")).toHaveLength(2);
expect(screen.getByText(/84532.*31337/)).toBeTruthy();
});
it("exposes no forms, buttons, wallet connection, signing, or transaction controls", () => {
const { container } = render(<App dashboard={ready()} />);
expect(container.querySelector("button")).toBeNull();
expect(container.querySelector("form")).toBeNull();
expect(container.textContent).not.toMatch(/connect wallet|sign transaction|submit transaction|deposit funds|withdraw funds/i);
});
});