Skip to content

Commit

Permalink
test: use JSON object schemas for validation
Browse files Browse the repository at this point in the history
  • Loading branch information
irisdv authored Mar 6, 2023
1 parent 04486db commit ffbf9f8
Show file tree
Hide file tree
Showing 12 changed files with 1,119 additions and 19,067 deletions.
52 changes: 22 additions & 30 deletions __tests__/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getTestAccount,
getTestProvider,
} from './fixtures';
import { initializeMatcher } from './schema';

describe('deploy and test Wallet', () => {
const provider = getTestProvider();
Expand All @@ -27,6 +28,7 @@ describe('deploy and test Wallet', () => {
let dapp: Contract;

beforeAll(async () => {
initializeMatcher(expect);
expect(account).toBeInstanceOf(Account);

const declareDeploy = await account.declareAndDeploy({
Expand Down Expand Up @@ -54,12 +56,13 @@ describe('deploy and test Wallet', () => {

test('estimate fee', async () => {
const innerInvokeEstFeeSpy = jest.spyOn(account.signer, 'signTransaction');
const { overall_fee } = await account.estimateInvokeFee({
const result = await account.estimateInvokeFee({
contractAddress: erc20Address,
entrypoint: 'transfer',
calldata: [erc20.address, '10', '0'],
});
expect(typeof overall_fee === 'bigint').toBe(true);

expect(result).toMatchSchemaRef('EstimateFee');
expect(innerInvokeEstFeeSpy.mock.calls[0][1].version).toBe(feeTransactionVersion);
innerInvokeEstFeeSpy.mockClear();
});
Expand All @@ -86,9 +89,10 @@ describe('deploy and test Wallet', () => {
},
]);

expect(estimatedFeeBulk[0]).toHaveProperty('suggestedMaxFee');
estimatedFeeBulk.forEach((value) => {
expect(value).toMatchSchemaRef('EstimateFee');
});
expect(estimatedFeeBulk.length).toEqual(2);
expect(typeof estimatedFeeBulk[0].overall_fee === 'bigint').toBe(true);
expect(innerInvokeEstFeeSpy.mock.calls[0][1].version).toBe(feeTransactionVersion);
innerInvokeEstFeeSpy.mockClear();
});
Expand All @@ -102,9 +106,8 @@ describe('deploy and test Wallet', () => {
entrypoint: 'transfer',
calldata: [erc20.address, '10', '0'],
});
expect(res).toHaveProperty('fee_estimation');
expect(res.fee_estimation).toHaveProperty('suggestedMaxFee');
expect(res).toHaveProperty('trace');

expect(res).toMatchSchemaRef('TransactionSimulation');
expect(innerInvokeEstFeeSpy.mock.calls[0][1].version).toBe(feeTransactionVersion);
innerInvokeEstFeeSpy.mockClear();
});
Expand Down Expand Up @@ -221,8 +224,7 @@ describe('deploy and test Wallet', () => {
classHash: '0x54328a1075b8820eb43caf0caa233923148c983742402dcfc38541dd843d01a',
});
await provider.waitForTransaction(declareTx.transaction_hash);

expect(declareTx.class_hash).toBeDefined();
expect(declareTx).toMatchSchemaRef('DeclareContractResponse');
});

test('Get the stark name of the account and account from stark name (using starknet.id)', async () => {
Expand Down Expand Up @@ -305,8 +307,7 @@ describe('deploy and test Wallet', () => {
});

await provider.waitForTransaction(declareTx.transaction_hash);

expect(declareTx).toHaveProperty('class_hash');
expect(declareTx).toMatchSchemaRef('DeclareContractResponse');
expect(hexToDecimalString(declareTx.class_hash)).toEqual(hexToDecimalString(erc20ClassHash));
});

Expand All @@ -319,16 +320,7 @@ describe('deploy and test Wallet', () => {
account.address,
],
});

expect(deployResponse.contract_address).toBeDefined();
expect(deployResponse.transaction_hash).toBeDefined();
expect(deployResponse.address).toBeDefined();
expect(deployResponse.deployer).toBeDefined();
expect(deployResponse.unique).toBeDefined();
expect(deployResponse.classHash).toBeDefined();
expect(deployResponse.calldata_len).toBeDefined();
expect(deployResponse.calldata).toBeDefined();
expect(deployResponse.salt).toBeDefined();
expect(deployResponse).toMatchSchemaRef('DeployContractUDCResponse');
});

test('UDC Deploy unique', async () => {
Expand All @@ -344,7 +336,7 @@ describe('deploy and test Wallet', () => {
salt,
unique: true,
});
expect(deployment).toHaveProperty('transaction_hash');
expect(deployment).toMatchSchemaRef('MultiDeployContractResponse');

// check pre-calculated address
const txReceipt = await provider.waitForTransaction(deployment.transaction_hash);
Expand All @@ -365,7 +357,7 @@ describe('deploy and test Wallet', () => {
salt,
unique: false,
});
expect(deployment).toHaveProperty('transaction_hash');
expect(deployment).toMatchSchemaRef('MultiDeployContractResponse');

// check pre-calculated address
const txReceipt = await provider.waitForTransaction(deployment.transaction_hash);
Expand All @@ -387,9 +379,7 @@ describe('deploy and test Wallet', () => {
],
},
]);
expect(deployments).toHaveProperty('transaction_hash');
expect(deployments.contract_address[0]).toBeDefined();
expect(deployments.contract_address[1]).toBeDefined();
expect(deployments).toMatchSchemaRef('MultiDeployContractResponse');

await provider.waitForTransaction(deployments.transaction_hash);
});
Expand Down Expand Up @@ -455,8 +445,9 @@ describe('deploy and test Wallet', () => {
},
]);
expect(res).toHaveLength(2);
expect(res[0]).toHaveProperty('overall_fee');
expect(res[0]).toHaveProperty('suggestedMaxFee');
res.forEach((value) => {
expect(value).toMatchSchemaRef('EstimateFee');
});
});

