diff --git a/test/alexSDK.mock-exceptions.test.ts b/test/alexSDK.mock-exceptions.test.ts index 9953cb0..4af00d9 100644 --- a/test/alexSDK.mock-exceptions.test.ts +++ b/test/alexSDK.mock-exceptions.test.ts @@ -1,5 +1,6 @@ import { AlexSDK, Currency } from '../src'; import * as ammRouteResolver from '../src/utils/ammRouteResolver'; +import { assertNever } from '../src/utils/utils'; import { configs } from '../src/config'; const sdk = new AlexSDK(); @@ -37,4 +38,11 @@ describe('AlexSDK - mock exceptions', () => { ) ).rejects.toThrow('Too many AMM pools in route'); }, 10000); + + it('Attempt assertNever to throw unexpected object', () => { + const unexpectedObject = '' as never; + expect(() => assertNever(unexpectedObject)).toThrowError( + 'Unexpected object: ' + unexpectedObject + ); + }); }); diff --git a/test/alexSDK.mock-externals.test.ts b/test/alexSDK.mock-externals.test.ts index 768c34c..c549bfc 100644 --- a/test/alexSDK.mock-externals.test.ts +++ b/test/alexSDK.mock-externals.test.ts @@ -24,9 +24,14 @@ const tokenMappings: TokenInfo[] = [ const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; -describe('AlexSDK - mock externals', () => { +describe('AlexSDK - mock externals - SDK_API_HOST - BACKEND_API_HOST - STACKS_API_HOST (Internal Server Error)', () => { beforeEach(() => { fetchMock.get(configs.SDK_API_HOST, 500); + fetchMock.get(`${configs.BACKEND_API_HOST}/v2/public/token-prices`, 500); + fetchMock.get( + `${configs.STACKS_API_HOST}/extended/v1/address/${stxAddress}/balances`, + 500 + ); }); afterEach(() => { fetchMock.restore(); @@ -36,24 +41,24 @@ describe('AlexSDK - mock externals', () => { await expect(sdk.getLatestPrices()).rejects.toThrow( 'Failed to fetch token mappings' ); - }, 10000); + }); it('Attempt to Get Fee with incorrect Alex SDK Data', async () => { await expect(sdk.getFeeRate(tokenAlex, Currency.STX)).rejects.toThrow( 'Failed to fetch token mappings' ); - }, 10000); + }); it('Attempt to Get Router with incorrect Alex SDK Data', async () => { await expect(sdk.getRouter(tokenAlex, Currency.STX)).rejects.toThrow( 'Failed to fetch token mappings' ); - }, 10000); + }); it('Attempt to Get Amount with incorrect Alex SDK Data', async () => { await expect( sdk.getAmountTo(Currency.STX, BigInt(2) * BigInt(1e8), tokenWUSDA) ).rejects.toThrow('Failed to fetch token mappings'); - }, 10000); + }); it('Attempt to Run Swap with incorrect Alex SDK Data', async () => { await expect( @@ -65,38 +70,25 @@ describe('AlexSDK - mock externals', () => { BigInt(0) ) ).rejects.toThrow('Failed to fetch token mappings'); - }, 10000); + }); it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => { await expect(sdk.getLatestPrices()).rejects.toThrow( 'Failed to fetch token mappings' ); - }, 10000); + }); it('Attempt to Get Balances with incorrect Alex SDK Data', async () => { const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; await expect(sdk.getBalances(stxAddress)).rejects.toThrow( 'Failed to fetch token mappings' ); - }, 10000); + }); it('Attempt to Fetch Swappable Currency with incorrect Alex SDK Data', async () => { await expect(sdk.fetchSwappableCurrency()).rejects.toThrow( 'Failed to fetch token mappings' ); - }, 10000); -}); - -describe('AlexSDK - mock externals - BACKEND_API_HOST', () => { - beforeEach(() => { - fetchMock.get(`${configs.BACKEND_API_HOST}/v2/public/token-prices`, { - status: 500, - body: 'Internal Server Error', - }); - }); - - afterEach(() => { - fetchMock.restore(); }); it('Attempt to get token prices with incorrect data', async () => { @@ -107,9 +99,180 @@ describe('AlexSDK - mock externals - BACKEND_API_HOST', () => { fetchMock.calls(`${configs.BACKEND_API_HOST}/v2/public/token-prices`) .length ).toBe(1); - }, 10000); -}); + }); + it('Attempt to Get Balances with incorrect data', async () => { + await expect( + fetchBalanceForAccount(stxAddress, tokenMappings) + ).rejects.toThrow('Unexpected'); + }); +}); +describe('AlexSDK - mock externals - SDK_API_HOST - BACKEND_API_HOST - STACKS_API_HOST (Gateway Timeout)', () => { + beforeEach(() => { + fetchMock.get(configs.SDK_API_HOST, 504); + fetchMock.get(`${configs.BACKEND_API_HOST}/v2/public/token-prices`, 504); + fetchMock.get( + `${configs.STACKS_API_HOST}/extended/v1/address/${stxAddress}/balances`, + 504 + ); + }); + afterEach(() => { + fetchMock.restore(); + }); + + it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => { + await expect(sdk.getLatestPrices()).rejects.toThrow( + 'Failed to fetch token mappings' + ); + }); + it('Attempt to Get Fee with incorrect Alex SDK Data', async () => { + await expect(sdk.getFeeRate(tokenAlex, Currency.STX)).rejects.toThrow( + 'Failed to fetch token mappings' + ); + }); + + it('Attempt to Get Router with incorrect Alex SDK Data', async () => { + await expect(sdk.getRouter(tokenAlex, Currency.STX)).rejects.toThrow( + 'Failed to fetch token mappings' + ); + }); + + it('Attempt to Get Amount with incorrect Alex SDK Data', async () => { + await expect( + sdk.getAmountTo(Currency.STX, BigInt(2) * BigInt(1e8), tokenWUSDA) + ).rejects.toThrow('Failed to fetch token mappings'); + }); + + it('Attempt to Run Swap with incorrect Alex SDK Data', async () => { + await expect( + sdk.runSwap( + configs.CONTRACT_DEPLOYER, + tokenAlex, + tokenWUSDA, + BigInt(2) * BigInt(1e8), + BigInt(0) + ) + ).rejects.toThrow('Failed to fetch token mappings'); + }); + + it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => { + await expect(sdk.getLatestPrices()).rejects.toThrow( + 'Failed to fetch token mappings' + ); + }); + + it('Attempt to Get Balances with incorrect Alex SDK Data', async () => { + const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; + await expect(sdk.getBalances(stxAddress)).rejects.toThrow( + 'Failed to fetch token mappings' + ); + }); + + it('Attempt to Fetch Swappable Currency with incorrect Alex SDK Data', async () => { + await expect(sdk.fetchSwappableCurrency()).rejects.toThrow( + 'Failed to fetch token mappings' + ); + }); + + it('Attempt to get token prices with incorrect data', async () => { + await expect(getPrices(tokenMappings)).rejects.toThrow( + 'Failed to fetch token mappings' + ); + expect( + fetchMock.calls(`${configs.BACKEND_API_HOST}/v2/public/token-prices`) + .length + ).toBe(1); + }); + + it('Attempt to Get Balances with incorrect data', async () => { + await expect( + fetchBalanceForAccount(stxAddress, tokenMappings) + ).rejects.toThrow('Unexpected'); + }); +}); +describe('AlexSDK - mock externals - SDK_API_HOST - BACKEND_API_HOST - STACKS_API_HOST (Not Found)', () => { + beforeEach(() => { + fetchMock.get(configs.SDK_API_HOST, 404); + fetchMock.get(`${configs.BACKEND_API_HOST}/v2/public/token-prices`, 404); + fetchMock.get( + `${configs.STACKS_API_HOST}/extended/v1/address/${stxAddress}/balances`, + 404 + ); + }); + afterEach(() => { + fetchMock.restore(); + }); + + it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => { + await expect(sdk.getLatestPrices()).rejects.toThrow( + 'Failed to fetch token mappings' + ); + }); + it('Attempt to Get Fee with incorrect Alex SDK Data', async () => { + await expect(sdk.getFeeRate(tokenAlex, Currency.STX)).rejects.toThrow( + 'Failed to fetch token mappings' + ); + }); + + it('Attempt to Get Router with incorrect Alex SDK Data', async () => { + await expect(sdk.getRouter(tokenAlex, Currency.STX)).rejects.toThrow( + 'Failed to fetch token mappings' + ); + }); + + it('Attempt to Get Amount with incorrect Alex SDK Data', async () => { + await expect( + sdk.getAmountTo(Currency.STX, BigInt(2) * BigInt(1e8), tokenWUSDA) + ).rejects.toThrow('Failed to fetch token mappings'); + }); + + it('Attempt to Run Swap with incorrect Alex SDK Data', async () => { + await expect( + sdk.runSwap( + configs.CONTRACT_DEPLOYER, + tokenAlex, + tokenWUSDA, + BigInt(2) * BigInt(1e8), + BigInt(0) + ) + ).rejects.toThrow('Failed to fetch token mappings'); + }); + + it('Attempt to Get Latest Prices with incorrect Alex SDK Data', async () => { + await expect(sdk.getLatestPrices()).rejects.toThrow( + 'Failed to fetch token mappings' + ); + }); + + it('Attempt to Get Balances with incorrect Alex SDK Data', async () => { + const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; + await expect(sdk.getBalances(stxAddress)).rejects.toThrow( + 'Failed to fetch token mappings' + ); + }); + + it('Attempt to Fetch Swappable Currency with incorrect Alex SDK Data', async () => { + await expect(sdk.fetchSwappableCurrency()).rejects.toThrow( + 'Failed to fetch token mappings' + ); + }); + + it('Attempt to get token prices with incorrect data', async () => { + await expect(getPrices(tokenMappings)).rejects.toThrow( + 'Failed to fetch token mappings' + ); + expect( + fetchMock.calls(`${configs.BACKEND_API_HOST}/v2/public/token-prices`) + .length + ).toBe(1); + }); + + it('Attempt to Get Balances with incorrect data', async () => { + await expect( + fetchBalanceForAccount(stxAddress, tokenMappings) + ).rejects.toThrow('Unexpected'); + }); +}); describe('Transfer Factory', () => { it('Throws error in Transfer Factory', () => { const transfer = transferFactory(tokenMappings); @@ -118,29 +281,3 @@ describe('Transfer Factory', () => { ); }); }); - -describe('AlexSDK - mock externals - STACKS_API_HOST', () => { - beforeEach(() => { - fetchMock.get( - `${configs.STACKS_API_HOST}/extended/v1/address/${stxAddress}/balances`, - { - status: 500, - body: 'Internal Server Error', - } - ); - }); - - afterEach(() => { - fetchMock.restore(); - }); - - it('Attempt to Get Balances with incorrect data', async () => { - await expect( - fetchBalanceForAccount(stxAddress, tokenMappings) - ).rejects.toThrow( - new SyntaxError( - 'Unexpected token \'I\', "Internal S"... is not valid JSON' - ) - ); - }, 10000); -}); diff --git a/test/alexSDK.mock-helpers.test.ts b/test/alexSDK.mock-helpers.test.ts index 4803590..a1ad1e4 100644 --- a/test/alexSDK.mock-helpers.test.ts +++ b/test/alexSDK.mock-helpers.test.ts @@ -13,7 +13,6 @@ import { dummyBalances, dummyCurrencies, dummyFee, - dummyPrices, dummyRate, dummyTx, parsedDummyPrices, @@ -22,6 +21,7 @@ import { dummyFactorA, dummyFactorB, dummyTokenC, + DUMMY_DEPLOYER, } from './mock-data/alexSDKMockResponses'; import { cvToValue, FungibleConditionCode } from '@stacks/transactions'; @@ -53,17 +53,31 @@ jest.mock('../src/helpers/SwapHelper', () => { }); jest.mock('../src/utils/fetchData', () => { const originalModule = jest.requireActual('../src/utils/fetchData'); + const { dummyPrices, dummyCurrencies } = jest.requireActual( + './mock-data/alexSDKMockResponses' + ); return { __esModule: true, ...originalModule, - getPrices: jest.fn(async () => dummyPrices), + getPrices: jest + .fn() + .mockReturnValueOnce(dummyPrices) + .mockReturnValueOnce(originalModule.getPrices(dummyCurrencies)), fetchBalanceForAccount: jest.fn(async () => dummyBalances), getAlexSDKData: jest.fn(async () => dummyAlexSDKData), }; }); -jest.mock('../src/utils/ammRouteResolver', () => ({ - resolveAmmRoute: jest.fn(() => dummyAmmRoute), -})); +jest.mock('../src/utils/ammRouteResolver', () => { + const originalModule = jest.requireActual('../src/utils/ammRouteResolver'); + return { + resolveAmmRoute: jest.fn((tokenX, ...args) => { + if (tokenX === dummyTokenA) { + return dummyAmmRoute; + } + return originalModule.resolveAmmRoute(tokenX, ...args); + }), + }; +}); describe('AlexSDK - mock helpers', () => { it('Verify response value of getFeeRate function', async () => { @@ -97,7 +111,7 @@ describe('AlexSDK - mock helpers', () => { it('Verify response value of runSwap function', async () => { expect(jest.isMockFunction(SwapHelper.runSpot)).toBeTruthy(); const result = await sdk.runSwap( - 'SP111111111111111111111111111111111111111', + DUMMY_DEPLOYER, tokenAlex, tokenWUSDA, BigInt(1), @@ -126,12 +140,33 @@ describe('AlexSDK - mock helpers', () => { expect(result.postConditions[0].amount).toStrictEqual(BigInt(0)); }); + it('Verify response value of runSwap function (empty pools)', async () => { + expect(jest.isMockFunction(SwapHelper.runSpot)).toBeTruthy(); + expect(jest.isMockFunction(ammRouteResolver.resolveAmmRoute)).toBeTruthy(); + const amount = BigInt(2) * BigInt(1e8); + await expect( + sdk.runSwap( + configs.CONTRACT_DEPLOYER, + dummyTokenB, + dummyTokenC, + amount, + BigInt(0) + ) + ).rejects.toThrow("Can't find AMM route"); + }); + it('Verify response value of getLatestPrices function', async () => { expect(jest.isMockFunction(fetchData.getPrices)).toBeTruthy(); const result = await sdk.getLatestPrices(); expect(result).toStrictEqual(parsedDummyPrices); }); + it('Verify response value of getLatestPrices function (null token cases)', async () => { + expect(jest.isMockFunction(fetchData.getPrices)).toBeTruthy(); + const result = await sdk.getLatestPrices(); + expect(result).toBeDefined(); + }); + it('Verify response value of getBalances function', async () => { expect(jest.isMockFunction(fetchData.fetchBalanceForAccount)).toBeTruthy(); const stxAddress = 'SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT'; diff --git a/test/alexSDK.test.ts b/test/alexSDK.test.ts index 7299230..d17cdfb 100644 --- a/test/alexSDK.test.ts +++ b/test/alexSDK.test.ts @@ -4,6 +4,7 @@ import Ajv from 'ajv'; import { createGenerator } from 'ts-json-schema-generator'; import path from 'node:path'; import { getAlexSDKData, getPrices } from '../src/utils/fetchData'; +import { TxToBroadCast } from '../src/helpers/SwapHelper'; const runtimeTypescriptMatcher = (received: any, typeName: string) => { const validator = new Ajv().compile( @@ -33,30 +34,64 @@ declare global { } } +const checkSwapResult = (result: TxToBroadCast) => { + expect(typeof result).toBe('object'); + expect(result).toHaveProperty('contractAddress'); + expect(result).toHaveProperty('contractName'); + expect(result).toHaveProperty('functionName'); + expect(result).toHaveProperty('functionArgs'); + expect(result).toHaveProperty('postConditions'); + expect(result.contractAddress).toBe(configs.CONTRACT_DEPLOYER); + expect(result.contractName).toBe('amm-pool-v2-01'); + expect([ + 'swap-helper', + 'swap-helper-a', + 'swap-helper-b', + 'swap-helper-c', + ]).toContain(result.functionName); + expect(Array.isArray(result.functionArgs)).toBeTruthy(); + expect(Array.isArray(result.postConditions)).toBeTruthy(); +}; + expect.extend({ toMatchType: runtimeTypescriptMatcher }); const tokenAlex = 'age000-governance-token' as Currency; const tokenDiko = 'token-wdiko' as Currency; +const tokenWmick = 'token-wmick' as Currency; +const tokenSSL = 'token-ssl-all-AESDE' as Currency; +const tokenBRC20ORMM = 'brc20-ormm' as Currency; const wrongTokenAlex = '' as Currency; +const routeLength1 = { from: tokenAlex, to: Currency.STX }; +const routeLength2 = { from: tokenWmick, to: tokenDiko }; +const routeLength3 = { from: tokenSSL, to: tokenDiko }; +const routeLength4 = { from: tokenWmick, to: tokenBRC20ORMM }; +const alternativeRoutes = [routeLength2, routeLength3, routeLength4]; + const sdk = new AlexSDK(); const CLARITY_MAX_UNSIGNED_INT = BigInt( '340282366920938463463374607431768211455' ); describe('AlexSDK', () => { - it('Verify response of getFeeRate function', async () => { - const result = await sdk.getFeeRate(tokenAlex, Currency.STX); + it('Verify response of getFeeRate function (custom route)', async () => { + const customRoute = await sdk.getRoute(routeLength1.from, routeLength1.to); + const result = await sdk.getFeeRate( + routeLength1.from, + routeLength1.to, + customRoute + ); expect(typeof result).toBe('bigint'); expect(result >= BigInt(0)).toBeTruthy(); }, 10000); - it('Verify response of getFeeRate function (custom route)', async () => { - const customRoute = await sdk.getRoute(tokenAlex, Currency.STX); - const result = await sdk.getFeeRate(tokenAlex, Currency.STX, customRoute); - expect(typeof result).toBe('bigint'); - expect(result >= BigInt(0)).toBeTruthy(); - }, 10000); + it('Verify response of getFeeRate function (alternative routes)', async () => { + for (const route of alternativeRoutes) { + const result = await sdk.getFeeRate(route.from, route.to); + expect(typeof result).toBe('bigint'); + expect(result >= BigInt(0)).toBeTruthy(); + } + }, 40000); it('Attempt to Get Fee Rate with wrong tokens', async () => { await expect( @@ -83,16 +118,30 @@ describe('AlexSDK', () => { ); }, 10000); - it('Verify response of getAmountTo function', async () => { + it('Verify response of Get Rate function (custom route)', async () => { + const customRoute = await sdk.getRoute(routeLength1.from, routeLength1.to); const result = await sdk.getAmountTo( - Currency.STX, - BigInt(2) * BigInt(1e8), - tokenDiko + routeLength1.from, + BigInt(10000000) * BigInt(1e8), + routeLength1.to, + customRoute ); expect(typeof result).toBe('bigint'); expect(result > BigInt(0)).toBeTruthy(); }, 10000); + it('Verify response of Get Rate function (alternative routes)', async () => { + for (const route of alternativeRoutes) { + const result = await sdk.getAmountTo( + route.from, + BigInt(10000000) * BigInt(1e8), + route.to + ); + expect(typeof result).toBe('bigint'); + expect(result > BigInt(0)).toBeTruthy(); + } + }, 40000); + it('Attempt to Get Rate with a wrong From token', async () => { await expect( sdk.getAmountTo(wrongTokenAlex, BigInt(2) * BigInt(1e8), tokenDiko) @@ -119,33 +168,32 @@ describe('AlexSDK', () => { ).rejects.toThrow('ClarityError: 2011'); }, 10000); - it('Verify response of runSwap function', async () => { + it('Verify response of runSwap function (custom route)', async () => { + const customRoute = await sdk.getRoute(routeLength1.from, routeLength1.to); const result = await sdk.runSwap( configs.CONTRACT_DEPLOYER, - Currency.STX, - tokenDiko, + routeLength1.from, + routeLength1.to, BigInt(2) * BigInt(1e8), - BigInt(0) + BigInt(0), + customRoute ); - - expect(typeof result).toBe('object'); - expect(result).toHaveProperty('contractAddress'); - expect(result).toHaveProperty('contractName'); - expect(result).toHaveProperty('functionName'); - expect(result).toHaveProperty('functionArgs'); - expect(result).toHaveProperty('postConditions'); - expect(result.contractAddress).toBe(configs.CONTRACT_DEPLOYER); - expect(result.contractName).toBe('amm-pool-v2-01'); - expect([ - 'swap-helper', - 'swap-helper-a', - 'swap-helper-b', - 'swap-helper-c', - ]).toContain(result.functionName); - expect(Array.isArray(result.functionArgs)).toBeTruthy(); - expect(Array.isArray(result.postConditions)).toBeTruthy(); + checkSwapResult(result); }, 10000); + it('Verify response of runSwap function (alternative routes)', async () => { + for (const route of alternativeRoutes) { + const result = await sdk.runSwap( + configs.CONTRACT_DEPLOYER, + route.from, + route.to, + BigInt(2) * BigInt(1e8), + BigInt(0) + ); + checkSwapResult(result); + } + }, 40000); + it('Attempt to Get Tx with an invalid stx address (checksum mismatch)', async () => { await expect( sdk.runSwap( @@ -207,6 +255,12 @@ describe('AlexSDK', () => { }); }, 10000); + it('Verify response of getBalances function (with fungible token balance)', async () => { + const stxAddress = 'SP3ANPTPEQE72PNE31WE8BEV4VCKB2C38P48TPH0Q'; + const balances = await sdk.getBalances(stxAddress); + expect(balances).toBeDefined(); + }, 10000); + it('Attempt to Get Tx with an invalid stx address (checksum mismatch)', async () => { await expect( sdk.runSwap( diff --git a/test/mock-data/alexSDKMockResponses.ts b/test/mock-data/alexSDKMockResponses.ts index b9b5b3a..400c738 100644 --- a/test/mock-data/alexSDKMockResponses.ts +++ b/test/mock-data/alexSDKMockResponses.ts @@ -1,15 +1,11 @@ -import { - AlexSDKResponse, - PoolData, - PriceData, - TokenInfo, -} from '../../src/types'; +import { AlexSDKResponse, PriceData, TokenInfo } from '../../src/types'; import { TxToBroadCast } from '../../src/helpers/SwapHelper'; import { Currency } from '../../src'; import { AMMRouteSegment } from '../../src/utils/ammRouteResolver'; import { configs } from '../../src/config'; const validDeployer = configs.CONTRACT_DEPLOYER; +export const DUMMY_DEPLOYER = 'SP111111111111111111111111111111111111111'; export const dummyFee = BigInt(777); @@ -17,8 +13,6 @@ export const dummyTokenA = 'TokenA' as Currency; export const dummyTokenB = 'TokenB' as Currency; export const dummyTokenC = 'TokenC' as Currency; -export const dummyRoute = [dummyTokenA, dummyTokenB, dummyTokenC]; - export const dummyFactorA = BigInt(670000000); export const dummyFactorB = BigInt(680000000); export const dummyAmmRoute: AMMRouteSegment[] = [ @@ -103,20 +97,7 @@ export const dummyCurrencies: TokenInfo[] = [ }, ]; -export const dummyPools: PoolData[] = [ - { - tokenX: dummyTokenA, - tokenY: dummyTokenC, - factor: BigInt(5000000), - }, - { - tokenX: dummyTokenC, - tokenY: dummyTokenB, - factor: BigInt(5000000), - }, -]; - export const dummyAlexSDKData: AlexSDKResponse = { tokens: dummyCurrencies, - pools: dummyPools, + pools: [], };