Backlog tests and coverage (#12)

* add tests to mock-externals suite

* tests: complete custom and alternative routes coverage

* tests: lint fix

* tests: empty route coverage

* refactor mock-externals suite

* tests: coverage on fungible token balances and null token cases

* refactor mock-externals suite

* tests: remove unused structure

* remove redundant describe

* lint fix

---------

Co-authored-by: simsbluebox <simsbluebox@gmail.com>
This commit is contained in:
ignaciopenia
2024-07-23 04:32:09 -03:00
committed by GitHub
parent 924cca8a2e
commit 9c5de7dec5
5 changed files with 325 additions and 110 deletions

View File

@@ -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
);
});
});

View File

@@ -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);
});

View File

@@ -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';

View File

@@ -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(

View File

@@ -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: [],
};