test('declare, deploy & invoke functions', async () => {
Expand Down Expand Up @@ -496,8 +487,9 @@ describe('deploy and test Wallet', () => {
},
]);
expect(res).toHaveLength(3);
expect(res[0]).toHaveProperty('overall_fee');
expect(res[0]).toHaveProperty('suggestedMaxFee');
res.forEach((value) => {
expect(value).toMatchSchemaRef('EstimateFee');
});
});
});
});
12 changes: 4 additions & 8 deletions __tests__/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getTestAccount,
getTestProvider,
} from './fixtures';
import { initializeMatcher } from './schema';

describe('contract module', () => {
let erc20Address: string;
Expand All @@ -23,6 +24,7 @@ describe('contract module', () => {
const account = getTestAccount(provider);
const constructorCalldata = [encodeShortString('Token'), encodeShortString('ERC20'), wallet];
const classHash = '0x54328a1075b8820eb43caf0caa233923148c983742402dcfc38541dd843d01a';
initializeMatcher(expect);

describe('class Contract {}', () => {
describe('Basic Interaction', () => {
Expand Down Expand Up @@ -572,18 +574,12 @@ describe('Complex interaction', () => {

test('estimate fee', async () => {
const gas = await erc20Echo20Contract.estimateFee.iecho(CallData.compile(request));
expect(gas.gas_consumed).toBeDefined();
expect(gas.gas_price).toBeDefined();
expect(gas.overall_fee).toBeDefined();
expect(gas.suggestedMaxFee).toBeDefined();
expect(gas).toMatchSchemaRef('EstimateFee');
});

test('estimate fee transfer', async () => {
const gas = await erc20Echo20Contract.estimateFee.transfer(stark.randomAddress(), uint256(1));
expect(gas.gas_consumed).toBeDefined();
expect(gas.gas_price).toBeDefined();
expect(gas.overall_fee).toBeDefined();
expect(gas.suggestedMaxFee).toBeDefined();
expect(gas).toMatchSchemaRef('EstimateFee');
});
});
});
53 changes: 12 additions & 41 deletions __tests__/defaultProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getTestProvider,
wrongClassHash,
} from './fixtures';
import { initializeMatcher } from './schema';

const { compileCalldata } = stark;

Expand All @@ -21,6 +22,7 @@ describe('defaultProvider', () => {
let exampleBlockHash: string;
const wallet = stark.randomAddress();
const account = getTestAccount(testProvider);
initializeMatcher(expect);

beforeAll(async () => {
expect(testProvider).toBeInstanceOf(Provider);
Expand All @@ -47,56 +49,34 @@ describe('defaultProvider', () => {
describe('getBlock', () => {
test('getBlock(blockIdentifier=latest)', async () => {
expect(exampleBlock).not.toBeNull();
const { block_number, timestamp } = exampleBlock;
expect(typeof block_number).toEqual('number');
return expect(typeof timestamp).toEqual('number');
expect(exampleBlock).toMatchSchemaRef('GetBlockResponse');
});

test(`getBlock(blockHash=undefined, blockNumber=${exampleBlockNumber})`, async () => {
const block = await testProvider.getBlock(exampleBlockNumber);
expect(block).toHaveProperty('block_hash');
expect(block).toHaveProperty('parent_hash');
expect(block).toHaveProperty('block_number');
expect(block).toHaveProperty('status');
expect(block).toHaveProperty('new_root');
expect(block).toHaveProperty('timestamp');
expect(block).toHaveProperty('transactions');
expect(Array.isArray(block.transactions)).toBe(true);
expect(block).toMatchSchemaRef('GetBlockResponse');
});

test(`getBlock(blockHash=${exampleBlockHash}, blockNumber=undefined)`, async () => {
const block = await testProvider.getBlock(exampleBlockHash);
expect(block).toHaveProperty('block_hash');
expect(block).toHaveProperty('parent_hash');
expect(block).toHaveProperty('block_number');
expect(block).toHaveProperty('status');
expect(block).toHaveProperty('new_root');
expect(block).toHaveProperty('timestamp');
expect(block).toHaveProperty('transactions');
expect(Array.isArray(block.transactions)).toBe(true);
expect(block).toMatchSchemaRef('GetBlockResponse');
});

test('getBlock() -> { blockNumber }', async () => {
const block = await testProvider.getBlock('latest');
return expect(block).toHaveProperty('block_number');
expect(block).toMatchSchemaRef('GetBlockResponse');
});

test(`getStateUpdate(blockHash=${exampleBlockHash}, blockNumber=undefined)`, async () => {
const stateUpdate = await testProvider.getStateUpdate(exampleBlockHash);
expect(stateUpdate).toHaveProperty('block_hash');
expect(stateUpdate.block_hash).toBe(exampleBlockHash);
expect(stateUpdate).toHaveProperty('new_root');
expect(stateUpdate).toHaveProperty('old_root');
expect(stateUpdate).toHaveProperty('state_diff');
expect(stateUpdate).toMatchSchemaRef('StateUpdateResponse');
});

test(`getStateUpdate(blockHash=undefined, blockNumber=${exampleBlockNumber})`, async () => {
const stateUpdate = await testProvider.getStateUpdate(exampleBlockNumber);
expect(stateUpdate).toHaveProperty('block_hash');
expect(stateUpdate.block_hash).toBe(exampleBlockHash);
expect(stateUpdate).toHaveProperty('new_root');
expect(stateUpdate).toHaveProperty('old_root');
expect(stateUpdate).toHaveProperty('state_diff');
expect(stateUpdate).toMatchSchemaRef('StateUpdateResponse');
});
});

Expand All @@ -107,16 +87,12 @@ describe('defaultProvider', () => {

test('getClassAt(contractAddress, blockNumber="latest")', async () => {
const classResponse = await testProvider.getClassAt(erc20ContractAddress);

expect(classResponse).toHaveProperty('program');
expect(classResponse).toHaveProperty('entry_points_by_type');
expect(classResponse).toMatchSchemaRef('ContractClass');
});

test('GetClassByHash', async () => {
const classResponse = await testProvider.getClassByHash(erc20ClassHash);
expect(classResponse).toHaveProperty('program');
expect(classResponse).toHaveProperty('entry_points_by_type');
expect(classResponse).toHaveProperty('abi');
expect(classResponse).toMatchSchemaRef('ContractClass');
});

describe('getStorageAt', () => {
Expand All @@ -143,17 +119,12 @@ describe('defaultProvider', () => {

test('getTransaction() - successful deploy transaction', async () => {
const transaction = await testProvider.getTransaction(exampleTransactionHash);

expect(transaction).toHaveProperty('transaction_hash');
expect(transaction).toMatchSchemaRef('GetTransactionResponse');
});

test('getTransactionReceipt() - successful transaction', async () => {
const transactionReceipt = await testProvider.getTransactionReceipt(exampleTransactionHash);

expect(transactionReceipt).toHaveProperty('actual_fee');
expect(transactionReceipt).toHaveProperty('transaction_hash');
expect(transactionReceipt).toHaveProperty('status');
expect(transactionReceipt).toHaveProperty('actual_fee');
expect(transactionReceipt).toMatchSchemaRef('GetTransactionReceiptResponse');
});

describe('callContract()', () => {
Expand Down
10 changes: 4 additions & 6 deletions __tests__/rpcProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import {
getTestAccount,
getTestProvider,
} from './fixtures';
import { initializeMatcher } from './schema';

describeIfRpc('RPCProvider', () => {
const rpcProvider = getTestProvider() as RpcProvider;
const account = getTestAccount(rpcProvider);
let accountPublicKey: string;

beforeAll(async () => {
initializeMatcher(expect);
expect(account).toBeInstanceOf(Account);
const accountKeyPair = utils.randomPrivateKey();
accountPublicKey = getStarkKey(accountKeyPair);
Expand Down Expand Up @@ -45,10 +47,7 @@ describeIfRpc('RPCProvider', () => {

test('getStateUpdate', async () => {
const stateUpdate = await rpcProvider.getStateUpdate('latest');
expect(stateUpdate).toHaveProperty('block_hash');
expect(stateUpdate).toHaveProperty('new_root');
expect(stateUpdate).toHaveProperty('old_root');
expect(stateUpdate).toHaveProperty('state_diff');
expect(stateUpdate).toMatchSchemaRef('StateUpdateResponse');
});

xtest('getProtocolVersion - pathfinder not implement', async () => {
Expand Down Expand Up @@ -130,8 +129,7 @@ describeIfRpc('RPCProvider', () => {
const contractClass = await rpcProvider.getClass(
'0x058d97f7d76e78f44905cc30cb65b91ea49a4b908a76703c54197bca90f81773'
);
expect(contractClass).toHaveProperty('program');
expect(contractClass).toHaveProperty('entry_points_by_type');
expect(contractClass).toMatchSchemaRef('ContractClass');
});
});
});
45 changes: 45 additions & 0 deletions __tests__/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import ajvKeywords from 'ajv-keywords';
import { matchersWithOptions } from 'jest-json-schema';

import accountSchemas from './schemas/account.json';
import libSchemas from './schemas/lib.json';
import providerSchemas from './schemas/provider.json';
import sequencerSchemas from './schemas/sequencer.json';

const schemas = [accountSchemas, sequencerSchemas, providerSchemas, libSchemas];
const jestJsonMatchers = matchersWithOptions({ schemas }, (ajv: any) => {
// @ts-ignore
ajv.addKeyword({
keyword: 'isBigInt',
type: 'object',
validate: (_schema: any, data: any) => {
return typeof data === 'bigint' && data < 2n ** 64n && data >= 0n;
},
errors: true,
});
// This uses the `ajv-keywords` library to add pre-made custom keywords to the Ajv instance.
ajvKeywords(ajv, ['typeof', 'instanceof']);
});

export const initializeMatcher = (expect: jest.Expect) => {
expect.extend(jestJsonMatchers);
expect.extend({
toMatchSchemaRef(received: object, name: string) {
const schema = schemas.find((s) => Object.keys(s.definitions).includes(name));
const $ref = `${schema?.$id}#/definitions/${name}`;
return jestJsonMatchers.toMatchSchema.call(this, received, { $ref });
},
});
expect(accountSchemas).toBeValidSchema();
expect(sequencerSchemas).toBeValidSchema();
expect(providerSchemas).toBeValidSchema();
expect(libSchemas).toBeValidSchema();
};

declare global {
namespace jest {
interface Matchers<R> {
toMatchSchemaRef(name: string): R;
}
}
}
Loading

0 comments on commit ffbf9f8

Please sign in to comment.