From 484438044495fe0e252550fd2b1819d215a91841 Mon Sep 17 00:00:00 2001 From: bgptr Date: Thu, 10 Nov 2022 09:04:17 +0100 Subject: [PATCH 1/7] paths.js --- app/main_dev/paths.js | 21 +- test/unit/main_dev/paths.spec.js | 713 +++++++++++++++++++++++++++++++ 2 files changed, 720 insertions(+), 14 deletions(-) create mode 100644 test/unit/main_dev/paths.spec.js diff --git a/app/main_dev/paths.js b/app/main_dev/paths.js index f9c1509914..d18d8f01cd 100644 --- a/app/main_dev/paths.js +++ b/app/main_dev/paths.js @@ -37,11 +37,7 @@ export function getWalletsDirectoryPath() { // getWalletsDirectoryPathNetwork gets the wallets directory. // Example in unix if testnet equals true: ~/.config/decrediton/wallets/testnet export function getWalletsDirectoryPathNetwork(testnet) { - return path.join( - getAppDataDirectory(), - "wallets", - testnet ? TESTNET : MAINNET - ); + return path.join(getWalletsDirectoryPath(), testnet ? TESTNET : MAINNET); } // getWalletPath returns the directory of a selected wallet byt its name. @@ -54,9 +50,7 @@ export function getWalletPath(testnet, walletName = "") { // walletPath represents the wallet name decrediton has loaded. export function getWalletDb(testnet, walletPath) { return path.join( - getWalletsDirectoryPath(), - testnet ? TESTNET : MAINNET, - walletPath, + getWalletPath(testnet, walletPath), testnet ? "testnet3" : MAINNET, "wallet.db" ); @@ -90,12 +84,11 @@ export function getDcrdRpcCert(appDataPath) { return path.resolve(appDataPath ? appDataPath : getDcrdPath(), "rpc.cert"); } -export function getCertsPath(name, custombinpath) { - const binPath = custombinpath - ? custombinpath - : process.env.NODE_ENV === "development" - ? path.join(__dirname, "..", "certs") - : path.join(process.resourcesPath, "certs"); +export function getCertsPath() { + const binPath = + process.env.NODE_ENV === "development" + ? path.join(__dirname, "..", "certs") + : path.join(process.resourcesPath, "certs"); return binPath; } diff --git a/test/unit/main_dev/paths.spec.js b/test/unit/main_dev/paths.spec.js new file mode 100644 index 0000000000..74913597ce --- /dev/null +++ b/test/unit/main_dev/paths.spec.js @@ -0,0 +1,713 @@ +import os from "os"; +import path from "path"; +import fs from "fs"; +import * as p from "../../../app/main_dev/paths"; +import * as c from "../../../app/config"; +import { TESTNET, MAINNET } from "constants"; + +const paths = p; +const config = c; + +jest.mock("fs"); + +const testHomeDir = "/home/testUser"; +const testWalletName = "test-wallet-name"; +const testConfigPath = "/test-config-path"; +const testAppDataPath = "/test-appdata-path"; +const testResourcePath = "/test-resource-path"; +const testExecutableName = "test-executable-name"; +const testLogDir = "test-log-dir"; +const testPoliteiaPath = `${testHomeDir}/.config/decrediton/politeia`; +const testToken = "test-token"; +const testProposalPath = `${testPoliteiaPath}/${testToken}`; +const testProposalEligibleTicketsPath = `${testProposalPath}/eligibletickets.json`; +const testEligibleTickets = { t: 1 }; +const testError = "test-error"; +const testVote = "test-vote"; +const testCustombinpath = "test-custom-bin-path"; +const testInventoryProposals = ["1", "2", "3"]; +const testVoteDirPath = `${testPoliteiaPath}/4`; +const testDirProposals = ["1", "3", "4"]; +const testVoteDirFilesWithOneDirectory = ["file1", "file2", "dir1"]; +const testVoteDirFiles = ["file4", "file5"]; + +let mockMkdirSync; +let mockCopyFileSync; +let mockInitWalletCfg; +let mockNewWalletConfigCreation; +let mockWriteFile; +let mockReadFileSync; +let mockReaddirSync; +let mockLstatSync; +let mockUnlinkSync; +let mockRmdirSync; +let mockExistsSync; + +beforeEach(() => { + jest.spyOn(os, "homedir").mockImplementation(() => testHomeDir); + Object.defineProperty(process, "platform", { + value: "linux" + }); + Object.defineProperty(process, "resourcesPath", { + value: testResourcePath + }); + Object.defineProperty(process, "env", { + value: { + NODE_ENV: "not-development" + } + }); + mockMkdirSync = fs.mkdirSync = jest.fn(() => {}); + mockCopyFileSync = fs.copyFileSync = jest.fn(() => {}); + mockInitWalletCfg = config.initWalletCfg = jest.fn(() => {}); + mockNewWalletConfigCreation = config.newWalletConfigCreation = jest.fn( + () => {} + ); + mockExistsSync = fs.existsSync = jest.fn(() => {}); + mockWriteFile = fs.writeFile = jest.fn(() => {}); + mockReadFileSync = fs.readFileSync = jest.fn(() => + JSON.stringify(testEligibleTickets) + ); + mockReaddirSync = fs.readdirSync = jest.fn((dir) => { + if (dir === testVoteDirPath) { + return testVoteDirFilesWithOneDirectory; + } else if (dir === `${testVoteDirPath}/dir1`) { + return testVoteDirFiles; + } else { + return testDirProposals; + } + }); + mockLstatSync = fs.lstatSync = jest.fn(() => {}); + mockUnlinkSync = fs.unlinkSync = jest.fn(() => {}); + mockRmdirSync = fs.rmdirSync = jest.fn(() => {}); +}); + +test("test getAppDataDirectory - on windows", () => { + jest.spyOn(os, "platform").mockImplementation(() => "win32"); + const res = paths.getAppDataDirectory(); + expect(res).toBe(`${testHomeDir}/AppData/Local/Decrediton`); +}); + +test("test getAppDataDirectory - on darwin", () => { + jest.spyOn(os, "platform").mockImplementation(() => null); + Object.defineProperty(process, "platform", { + value: "darwin" + }); + const res = paths.getAppDataDirectory(); + expect(res).toBe(`${testHomeDir}/Library/Application Support/decrediton`); +}); + +test("test getAppDataDirectory - on linux", () => { + const res = paths.getAppDataDirectory(); + expect(res).toBe(`${testHomeDir}/.config/decrediton`); +}); + +test("test getGlobalCfgPath - on linux", () => { + const res = paths.getGlobalCfgPath(); + expect(res).toBe(`${testHomeDir}/.config/decrediton/config.json`); +}); + +test("test getWalletsDirectoryPath - on linux", () => { + const res = paths.getWalletsDirectoryPath(); + expect(res).toBe(`${testHomeDir}/.config/decrediton/wallets`); +}); + +test("test getWalletsDirectoryPathNetwork - on linux, testnet", () => { + const res = paths.getWalletsDirectoryPathNetwork(true); + expect(res).toBe(`${testHomeDir}/.config/decrediton/wallets/testnet`); +}); + +test("test getWalletsDirectoryPathNetwork - on linux, mainnet", () => { + const res = paths.getWalletsDirectoryPathNetwork(); + expect(res).toBe(`${testHomeDir}/.config/decrediton/wallets/mainnet`); +}); + +test("test getWalletPath - on linux, mainnet", () => { + const res = paths.getWalletPath(false, testWalletName); + expect(res).toBe( + `${testHomeDir}/.config/decrediton/wallets/mainnet/${testWalletName}` + ); +}); + +test("test getWalletPath - on linux, mainnet without name parameter", () => { + const res = paths.getWalletPath(false); + expect(res).toBe(`${testHomeDir}/.config/decrediton/wallets/mainnet`); +}); + +test("test getWalletDb - on linux, mainnet", () => { + const res = paths.getWalletDb(false, testWalletName); + expect(res).toBe( + `${testHomeDir}/.config/decrediton/wallets/mainnet/${testWalletName}/mainnet/wallet.db` + ); +}); + +test("test getWalletDb - on linux, testnet", () => { + const res = paths.getWalletDb(true, testWalletName); + expect(res).toBe( + `${testHomeDir}/.config/decrediton/wallets/testnet/${testWalletName}/testnet3/wallet.db` + ); +}); + +test("test dcrdCfg", () => { + const res = paths.dcrdCfg(testConfigPath); + expect(res).toBe(`${testConfigPath}/dcrd.conf`); +}); + +test("test dcrwalletConf", () => { + const res = paths.dcrwalletConf(testConfigPath); + expect(res).toBe(`${testConfigPath}/dcrwallet.conf`); +}); + +test("test getDcrdPath - on windows", () => { + jest.spyOn(os, "platform").mockImplementation(() => "win32"); + const res = paths.getDcrdPath(); + expect(res).toBe(`${testHomeDir}/AppData/Local/Dcrd`); +}); + +test("test getDcrdPath - on darwin", () => { + jest.spyOn(os, "platform").mockImplementation(() => null); + Object.defineProperty(process, "platform", { + value: "darwin" + }); + const res = paths.getDcrdPath(); + expect(res).toBe(`${testHomeDir}/Library/Application Support/dcrd`); +}); + +test("test getDcrdPath - on linux", () => { + const res = paths.getDcrdPath(); + expect(res).toBe(`${testHomeDir}/.dcrd`); +}); + +test("test getDcrdRpcCert - on linux", () => { + const res = paths.getDcrdRpcCert(); + expect(res).toBe(`${testHomeDir}/.dcrd/rpc.cert`); +}); + +test("test getDcrdRpcCert - on linux with app data path parameter", () => { + const res = paths.getDcrdRpcCert(testAppDataPath); + expect(res).toBe(`${testAppDataPath}/rpc.cert`); +}); + +test("test getCertsPath", () => { + const res = paths.getCertsPath(); + expect(res).toBe(`${testResourcePath}/certs`); +}); + +test("test getCertsPath - in dev env", () => { + Object.defineProperty(process, "env", { + value: { + NODE_ENV: "development" + } + }); + const res = paths.getCertsPath(); + expect(res).toBe(path.join(__dirname, "..", "..", "..", "app", "certs")); +}); + +test("test getExecutablePath", () => { + const res = paths.getExecutablePath(testExecutableName); + expect(res).toBe(`${testResourcePath}/bin/${testExecutableName}`); +}); + +test("test getExecutablePath - custom bin path", () => { + const res = paths.getExecutablePath(testExecutableName, testCustombinpath); + expect(res).toBe(`${testCustombinpath}/${testExecutableName}`); +}); + +test("test getExecutablePath - on windows in development env", () => { + Object.defineProperty(process, "env", { + value: { + NODE_ENV: "development" + } + }); + jest.spyOn(os, "platform").mockImplementation(() => "win32"); + const res = paths.getExecutablePath(testExecutableName); + expect(res).toBe( + `${path.join( + __dirname, + "..", + "..", + "..", + "app", + "bin" + )}/${testExecutableName}.exe` + ); +}); + +test("test getDirectoryLogs", () => { + const res = paths.getDirectoryLogs(testLogDir); + expect(res).toBe(`${testLogDir}/logs`); +}); + +const getTestWalletDirectory = (network) => + `${testHomeDir}/.config/decrediton/wallets/${network}/default-wallet`; +const getTestOldWalletDirectory = (network) => + `${testHomeDir}/.config/decrediton/${ + network === TESTNET ? "testnet3" : network + }`; +const getTestGlobalConfigPath = () => + `${testHomeDir}/.config/decrediton/config.json`; +const getTestConfigJsonPath = (network) => + `${testHomeDir}/.config/decrediton/wallets/${network}/default-wallet/config.json`; + +test("test checkAndInitWalletCfg - on mainnet", () => { + jest.spyOn(os, "platform").mockImplementation(() => null); + const testWalletDirectory = getTestWalletDirectory(MAINNET); + const testOldWalletDirectory = getTestOldWalletDirectory(MAINNET); + const testGlobalConfigPath = getTestGlobalConfigPath(); + const testConfigJsonPath = getTestConfigJsonPath(MAINNET); + mockExistsSync = fs.existsSync = jest.fn((path) => { + if (path === testWalletDirectory) { + return false; + } else if (path === testOldWalletDirectory) { + return true; + } else if (path === testGlobalConfigPath) { + return true; + } + }); + paths.checkAndInitWalletCfg(); + + expect(mockMkdirSync).toHaveBeenCalledWith( + `${testWalletDirectory}/${MAINNET}`, + { + recursive: true + } + ); + + expect(mockCopyFileSync).toHaveBeenNthCalledWith( + 1, + `${testOldWalletDirectory}/wallet.db`, + `${testWalletDirectory}/${MAINNET}/wallet.db` + ); + expect(mockCopyFileSync).toHaveBeenNthCalledWith( + 2, + testGlobalConfigPath, + testConfigJsonPath + ); + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testWalletDirectory); + expect(mockExistsSync).toHaveBeenNthCalledWith(2, testOldWalletDirectory); + expect(mockExistsSync).toHaveBeenNthCalledWith(3, testGlobalConfigPath); + + expect(mockInitWalletCfg).toHaveBeenCalledWith(undefined, "default-wallet"); + + expect(mockNewWalletConfigCreation).toHaveBeenCalledWith( + undefined, + "default-wallet" + ); +}); + +test("test checkAndInitWalletCfg - on testnet", () => { + jest.spyOn(os, "platform").mockImplementation(() => null); + const testWalletDirectory = getTestWalletDirectory(TESTNET); + const testOldWalletDirectory = getTestOldWalletDirectory(TESTNET); + const testGlobalConfigPath = getTestGlobalConfigPath(TESTNET); + + mockExistsSync = fs.existsSync = jest.fn((path) => { + if (path === testWalletDirectory) { + return false; + } else if (path === testOldWalletDirectory) { + return true; + } else if (path === testGlobalConfigPath) { + return true; + } + }); + paths.checkAndInitWalletCfg(true); + + expect(mockMkdirSync).toHaveBeenCalledWith( + `${testWalletDirectory}/${TESTNET}3`, + { + recursive: true + } + ); + + expect(mockCopyFileSync).toHaveBeenNthCalledWith( + 1, + `${testOldWalletDirectory}/wallet.db`, + `${testWalletDirectory}/${TESTNET}3/wallet.db` + ); +}); + +test("test checkAndInitWalletCfg - on testnet, wallet directory exists", () => { + jest.spyOn(os, "platform").mockImplementation(() => null); + const testWalletDirectory = getTestWalletDirectory(TESTNET); + + mockExistsSync = fs.existsSync = jest.fn((path) => { + if (path === testWalletDirectory) { + return true; + } + }); + paths.checkAndInitWalletCfg(true); + + expect(mockMkdirSync).not.toHaveBeenCalled(); + expect(mockCopyFileSync).not.toHaveBeenCalled(); + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testWalletDirectory); +}); + +test("test checkAndInitWalletCfg - on mainnet - global config doesn't exist'", () => { + jest.spyOn(os, "platform").mockImplementation(() => null); + const testWalletDirectory = getTestWalletDirectory(MAINNET); + const testOldWalletDirectory = getTestOldWalletDirectory(MAINNET); + const testGlobalConfigPath = getTestGlobalConfigPath(); + mockExistsSync = fs.existsSync = jest.fn((path) => { + if (path === testWalletDirectory) { + return false; + } else if (path === testOldWalletDirectory) { + return true; + } else if (path === testGlobalConfigPath) { + return false; + } + }); + paths.checkAndInitWalletCfg(); + + expect(mockMkdirSync).toHaveBeenCalledWith( + `${testWalletDirectory}/${MAINNET}`, + { + recursive: true + } + ); + + expect(mockCopyFileSync).toHaveBeenCalledWith( + `${testOldWalletDirectory}/wallet.db`, + `${testWalletDirectory}/${MAINNET}/wallet.db` + ); + expect(mockCopyFileSync).toHaveBeenCalledTimes(1); + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testWalletDirectory); + expect(mockExistsSync).toHaveBeenNthCalledWith(2, testOldWalletDirectory); + expect(mockExistsSync).toHaveBeenNthCalledWith(3, testGlobalConfigPath); + + expect(mockInitWalletCfg).toHaveBeenCalledWith(undefined, "default-wallet"); + + expect(mockNewWalletConfigCreation).toHaveBeenCalledWith( + undefined, + "default-wallet" + ); +}); + +test("test setPoliteiaPath", () => { + mockExistsSync = fs.existsSync = jest.fn(() => false); + paths.setPoliteiaPath(); + + expect(mockExistsSync).toHaveBeenCalledWith(testPoliteiaPath); + expect(mockMkdirSync).toHaveBeenCalledWith(testPoliteiaPath, { + recursive: true, + mode: 0o700 + }); +}); + +test("test setPoliteiaPath - already exists", () => { + mockExistsSync = fs.existsSync = jest.fn(() => true); + paths.setPoliteiaPath(); + + expect(mockExistsSync).toHaveBeenCalledWith(testPoliteiaPath); + expect(mockMkdirSync).not.toHaveBeenCalled(); +}); + +test("test setPoliteiaProposalPath", () => { + mockExistsSync = fs.existsSync = jest.fn(() => false); + const res = paths.setPoliteiaProposalPath(testToken); + + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testProposalPath); + expect(mockExistsSync).toHaveBeenNthCalledWith(2, ""); + expect(res).toBe(testProposalPath); + expect(mockMkdirSync).toHaveBeenCalledWith(testProposalPath, { + recursive: true, + mode: 0o700 + }); +}); + +test("test setPoliteiaProposalPath - already exists", () => { + mockExistsSync = fs.existsSync = jest.fn(() => true); + const res = paths.setPoliteiaProposalPath(testToken); + + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testProposalPath); + expect(mockExistsSync).toHaveBeenNthCalledWith(2, testProposalPath); + expect(res).toBe(undefined); + expect(mockMkdirSync).not.toHaveBeenCalled(); +}); + +test("test saveEligibleTickets", () => { + mockExistsSync = fs.existsSync = jest.fn(() => false); + paths.saveEligibleTickets(testToken, testEligibleTickets); + + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testProposalPath); + expect(mockExistsSync).toHaveBeenNthCalledWith(2, ""); + expect(mockMkdirSync).toHaveBeenCalledWith(testProposalPath, { + recursive: true, + mode: 0o700 + }); + + expect(mockWriteFile).toHaveBeenCalledWith( + `${testProposalPath}/eligibletickets.json`, + JSON.stringify(testEligibleTickets), + expect.any(Function) + ); +}); + +test("test saveEligibleTickets - proposalPath already exists", () => { + mockExistsSync = fs.existsSync = jest.fn(() => true); + paths.saveEligibleTickets(testToken, testEligibleTickets); + + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testProposalPath); + expect(mockExistsSync).toHaveBeenNthCalledWith(2, testProposalPath); + + expect(mockWriteFile).toHaveBeenCalledWith( + `${testProposalPath}/eligibletickets.json`, + JSON.stringify(testEligibleTickets), + expect.any(Function) + ); +}); + +test("test saveEligibleTickets - failed to write file", () => { + mockExistsSync = fs.existsSync = jest.fn(() => false); + mockWriteFile = fs.writeFile = jest.fn(() => { + throw testError; + }); + let catchedError; + try { + paths.saveEligibleTickets(testToken, testEligibleTickets); + } catch (error) { + catchedError = error; + } + expect(catchedError).toBe(testError); + + expect(mockWriteFile).toHaveBeenCalledWith( + testProposalEligibleTicketsPath, + JSON.stringify(testEligibleTickets), + expect.any(Function) + ); +}); + +test("test getEligibleTickets", () => { + mockExistsSync = fs.existsSync = jest.fn(() => true); + const res = paths.getEligibleTickets(testToken); + + expect(mockExistsSync).toHaveBeenCalledWith(testProposalEligibleTicketsPath); + expect(mockReadFileSync).toHaveBeenCalledWith( + testProposalEligibleTicketsPath + ); + expect(res).toStrictEqual(testEligibleTickets); +}); + +test("test getEligibleTickets - proposal path does not exist", () => { + mockExistsSync = fs.existsSync = jest.fn(() => false); + const res = paths.getEligibleTickets(testToken); + + expect(mockExistsSync).toHaveBeenCalledTimes(1); + expect(mockReadFileSync).not.toHaveBeenCalled(); + expect(res).toBeNull(); +}); + +test("test getEligibleTickets - proposal eligible ticket path does not exist", () => { + mockExistsSync = fs.existsSync = jest.fn((path) => + path.includes("eligibletickets") ? false : true + ); + const res = paths.getEligibleTickets(testToken); + + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testProposalPath); + expect(mockExistsSync).toHaveBeenNthCalledWith( + 2, + testProposalEligibleTicketsPath + ); + expect(res).toBeNull(); +}); + +const getWalletPiPath = (network) => + `${testHomeDir}/.config/decrediton/wallets/${network}/${testWalletName}/politeia`; +const getProposalPath = (network) => `${getWalletPiPath(network)}/${testToken}`; +const getVotePath = (network) => `${getProposalPath(network)}/vote.json`; + +test("test savePiVote - on mainnet, wallet pi, proposal and vote path does no exist", () => { + const testWalletPiPath = getWalletPiPath(MAINNET); + const testProposalPath = getProposalPath(MAINNET); + const testVotePath = getVotePath(MAINNET); + mockExistsSync = fs.existsSync = jest.fn(() => false); + paths.savePiVote(testVote, testToken, false /*not testnet*/, testWalletName); + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testWalletPiPath); + expect(mockExistsSync).toHaveBeenNthCalledWith(2, testProposalPath); + expect(mockExistsSync).toHaveBeenNthCalledWith(3, testVotePath); + expect(mockMkdirSync).toHaveBeenNthCalledWith(1, testWalletPiPath, { + recursive: true, + mode: 0o700 + }); + expect(mockMkdirSync).toHaveBeenNthCalledWith(2, testProposalPath, { + recursive: true, + mode: 0o700 + }); + expect(mockWriteFile).toHaveBeenCalledWith( + testVotePath, + JSON.stringify(testVote), + { mode: 0o600 }, + expect.any(Function) + ); +}); + +test("test savePiVote - on mainnet, all path exists", () => { + const testWalletPiPath = getWalletPiPath(MAINNET); + const testProposalPath = getProposalPath(MAINNET); + const testVotePath = getVotePath(MAINNET); + mockExistsSync = fs.existsSync = jest.fn(() => true); + paths.savePiVote(testVote, testToken, false /*not testnet*/, testWalletName); + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testWalletPiPath); + expect(mockExistsSync).toHaveBeenNthCalledWith(2, testProposalPath); + expect(mockExistsSync).toHaveBeenNthCalledWith(3, testVotePath); + expect(mockMkdirSync).not.toHaveBeenCalled(); + expect(mockWriteFile).not.toHaveBeenCalled(); +}); + +test("test getProposalWalletVote - on mainnet", () => { + mockReadFileSync = fs.readFileSync = jest.fn(() => JSON.stringify(testVote)); + const testWalletPiPath = getWalletPiPath(MAINNET); + const testProposalPath = getProposalPath(MAINNET); + const testVotePath = getVotePath(MAINNET); + mockExistsSync = fs.existsSync = jest.fn(() => true); + const res = paths.getProposalWalletVote( + testToken, + false /*not testnet*/, + testWalletName + ); + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testWalletPiPath); + expect(mockExistsSync).toHaveBeenNthCalledWith(2, testProposalPath); + expect(mockReadFileSync).toHaveBeenCalledWith(testVotePath); + expect(res).toBe(testVote); +}); + +test("test getProposalWalletVote - on mainnet, proposal path does not exist", () => { + mockReadFileSync = fs.readFileSync = jest.fn(() => JSON.stringify(testVote)); + const testWalletPiPath = getWalletPiPath(MAINNET); + const testProposalPath = getProposalPath(MAINNET); + mockExistsSync = fs.existsSync = jest.fn((path) => path !== testProposalPath); + const res = paths.getProposalWalletVote( + testToken, + false /*not testnet*/, + testWalletName + ); + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testWalletPiPath); + expect(mockExistsSync).toHaveBeenNthCalledWith(2, testProposalPath); + expect(mockReadFileSync).not.toHaveBeenCalled(); + expect(res).toBeNull(); +}); + +test("test getProposalWalletVote - on mainnet, failed to read file", () => { + mockReadFileSync = fs.readFileSync = jest.fn(() => { + throw testError; + }); + const testWalletPiPath = getWalletPiPath(MAINNET); + const testProposalPath = getProposalPath(MAINNET); + const testVotePath = getVotePath(MAINNET); + mockExistsSync = fs.existsSync = jest.fn(() => true); + + let catchedError; + try { + paths.getProposalWalletVote( + testToken, + false /*not testnet*/, + testWalletName + ); + } catch (error) { + catchedError = error; + } + expect(catchedError).toBe(testError); + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testWalletPiPath); + expect(mockExistsSync).toHaveBeenNthCalledWith(2, testProposalPath); + expect(mockReadFileSync).toHaveBeenCalledWith(testVotePath); +}); + +test("test getProposalWalletVote - on mainnet, failed to read file (ENOENT)", () => { + const testENOENTError = { + message: "..." + "ENOENT: no such file or directory" + "..." + }; + mockReadFileSync = fs.readFileSync = jest.fn(() => { + throw testENOENTError; + }); + const testWalletPiPath = getWalletPiPath(MAINNET); + const testProposalPath = getProposalPath(MAINNET); + const testVotePath = getVotePath(MAINNET); + mockExistsSync = fs.existsSync = jest.fn(() => true); + + let catchedError; + let res; + try { + res = paths.getProposalWalletVote( + testToken, + false /*not testnet*/, + testWalletName + ); + } catch (error) { + catchedError = error; + } + expect(catchedError).toBe(undefined); + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testWalletPiPath); + expect(mockExistsSync).toHaveBeenNthCalledWith(2, testProposalPath); + expect(mockReadFileSync).toHaveBeenCalledWith(testVotePath); + expect(res).toBeNull(); +}); + +test("test removeCachedProposals", () => { + /* + |- "/home/testUser/.config/decrediton/politeia/4/ + |- file1 + |- file2 + |- dir1 + |- file4 + |- file5 + */ + mockLstatSync = fs.lstatSync = jest.fn((dir) => ({ + isDirectory: () => /dir1$/.test(dir) + })); + mockExistsSync = fs.existsSync = jest.fn(() => true); + + paths.removeCachedProposals(testInventoryProposals); + expect(mockReaddirSync).toHaveBeenNthCalledWith(1, testPoliteiaPath); + + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testVoteDirPath); + + expect(mockLstatSync).toHaveBeenCalled(); + expect(mockUnlinkSync).toHaveBeenNthCalledWith(1, `${testVoteDirPath}/file1`); + expect(mockUnlinkSync).toHaveBeenNthCalledWith(2, `${testVoteDirPath}/file2`); + expect(mockUnlinkSync).toHaveBeenNthCalledWith( + 3, + `${testVoteDirPath}/dir1/file4` + ); + expect(mockUnlinkSync).toHaveBeenNthCalledWith( + 4, + `${testVoteDirPath}/dir1/file5` + ); + expect(mockRmdirSync).toHaveBeenNthCalledWith(1, `${testVoteDirPath}/dir1`); + expect(mockRmdirSync).toHaveBeenNthCalledWith(2, `${testVoteDirPath}`); +}); + +test("test removeCachedProposals - vote dir does not exits ", () => { + const testInventoryProposals = ["1", "2", "3"]; + const testVoteDirPath = `${testPoliteiaPath}/4`; + mockExistsSync = fs.existsSync = jest.fn((path) => testVoteDirPath !== path); + + paths.removeCachedProposals(testInventoryProposals); + expect(mockReaddirSync).toHaveBeenNthCalledWith(1, testPoliteiaPath); + + expect(mockExistsSync).toHaveBeenNthCalledWith(1, testVoteDirPath); + + expect(mockLstatSync).not.toHaveBeenCalled(); + expect(mockUnlinkSync).not.toHaveBeenCalled(); + expect(mockRmdirSync).not.toHaveBeenCalled(); +}); + +test("test getSitePath", () => { + const res = paths.getSitePath(); + expect(res).toBe(`${testResourcePath}/bin/site`); +}); + +test("test getSitePath - with custom path", () => { + const res = paths.getSitePath(testCustombinpath); + expect(res).toBe(`${testCustombinpath}/site`); +}); + +test("test getSitePath - on windows in development env", () => { + Object.defineProperty(process, "env", { + value: { + NODE_ENV: "development" + } + }); + jest.spyOn(os, "platform").mockImplementation(() => "win32"); + const res = paths.getSitePath(); + expect(res).toBe( + `${path.join(__dirname, "..", "..", "..", "app", "bin")}/site` + ); +}); From 95c5d2eac4fd59b3abae6ee86ca4196dbf22e908 Mon Sep 17 00:00:00 2001 From: bgptr Date: Thu, 10 Nov 2022 13:13:34 +0100 Subject: [PATCH 2/7] proxy.js --- app/main_dev/proxy.js | 2 +- test/mocks/electronMock.js | 6 ++ test/unit/main_dev/proxy.spec.js | 145 +++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 test/unit/main_dev/proxy.spec.js diff --git a/app/main_dev/proxy.js b/app/main_dev/proxy.js index 5683e969e3..3ded71705c 100644 --- a/app/main_dev/proxy.js +++ b/app/main_dev/proxy.js @@ -39,7 +39,7 @@ export const setupProxy = (logger) => default: if (proxyType) { logger.log("error", "Unknown proxy type " + proxyType); - reject("Unknown proxy type: " + proxyType); + return reject("Unknown proxy type: " + proxyType); } } diff --git a/test/mocks/electronMock.js b/test/mocks/electronMock.js index 8c3182c32f..ffeabdf12a 100644 --- a/test/mocks/electronMock.js +++ b/test/mocks/electronMock.js @@ -17,3 +17,9 @@ export const ipcRenderer = { export const clipboard = { readText: jest.fn(() => "") }; + +export const session = { + defaultSession: { + setProxy: jest.fn(() => Promise.resolve()) + } +}; diff --git a/test/unit/main_dev/proxy.spec.js b/test/unit/main_dev/proxy.spec.js new file mode 100644 index 0000000000..942e98f98c --- /dev/null +++ b/test/unit/main_dev/proxy.spec.js @@ -0,0 +1,145 @@ +import * as p from "../../../app/main_dev/proxy"; +import { session } from "../../mocks/electronMock"; +import * as con from "../../../app/config"; +import { PROXY_TYPE, PROXY_LOCATION } from "constants/config"; + +import { + PROXYTYPE_PAC, + PROXYTYPE_HTTP, + PROXYTYPE_SOCKS4, + PROXYTYPE_SOCKS5 +} from "constants"; + +const proxy = p; +const config = con; + +let mockLog; +let mockGlobalCfgGet; +let mockGlobalCfgSet; +const logger = {}; +const testProxyLocation = "test-proxy-location"; + +beforeEach(() => { + mockLog = logger.log = jest.fn(() => {}); + mockGlobalCfgGet = jest.fn((key) => { + switch (key) { + case PROXY_TYPE: + return PROXYTYPE_PAC; + case PROXY_LOCATION: + return testProxyLocation; + } + }); + mockGlobalCfgSet = jest.fn(() => {}); + config.getGlobalCfg = jest.fn(() => ({ + get: mockGlobalCfgGet, + set: mockGlobalCfgSet + })); + session.defaultSession.setProxy.mockClear(); +}); + +test("test setupProxy - no proxy has been set", async () => { + mockGlobalCfgGet = jest.fn(() => {}); + await proxy.setupProxy(logger); + expect(mockLog).toHaveBeenCalled(); + expect(session.defaultSession.setProxy).toHaveBeenCalledWith({ + pacScript: null, + proxyRules: null, + proxyBypassRules: null + }); +}); + +test("test setupProxy - proxy type is pac", async () => { + await proxy.setupProxy(logger); + expect(session.defaultSession.setProxy).toHaveBeenCalledWith({ + pacScript: testProxyLocation, + proxyRules: null, + proxyBypassRules: null + }); +}); + +test("test setupProxy - proxy type is http", async () => { + mockGlobalCfgGet = jest.fn((key) => { + switch (key) { + case PROXY_TYPE: + return PROXYTYPE_HTTP; + case PROXY_LOCATION: + return testProxyLocation; + } + }); + await proxy.setupProxy(logger); + expect(session.defaultSession.setProxy).toHaveBeenCalledWith({ + pacScript: null, + proxyRules: testProxyLocation, + proxyBypassRules: null + }); +}); + +test("test setupProxy - proxy type is sock4", async () => { + mockGlobalCfgGet = jest.fn((key) => { + switch (key) { + case PROXY_TYPE: + return PROXYTYPE_SOCKS4; + case PROXY_LOCATION: + return testProxyLocation; + } + }); + await proxy.setupProxy(logger); + expect(session.defaultSession.setProxy).toHaveBeenCalledWith({ + pacScript: null, + proxyRules: `socks4://${testProxyLocation}`, + proxyBypassRules: null + }); +}); + +test("test setupProxy - proxy type is sock5", async () => { + mockGlobalCfgGet = jest.fn((key) => { + switch (key) { + case PROXY_TYPE: + return PROXYTYPE_SOCKS5; + case PROXY_LOCATION: + return testProxyLocation; + } + }); + await proxy.setupProxy(logger); + expect(session.defaultSession.setProxy).toHaveBeenCalledWith({ + pacScript: null, + proxyRules: `socks5://${testProxyLocation}`, + proxyBypassRules: null + }); +}); + +test("test setupProxy - failed: proxy type is unknown", async () => { + const testUnknownProxyType = "unknown-proxy-type"; + mockGlobalCfgGet = jest.fn((key) => { + switch (key) { + case PROXY_TYPE: + return testUnknownProxyType; + case PROXY_LOCATION: + return testProxyLocation; + } + }); + let catchedError; + try { + await proxy.setupProxy(logger); + } catch (error) { + catchedError = error; + } + expect(catchedError).toBe(`Unknown proxy type: ${testUnknownProxyType}`); + expect(session.defaultSession.setProxy).not.toHaveBeenCalled(); +}); + +test("test setupProxy - in dev mode", async () => { + Object.defineProperty(process, "env", { + value: { + NODE_ENV: "development" + } + }); + mockGlobalCfgGet = jest.fn(() => {}); + await proxy.setupProxy(logger); + expect(mockLog).toHaveBeenCalled(); + expect(session.defaultSession.setProxy).toHaveBeenCalledWith({ + pacScript: null, + proxyRules: null, + proxyBypassRules: "http://localhost:3000" + }); +}); From 8c1871ea0e8434e65f7af4bc2760815a36ad1d99 Mon Sep 17 00:00:00 2001 From: bgptr Date: Sun, 13 Nov 2022 10:42:52 +0100 Subject: [PATCH 3/7] externalRequest.js --- test/mocks/electronMock.js | 13 +- test/unit/main_dev/externalRequest.spec.js | 273 +++++++++++++++++++++ 2 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 test/unit/main_dev/externalRequest.spec.js diff --git a/test/mocks/electronMock.js b/test/mocks/electronMock.js index ffeabdf12a..2593cb7bfc 100644 --- a/test/mocks/electronMock.js +++ b/test/mocks/electronMock.js @@ -18,8 +18,19 @@ export const clipboard = { readText: jest.fn(() => "") }; +export let onBeforeSendHeadersListener; +export let onHeadersReceivedListener; + export const session = { defaultSession: { - setProxy: jest.fn(() => Promise.resolve()) + setProxy: jest.fn(() => Promise.resolve()), + webRequest: { + onBeforeSendHeaders: jest.fn((_, cb) => { + onBeforeSendHeadersListener = cb; + }), + onHeadersReceived: jest.fn((cb) => { + onHeadersReceivedListener = cb; + }) + } } }; diff --git a/test/unit/main_dev/externalRequest.spec.js b/test/unit/main_dev/externalRequest.spec.js new file mode 100644 index 0000000000..88ffb093d1 --- /dev/null +++ b/test/unit/main_dev/externalRequest.spec.js @@ -0,0 +1,273 @@ +import * as ext from "../../../app/main_dev/externalRequests"; +import * as con from "../../../app/config"; +import { + onBeforeSendHeadersListener, + onHeadersReceivedListener +} from "../../mocks/electronMock"; +import { + EXTERNALREQUEST_DEX, + EXTERNALREQUEST_NETWORK_STATUS, + EXTERNALREQUEST_STAKEPOOL_LISTING, + EXTERNALREQUEST_UPDATE_CHECK, + EXTERNALREQUEST_POLITEIA, + EXTERNALREQUEST_DCRDATA, + EXTERNALREQUEST_TREZOR_BRIDGE, + ALLOWED_EXTERNAL_REQUESTS, + ALLOWED_VSP_HOSTS +} from "constants"; +import { + POLITEIA_URL_TESTNET, + POLITEIA_URL_MAINNET +} from "../../../app/middleware/politeiaapi"; +import { cloneDeep } from "fp"; + +const externalRequests = ext; +const logger = {}; +const config = con; +let mockGlobalCfgGet; +let mockGlobalCfgSet; + +const testDefaultAllowedExternalRequests = [ + EXTERNALREQUEST_NETWORK_STATUS, + EXTERNALREQUEST_STAKEPOOL_LISTING, + EXTERNALREQUEST_UPDATE_CHECK, + EXTERNALREQUEST_DCRDATA, + EXTERNALREQUEST_DEX, + EXTERNALREQUEST_POLITEIA, + EXTERNALREQUEST_TREZOR_BRIDGE +]; + +const testDefaultAllowedVSPHosts = ["host-1", "http://host-2"]; +const testOnBeforeSendHeadersDetails = { + url: "test-url", + method: "test-method", + requestHeaders: { + testKey: "test-key" + } +}; +const testOnHeadersReceivedDetails = { + url: "test-url", + statusLine: "test-statusline", + responseHeaders: { + testKey: "test-key" + } +}; +const mockOnBeforeSendHeadersCallback = jest.fn(() => {}); +const mockOnHeadersReceivedCallback = jest.fn(() => {}); + +beforeEach(() => { + logger.log = jest.fn(() => {}); + mockGlobalCfgGet = jest.fn((key) => { + switch (key) { + case ALLOWED_EXTERNAL_REQUESTS: + return testDefaultAllowedExternalRequests; + case ALLOWED_VSP_HOSTS: + return testDefaultAllowedVSPHosts; + } + }); + mockGlobalCfgSet = jest.fn(() => {}); + config.getGlobalCfg = jest.fn(() => ({ + get: mockGlobalCfgGet, + set: mockGlobalCfgSet + })); + Object.defineProperty(process, "env", { + value: { + NODE_ENV: "not-development" + } + }); +}); + +test("test installSessionHandlers - call a not allowed url", () => { + externalRequests.installSessionHandlers(logger); + + onBeforeSendHeadersListener( + cloneDeep(testOnBeforeSendHeadersDetails), + mockOnBeforeSendHeadersCallback + ); + expect(mockOnBeforeSendHeadersCallback).toHaveBeenCalledWith({ + cancel: true, + requestHeaders: testOnBeforeSendHeadersDetails.requestHeaders + }); +}); + +test("test installSessionHandlers - call a allowed url", () => { + externalRequests.installSessionHandlers(logger); + + // call an allowed url + onBeforeSendHeadersListener( + { + ...cloneDeep(testOnBeforeSendHeadersDetails), + url: `${testDefaultAllowedVSPHosts[0]}/api/v3/vspinfo` + }, + mockOnBeforeSendHeadersCallback + ); + expect(mockOnBeforeSendHeadersCallback).toHaveBeenLastCalledWith({ + cancel: false, + requestHeaders: testOnBeforeSendHeadersDetails.requestHeaders + }); +}); + +test("test installSessionHandlers - call trezor", () => { + externalRequests.installSessionHandlers(logger); + + onBeforeSendHeadersListener( + { + ...cloneDeep(testOnBeforeSendHeadersDetails), + url: "http://127.0.0.1:21325/" + }, + mockOnBeforeSendHeadersCallback + ); + expect(mockOnBeforeSendHeadersCallback).toHaveBeenLastCalledWith({ + cancel: false, + requestHeaders: { + ...cloneDeep(testOnBeforeSendHeadersDetails.requestHeaders), + Origin: "https://dummy-origin-to-fool-trezor-bridge.trezor.io" + } + }); +}); + +test("test installSessionHandlers - called an arbitrary url, headers received in not a dev env", () => { + externalRequests.installSessionHandlers(logger); + + onHeadersReceivedListener( + cloneDeep(testOnHeadersReceivedDetails), + mockOnHeadersReceivedCallback + ); + expect(mockOnHeadersReceivedCallback).toHaveBeenLastCalledWith({ + responseHeaders: testOnHeadersReceivedDetails.responseHeaders, + statusLine: testOnHeadersReceivedDetails.statusLine + }); +}); + +test("test installSessionHandlers - called politeia testnet url, headers received in dev env", () => { + Object.defineProperty(process, "env", { + value: { + NODE_ENV: "development" + } + }); + externalRequests.installSessionHandlers(logger); + + onHeadersReceivedListener( + cloneDeep({ + ...testOnHeadersReceivedDetails, + url: POLITEIA_URL_TESTNET, + + responseHeaders: { + ...cloneDeep(testOnHeadersReceivedDetails.responseHeaders), + "Access-Control-Allow-Origin": "this-should-be-deleted" + } + }), + mockOnHeadersReceivedCallback + ); + expect(mockOnHeadersReceivedCallback).toHaveBeenLastCalledWith({ + responseHeaders: { + ...cloneDeep(testOnHeadersReceivedDetails.responseHeaders), + "Access-Control-Allow-Origin": "http://localhost:3000" + }, + statusLine: testOnHeadersReceivedDetails.statusLine + }); +}); + +test("test installSessionHandlers - called politeia mainnet url, headers received in dev env, received OPTIONS method", () => { + Object.defineProperty(process, "env", { + value: { + NODE_ENV: "development" + } + }); + externalRequests.installSessionHandlers(logger); + + onHeadersReceivedListener( + cloneDeep({ + ...testOnHeadersReceivedDetails, + url: POLITEIA_URL_MAINNET, + responseHeaders: { + ...cloneDeep(testOnHeadersReceivedDetails.responseHeaders), + "Access-Control-Allow-Origin": "this-should-be-deleted" + }, + method: "OPTIONS" + }), + mockOnHeadersReceivedCallback + ); + expect(mockOnHeadersReceivedCallback).toHaveBeenLastCalledWith({ + responseHeaders: { + ...cloneDeep(testOnHeadersReceivedDetails.responseHeaders), + "Access-Control-Allow-Origin": "http://localhost:3000", + "Access-Control-Allow-Headers": "Content-Type" + }, + statusLine: "OK" + }); +}); + +test("test installSessionHandlers - called an allowed VSP url, headers received in dev env", () => { + Object.defineProperty(process, "env", { + value: { + NODE_ENV: "development" + } + }); + externalRequests.installSessionHandlers(logger); + + onHeadersReceivedListener( + cloneDeep({ + ...testOnHeadersReceivedDetails, + url: `${testDefaultAllowedVSPHosts[1]}/api/v3/vspinfo`, + responseHeaders: { + ...cloneDeep(testOnHeadersReceivedDetails.responseHeaders), + "Access-Control-Allow-Origin": "this-should-be-deleted" + } + }), + mockOnHeadersReceivedCallback + ); + expect(mockOnHeadersReceivedCallback).toHaveBeenLastCalledWith({ + responseHeaders: { + ...cloneDeep(testOnHeadersReceivedDetails.responseHeaders), + "Access-Control-Allow-Origin": "http://localhost:3000", + "Access-Control-Allow-Headers": "Content-Type, VSP-Client-Signature" + }, + statusLine: "OK" + }); +}); + +test("test installSessionHandlers - called an app.html url, headers received in not a dev env", () => { + externalRequests.installSessionHandlers(logger); + + onHeadersReceivedListener( + cloneDeep({ + ...testOnHeadersReceivedDetails, + url: "app.html" + }), + mockOnHeadersReceivedCallback + ); + expect(mockOnHeadersReceivedCallback).toHaveBeenLastCalledWith({ + responseHeaders: { + ...cloneDeep(testOnHeadersReceivedDetails.responseHeaders), + "Content-Security-Policy": + "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src data: 'self'; connect-src https:; " + }, + statusLine: testOnHeadersReceivedDetails.statusLine + }); +}); + +test("test installSessionHandlers - called an app.html url, headers received in dev env", () => { + Object.defineProperty(process, "env", { + value: { + NODE_ENV: "development" + } + }); + externalRequests.installSessionHandlers(logger); + + onHeadersReceivedListener( + cloneDeep({ + ...testOnHeadersReceivedDetails, + url: "app.html" + }), + mockOnHeadersReceivedCallback + ); + expect(mockOnHeadersReceivedCallback).toHaveBeenLastCalledWith({ + responseHeaders: { + ...cloneDeep(testOnHeadersReceivedDetails.responseHeaders), + "Content-Security-Policy": + "default-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src data: 'self'; connect-src https: http:; " + }, + statusLine: testOnHeadersReceivedDetails.statusLine + }); +}); From f4374432fc59122e568de888ee3ea5062be6d6dc Mon Sep 17 00:00:00 2001 From: bgptr Date: Wed, 16 Nov 2022 18:14:42 +0100 Subject: [PATCH 4/7] test logging --- app/main_dev/logging.js | 52 ++-- test/unit/main_dev/logging.spec.js | 378 +++++++++++++++++++++++++++++ 2 files changed, 405 insertions(+), 25 deletions(-) create mode 100644 test/unit/main_dev/logging.spec.js diff --git a/app/main_dev/logging.js b/app/main_dev/logging.js index 237f07c276..a0152f1cc9 100644 --- a/app/main_dev/logging.js +++ b/app/main_dev/logging.js @@ -47,7 +47,7 @@ const logLevelsPrintable = { export const getLogFileName = () => path.join(getAppDataDirectory(), "decrediton.log"); -class Logger { +export class Logger { constructor(debug) { this.debug = debug; this.logLevels = { @@ -68,24 +68,22 @@ class Logger { this.drained = true; this.buffer = []; this.logFile = fs.createWriteStream(getLogFileName()); - this.logFile.on("drain", this.dequeue); - } - - dequeue() { - if (this.buffer.length === 0) { - this.drained = true; - return; - } - - // Keep writing data to the file until we either write all available data or - // the file becomes busy for writes (in which case this function will be - // called again once the file can be written to again). - let drained = true; - while (this.buffer.length() > 0 && drained) { - const data = this.buffer.shift(); - this.drained = this.logFile.write(data); - drained = this.drained; - } + this.logFile.on("drain", () => { + if (this.buffer.length === 0) { + this.drained = true; + return; + } + + // Keep writing data to the file until we either write all available data or + // the file becomes busy for writes (in which case this function will be + // called again once the file can be written to again). + let drained = true; + while (this.buffer.length > 0 && drained) { + const data = this.buffer.shift(); + this.drained = this.logFile.write(data); + drained = this.drained; + } + }); } queue(data) { @@ -100,7 +98,8 @@ class Logger { log(level, msg) { const levelLower = level.toLowerCase(); - const logLevel = this.logLevels[levelLower] || 3; + const logLevel = + levelLower in this.logLevels ? this.logLevels[levelLower] : 3; const subsys = "DCTN"; const lvl = logLevelsPrintable[levelLower] || "UNK"; @@ -197,7 +196,7 @@ const panicErr = "panic"; export function lastLogLine(log) { const lastLineIdx = log.lastIndexOf(os.EOL, log.length - os.EOL.length - 1); - const lastLineBuff = log.slice(lastLineIdx).toString("utf-8"); + const lastLineBuff = log.slice(Math.max(0, lastLineIdx)).toString("utf-8"); return lastLineBuff.trim(); } @@ -211,10 +210,13 @@ export function lastErrorLine(log) { } export function lastPanicLine(log) { - let lastLineIdx = log.indexOf(panicErr); - if (lastLineIdx < 0) lastLineIdx = log.indexOf("goroutine"); - const lastLineBuff = log.slice(lastLineIdx).toString("utf-8"); - return lastLineBuff; + let lastLineIdx = log.lastIndexOf(panicErr); + if (lastLineIdx < 0) lastLineIdx = log.lastIndexOf("goroutine"); + const endOfErrorLineIdx = log.indexOf(os.EOL, lastLineIdx); + const lastLineBuff = log + .slice(lastLineIdx, endOfErrorLineIdx) + .toString("utf-8"); + return lastLineBuff.trim(); } export function ClearDcrwalletLogs() { diff --git a/test/unit/main_dev/logging.spec.js b/test/unit/main_dev/logging.spec.js new file mode 100644 index 0000000000..37b030ee81 --- /dev/null +++ b/test/unit/main_dev/logging.spec.js @@ -0,0 +1,378 @@ +import os from "os"; +import fs from "fs"; +import * as l from "../../../app/main_dev/logging"; +import * as p from "../../../app/main_dev/paths"; +import { isEqual } from "lodash"; + +const logging = l; +const paths = p; + +jest.mock("fs"); + +const testLogMsg = "test-log-msg"; +const testAppDataDir = "test-app-data-dir"; +const testDataMsg = "panic test-data" + os.EOL; +const testDataMsg2 = "[ERR] test-data2" + os.EOL; +const testDataMsg3 = "goroutine test-data3" + os.EOL; +const testData = Buffer.from(testDataMsg); +const testData2 = Buffer.from(testDataMsg2); +const testData3 = Buffer.from(testDataMsg3); + +let mockGetAppDataDirectory; +let mockCreateWriteStream; +let mockCreateWriteStreamOn; +let mockCreateWriteStreamWrite; +let mockCreateWriteStreamOnce; +let mockCreateWriteStreamEnd; +let mockIOWrite; +beforeEach(() => { + mockCreateWriteStream = fs.createWriteStream = jest.fn(() => ({ + on: mockCreateWriteStreamOn, + write: mockCreateWriteStreamWrite, + once: mockCreateWriteStreamOnce, + end: mockCreateWriteStreamEnd + })); + mockGetAppDataDirectory = paths.getAppDataDirectory = jest.fn( + () => testAppDataDir + ); + mockCreateWriteStreamOn = jest.fn(() => ({})); + mockCreateWriteStreamWrite = jest.fn(() => true); + mockCreateWriteStreamOnce = jest.fn(() => {}); + mockCreateWriteStreamEnd = jest.fn(() => {}); + + mockIOWrite = jest.fn(() => {}); +}); + +test("test logging in debug mode", () => { + const onCbs = {}; + mockCreateWriteStreamOn = jest.fn((name, cb) => { + onCbs[name] = cb; + }); + const mockConsoleLog = jest.fn((msg) => { + console.info(msg); + }); + jest.spyOn(console, "log").mockImplementation(mockConsoleLog); + + const logger = new logging.Logger(true); // doesn't use createLogger to enable async testing + + /* error */ + logger.log("error", testLogMsg); + + expect(mockGetAppDataDirectory).toHaveBeenCalled(); + expect(mockCreateWriteStream).toHaveBeenLastCalledWith( + `${testAppDataDir}/decrediton.log` + ); + let expectdLogRegExp = new RegExp(`ERR] DCTN: ${testLogMsg}`, "g"); + expect(mockConsoleLog).toHaveBeenLastCalledWith( + expect.stringMatching(expectdLogRegExp) + ); + expect(mockCreateWriteStreamWrite).toHaveBeenLastCalledWith( + expect.stringMatching(expectdLogRegExp) + ); + + /* warning */ + logger.log("warn", testLogMsg + "-warn"); + expectdLogRegExp = new RegExp(`WRN] DCTN: ${testLogMsg + "-warn"}`, "g"); + expect(mockConsoleLog).toHaveBeenLastCalledWith( + expect.stringMatching(expectdLogRegExp) + ); + expect(mockCreateWriteStreamWrite).toHaveBeenLastCalledWith( + expect.stringMatching(expectdLogRegExp) + ); + + /* info */ + logger.log("info", testLogMsg + "-info"); + expectdLogRegExp = new RegExp(`INF] DCTN: ${testLogMsg + "-info"}`, "g"); + expect(mockConsoleLog).toHaveBeenLastCalledWith( + expect.stringMatching(expectdLogRegExp) + ); + expect(mockCreateWriteStreamWrite).toHaveBeenLastCalledWith( + expect.stringMatching(expectdLogRegExp) + ); + + /* verbose */ + mockCreateWriteStreamWrite.mockClear(); + logger.log("verbose", testLogMsg + "-verbose"); + expectdLogRegExp = new RegExp(`VBS] DCTN: ${testLogMsg + "-verbose"}`, "g"); + expect(mockConsoleLog).toHaveBeenLastCalledWith( + expect.stringMatching(expectdLogRegExp) + ); + expect(mockCreateWriteStreamWrite).not.toHaveBeenCalled(); + + /* debug */ + mockCreateWriteStreamWrite.mockClear(); + logger.log("debug", testLogMsg + "-debug"); + expectdLogRegExp = new RegExp(`DBG] DCTN: ${testLogMsg + "-debug"}`, "g"); + expect(mockConsoleLog).toHaveBeenLastCalledWith( + expect.stringMatching(expectdLogRegExp) + ); + expect(mockCreateWriteStreamWrite).not.toHaveBeenCalled(); + + /* silly */ + mockCreateWriteStreamWrite.mockClear(); + logger.log("silly", testLogMsg + "-silly"); + expectdLogRegExp = new RegExp(`TRC] DCTN: ${testLogMsg + "-silly"}`, "g"); + expect(mockConsoleLog).toHaveBeenLastCalledWith( + expect.stringMatching(expectdLogRegExp) + ); + expect(mockCreateWriteStreamWrite).not.toHaveBeenCalled(); + + /* there is nothing in the buffer */ + mockConsoleLog.mockClear(); + mockCreateWriteStreamWrite.mockClear(); + onCbs["drain"].call(); + expect(mockConsoleLog).not.toHaveBeenCalled(); + expect(mockCreateWriteStreamWrite).not.toHaveBeenCalled(); + + /* end */ + logger.close(); + expect(mockCreateWriteStreamEnd).toHaveBeenCalled(); + expect(mockCreateWriteStreamOnce).toHaveBeenCalled(); +}); + +test("test logging in non debug mode - log file is busy", () => { + const onCbs = {}; + let drainResponse = false; + mockCreateWriteStreamOn = jest.fn((name, cb) => { + onCbs[name] = cb; + }); + const mockConsoleLog = jest.fn((msg) => { + console.info(msg); + }); + jest.spyOn(console, "log").mockImplementation(mockConsoleLog); + mockCreateWriteStreamWrite = jest.fn(() => drainResponse); + + const logger = new logging.Logger(false); // doesn't use createLogger to enable async testing + + /* error */ + logger.log("error", testLogMsg); + + expect(mockGetAppDataDirectory).toHaveBeenCalled(); + expect(mockCreateWriteStream).toHaveBeenLastCalledWith( + `${testAppDataDir}/decrediton.log` + ); + expect(mockCreateWriteStreamWrite).toHaveBeenCalled(); + mockCreateWriteStreamWrite.mockClear(); + + /* log file has been drained */ + + /* error again */ + logger.log("error", testLogMsg + "-error2"); + + /* warning */ + logger.log("warn", testLogMsg + "-warn"); + + /* info */ + logger.log("info", testLogMsg + "-info"); + + /* verbose */ + logger.log("verbose", testLogMsg + "-verbose"); + + /* debug */ + logger.log("debug", testLogMsg + "-debug"); + + /* silly */ + logger.log("silly", testLogMsg + "-silly"); + + drainResponse = true; + expect(mockConsoleLog).not.toHaveBeenCalled(); + expect(mockCreateWriteStreamWrite).not.toHaveBeenCalled(); + onCbs["drain"].call(); + + /* the second error log write */ + let expectdLogRegExp = new RegExp(`ERR] DCTN: ${testLogMsg}-error2`, "g"); + expect(mockCreateWriteStreamWrite).toHaveBeenNthCalledWith( + 1, + expect.stringMatching(expectdLogRegExp) + ); + + /* warning */ + expectdLogRegExp = new RegExp(`WRN] DCTN: ${testLogMsg + "-warn"}`, "g"); + expect(mockCreateWriteStreamWrite).toHaveBeenNthCalledWith( + 2, + expect.stringMatching(expectdLogRegExp) + ); + + /* info */ + expectdLogRegExp = new RegExp(`INF] DCTN: ${testLogMsg + "-info"}`, "g"); + expect(mockCreateWriteStreamWrite).toHaveBeenNthCalledWith( + 3, + expect.stringMatching(expectdLogRegExp) + ); +}); + +test("test logging - unknown log level", () => { + const onCbs = {}; + const drainResponse = false; + mockCreateWriteStreamOn = jest.fn((name, cb) => { + onCbs[name] = cb; + }); + const mockConsoleLog = jest.fn((msg) => { + console.info(msg); + }); + jest.spyOn(console, "log").mockImplementation(mockConsoleLog); + mockCreateWriteStreamWrite = jest.fn(() => drainResponse); + + const logger = new logging.Logger(true); + + logger.log("unknown-loglevel", testLogMsg); + + expect(mockGetAppDataDirectory).toHaveBeenCalled(); + expect(mockCreateWriteStream).toHaveBeenLastCalledWith( + `${testAppDataDir}/decrediton.log` + ); + const expectdLogRegExp = new RegExp(`UNK] DCTN: ${testLogMsg}`, "g"); + expect(mockConsoleLog).toHaveBeenLastCalledWith( + expect.stringMatching(expectdLogRegExp) + ); + expect(mockCreateWriteStreamWrite).not.toHaveBeenCalled(); +}); + +test("test create logger", () => { + const logger3 = new logging.Logger(true); + const logger4 = new logging.Logger(false); + expect(isEqual(logger3, logger4)).toBeFalsy(); + + const logger = logging.createLogger(true); + const logger2 = logging.createLogger(false); + expect(isEqual(logger, logger2)).toBeTruthy(); +}); + +test("test AddToDcrdLog", () => { + const destIO = { + write: mockIOWrite + }; + /* in debug mode */ + logging.AddToDcrdLog(destIO, testData, true); + expect(mockIOWrite).toHaveBeenCalledWith(testData); + expect(logging.lastLogLine(logging.GetDcrdLogs())).toBe(testDataMsg.trim()); + expect(logging.lastPanicLine(logging.GetDcrdLogs())).toBe(testDataMsg.trim()); + + /* in non debug mode */ + mockIOWrite.mockClear(); + logging.AddToDcrdLog(destIO, testData2, false); + expect(mockIOWrite).not.toHaveBeenCalled(); + + expect(logging.GetDcrdLogs()).toStrictEqual( + Buffer.from(`${testDataMsg}${testDataMsg2}`) + ); + expect(logging.lastLogLine(logging.GetDcrdLogs())).toBe(testDataMsg2.trim()); + expect(logging.lastErrorLine(logging.GetDcrdLogs())).toBe( + testDataMsg2.trim() + ); + expect(logging.lastPanicLine(logging.GetDcrdLogs())).toBe(testDataMsg.trim()); + + /* third log */ + mockIOWrite.mockClear(); + logging.AddToDcrdLog(destIO, testData3, false); + expect(mockIOWrite).not.toHaveBeenCalled(); + + expect(logging.GetDcrdLogs()).toStrictEqual( + Buffer.from(`${testDataMsg}${testDataMsg2}${testDataMsg3}`) + ); + expect(logging.lastLogLine(logging.GetDcrdLogs())).toBe(testDataMsg3.trim()); + expect(logging.lastErrorLine(logging.GetDcrdLogs())).toBe( + testDataMsg2.trim() + ); + expect(logging.lastPanicLine(logging.GetDcrdLogs())).toBe(testDataMsg.trim()); +}); + +test("test AddToDcrwalletLog", () => { + const destIO = { write: mockIOWrite }; + /* in debug mode */ + logging.AddToDcrwalletLog(destIO, testData3, true); + expect(mockIOWrite).toHaveBeenCalledWith(testData3); + + /* in non debug mode */ + mockIOWrite.mockClear(); + logging.AddToDcrwalletLog(destIO, testData2, false); + expect(mockIOWrite).not.toHaveBeenCalled(); + + expect(logging.GetDcrwalletLogs()).toStrictEqual( + Buffer.from(`${testDataMsg3}${testDataMsg2}`) + ); + expect(logging.lastPanicLine(logging.GetDcrwalletLogs())).toBe( + testDataMsg3.trim() + ); + logging.ClearDcrwalletLogs(); + expect(logging.GetDcrwalletLogs()).toStrictEqual(Buffer.from("")); +}); + +test("test AddToDcrlndLog", () => { + const destIO = { + write: mockIOWrite + }; + /* in debug mode */ + logging.AddToDcrlndLog(destIO, testData, true); + expect(mockIOWrite).toHaveBeenCalledWith(testData); + + /* in non debug mode */ + mockIOWrite.mockClear(); + logging.AddToDcrlndLog(destIO, testData2, false); + expect(mockIOWrite).not.toHaveBeenCalled(); + + expect(logging.GetDcrlndLogs()).toStrictEqual( + Buffer.from(`${testDataMsg}${testDataMsg2}`) + ); +}); + +test("test AddToDexcLog", () => { + const destIO = { + write: mockIOWrite + }; + /* in debug mode */ + logging.AddToDexcLog(destIO, testData, true); + expect(mockIOWrite).toHaveBeenCalledWith(testData); + + /* in non debug mode */ + mockIOWrite.mockClear(); + logging.AddToDexcLog(destIO, testData2, false); + expect(mockIOWrite).not.toHaveBeenCalled(); + + expect(logging.GetDexcLogs()).toStrictEqual( + Buffer.from(`${testDataMsg}${testDataMsg2}`) + ); +}); + +test("test AddToPrivacyLog", () => { + const destIO = { + write: mockIOWrite + }; + const msgs = [ + " Dialed CSPPServer", + " Mixing output", + " Completed CoinShuffle++ mix of output", + " wallet.MixOutput", + " AccountMixer" + ]; + + /* in debug mode */ + + logging.AddToPrivacyLog(destIO, "random not privacy msg", true); + expect(mockIOWrite).not.toHaveBeenCalled(); + for (let index = 0; index < msgs.length; index++) { + logging.AddToPrivacyLog(destIO, msgs[index], true); + expect(mockIOWrite).toHaveBeenCalledWith(msgs[index]); + } + + expect(logging.getPrivacyLogs()).toStrictEqual(msgs.join("")); + + logging.cleanPrivacyLogs(); + expect(logging.getPrivacyLogs()).toStrictEqual(""); +}); + +test("test CheckDaemonLogs", () => { + const msgs = [ + " Reindexing to height", + " Upgrading database to version 6", + " Reindexing block information in the database", + " Upgrading database to version 7", + " Upgrading database to version 12", + " Upgrading spend journal to version 3" + ]; + msgs.forEach((msg) => { + expect(logging.CheckDaemonLogs(msg)).toBeTruthy(); + }); + + expect(logging.CheckDaemonLogs(testDataMsg2)).toBeFalsy(); +}); From 15f706a188f08e80f840fe2bbf89120d88766295 Mon Sep 17 00:00:00 2001 From: bgptr Date: Thu, 17 Nov 2022 18:22:37 +0100 Subject: [PATCH 5/7] test constants.js --- test/mocks/electronMock.js | 5 +++++ test/unit/main_dev/constants.spec.js | 33 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 test/unit/main_dev/constants.spec.js diff --git a/test/mocks/electronMock.js b/test/mocks/electronMock.js index 2593cb7bfc..0764ac1aa1 100644 --- a/test/mocks/electronMock.js +++ b/test/mocks/electronMock.js @@ -34,3 +34,8 @@ export const session = { } } }; + +export const app = { + name: "testAppName", + getVersion: () => "testVersion" +}; diff --git a/test/unit/main_dev/constants.spec.js b/test/unit/main_dev/constants.spec.js new file mode 100644 index 0000000000..62a2214978 --- /dev/null +++ b/test/unit/main_dev/constants.spec.js @@ -0,0 +1,33 @@ +import * as con from "../../../app/main_dev/constants"; +const constants = con; + +test("test USAGE_MESSAGE", () => { + expect(constants.USAGE_MESSAGE).toMatchInlineSnapshot(` + "testAppName version testVersion + Usage + $ testAppName [OPTIONS] + + Options + --help -h Show help and exit. + --version -v Show version and exit. + --debug -d Debug daemon/wallet messages. + --testnet Connect to testnet. + --mainnet Connect to mainnet. + --advanced Start in advanced daemon mode. + --spv Start in SPV mode (cannot be used at the same time as advanced daemon mode). + --spvconnect Specify direct peer for SPV connection in 'host:port' or 'host' format (latter uses the default SPV port). Supports comma-separated list of peers. Always use with --spv. + --rpcuser Specify RPC username for advanced daemon mode connection + --rpcpass Specify RPC password + --rpccert Specify RPC Certificate + --rpcconnect Specify RPC connection in 'host:port' or 'host' format (latter uses the default RPC port). Note that different ports are used for RPC and SPV connections. + --extrawalletargs Pass extra arguments to dcrwallet. + --custombinpath Custom path for dcrd/dcrwallet/dcrctl binaries. + " + `); +}); + +test("test VERSION_MESSAGE", () => { + expect(constants.VERSION_MESSAGE).toMatchInlineSnapshot( + '"testAppName version testVersion"' + ); +}); From e6d72fcdb708e7061c5b58dc5527a8d1da5bac6e Mon Sep 17 00:00:00 2001 From: bgptr Date: Thu, 17 Nov 2022 19:51:17 +0100 Subject: [PATCH 6/7] test templates.js --- test/unit/main_dev/templates.spec.js | 324 +++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 test/unit/main_dev/templates.spec.js diff --git a/test/unit/main_dev/templates.spec.js b/test/unit/main_dev/templates.spec.js new file mode 100644 index 0000000000..897f1df5ea --- /dev/null +++ b/test/unit/main_dev/templates.spec.js @@ -0,0 +1,324 @@ +import * as t from "../../../app/main_dev/templates"; +import * as ct from "../../../app/main_dev/customTranslation"; +import { default as locales } from "../../../app/i18n/locales"; +import { cloneDeep } from "lodash"; +const templates = t; +const customTranslation = ct; +const enLocale = locales.find((value) => value.key === "en"); +const testMainWindow = { + setBounds: jest.fn(() => {}) +}; +const testConfirmBrowserView = { + setAutoResize: () => {}, + setBounds: () => {}, + getBounds: () => {}, + setBackgroundColor: () => {}, + webContents: () => {} +}; + +let mockLoadCustomTranslation; + +beforeEach(() => { + Object.defineProperty(process, "platform", { + value: "linux" + }); + mockLoadCustomTranslation = customTranslation.loadCustomTranslation = jest.fn( + () => {} + ); +}); + +const testClickOnMenu = ( + template, + menuLabel, + submenuLabel, + expectedMockedFunction, + expectedMockFunctionParameter +) => { + expectedMockedFunction.mockClear(); + template + .find((menu) => menu.label === menuLabel) + .submenu.find((sm) => sm.label === submenuLabel) + .click(); + if (expectedMockFunctionParameter) { + expect(expectedMockedFunction).toHaveBeenCalledWith( + expectedMockFunctionParameter + ); + } else { + expect(expectedMockedFunction).toHaveBeenCalledTimes(1); + } +}; + +test("test initTemplate", () => { + const mockSend = jest.fn(() => {}); + const mockClose = jest.fn(() => {}); + const mockFullScreen = jest.fn(() => {}); + const mockToggleDevToolsMainWindow = jest.fn(() => {}); + const mockToggleDevTools = jest.fn(() => {}); + const testConfirmBrowserViewCopy = { + ...cloneDeep(testConfirmBrowserView), + webContents: { + send: mockSend, + toggleDevTools: mockToggleDevTools + } + }; + + const testMainWindowCopy = { + ...cloneDeep(testMainWindow), + close: mockClose, + setFullScreen: mockFullScreen, + isFullScreen: () => false, + webContents: { + send: mockSend + }, + toggleDevTools: mockToggleDevToolsMainWindow + }; + const res = templates.initTemplate( + testMainWindowCopy, + testConfirmBrowserViewCopy, + mockLoadCustomTranslation, + enLocale + ); + expect(res).toStrictEqual([ + { + label: "&File", + submenu: [ + { accelerator: "Ctrl+W", click: expect.any(Function), label: "&Close" } + ] + }, + { + label: "&View", + submenu: [ + { + accelerator: "F11", + click: expect.any(Function), + label: "Toggle &Full Screen" + }, + { accelerator: "F5", click: expect.any(Function), label: "Reload UI" }, + { + accelerator: "Alt+Ctrl+I", + click: expect.any(Function), + label: "Toggle Developer Tools" + }, + { + click: expect.any(Function), + label: "Toggle Developer Tools for Confirmation Window" + }, + { + accelerator: "", + click: expect.any(Function), + label: "Load Custom Translation" + } + ] + } + ]); + + testClickOnMenu(res, "&File", "&Close", mockClose); + testClickOnMenu(res, "&View", "Toggle &Full Screen", mockFullScreen, true); + testClickOnMenu(res, "&View", "Reload UI", mockSend, "app-reload-requested"); + testClickOnMenu( + res, + "&View", + "Toggle Developer Tools", + mockToggleDevToolsMainWindow + ); + testClickOnMenu( + res, + "&View", + "Toggle Developer Tools for Confirmation Window", + mockToggleDevTools + ); + testClickOnMenu( + res, + "&View", + "Load Custom Translation", + mockLoadCustomTranslation + ); +}); + +test("test initTemplate on darwin", () => { + Object.defineProperty(process, "platform", { + value: "darwin" + }); + const mockSend = jest.fn(() => {}); + const mockClose = jest.fn(() => {}); + const mockFullScreen = jest.fn(() => {}); + const mockToggleDevTools = jest.fn(() => {}); + const testConfirmBrowserViewCopy = { + ...cloneDeep(testConfirmBrowserView), + webContents: { + send: mockSend, + toggleDevTools: mockToggleDevTools + } + }; + + const testMainWindowCopy = { + ...cloneDeep(testMainWindow), + close: mockClose, + setFullScreen: mockFullScreen, + isFullScreen: () => false, + webContents: { + send: mockSend + } + }; + const res = templates.initTemplate( + testMainWindowCopy, + testConfirmBrowserViewCopy, + mockLoadCustomTranslation, + enLocale + ); + expect(res).toStrictEqual([ + { + label: "Decrediton", + submenu: [ + { + click: expect.any(Function), + label: "About Decrediton", + selector: "orderFrontStandardAboutPanel:" + }, + { type: "separator" }, + { label: "Services", submenu: [] }, + { type: "separator" }, + { + accelerator: "Command+H", + label: "Hide Decrediton", + selector: "hide:" + }, + { + accelerator: "Command+Shift+H", + label: "Hide Others", + selector: "hideOtherApplications:" + }, + { label: "Show All", selector: "unhideAllApplications:" }, + { type: "separator" }, + { accelerator: "Command+Q", click: expect.any(Function), label: "Quit" } + ] + }, + { + label: "Edit", + submenu: [ + { accelerator: "Command+Z", label: "Undo", selector: "undo:" }, + { accelerator: "Shift+Command+Z", label: "Redo", selector: "redo:" }, + { type: "separator" }, + { accelerator: "Command+X", label: "Cut", selector: "cut:" }, + { accelerator: "Command+C", label: "Copy", selector: "copy:" }, + { accelerator: "Command+V", label: "Paste", selector: "paste:" }, + { + accelerator: "Command+A", + label: "Select All", + selector: "selectAll:" + } + ] + }, + { + label: "&View", + submenu: [ + { + accelerator: "Ctrl+Command+F", + click: expect.any(Function), + label: "Toggle Full Screen" + } + ] + }, + { + label: "Window", + submenu: [ + { + accelerator: "Command+M", + label: "Minimize", + selector: "performMiniaturize:" + }, + { + accelerator: "Command+W", + label: "&Close", + selector: "performClose:" + }, + { type: "separator" }, + { label: "Bring All to Front", selector: "arrangeInFront:" } + ] + } + ]); + + testClickOnMenu( + res, + "Decrediton", + "About Decrediton", + mockSend, + "show-about-modal" + ); + testClickOnMenu(res, "Decrediton", "Quit", mockSend, "check-can-close"); + testClickOnMenu(res, "&View", "Toggle Full Screen", mockFullScreen, true); +}); + +test("test getVersionWin", () => { + expect(templates.getVersionWin()).toBeNull(); // ?? +}); + +test("test getGrpcVersions", () => { + const testGrpcVersions = "testGrpcVersions"; + expect(templates.getGrpcVersions()).toStrictEqual({ + requiredVersion: null, + walletVersion: null + }); + templates.setGrpcVersions(testGrpcVersions); + expect(templates.getGrpcVersions()).toStrictEqual(testGrpcVersions); +}); + +test("test inputMenu", () => { + const res = templates.inputMenu(false, testMainWindow, 3, 4, enLocale); + expect(res).toStrictEqual([ + { label: "Cut", role: "cut" }, + { label: "Copy", role: "copy" }, + { label: "Paste", role: "paste" }, + { type: "separator" }, + { label: "Select All", role: "selectall" } + ]); +}); + +test("test inputMenu - in dev mode", () => { + const mockInspectElement = jest.fn(() => {}); + + const testMainWindowCopy = { + ...cloneDeep(testMainWindow), + inspectElement: mockInspectElement + }; + const res = templates.inputMenu(true, testMainWindowCopy, 3, 4, enLocale); + expect(res).toStrictEqual([ + { label: "Cut", role: "cut" }, + { label: "Copy", role: "copy" }, + { label: "Paste", role: "paste" }, + { type: "separator" }, + { label: "Select All", role: "selectall" }, + { click: expect.any(Function), label: "Inspect element" } + ]); + mockInspectElement.mockClear(); + res.find((menu) => menu.label === "Inspect element").click(); + expect(mockInspectElement).toHaveBeenCalledWith(3, 4); +}); + +test("test selectionMenu", () => { + const res = templates.selectionMenu(false, testMainWindow, 3, 4, enLocale); + expect(res).toStrictEqual([ + { label: "Copy", role: "copy" }, + { type: "separator" }, + { label: "Select All", role: "selectall" } + ]); +}); + +test("test selectionMenu - in dev mode", () => { + const mockInspectElement = jest.fn(() => {}); + + const testMainWindowCopy = { + ...cloneDeep(testMainWindow), + inspectElement: mockInspectElement + }; + const res = templates.selectionMenu(true, testMainWindowCopy, 3, 4, enLocale); + expect(res).toStrictEqual([ + { label: "Copy", role: "copy" }, + { type: "separator" }, + { label: "Select All", role: "selectall" }, + { click: expect.any(Function), label: "Inspect element" } + ]); + mockInspectElement.mockClear(); + res.find((menu) => menu.label === "Inspect element").click(); + expect(mockInspectElement).toHaveBeenCalledWith(3, 4); +}); From c4b16406441cc4be6d4c893527119ff5964168b8 Mon Sep 17 00:00:00 2001 From: bgptr Date: Fri, 2 Dec 2022 17:26:03 +0100 Subject: [PATCH 7/7] test ipc.js + fix dcrIsRemote flag settings + startDex does not need to be async --- app/main_dev/ipc.js | 10 +- package.json | 3 +- test/mocks/loggingMock.js | 7 + test/unit/main_dev/ipc.spec.js | 1055 ++++++++++++++++++++++++++++ test/unit/main_dev/logging.spec.js | 2 +- 5 files changed, 1072 insertions(+), 5 deletions(-) create mode 100644 test/mocks/loggingMock.js create mode 100644 test/unit/main_dev/ipc.spec.js diff --git a/app/main_dev/ipc.js b/app/main_dev/ipc.js index 204968eb5e..cf8b07c2a7 100644 --- a/app/main_dev/ipc.js +++ b/app/main_dev/ipc.js @@ -246,7 +246,7 @@ export const startDcrlnd = async ( } }; -export const startDex = async (walletPath, testnet, locale) => { +export const startDex = (walletPath, testnet, locale) => { if (GetDexPID()) { logger.log( "info", @@ -257,7 +257,7 @@ export const startDex = async (walletPath, testnet, locale) => { } try { - const started = await launchDex(walletPath, testnet, locale); + const started = launchDex(walletPath, testnet, locale); return started; } catch (e) { logger.log("error", `error launching dex: ${e}`); @@ -417,7 +417,11 @@ export const userDex = async () => { }; export const stopDaemon = () => { - return closeDCRD(); + const res = closeDCRD(); + if (res) { + dcrdIsRemote = null; + } + return res; }; export const stopWallet = () => { diff --git a/package.json b/package.json index c19e3c54ec..b1321fd7a9 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ "^dex$": "/test/mocks/dexMock.js", "^walletCrypto$": "/app/wallet/crypto.js", "^fetchModule$": "/app/helpers/fetchModule.js", - "wallet-preload-shim$": "/test/mocks/walletPreloadShimMock.js" + "wallet-preload-shim$": "/test/mocks/walletPreloadShimMock.js", + "logging$": "/test/mocks/loggingMock.js" }, "transformIgnorePatterns": [ "/node_modules/", diff --git a/test/mocks/loggingMock.js b/test/mocks/loggingMock.js new file mode 100644 index 0000000000..a3c2f42646 --- /dev/null +++ b/test/mocks/loggingMock.js @@ -0,0 +1,7 @@ +export function createLogger() { + return { + log: () => {} + }; +} + +export function trace() {} diff --git a/test/unit/main_dev/ipc.spec.js b/test/unit/main_dev/ipc.spec.js new file mode 100644 index 0000000000..f4400aca18 --- /dev/null +++ b/test/unit/main_dev/ipc.spec.js @@ -0,0 +1,1055 @@ +import os from "os"; +import fs from "fs"; +import * as c from "../../../app/config"; +import * as i from "../../../app/main_dev/ipc"; +import * as lau from "../../../app/main_dev/launch"; +import { TESTNET, MAINNET } from "constants"; +import * as cfgConstants from "constants/config"; +import { wait } from "@testing-library/react"; +import { DEX_LOCALPAGE } from "../../../app/main_dev/externalRequests"; + +jest.mock("fs"); + +const config = c; +const ipc = i; +const launch = lau; + +const testHomeDir = "/home/testUser"; +const testWalletName = "test-wallet-name"; +const testResourcePath = "/test-resource-path"; +const testError = "test-error"; +const testBasePathMainnet = `${testHomeDir}/.config/decrediton/wallets/${MAINNET}`; +const testBasePathTestnet = `${testHomeDir}/.config/decrediton/wallets/${TESTNET}`; +const testWalletDirectories = ["dir1", "dir2", "file1"]; +const testWalletConfigs = { + [testWalletDirectories[0]]: { + [cfgConstants.LAST_ACCESS]: `test-last-access-${testWalletDirectories[0]}`, + [cfgConstants.IS_WATCH_ONLY]: `test-is-watching-only-${testWalletDirectories[0]}`, + [cfgConstants.TREZOR]: `test-trezor-${testWalletDirectories[0]}`, + [cfgConstants.MIXED_ACCOUNT_CFG]: `test-mixed-account-cfg-${testWalletDirectories[0]}` + }, + [testWalletDirectories[1]]: { + [cfgConstants.LAST_ACCESS]: `test-last-access-${testWalletDirectories[1]}`, + [cfgConstants.IS_WATCH_ONLY]: `test-is-watching-only-${testWalletDirectories[1]}`, + [cfgConstants.TREZOR]: `test-trezor-${testWalletDirectories[1]}`, + [cfgConstants.MIXED_ACCOUNT_CFG]: `test-mixed-account-cfg-${testWalletDirectories[1]}` + } +}; +const testDcrdPath = `${testHomeDir}/.dcrd`; +const testReactIPC = "test-react-IPC"; +const testMainWindow = "test-mainWindow"; +const testDaemonIsAdvanced = "test-daemonIsAdvanced"; +const testTestnet = "test-testnet"; +const testWalletPath = "test-walletPath"; +const testRpcUser = "test-rpcUser"; +const testRpcPass = "test-rpcPass"; +const testRpcHost = "test-rpcHost"; +const testRpcListen = "test-rpcListen"; +const testGapLimit = "test-gapLimit"; +const testDisableCoinTypeUpgrades = "test-disableCoinTypeUpgrades"; + +const testWalletAccount = "test-walletAccount"; +const testWalletPort = "test-walletPort"; +const testRpcCreds = "test-rpcCreds"; +const testAutopilotEnabled = "test-autopilotEnabled"; +const testDcrlndCreds = { key: "test-dcrlndCreds" }; +const testLocale = "test-locale"; +const testPassphrase = "test-passphrase"; +const testSeed = "test-seed"; + +const testAssetID = "test-assetID"; +const testWalletType = "test-walletType"; +const testAppPassphrase = "test-appPassphrase"; +const testAccount = "test-account"; +const testRpcuser = "test-rpcuser"; +const testRpcpass = "test-rpcpass"; +const testRpclisten = "test-rpclisten"; +const testRpccert = "test-rpccert"; + +const testDcrlndPath = `${testBasePathMainnet}/${testWalletName}/dcrlnd`; +const testMTime = "test-mtime"; + +let mockMkdirSync; +let mockInitWalletCfg; +let mockNewWalletConfigCreation; +let mockReaddirSync; +let mockStatSync; +let mockExistsSync; +let mockIsDirectory; +let mockGetWalletCfg; +let mockRmSync; +let mockLaunchDCRD; +let mockSetDcrdRpcCredentials; +let mockLaunchDCRWallet; +let mockGetDcrwPID; +let mockCheckNoLegacyWalletConfig; +let mockLaunchDCRLnd; +let mockGetDcrlndPID; +let mockGetDcrlndCreds; +let mockGetDexPID; +let mockLaunchDex; +let mockInitCheckDex; +let mockInitDexCall; +let mockLoginDexCall; +let mockExportSeedDexCall; +let mockLogoutDexCall; +let mockCreateWalletDexCall; +let mockSetWalletPasswordDexCall; +let mockUserDexCall; +let mockCloseDCRD; +let mockCloseDCRW; +let mockCloseDcrlnd; +let mockCloseDex; +let mockWalletCfgSet; + +beforeEach(() => { + jest.spyOn(os, "homedir").mockImplementation(() => testHomeDir); + Object.defineProperty(process, "platform", { + value: "linux" + }); + Object.defineProperty(process, "resourcesPath", { + value: testResourcePath + }); + Object.defineProperty(process, "env", { + value: { + NODE_ENV: "not-development" + } + }); + mockMkdirSync = fs.mkdirSync = jest.fn(() => {}); + mockInitWalletCfg = config.initWalletCfg = jest.fn(() => {}); + mockNewWalletConfigCreation = config.newWalletConfigCreation = jest.fn( + () => {} + ); + mockReaddirSync = fs.readdirSync = jest.fn((dir) => { + if (dir === testBasePathMainnet || dir === testBasePathTestnet) { + return testWalletDirectories; + } else { + console.error("unknown path: " + { dir }); + } + }); + mockStatSync = fs.statSync = jest.fn((dir) => ({ + isDirectory: (mockIsDirectory = jest.fn(() => /dir/.test(dir))), + mtime: testMTime + })); + mockWalletCfgSet = jest.fn(() => {}); + mockGetWalletCfg = config.getWalletCfg = jest.fn((_, wallet) => ({ + set: mockWalletCfgSet, + get: jest.fn((configKey) => testWalletConfigs[wallet][configKey]) + })); + mockExistsSync = fs.existsSync = jest.fn((path) => { + // dir1 wallet is finished + if (/dir1/.test(path)) { + return true; + } else { + return false; + } + }); + mockRmSync = fs.rmSync = jest.fn(() => {}); + mockLaunchDCRD = launch.launchDCRD = jest.fn(() => Promise.resolve()); + mockSetDcrdRpcCredentials = launch.setDcrdRpcCredentials = jest.fn(() => {}); + mockLaunchDCRWallet = launch.launchDCRWallet = jest.fn(() => {}); + mockCheckNoLegacyWalletConfig = config.checkNoLegacyWalletConfig = jest.fn( + () => {} + ); + mockGetDcrwPID = launch.GetDcrwPID = jest.fn(() => null); + mockLaunchDCRLnd = launch.launchDCRLnd = jest.fn( + () => + new Promise((resolve) => { + resolve(testDcrlndCreds); + }) + ); + mockGetDcrlndPID = launch.GetDcrlndPID = jest.fn(() => {}); + mockGetDcrlndCreds = launch.GetDcrlndCreds = jest.fn(() => testDcrlndCreds); + mockLaunchDex = launch.launchDex = jest.fn(() => DEX_LOCALPAGE); + mockGetDexPID = launch.GetDexPID = jest.fn(() => null); + mockInitCheckDex = launch.initCheckDex = jest.fn(() => {}); + mockLoginDexCall = launch.loginDexCall = jest.fn(() => {}); + mockExportSeedDexCall = launch.exportSeedDexCall = jest.fn(() => {}); + mockLogoutDexCall = launch.logoutDexCall = jest.fn(() => {}); + mockInitDexCall = launch.initDexCall = jest.fn(() => {}); + mockSetWalletPasswordDexCall = launch.setWalletPasswordDexCall = jest.fn( + () => {} + ); + mockUserDexCall = launch.userDexCall = jest.fn(() => {}); + mockCreateWalletDexCall = launch.createWalletDexCall = jest.fn(() => {}); + mockCloseDCRD = launch.closeDCRD = jest.fn(() => true); + mockCloseDCRW = launch.closeDCRW = jest.fn(() => true); + mockCloseDcrlnd = launch.closeDcrlnd = jest.fn(() => true); + mockCloseDex = launch.closeDex = jest.fn(() => true); +}); + +const testGetAvailableWallets = (network) => { + const res = ipc.getAvailableWallets(network); + const testBasePath = + network === TESTNET ? testBasePathTestnet : testBasePathMainnet; + expect(mockReaddirSync).toHaveBeenCalledWith(testBasePath); + expect(mockStatSync).toHaveBeenNthCalledWith( + 1, + `${testBasePath}/${testWalletDirectories[0]}` + ); + expect(mockStatSync).toHaveBeenNthCalledWith( + 2, + `${testBasePath}/${testWalletDirectories[1]}` + ); + expect(mockStatSync).toHaveBeenNthCalledWith( + 3, + `${testBasePath}/${testWalletDirectories[2]}` + ); + expect(mockIsDirectory).toHaveBeenCalled(); + + expect(mockGetWalletCfg).toHaveBeenNthCalledWith( + 1, + network === TESTNET, + testWalletDirectories[0] + ); + expect(mockGetWalletCfg).toHaveBeenNthCalledWith( + 2, + network === TESTNET, + testWalletDirectories[1] + ); + // there is no third call, since 'file1' is a file + + expect(mockExistsSync).toHaveBeenNthCalledWith( + 1, + `${testBasePath}/${testWalletDirectories[0]}/${ + network === TESTNET ? "testnet3" : network + }/wallet.db` + ); + expect(mockExistsSync).toHaveBeenNthCalledWith( + 2, + `${testBasePath}/${testWalletDirectories[1]}/${ + network === TESTNET ? "testnet3" : network + }/wallet.db` + ); + + expect(res).toStrictEqual([ + { + displayWalletGradient: undefined, + isLN: undefined, + network: network, + wallet: "dir1", + finished: true, + lastAccess: "test-last-access-dir1", + isWatchingOnly: "test-is-watching-only-dir1", + isTrezor: "test-trezor-dir1", + isPrivacy: "test-mixed-account-cfg-dir1" + }, + { + displayWalletGradient: undefined, + isLN: undefined, + network: network, + wallet: "dir2", + finished: false, + lastAccess: "test-last-access-dir2", + isWatchingOnly: "test-is-watching-only-dir2", + isTrezor: "test-trezor-dir2", + isPrivacy: "test-mixed-account-cfg-dir2" + } + ]); +}; + +test("test getAvailableWallets - on mainnet", () => { + testGetAvailableWallets(MAINNET); +}); + +test("test getAvailableWallets - on testnet", () => { + testGetAvailableWallets(TESTNET); +}); + +const testDeleteDaemon = (isTestnet) => { + const network = isTestnet ? `${TESTNET}3` : MAINNET; + mockExistsSync = fs.existsSync = jest.fn(() => true); + let res = ipc.deleteDaemon(null, isTestnet); + expect(res).toBeTruthy(); + expect(mockExistsSync).toHaveBeenCalledWith( + `${testDcrdPath}/data/${network}` + ); + + expect(mockRmSync).toHaveBeenCalledWith(`${testDcrdPath}/data/${network}`, { + force: true, + recursive: true, + maxRetries: 30 + }); + + /* call custom appdata parameter, that not exists */ + + mockExistsSync.mockClear(); + mockRmSync.mockClear(); + const testAppData = "test-app-data"; + res = ipc.deleteDaemon(testAppData, isTestnet); + expect(res).toBeTruthy(); + expect(mockExistsSync).toHaveBeenCalledWith(`${testAppData}/data/${network}`); + + expect(mockRmSync).toHaveBeenCalledWith(`${testAppData}/data/${network}`, { + force: true, + recursive: true, + maxRetries: 30 + }); +}; + +test("test deleteDaemon - on mainnet", () => { + testDeleteDaemon(); +}); + +test("test deleteDaemon - on testnet", () => { + testDeleteDaemon(true); +}); + +test("test deleteDaemon - dir does not exists", () => { + mockExistsSync = fs.existsSync = jest.fn(() => false); + const res = ipc.deleteDaemon(); + expect(res).toBeTruthy(); + expect(mockExistsSync).toHaveBeenCalled(); + expect(mockRmSync).not.toHaveBeenCalled(); +}); + +test("test deleteDaemon - failed to delete dir", () => { + mockExistsSync = fs.existsSync = jest.fn(() => true); + mockRmSync = fs.rmSync = jest.fn(() => { + throw testError; + }); + const res = ipc.deleteDaemon(); + expect(res).toBeFalsy(); + expect(mockExistsSync).toHaveBeenCalled(); + expect(mockRmSync).toHaveBeenCalled(); +}); + +test("test startDaemon", async () => { + await ipc.startDaemon(null, false, testReactIPC); + expect(mockLaunchDCRD).toHaveBeenCalledWith(testReactIPC, false, null); + + /* call with custom appdata */ + mockLaunchDCRD.mockClear(); + let testParams = { + appdata: "test-app-data" + }; + await ipc.startDaemon(testParams, true, testReactIPC); + await wait(() => + expect(mockLaunchDCRD).toHaveBeenCalledWith( + testReactIPC, + true, + testParams.appdata + ) + ); + + /* call with custom rpcCreds */ + mockLaunchDCRD.mockClear(); + testParams = { + rpcCreds: "test-rpcCreds" + }; + let res = await ipc.startDaemon(testParams, false, testReactIPC); + expect(res).toBe(testParams.rpcCreds); + expect(mockLaunchDCRD).not.toHaveBeenCalled(); + expect(mockSetDcrdRpcCredentials).toHaveBeenCalled(); + + /* call with custom rpcCreds again, skipping restart of daemon as it is connected as remote */ + mockLaunchDCRD.mockClear(); + mockSetDcrdRpcCredentials.mockClear(); + testParams = { + rpcCreds: "test-rpcCreds" + }; + res = await ipc.startDaemon(testParams, false, testReactIPC); + expect(res).toBe(undefined); + expect(mockLaunchDCRD).not.toHaveBeenCalled(); + expect(mockSetDcrdRpcCredentials).not.toHaveBeenCalled(); + + /* try to stop daemon, but it fails. still skipping restarting */ + mockCloseDCRD = launch.closeDCRD = jest.fn(() => false); + res = ipc.stopDaemon(); + expect(mockCloseDCRD).toHaveBeenCalled(); + expect(res).toBeFalsy(); + + mockLaunchDCRD.mockClear(); + mockSetDcrdRpcCredentials.mockClear(); + testParams = { + rpcCreds: "test-rpcCreds" + }; + res = await ipc.startDaemon(testParams, false, testReactIPC); + expect(res).toBe(undefined); + expect(mockLaunchDCRD).not.toHaveBeenCalled(); + expect(mockSetDcrdRpcCredentials).not.toHaveBeenCalled(); + + /* stop daemon */ + mockCloseDCRD = launch.closeDCRD = jest.fn(() => true); + let catchedError; + res = ipc.stopDaemon(); + expect(res).toBeTruthy(); + + // try to start again. dcrdIsRemote should be false now. + // launchDCRD will be rejected + mockLaunchDCRD = launch.launchDCRD = jest.fn( + () => new Promise((_, reject) => reject(testError)) + ); + try { + res = await ipc.startDaemon(null, false, testReactIPC); + } catch (error) { + catchedError = error; + } + expect(mockLaunchDCRD).toHaveBeenCalledWith(testReactIPC, false, null); + await wait(() => expect(catchedError).toBe(testError)); +}); + +const testCreateWallet = (network) => { + /* wallet not exists yet */ + mockExistsSync = fs.existsSync = jest.fn(() => false); + const res = ipc.createWallet(network === TESTNET, testWalletName); + expect(res).toBeTruthy(); + expect(mockMkdirSync).toHaveBeenCalledWith( + `${ + network === TESTNET ? testBasePathTestnet : testBasePathMainnet + }/${testWalletName}`, + { recursive: true } + ); + expect(mockInitWalletCfg).toHaveBeenCalled(); + expect(mockNewWalletConfigCreation).toHaveBeenCalled(); + + /* wallet exists now */ + mockMkdirSync.mockClear(); + mockInitWalletCfg.mockClear(); + mockNewWalletConfigCreation.mockClear(); + mockExistsSync = fs.existsSync = jest.fn(() => true); + ipc.createWallet(false, testWalletName); + expect(res).toBeTruthy(); + expect(mockMkdirSync).not.toHaveBeenCalled(); + expect(mockInitWalletCfg).not.toHaveBeenCalled(); + expect(mockNewWalletConfigCreation).not.toHaveBeenCalled(); +}; + +test("test createWallet - on mainnet", () => { + testCreateWallet(MAINNET); +}); + +test("test createWallet - on testnet", () => { + testCreateWallet(TESTNET); +}); + +test("test createWallet - failed to init wallet config", () => { + mockInitWalletCfg = config.initWalletCfg = jest.fn(() => { + throw testError; + }); + mockExistsSync = fs.existsSync = jest.fn(() => false); + const res = ipc.createWallet(false, testWalletName); + expect(res).toBeFalsy(); + expect(mockMkdirSync).toHaveBeenCalledWith( + `${testBasePathMainnet}/${testWalletName}`, + { recursive: true } + ); + expect(mockInitWalletCfg).toHaveBeenCalled(); + expect(mockNewWalletConfigCreation).not.toHaveBeenCalled(); +}); + +const testRemoveWallet = (network) => { + /* wallet exists */ + mockExistsSync = fs.existsSync = jest.fn(() => true); + let res = ipc.removeWallet(network === TESTNET, testWalletName); + expect(res).toBeTruthy(); + expect(mockRmSync).toHaveBeenCalledWith( + `${ + network === TESTNET ? testBasePathTestnet : testBasePathMainnet + }/${testWalletName}`, + { + force: true, + recursive: true, + maxRetries: 30 + } + ); + + /* wallet does not exist now */ + mockRmSync.mockClear(); + mockExistsSync = fs.existsSync = jest.fn(() => false); + res = ipc.removeWallet(false, testWalletName); + expect(res).toBeFalsy(); + expect(mockRmSync).not.toHaveBeenCalled(); + + /* undefined wallet path */ + mockExistsSync.mockClear(); + mockRmSync.mockClear(); + ipc.removeWallet(false); + expect(mockExistsSync).not.toHaveBeenCalled(); + expect(mockRmSync).not.toHaveBeenCalled(); + + /* empty wallet path */ + mockExistsSync.mockClear(); + mockRmSync.mockClear(); + ipc.removeWallet(false, ""); + expect(mockExistsSync).not.toHaveBeenCalled(); + expect(mockRmSync).not.toHaveBeenCalled(); +}; + +test("test removeWallet - on mainnet", () => { + testRemoveWallet(MAINNET); +}); + +test("test removeWallet - on testnet", () => { + testRemoveWallet(TESTNET); +}); + +test("test removeWallet - failed to delete wallet directory", () => { + mockRmSync = fs.rmSync = jest.fn(() => { + throw testError; + }); + mockExistsSync = fs.existsSync = jest.fn(() => true); + const res = ipc.removeWallet(false, testWalletName); + expect(res).toBeFalsy(); + expect(mockRmSync).toHaveBeenCalled(); +}); + +test("test startWallet", () => { + ipc.startWallet( + testMainWindow, + testDaemonIsAdvanced, + testTestnet, + testWalletPath, + testReactIPC, + testRpcUser, + testRpcPass, + testRpcHost, + testRpcListen, + testGapLimit, + testDisableCoinTypeUpgrades + ); + + expect(mockGetDcrwPID).toHaveBeenCalled(); + expect(mockInitWalletCfg).toHaveBeenCalledWith(testTestnet, testWalletPath); + expect(mockCheckNoLegacyWalletConfig).toHaveBeenCalledWith( + testTestnet, + testWalletPath, + testRpcUser && testRpcPass && testRpcHost && testRpcListen + ); + expect(mockLaunchDCRWallet).toHaveBeenCalledWith( + testMainWindow, + testDaemonIsAdvanced, + testWalletPath, + testTestnet, + testReactIPC, + testRpcUser, + testRpcPass, + testRpcHost, + testRpcListen, + testGapLimit, + testDisableCoinTypeUpgrades + ); +}); + +test("test startWallet - wallet already started", () => { + mockGetDcrwPID = launch.GetDcrwPID = jest.fn(() => 122); + ipc.startWallet( + testMainWindow, + testDaemonIsAdvanced, + testTestnet, + testWalletPath, + testReactIPC, + testRpcUser, + testRpcPass, + testRpcHost, + testRpcListen, + testGapLimit, + testDisableCoinTypeUpgrades + ); + + expect(mockGetDcrwPID).toHaveBeenCalled(); + expect(mockInitWalletCfg).not.toHaveBeenCalled(); + expect(mockCheckNoLegacyWalletConfig).not.toHaveBeenCalled(); + expect(mockLaunchDCRWallet).not.toHaveBeenCalled(); +}); + +test("test startWallet - failed", async () => { + mockLaunchDCRWallet = launch.launchDCRWallet = jest.fn(() => { + throw testError; + }); + let catchedError; + try { + await ipc.startWallet( + testMainWindow, + testDaemonIsAdvanced, + testTestnet, + testWalletPath, + testReactIPC, + testRpcUser, + testRpcPass, + testRpcHost, + testRpcListen, + testGapLimit, + testDisableCoinTypeUpgrades + ); + } catch (error) { + catchedError = error; + } + expect(catchedError).toBe(testError); + expect(mockGetDcrwPID).toHaveBeenCalled(); + expect(mockInitWalletCfg).toHaveBeenCalled(); + expect(mockCheckNoLegacyWalletConfig).toHaveBeenCalled(); + expect(mockLaunchDCRWallet).toHaveBeenCalled(); +}); + +test("test startDcrlnd", async () => { + const res = await ipc.startDcrlnd( + testWalletAccount, + testWalletPort, + testRpcCreds, + testWalletPath, + testTestnet, + testAutopilotEnabled + ); + + expect(mockGetDcrlndPID).toHaveBeenCalled(); + expect(mockGetDcrlndCreds).not.toHaveBeenCalled(); + expect(mockLaunchDCRLnd).toHaveBeenCalledWith( + testWalletAccount, + testWalletPort, + testRpcCreds, + testWalletPath, + testTestnet, + testAutopilotEnabled + ); + expect(res).toBe(testDcrlndCreds); +}); + +test("test startDcrlnd - dcrlnd already started", async () => { + mockGetDcrlndPID = launch.GetDcrlndPID = jest.fn(() => 1); + const res = await ipc.startDcrlnd( + testWalletAccount, + testWalletPort, + testRpcCreds, + testWalletPath, + testTestnet, + testAutopilotEnabled + ); + + expect(mockGetDcrlndPID).toHaveBeenCalled(); + expect(mockGetDcrlndCreds).toHaveBeenCalled(); + expect(mockLaunchDCRLnd).not.toHaveBeenCalledWith( + testWalletAccount, + testWalletPort, + testRpcCreds, + testWalletPath, + testTestnet, + testAutopilotEnabled + ); + expect(res).toStrictEqual({ ...testDcrlndCreds, wasRunning: true }); +}); + +test("test startWallet - failed", async () => { + mockLaunchDCRLnd = launch.launchDCRLnd = jest.fn( + () => + new Promise((_, reject) => { + reject(testError); + }) + ); + const res = await ipc.startDcrlnd( + testWalletAccount, + testWalletPort, + testRpcCreds, + testWalletPath, + testTestnet, + testAutopilotEnabled + ); + expect(res).toBe(testError); + expect(mockGetDcrlndPID).toHaveBeenCalled(); + expect(mockGetDcrlndCreds).not.toHaveBeenCalled(); + expect(mockLaunchDCRLnd).toHaveBeenCalled(); +}); + +test("test startDex", () => { + const res = ipc.startDex(testWalletPath, testTestnet, testLocale); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockLaunchDex).toHaveBeenCalledWith( + testWalletPath, + testTestnet, + testLocale + ); + expect(res).toBe(DEX_LOCALPAGE); +}); + +test("test startDex - dex already started", () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + const res = ipc.startDex(testWalletPath, testTestnet, testLocale); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockLaunchDex).not.toHaveBeenCalled(); + expect(res).toBe(DEX_LOCALPAGE); +}); + +test("test startDex - failed", () => { + mockLaunchDex = launch.launchDex = jest.fn(() => { + throw testError; + }); + + const res = ipc.startDex(testWalletPath, testTestnet, testLocale); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockLaunchDex).toHaveBeenCalled(); + expect(res).toBe(testError); +}); + +test("test checkInitDex", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + await ipc.checkInitDex(); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockInitCheckDex).toHaveBeenCalled(); +}); + +test("test checkInitDex - dex already started", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => null); + await ipc.checkInitDex(); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockInitCheckDex).not.toHaveBeenCalled(); +}); + +test("test checkInitDex - failed", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + mockInitCheckDex = launch.initCheckDex = jest.fn(() => { + throw testError; + }); + + const res = await ipc.checkInitDex(); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockInitCheckDex).toHaveBeenCalled(); + expect(res).toBe(testError); +}); + +test("test initDex", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + await ipc.initDex(testPassphrase, testSeed); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockInitDexCall).toHaveBeenCalledWith(testPassphrase, testSeed); +}); + +test("test initDex - dex already started", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => null); + await ipc.initDex(testPassphrase, testSeed); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockInitDexCall).not.toHaveBeenCalled(); +}); + +test("test initDex - failed", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + mockInitDexCall = launch.initDexCall = jest.fn(() => { + throw testError; + }); + + const res = await ipc.initDex(testPassphrase, testSeed); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockInitDexCall).toHaveBeenCalled(); + expect(res).toBe(testError); +}); + +test("test loginDex", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + await ipc.loginDex(testPassphrase); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockLoginDexCall).toHaveBeenCalledWith(testPassphrase); +}); + +test("test loginDex - dex already started", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => null); + await ipc.loginDex(testPassphrase); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockLoginDexCall).not.toHaveBeenCalled(); +}); + +test("test loginDex - failed", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + mockLoginDexCall = launch.loginDexCall = jest.fn(() => { + throw testError; + }); + + const res = await ipc.loginDex(testPassphrase); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockLoginDexCall).toHaveBeenCalled(); + expect(res).toBe(testError); +}); + +test("test exportSeed", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + await ipc.exportSeed(testPassphrase); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockExportSeedDexCall).toHaveBeenCalledWith(testPassphrase); +}); + +test("test exportSeed - dex already started", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => null); + await ipc.exportSeed(testPassphrase); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockExportSeedDexCall).not.toHaveBeenCalled(); +}); + +test("test exportSeed - failed", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + mockExportSeedDexCall = launch.exportSeedDexCall = jest.fn(() => { + throw testError; + }); + + const res = await ipc.exportSeed(testPassphrase); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockExportSeedDexCall).toHaveBeenCalled(); + expect(res).toBe(testError); +}); + +test("test logoutDex", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + await ipc.logoutDex(); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockLogoutDexCall).toHaveBeenCalled(); +}); + +test("test logoutDex - dex already started", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => null); + await ipc.logoutDex(); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockLogoutDexCall).not.toHaveBeenCalled(); +}); + +test("test logoutDex - failed", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + mockLogoutDexCall = launch.logoutDexCall = jest.fn(() => { + throw testError; + }); + + const res = await ipc.logoutDex(); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockLogoutDexCall).toHaveBeenCalled(); + expect(res).toBe(testError); +}); + +test("test createWalletDex", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + await ipc.createWalletDex( + testAssetID, + testWalletType, + testPassphrase, + testAppPassphrase, + testAccount, + testRpcuser, + testRpcpass, + testRpclisten, + testRpccert + ); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockCreateWalletDexCall).toHaveBeenCalledWith( + testAssetID, + testWalletType, + testPassphrase, + testAppPassphrase, + testAccount, + testRpcuser, + testRpcpass, + testRpclisten, + testRpccert + ); +}); + +test("test createWalletDex - dex already started", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => null); + await ipc.createWalletDex( + testAssetID, + testWalletType, + testPassphrase, + testAppPassphrase, + testAccount, + testRpcuser, + testRpcpass, + testRpclisten, + testRpccert + ); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockCreateWalletDexCall).not.toHaveBeenCalled(); +}); + +test("test createWalletDex - failed", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + mockCreateWalletDexCall = launch.createWalletDexCall = jest.fn(() => { + throw testError; + }); + + const res = await ipc.createWalletDex( + testAssetID, + testWalletType, + testPassphrase, + testAppPassphrase, + testAccount, + testRpcuser, + testRpcpass, + testRpclisten, + testRpccert + ); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockCreateWalletDexCall).toHaveBeenCalled(); + expect(res).toBe(testError); +}); + +test("test setWalletPasswordDex", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + await ipc.setWalletPasswordDex( + testAssetID, + testPassphrase, + testAppPassphrase + ); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockSetWalletPasswordDexCall).toHaveBeenCalledWith( + testAssetID, + testPassphrase, + testAppPassphrase + ); +}); + +test("test setWalletPasswordDex - dex already started", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => null); + await ipc.setWalletPasswordDex( + testAssetID, + testPassphrase, + testAppPassphrase + ); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockSetWalletPasswordDexCall).not.toHaveBeenCalled(); +}); + +test("test setWalletPasswordDex - failed", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + mockSetWalletPasswordDexCall = launch.setWalletPasswordDexCall = jest.fn( + () => { + throw testError; + } + ); + + const res = await ipc.setWalletPasswordDex( + testAssetID, + testPassphrase, + testAppPassphrase + ); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockSetWalletPasswordDexCall).toHaveBeenCalled(); + expect(res).toBe(testError); +}); + +test("test userDex", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + await ipc.userDex(); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockUserDexCall).toHaveBeenCalled(); +}); + +test("test userDex - dex already started", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => null); + await ipc.userDex(); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockUserDexCall).not.toHaveBeenCalled(); +}); + +test("test userDex - failed", async () => { + mockGetDexPID = launch.GetDexPID = jest.fn(() => 234); + mockUserDexCall = launch.userDexCall = jest.fn(() => { + throw testError; + }); + + const res = await ipc.userDex(); + + expect(mockGetDexPID).toHaveBeenCalled(); + expect(mockUserDexCall).toHaveBeenCalled(); + expect(res).toBe(testError); +}); + +test("test stopWallet", () => { + const res = ipc.stopWallet(); + expect(mockCloseDCRW).toHaveBeenCalled(); + expect(res).toBeTruthy(); +}); + +test("test stopDcrlnd", () => { + const res = ipc.stopDcrlnd(); + expect(mockCloseDcrlnd).toHaveBeenCalled(); + expect(res).toBeTruthy(); +}); + +test("test stopDex", () => { + const res = ipc.stopDex(); + expect(mockCloseDex).toHaveBeenCalled(); + expect(res).toBeTruthy(); +}); + +test("test get/setWatchingOnlyWallet", () => { + const testIsWatchingOnly = "testIsWatchingOnly"; + + let isWatchingOnly = ipc.getWatchingOnlyWallet(); + expect(isWatchingOnly).toBe(undefined); + + ipc.setWatchingOnlyWallet(testIsWatchingOnly); + + isWatchingOnly = ipc.getWatchingOnlyWallet(); + expect(isWatchingOnly).toBe(testIsWatchingOnly); +}); + +test("test removeDcrlnd", () => { + mockExistsSync = fs.existsSync = jest.fn(() => true); + const res = ipc.removeDcrlnd(testWalletName); + expect(res).toBeTruthy(); + expect(mockExistsSync).toHaveBeenCalledWith(testDcrlndPath); + expect(mockRmSync).toHaveBeenCalledWith(testDcrlndPath, { + recursive: true, + force: true, + maxRetries: 30 + }); +}); + +test("test removeDcrlnd - dcrlnd path does not exists", () => { + mockExistsSync = fs.existsSync = jest.fn(() => false); + const res = ipc.removeDcrlnd(testWalletName); + expect(res).toBeFalsy(); + expect(mockExistsSync).toHaveBeenCalledWith(testDcrlndPath); + expect(mockRmSync).not.toHaveBeenCalled(); +}); + +test("test removeDcrlnd - failed ", () => { + mockExistsSync = fs.existsSync = jest.fn(() => true); + mockRmSync = fs.rmSync = jest.fn(() => { + throw testError; + }); + const res = ipc.removeDcrlnd(testWalletName); + expect(res).toBeFalsy(); + expect(mockExistsSync).toHaveBeenCalledWith(testDcrlndPath); + expect(mockRmSync).toHaveBeenCalledWith(testDcrlndPath, { + recursive: true, + force: true, + maxRetries: 30 + }); +}); + +test("test lnScbInfo", () => { + const res = ipc.lnScbInfo(testWalletPath); + expect(res).toStrictEqual({ + channelBackupMTime: testMTime, + channelBackupPath: `${testWalletPath}/dcrlnd/data/chain/decred/${MAINNET}/channel.backup` + }); +}); + +test("test lnScbInfo - on testnet", () => { + const res = ipc.lnScbInfo(testWalletPath, true); + expect(res).toStrictEqual({ + channelBackupMTime: testMTime, + channelBackupPath: `${testWalletPath}/dcrlnd/data/chain/decred/${TESTNET}/channel.backup` + }); +}); diff --git a/test/unit/main_dev/logging.spec.js b/test/unit/main_dev/logging.spec.js index 37b030ee81..9dff6eaa4d 100644 --- a/test/unit/main_dev/logging.spec.js +++ b/test/unit/main_dev/logging.spec.js @@ -1,6 +1,6 @@ import os from "os"; import fs from "fs"; -import * as l from "../../../app/main_dev/logging"; +import * as l from "../../../app/main_dev/logging.js"; import * as p from "../../../app/main_dev/paths"; import { isEqual } from "lodash";