// The balance refresh, and the address scan after a wallet is created, do not // report a request the popup's own closing cancelled, and still report one // that failed while the popup was open // (https://git.eeqj.de/sneak/AutistMask/issues/218, // https://git.eeqj.de/sneak/AutistMask/issues/475). // // In the popup a cancelled fetch() fails with the same "Failed to fetch" as a // server that cannot be reached, so every request here fails that way, and // only the signal the popup aborts on pagehide tells the two cases apart. const { FetchRequest } = require("ethers"); const { refreshBalances, scanForAddresses } = require("../src/shared/balances"); const { generateMnemonic, hdWalletFromMnemonic, } = require("../src/shared/wallet"); const RPC_URL = "https://rpc.example.invalid"; const EXPLORER_URL = "https://explorer.example.invalid/api/v2"; const ADDRESS = "0x1111111111111111111111111111111111111111"; const realFetch = globalThis.fetch; let logged; beforeEach(() => { logged = []; jest.spyOn(console, "error").mockImplementation((...args) => { logged.push(args.map(String).join(" ")); }); const failedToFetch = async () => { throw new TypeError("Failed to fetch"); }; // The RPC calls (ETH balance, ENS name, and the scan's balance and // transaction count) and the explorer request (token balances) all fail // the same way. FetchRequest.registerGetUrl(failedToFetch); globalThis.fetch = jest.fn(failedToFetch); }); afterEach(() => { FetchRequest.registerGetUrl(FetchRequest.createGetUrlFunc()); globalThis.fetch = realFetch; jest.restoreAllMocks(); }); function refresh(signal) { const wallets = [{ addresses: [{ address: ADDRESS }] }]; return refreshBalances( wallets, RPC_URL, EXPLORER_URL, [], "mainnet", signal, ); } test("a failure while the popup is open is reported", async () => { await refresh(new AbortController().signal); for (const label of [ "ETH balance failed", "ENS reverse failed", "fetchTokenBalances failed: Failed to fetch", ]) { expect(logged.some((line) => line.includes(label))).toBe(true); } }); test("a failure once the popup has closed is not", async () => { const pageClosed = new AbortController(); pageClosed.abort(); await refresh(pageClosed.signal); expect(logged).toEqual([]); }); function scan(signal) { // What the scan found is logged at info level, which is not under test. jest.spyOn(console, "log").mockImplementation(() => {}); const { xpub } = hdWalletFromMnemonic(generateMnemonic()); return scanForAddresses(xpub, RPC_URL, "mainnet", signal); } test("a scan failure while the popup is open is reported", async () => { await scan(new AbortController().signal); expect( logged.some((line) => line.includes("scanForAddresses check failed")), ).toBe(true); }); test("a scan failure once the popup has closed is not", async () => { const pageClosed = new AbortController(); pageClosed.abort(); await scan(pageClosed.signal); expect(logged).toEqual([]); });