From a507f491dcc844044a29bdebbb0ce1bc072e9e25 Mon Sep 17 00:00:00 2001 From: Erdi Maden Date: Tue, 21 May 2024 00:19:29 -0500 Subject: [PATCH] Updating mock usages --- src/coinbase/tests/address_test.ts | 126 ++++++++++++++-------------- src/coinbase/tests/coinbase_test.ts | 60 ++++++++++--- src/coinbase/tests/utils.ts | 32 +++++-- src/coinbase/tests/wallet_test.ts | 100 +++++++++++----------- 4 files changed, 186 insertions(+), 132 deletions(-) diff --git a/src/coinbase/tests/address_test.ts b/src/coinbase/tests/address_test.ts index 5fdbb907..9a44296b 100644 --- a/src/coinbase/tests/address_test.ts +++ b/src/coinbase/tests/address_test.ts @@ -8,19 +8,37 @@ import { InternalError } from "../errors"; import { VALID_ADDRESS_BALANCE_LIST, VALID_ADDRESS_MODEL, - VALID_BALANCE_MODEL, addressesApiMock, + generateRandomHash, + mockFn, + mockReturnRejectedValue, } from "./utils"; // Test suite for Address class describe("Address", () => { - const transactionHash = "0xdeadbeef"; - let address: Address; - const getAddressBalance = jest.fn().mockResolvedValue({ data: VALID_BALANCE_MODEL }); - const listAddressBalances = jest.fn().mockResolvedValue({ data: VALID_ADDRESS_BALANCE_LIST }); - const requestFaucetFunds = jest - .fn() - .mockResolvedValue({ data: { transaction_hash: transactionHash } }); + const transactionHash = generateRandomHash(); + let address: Address, balanceModel; + + beforeAll(() => { + Coinbase.apiClients.address = addressesApiMock; + Coinbase.apiClients.address!.getAddressBalance = mockFn(request => { + const { asset_id } = request; + balanceModel = { + amount: "1000000000000000000", + asset: { + asset_id, + network_id: Coinbase.networkList.BaseSepolia, + }, + }; + return { data: balanceModel }; + }); + Coinbase.apiClients.address!.listAddressBalances = mockFn(() => { + return { data: VALID_ADDRESS_BALANCE_LIST }; + }); + Coinbase.apiClients.address!.requestFaucetFunds = mockFn(() => { + return { data: { transaction_hash: transactionHash } }; + }); + }); beforeEach(() => { address = new Address(VALID_ADDRESS_MODEL); @@ -40,67 +58,59 @@ describe("Address", () => { }); it("should return the correct list of balances", async () => { - Coinbase.apiClients.address = { - ...addressesApiMock, - listAddressBalances, - }; const balances = await address.listBalances(); expect(balances.get(Coinbase.assetList.Eth)).toEqual(new Decimal(1)); expect(balances.get("usdc")).toEqual(new Decimal(5000)); expect(balances.get("weth")).toEqual(new Decimal(3)); - expect(listAddressBalances).toHaveBeenCalledWith(address.getWalletId(), address.getId()); - expect(listAddressBalances).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.address!.listAddressBalances).toHaveBeenCalledWith( + address.getWalletId(), + address.getId(), + ); + expect(Coinbase.apiClients.address!.listAddressBalances).toHaveBeenCalledTimes(1); }); it("should return the correct ETH balance", async () => { - Coinbase.apiClients.address = { - ...addressesApiMock, - getAddressBalance, - }; const ethBalance = await address.getBalance(Coinbase.assetList.Eth); expect(ethBalance).toBeInstanceOf(Decimal); expect(ethBalance).toEqual(new Decimal(1)); - expect(getAddressBalance).toHaveBeenCalledWith( + expect(Coinbase.apiClients.address!.getAddressBalance).toHaveBeenCalledWith( address.getWalletId(), address.getId(), Coinbase.assetList.Eth, ); - expect(getAddressBalance).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.address!.getAddressBalance).toHaveBeenCalledTimes(1); }); it("should return the correct Gwei balance", async () => { const assetId = "gwei"; - Coinbase.apiClients.address = { - ...addressesApiMock, - getAddressBalance, - }; const ethBalance = await address.getBalance(assetId); expect(ethBalance).toBeInstanceOf(Decimal); expect(ethBalance).toEqual(new Decimal(1000000000)); - expect(getAddressBalance).toHaveBeenCalledWith(address.getWalletId(), address.getId(), assetId); - expect(getAddressBalance).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.address!.getAddressBalance).toHaveBeenCalledWith( + address.getWalletId(), + address.getId(), + assetId, + ); + expect(Coinbase.apiClients.address!.getAddressBalance).toHaveBeenCalledTimes(1); }); it("should return the correct Wei balance", async () => { const assetId = "wei"; - Coinbase.apiClients.address = { - ...addressesApiMock, - getAddressBalance, - }; const ethBalance = await address.getBalance(assetId); expect(ethBalance).toBeInstanceOf(Decimal); expect(ethBalance).toEqual(new Decimal(1000000000000000000)); - expect(getAddressBalance).toHaveBeenCalledWith(address.getWalletId(), address.getId(), assetId); - expect(getAddressBalance).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.address!.getAddressBalance).toHaveBeenCalledWith( + address.getWalletId(), + address.getId(), + assetId, + ); + expect(Coinbase.apiClients.address!.getAddressBalance).toHaveBeenCalledTimes(1); }); it("should return an error for an unsupported asset", async () => { const getAddressBalance = jest.fn().mockRejectedValue(new APIError("")); const assetId = "unsupported-asset"; - Coinbase.apiClients.address = { - ...addressesApiMock, - getAddressBalance, - }; + Coinbase.apiClients.address!.getAddressBalance = getAddressBalance; await expect(address.getBalance(assetId)).rejects.toThrow(APIError); expect(getAddressBalance).toHaveBeenCalledWith(address.getWalletId(), address.getId(), assetId); expect(getAddressBalance).toHaveBeenCalledTimes(1); @@ -115,46 +125,40 @@ describe("Address", () => { }); it("should request funds from the faucet and returns the faucet transaction", async () => { - Coinbase.apiClients.address = { - ...addressesApiMock, - requestFaucetFunds, - }; const faucetTransaction = await address.faucet(); expect(faucetTransaction).toBeInstanceOf(FaucetTransaction); expect(faucetTransaction.getTransactionHash()).toBe(transactionHash); - expect(requestFaucetFunds).toHaveBeenCalledWith(address.getWalletId(), address.getId()); - expect(requestFaucetFunds).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.address!.requestFaucetFunds).toHaveBeenCalledWith( + address.getWalletId(), + address.getId(), + ); + expect(Coinbase.apiClients.address!.requestFaucetFunds).toHaveBeenCalledTimes(1); }); it("should throw an APIError when the request is unsuccesful", async () => { - const requestFaucetFunds = jest.fn().mockRejectedValue(new APIError("")); - Coinbase.apiClients.address = { - ...addressesApiMock, - requestFaucetFunds, - }; + Coinbase.apiClients.address!.requestFaucetFunds = mockReturnRejectedValue(new APIError("")); await expect(address.faucet()).rejects.toThrow(APIError); - expect(requestFaucetFunds).toHaveBeenCalledWith(address.getWalletId(), address.getId()); - expect(requestFaucetFunds).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.address!.requestFaucetFunds).toHaveBeenCalledWith( + address.getWalletId(), + address.getId(), + ); + expect(Coinbase.apiClients.address!.requestFaucetFunds).toHaveBeenCalledTimes(1); }); it("should throw a FaucetLimitReachedError when the faucet limit is reached", async () => { - const requestFaucetFunds = jest.fn().mockRejectedValue(new FaucetLimitReachedError("")); - Coinbase.apiClients.address = { - ...addressesApiMock, - requestFaucetFunds, - }; + Coinbase.apiClients.address!.requestFaucetFunds = mockReturnRejectedValue( + new FaucetLimitReachedError(""), + ); await expect(address.faucet()).rejects.toThrow(FaucetLimitReachedError); - expect(requestFaucetFunds).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.address!.requestFaucetFunds).toHaveBeenCalledTimes(1); }); it("should throw an InternalError when the request fails unexpectedly", async () => { - const requestFaucetFunds = jest.fn().mockRejectedValue(new InternalError("")); - Coinbase.apiClients.address = { - ...addressesApiMock, - requestFaucetFunds, - }; + Coinbase.apiClients.address!.requestFaucetFunds = mockReturnRejectedValue( + new InternalError(""), + ); await expect(address.faucet()).rejects.toThrow(InternalError); - expect(requestFaucetFunds).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.address!.requestFaucetFunds).toHaveBeenCalledTimes(1); }); it("should return the correct string representation", () => { diff --git a/src/coinbase/tests/coinbase_test.ts b/src/coinbase/tests/coinbase_test.ts index f0fdf8a7..e5528fec 100644 --- a/src/coinbase/tests/coinbase_test.ts +++ b/src/coinbase/tests/coinbase_test.ts @@ -1,6 +1,15 @@ +import { randomUUID } from "crypto"; import { APIError } from "../api_error"; import { Coinbase } from "../coinbase"; -import { VALID_WALLET_MODEL, addressesApiMock, usersApiMock, walletsApiMock } from "./utils"; +import { + VALID_WALLET_MODEL, + addressesApiMock, + generateRandomHash, + mockReturnRejectedValue, + mockReturnValue, + usersApiMock, + walletsApiMock, +} from "./utils"; const PATH_PREFIX = "./src/coinbase/tests/config"; @@ -34,38 +43,67 @@ describe("Coinbase tests", () => { }); describe("should able to interact with the API", () => { - let user; + let user, walletId, publicKey, addressId, transactionHash; const cbInstance = Coinbase.configureFromJson( `${PATH_PREFIX}/coinbase_cloud_api_key.json`, true, ); - it("should return the correct user ID", async () => { + beforeAll(async () => { Coinbase.apiClients = { user: usersApiMock, wallet: walletsApiMock, address: addressesApiMock, }; + + walletId = randomUUID(); + publicKey = generateRandomHash(8); + addressId = randomUUID(); + transactionHash = generateRandomHash(8); + + const walletModel = { + id: walletId, + network_id: Coinbase.networkList.BaseSepolia, + default_address: { + wallet_id: walletId, + address_id: addressId, + public_key: publicKey, + network_id: Coinbase.networkList.BaseSepolia, + }, + }; + + Coinbase.apiClients.user!.getCurrentUser = mockReturnValue({ id: 123 }); + Coinbase.apiClients.wallet!.createWallet = mockReturnValue(walletModel); + Coinbase.apiClients.wallet!.getWallet = mockReturnValue(walletModel); + Coinbase.apiClients.address!.requestFaucetFunds = mockReturnValue({ + transaction_hash: transactionHash, + }); + Coinbase.apiClients.address!.createAddress = mockReturnValue( + VALID_WALLET_MODEL.default_address, + ); + user = await cbInstance.getDefaultUser(); + }); + it("should return the correct user ID", async () => { expect(user.getId()).toBe(123); expect(user.toString()).toBe("User{ userId: 123 }"); - expect(usersApiMock.getCurrentUser).toHaveBeenCalledWith(); + expect(Coinbase.apiClients.user!.getCurrentUser).toHaveBeenCalledWith(); expect(usersApiMock.getCurrentUser).toHaveBeenCalledTimes(1); }); it("should be able to get faucet funds", async () => { const wallet = await user.createWallet(); - expect(wallet.getId()).toBe(VALID_WALLET_MODEL.id); + expect(wallet.getId()).toBe(walletId); const payload = { wallet: { network_id: Coinbase.networkList.BaseSepolia } }; expect(walletsApiMock.createWallet).toHaveBeenCalledWith(payload); expect(walletsApiMock.createWallet).toHaveBeenCalledTimes(1); const defaultAddress = wallet.defaultAddress(); - expect(defaultAddress?.getId()).toBe(VALID_WALLET_MODEL.default_address?.address_id); + expect(defaultAddress?.getId()).toBe(addressId); const faucetTransaction = await wallet?.faucet(); - expect(faucetTransaction.getTransactionHash()).toBe("0xdeadbeef"); + expect(faucetTransaction.getTransactionHash()).toBe(transactionHash); expect(addressesApiMock.requestFaucetFunds).toHaveBeenCalledWith( defaultAddress.getWalletId(), defaultAddress?.getId(), @@ -75,11 +113,11 @@ describe("Coinbase tests", () => { }); it("should raise an error if the user is not found", async () => { - Coinbase.apiClients.user = { - ...usersApiMock, - getCurrentUser: jest.fn().mockRejectedValue(new APIError("User not found")), - }; const cbInstance = Coinbase.configureFromJson(`${PATH_PREFIX}/coinbase_cloud_api_key.json`); + Coinbase.apiClients.user!.getCurrentUser = mockReturnRejectedValue( + new APIError("User not found"), + ); + await expect(cbInstance.getDefaultUser()).rejects.toThrow(APIError); expect(usersApiMock.getCurrentUser).toHaveBeenCalledWith(); expect(usersApiMock.getCurrentUser).toHaveBeenCalledTimes(1); diff --git a/src/coinbase/tests/utils.ts b/src/coinbase/tests/utils.ts index d946ce40..1a2ed136 100644 --- a/src/coinbase/tests/utils.ts +++ b/src/coinbase/tests/utils.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import axios, { AxiosInstance } from "axios"; import { ethers } from "ethers"; import { randomUUID } from "crypto"; @@ -12,8 +13,21 @@ import { BASE_PATH } from "../../client/base"; import { Coinbase } from "../coinbase"; import { registerAxiosInterceptors } from "../utils"; +export const mockFn = (...args) => jest.fn(...args) as any; +export const mockReturnValue = data => jest.fn().mockResolvedValue({ data }); +export const mockReturnRejectedValue = data => jest.fn().mockRejectedValue(data); + export const walletId = randomUUID(); +export const generateRandomHash = (length = 8) => { + const characters = "abcdef0123456789"; + let hash = "0x"; + for (let i = 0; i < length; i++) { + hash += characters[Math.floor(Math.random() * characters.length)]; + } + return hash; +}; + // newAddressModel creates a new AddressModel with a random wallet ID and a random Ethereum address. export const newAddressModel = (walletId: string): AddressModel => { const ethAddress = ethers.Wallet.createRandom(); @@ -87,7 +101,7 @@ type AxiosMockType = [AxiosInstance, Configuration, string]; /** * Returns an Axios instance with interceptors and configuration for testing. * - * @returns {AxiosMockType} - The Axios instance, configuration, and base path. + * @returns The Axios instance, configuration, and base path. */ export const createAxiosMock = (): AxiosMockType => { const axiosInstance = axios.create(); @@ -101,18 +115,18 @@ export const createAxiosMock = (): AxiosMockType => { }; export const usersApiMock = { - getCurrentUser: jest.fn().mockResolvedValue({ data: { id: 123 } }), + getCurrentUser: jest.fn(), }; export const walletsApiMock = { - getWallet: jest.fn().mockResolvedValue(Promise.resolve({ data: VALID_WALLET_MODEL })), - createWallet: jest.fn().mockResolvedValue(Promise.resolve({ data: VALID_WALLET_MODEL })), + getWallet: jest.fn(), + createWallet: jest.fn(), }; export const addressesApiMock = { - requestFaucetFunds: jest.fn().mockResolvedValue({ data: { transaction_hash: "0xdeadbeef" } }), - getAddress: jest.fn().mockResolvedValue({ data: VALID_ADDRESS_BALANCE_LIST }), - getAddressBalance: jest.fn().mockResolvedValue({ data: { VALID_BALANCE_MODEL } }), - listAddressBalances: jest.fn().mockResolvedValue({ data: VALID_ADDRESS_BALANCE_LIST }), - createAddress: jest.fn().mockResolvedValue({ data: VALID_WALLET_MODEL.default_address }), + requestFaucetFunds: jest.fn(), + getAddress: jest.fn(), + getAddressBalance: jest.fn(), + listAddressBalances: jest.fn(), + createAddress: jest.fn(), }; diff --git a/src/coinbase/tests/wallet_test.ts b/src/coinbase/tests/wallet_test.ts index 94507e42..0e517589 100644 --- a/src/coinbase/tests/wallet_test.ts +++ b/src/coinbase/tests/wallet_test.ts @@ -1,54 +1,45 @@ import { randomUUID } from "crypto"; import { Coinbase } from "../coinbase"; -import { ArgumentError } from "../errors"; import { Wallet } from "../wallet"; -import { addressesApiMock, newAddressModel, walletsApiMock } from "./utils"; - -const walletId = randomUUID(); - -const DEFAULT_ADDRESS_MODEL = newAddressModel(walletId); - -export const VALID_WALLET_MODEL = { - id: walletId, - network_id: Coinbase.networkList.BaseSepolia, - default_address: DEFAULT_ADDRESS_MODEL, -}; - -export const VALID_WALLET_MODEL_WITHOUT_DEFAULT_ADDRESS = { - id: walletId, - network_id: Coinbase.networkList.BaseSepolia, -}; +import { addressesApiMock, mockFn, newAddressModel, walletsApiMock } from "./utils"; +import { ArgumentError } from "../errors"; describe("Wallet Class", () => { - let wallet; - - describe(".create", () => { - beforeEach(async () => { - const createWallet = jest - .fn() - .mockResolvedValue({ data: VALID_WALLET_MODEL_WITHOUT_DEFAULT_ADDRESS }); - const getWallet = jest.fn().mockResolvedValue({ data: VALID_WALLET_MODEL }); - - Coinbase.apiClients.wallet = { - ...walletsApiMock, - createWallet, - getWallet, - }; - - Coinbase.apiClients.address = { - ...addressesApiMock, - createAddress: jest.fn().mockResolvedValue({ data: DEFAULT_ADDRESS_MODEL }), - }; - - wallet = await Wallet.create(); + let wallet, walletModel, walletId; + + beforeAll(async () => { + walletId = randomUUID(); + // Mock the API calls + Coinbase.apiClients.wallet = walletsApiMock; + Coinbase.apiClients.address = addressesApiMock; + Coinbase.apiClients.wallet!.createWallet = mockFn(request => { + const { network_id } = request.wallet; + walletModel = { id: walletId, network_id, default_address: newAddressModel(walletId) }; + return { data: walletModel }; + }); + Coinbase.apiClients.wallet!.getWallet = mockFn(() => { + return { data: walletModel }; }); + Coinbase.apiClients.address!.createAddress = mockFn(() => { + return { data: walletModel.default_address }; + }); + wallet = await Wallet.create(); + }); + describe(".create", () => { it("should return a Wallet instance", async () => { expect(wallet).toBeInstanceOf(Wallet); + expect(Coinbase.apiClients.wallet!.createWallet).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.wallet!.getWallet).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.address!.createAddress).toHaveBeenCalledTimes(1); + expect(Coinbase.apiClients.wallet!.createWallet).toHaveBeenCalledWith({ + wallet: { network_id: Coinbase.networkList.BaseSepolia }, + }); + expect(Coinbase.apiClients.wallet!.getWallet).toHaveBeenCalledWith(walletId); }); it("should return the correct wallet ID", async () => { - expect(wallet.getId()).toBe(VALID_WALLET_MODEL.id); + expect(wallet.getId()).toBe(walletModel.id); }); it("should return the correct network ID", async () => { @@ -56,7 +47,7 @@ describe("Wallet Class", () => { }); it("should return the correct default address", async () => { - expect(wallet.defaultAddress()?.getId()).toBe(DEFAULT_ADDRESS_MODEL.address_id); + expect(wallet.defaultAddress()?.getId()).toBe(walletModel.default_address.address_id); }); }); @@ -81,14 +72,21 @@ describe("Wallet Class", () => { beforeEach(async () => { jest.clearAllMocks(); const getAddress = jest.fn(); - addressList.forEach(address => getAddress.mockResolvedValue({ data: address })); - Coinbase.apiClients.address = { - ...addressesApiMock, - getAddress, - }; - - wallet = await Wallet.init(VALID_WALLET_MODEL, existingSeed, 2); - expect(Coinbase.apiClients.address.getAddress).toHaveBeenCalledTimes(2); + addressList.forEach(() => { + getAddress.mockImplementationOnce((wallet_id, address_id) => { + return Promise.resolve({ + data: { + address_id, + network_id: Coinbase.networkList.BaseSepolia, + public_key: "0x03c3379b488a32a432a4dfe91cc3a28be210eddc98b2005bb59a4cf4ed0646eb56", + wallet_id, + }, + }); + }); + }); + Coinbase.apiClients.address!.getAddress = getAddress; + wallet = await Wallet.init(walletModel, existingSeed, 2); + expect(Coinbase.apiClients.address!.getAddress).toHaveBeenCalledTimes(2); addressList.forEach((address, callIndex) => { expect(Coinbase.apiClients.address!.getAddress).toHaveBeenNthCalledWith( callIndex + 1, @@ -103,7 +101,7 @@ describe("Wallet Class", () => { }); it("should return the correct wallet ID", async () => { - expect(wallet.getId()).toBe(VALID_WALLET_MODEL.id); + expect(wallet.getId()).toBe(walletModel.id); }); it("should return the correct network ID", async () => { @@ -111,7 +109,7 @@ describe("Wallet Class", () => { }); it("should return the correct default address", async () => { - expect(wallet.defaultAddress()?.getId()).toBe(VALID_WALLET_MODEL.default_address?.address_id); + expect(wallet.defaultAddress()?.getId()).toBe(walletModel.default_address?.address_id); }); it("should derive the correct number of addresses", async () => { @@ -120,7 +118,7 @@ describe("Wallet Class", () => { it("should return the correct string representation", async () => { expect(wallet.toString()).toBe( - `Wallet{id: '${VALID_WALLET_MODEL.id}', networkId: '${Coinbase.networkList.BaseSepolia}'}`, + `Wallet{id: '${walletModel.id}', networkId: '${Coinbase.networkList.BaseSepolia}'}`, ); });