diff --git a/utils/handleAnything.test.ts b/utils/handleAnything.test.ts index 848d2f362..e02b0ba73 100644 --- a/utils/handleAnything.test.ts +++ b/utils/handleAnything.test.ts @@ -1,43 +1,68 @@ +import stores from '../stores/Stores'; +import handleAnything from './handleAnything'; + +let mockProcessBIP21Uri = jest.fn(); +let mockIsValidBitcoinAddress = false; +let mockIsValidLightningPubKey = false; +let mockIsValidLightningPaymentRequest = false; +let mockSupportsOnchainSends = true; +let mockGetLnurlParams = {}; + jest.mock('./AddressUtils', () => ({ - processBIP21Uri: () => mockProcessedSendAddress, + processBIP21Uri: (...args: string[]) => mockProcessBIP21Uri(...args), isValidBitcoinAddress: () => mockIsValidBitcoinAddress, isValidLightningPubKey: () => mockIsValidLightningPubKey, - isValidLightningPaymentRequest: () => mockIsValidLightningPaymentRequest + isValidLightningPaymentRequest: () => mockIsValidLightningPaymentRequest, + isValidLightningOffer: () => false, + isValidLNDHubAddress: () => false, + processLNDHubAddress: () => ({}), + isValidNpub: () => false, + isPsbt: () => false, + isValidTxHex: () => false })); jest.mock('./TorUtils', () => ({})); jest.mock('./BackendUtils', () => ({ - supportsOnchainSends: () => mockSupportsOnchainSends + supportsOnchainSends: () => mockSupportsOnchainSends, + supportsAccounts: () => false })); jest.mock('../stores/Stores', () => ({ nodeInfoStore: { nodeInfo: {} }, - invoicesStore: { getPayReq: jest.fn() } + invoicesStore: { getPayReq: jest.fn() }, + settingsStore: { settings: { locale: 'en' } } })); jest.mock('react-native-blob-util', () => ({})); jest.mock('react-native-encrypted-storage', () => ({})); jest.mock('react-native-fs', () => ({})); jest.mock('js-lnurl', () => ({ - getParams: () => mockGetLnurlParams + getParams: () => mockGetLnurlParams, + findlnurl: () => null })); -import stores from '../stores/Stores'; -import handleAnything from './handleAnything'; -let mockProcessedSendAddress = {}; -let mockIsValidBitcoinAddress = false; -let mockIsValidLightningPubKey = false; -let mockIsValidLightningPaymentRequest = false; -let mockSupportsOnchainSends = true; -let mockGetLnurlParams = {}; describe('handleAnything', () => { beforeEach(() => { + mockProcessBIP21Uri.mockReset(); mockIsValidBitcoinAddress = false; mockIsValidLightningPubKey = false; mockIsValidLightningPaymentRequest = false; }); + describe('input sanitization', () => { + it('should trim whitespace before processing', async () => { + mockIsValidBitcoinAddress = true; + mockProcessBIP21Uri.mockReturnValue({ value: 'some address' }); + + await handleAnything(' some address '); + expect(mockProcessBIP21Uri).toHaveBeenCalledWith('some address'); + }); + }); + describe('bitcoin address', () => { it('should return Send screen if not from clipboard', async () => { const data = 'test data'; - mockProcessedSendAddress = { value: 'some address', amount: 123 }; + mockProcessBIP21Uri.mockReturnValue({ + value: 'some address', + amount: 123 + }); mockIsValidBitcoinAddress = true; const result = await handleAnything(data); @@ -55,7 +80,10 @@ describe('handleAnything', () => { it('should return true if from clipboard', async () => { const data = 'test data'; - mockProcessedSendAddress = { value: 'some address', amount: 123 }; + mockProcessBIP21Uri.mockReturnValue({ + value: 'some address', + amount: 123 + }); mockIsValidBitcoinAddress = true; const result = await handleAnything(data, undefined, true); @@ -67,7 +95,7 @@ describe('handleAnything', () => { describe('lightning public key', () => { it('should return Send screen if not from clipboard', async () => { const data = 'test data'; - mockProcessedSendAddress = { value: 'some public key' }; + mockProcessBIP21Uri.mockReturnValue({ value: 'some public key' }); mockIsValidLightningPubKey = true; const result = await handleAnything(data); @@ -84,7 +112,7 @@ describe('handleAnything', () => { it('should return true if from clipboard', async () => { const data = 'test data'; - mockProcessedSendAddress = { value: 'some public key' }; + mockProcessBIP21Uri.mockReturnValue({ value: 'some public key' }); mockIsValidLightningPubKey = true; const result = await handleAnything(data, undefined, true); @@ -96,7 +124,9 @@ describe('handleAnything', () => { describe('lightning payment request', () => { it('should return PaymentRequest screen and call getPayReq if not from clipboard', async () => { const data = 'test data'; - mockProcessedSendAddress = { value: 'some payment request' }; + mockProcessBIP21Uri.mockReturnValue({ + value: 'some payment request' + }); mockIsValidLightningPaymentRequest = true; const result = await handleAnything(data); @@ -109,7 +139,9 @@ describe('handleAnything', () => { it('should return true if from clipboard', async () => { const data = 'test data'; - mockProcessedSendAddress = { value: 'some payment request' }; + mockProcessBIP21Uri.mockReturnValue({ + value: 'some payment request' + }); mockIsValidLightningPaymentRequest = true; const result = await handleAnything(data, undefined, true); @@ -119,16 +151,14 @@ describe('handleAnything', () => { }); describe('bitcoin URI with lnurl and backend not supporting on-chain sends', () => { - // test needs to be mocked out more as endpoint at https://ts.dergigi.com/BTC/UILNURL/pay/i/Wg7JV2ZFHs4cschM6bG5PT - // returns {"status":"ERROR","reason":"Invoice not in a valid payable state"} and throws on handleAnything.ts:54 it('should return LnurlPay screen if not from clipboard', async () => { const data = 'bitcoin:BC1QUXCS7V556UTNUKU93HSZ7LHHFFLWN9NF2UTQ6N?pj=https://ts.dergigi.com/BTC/pj&lightning=LNURL1DP68GURN8GHJ7ARN9EJX2UN8D9NKJTNRDAKJ7SJ5GVH42J2VFE24YNP0WPSHJTMF9ATKWD622CE953JGWV6XXUMRDPXNVCJ8X4G9GF2CHDF'; - mockProcessedSendAddress = { + mockProcessBIP21Uri.mockReturnValue({ value: 'BC1QUXCS7V556UTNUKU93HSZ7LHHFFLWN9NF2UTQ6N', lightning: 'LNURL1DP68GURN8GHJ7ARN9EJX2UN8D9NKJTNRDAKJ7SJ5GVH42J2VFE24YNP0WPSHJTMF9ATKWD622CE953JGWV6XXUMRDPXNVCJ8X4G9GF2CHDF' - }; + }); mockIsValidBitcoinAddress = true; mockSupportsOnchainSends = false; mockGetLnurlParams = { @@ -153,11 +183,11 @@ describe('handleAnything', () => { it('should return true if from clipboard', async () => { const data = 'bitcoin:BC1QUXCS7V556UTNUKU93HSZ7LHHFFLWN9NF2UTQ6N?pj=https://ts.dergigi.com/BTC/pj&lightning=LNURL1DP68GURN8GHJ7ARN9EJX2UN8D9NKJTNRDAKJ7SJ5GVH42J2VFE24YNP0WPSHJTMF9ATKWD622CE953JGWV6XXUMRDPXNVCJ8X4G9GF2CHDF'; - mockProcessedSendAddress = { + mockProcessBIP21Uri.mockReturnValue({ value: 'BC1QUXCS7V556UTNUKU93HSZ7LHHFFLWN9NF2UTQ6N', lightning: 'LNURL1DP68GURN8GHJ7ARN9EJX2UN8D9NKJTNRDAKJ7SJ5GVH42J2VFE24YNP0WPSHJTMF9ATKWD622CE953JGWV6XXUMRDPXNVCJ8X4G9GF2CHDF' - }; + }); mockIsValidBitcoinAddress = true; mockSupportsOnchainSends = false; @@ -171,6 +201,13 @@ describe('handleAnything', () => { it('should return PaymentRequest screen and call getPayReq if not from clipboard', async () => { const data = 'bitcoin:BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U?amount=0.00001&label=sbddesign%3A%20For%20lunch%20Tuesday&message=For%20lunch%20Tuesday&lightning=LNBC10U1P3PJ257PP5YZTKWJCZ5FTL5LAXKAV23ZMZEKAW37ZK6KMV80PK4XAEV5QHTZ7QDPDWD3XGER9WD5KWM36YPRX7U3QD36KUCMGYP282ETNV3SHJCQZPGXQYZ5VQSP5USYC4LK9CHSFP53KVCNVQ456GANH60D89REYKDNGSMTJ6YW3NHVQ9QYYSSQJCEWM5CJWZ4A6RFJX77C490YCED6PEMK0UPKXHY89CMM7SCT66K8GNEANWYKZGDRWRFJE69H9U5U0W57RRCSYSAS7GADWMZXC8C6T0SPJAZUP6'; + mockProcessBIP21Uri.mockReturnValue({ + value: 'BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U', + lightning: + 'LNBC10U1P3PJ257PP5YZTKWJCZ5FTL5LAXKAV23ZMZEKAW37ZK6KMV80PK4XAEV5QHTZ7QDPDWD3XGER9WD5KWM36YPRX7U3QD36KUCMGYP282ETNV3SHJCQZPGXQYZ5VQSP5USYC4LK9CHSFP53KVCNVQ456GANH60D89REYKDNGSMTJ6YW3NHVQ9QYYSSQJCEWM5CJWZ4A6RFJX77C490YCED6PEMK0UPKXHY89CMM7SCT66K8GNEANWYKZGDRWRFJE69H9U5U0W57RRCSYSAS7GADWMZXC8C6T0SPJAZUP6' + }); + mockIsValidBitcoinAddress = true; + mockSupportsOnchainSends = false; const result = await handleAnything(data); @@ -179,9 +216,9 @@ describe('handleAnything', () => { { amount: undefined, lightning: - 'LNURL1DP68GURN8GHJ7ARN9EJX2UN8D9NKJTNRDAKJ7SJ5GVH42J2VFE24YNP0WPSHJTMF9ATKWD622CE953JGWV6XXUMRDPXNVCJ8X4G9GF2CHDF', + 'LNBC10U1P3PJ257PP5YZTKWJCZ5FTL5LAXKAV23ZMZEKAW37ZK6KMV80PK4XAEV5QHTZ7QDPDWD3XGER9WD5KWM36YPRX7U3QD36KUCMGYP282ETNV3SHJCQZPGXQYZ5VQSP5USYC4LK9CHSFP53KVCNVQ456GANH60D89REYKDNGSMTJ6YW3NHVQ9QYYSSQJCEWM5CJWZ4A6RFJX77C490YCED6PEMK0UPKXHY89CMM7SCT66K8GNEANWYKZGDRWRFJE69H9U5U0W57RRCSYSAS7GADWMZXC8C6T0SPJAZUP6', offer: undefined, - value: 'BC1QUXCS7V556UTNUKU93HSZ7LHHFFLWN9NF2UTQ6N' + value: 'BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U' } ]); }); @@ -189,11 +226,11 @@ describe('handleAnything', () => { it('should return true if from clipboard', async () => { const data = 'bitcoin:BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U?amount=0.00001&label=sbddesign%3A%20For%20lunch%20Tuesday&message=For%20lunch%20Tuesday&lightning=LNBC10U1P3PJ257PP5YZTKWJCZ5FTL5LAXKAV23ZMZEKAW37ZK6KMV80PK4XAEV5QHTZ7QDPDWD3XGER9WD5KWM36YPRX7U3QD36KUCMGYP282ETNV3SHJCQZPGXQYZ5VQSP5USYC4LK9CHSFP53KVCNVQ456GANH60D89REYKDNGSMTJ6YW3NHVQ9QYYSSQJCEWM5CJWZ4A6RFJX77C490YCED6PEMK0UPKXHY89CMM7SCT66K8GNEANWYKZGDRWRFJE69H9U5U0W57RRCSYSAS7GADWMZXC8C6T0SPJAZUP6'; - mockProcessedSendAddress = { + mockProcessBIP21Uri.mockReturnValue({ value: 'BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U', lightning: 'LNBC10U1P3PJ257PP5YZTKWJCZ5FTL5LAXKAV23ZMZEKAW37ZK6KMV80PK4XAEV5QHTZ7QDPDWD3XGER9WD5KWM36YPRX7U3QD36KUCMGYP282ETNV3SHJCQZPGXQYZ5VQSP5USYC4LK9CHSFP53KVCNVQ456GANH60D89REYKDNGSMTJ6YW3NHVQ9QYYSSQJCEWM5CJWZ4A6RFJX77C490YCED6PEMK0UPKXHY89CMM7SCT66K8GNEANWYKZGDRWRFJE69H9U5U0W57RRCSYSAS7GADWMZXC8C6T0SPJAZUP6' - }; + }); mockIsValidBitcoinAddress = true; mockSupportsOnchainSends = false; diff --git a/utils/handleAnything.ts b/utils/handleAnything.ts index 9e93262d0..64e35b651 100644 --- a/utils/handleAnything.ts +++ b/utils/handleAnything.ts @@ -95,6 +95,7 @@ const handleAnything = async ( setAmount?: string, isClipboardValue?: boolean ): Promise => { + data = data.trim(); const { nodeInfo } = nodeInfoStore; const { isTestNet, isRegTest, isSigNet } = nodeInfo; const { value, amount, lightning, offer }: any =