chore: migrate tests to typescript

This commit is contained in:
Justin Starry 2021-03-15 13:08:10 +08:00 committed by Justin Starry
parent f912c63b22
commit 8ada44456d
27 changed files with 242 additions and 825 deletions

View File

@ -2,7 +2,6 @@
/coverage /coverage
/deploy /deploy
/doc /doc
/flow-typed
/lib /lib
/module.flow.js /module.flow.js
/.eslintrc.js /.eslintrc.js

View File

@ -1 +1,2 @@
test/dist test/dist
module.flow.js

View File

@ -8,8 +8,6 @@ test -r lib/index.iife.js
test -r lib/index.cjs.js test -r lib/index.cjs.js
test -r lib/index.esm.js test -r lib/index.esm.js
npm run doc npm run doc
npm run defs
npm run flow
npm run lint npm run lint
npm run codecov npm run codecov
make -C examples/bpf-c-noop/ make -C examples/bpf-c-noop/

File diff suppressed because it is too large Load Diff

View File

@ -56,12 +56,8 @@
"codecov": "set -ex; npm run test:cover; cat ./coverage/lcov.info | codecov", "codecov": "set -ex; npm run test:cover; cat ./coverage/lcov.info | codecov",
"dev": "cross-env NODE_ENV=development rollup -c", "dev": "cross-env NODE_ENV=development rollup -c",
"doc": "set -ex; typedoc", "doc": "set -ex; typedoc",
"defs": "set -ex; flow check-contents < module.flow.js; tsc module.d.ts",
"doc:watch": "watch 'npm run doc' . --wait=1 --ignoreDirectoryPattern=/doc/", "doc:watch": "watch 'npm run doc' . --wait=1 --ignoreDirectoryPattern=/doc/",
"examples": "set -ex; for example in examples/*.js; do node $example; done", "examples": "set -ex; for example in examples/*.js; do node $example; done",
"flow": "set -ex; flow stop; flow",
"flow:stop": "flow stop",
"flow:watch": "flow stop; watch 'flow' . --wait=1 --ignoreDirectoryPattern=/doc/",
"lint": "set -ex; npm run pretty; eslint . --ext .js,.ts", "lint": "set -ex; npm run pretty; eslint . --ext .js,.ts",
"lint:fix": "npm run pretty:fix && eslint . --fix", "lint:fix": "npm run pretty:fix && eslint . --fix",
"lint:watch": "watch 'npm run lint:fix' . --wait=1 --ignoreDirectoryPattern=/doc/", "lint:watch": "watch 'npm run lint:fix' . --wait=1 --ignoreDirectoryPattern=/doc/",
@ -69,13 +65,13 @@
"localnet:logs": "bin/localnet.sh logs -f", "localnet:logs": "bin/localnet.sh logs -f",
"localnet:up": "bin/localnet.sh up", "localnet:up": "bin/localnet.sh up",
"localnet:update": "bin/localnet.sh update", "localnet:update": "bin/localnet.sh update",
"ok": "run-s lint flow test doc defs", "ok": "run-s lint test doc",
"prepare": "run-s clean bpf-sdk:install bpf-sdk:remove-symlinks build", "prepare": "run-s clean bpf-sdk:install bpf-sdk:remove-symlinks build",
"pretty": "prettier --check '{,{examples,src,test}/**/}*.{j,t}s'", "pretty": "prettier --check '{,{examples,src,test}/**/}*.{j,t}s'",
"pretty:fix": "prettier --write '{,{examples,src,test}/**/}*.{j,t}s'", "pretty:fix": "prettier --write '{,{examples,src,test}/**/}*.{j,t}s'",
"re": "semantic-release --repository-url git@github.com:solana-labs/solana-web3.js.git", "re": "semantic-release --repository-url git@github.com:solana-labs/solana-web3.js.git",
"test": "npm run build:fixtures && mocha './test/**/*.test.js'", "test": "npm run build:fixtures && mocha -r ts-node/register './test/**/*.test.ts'",
"test:cover": "nyc --reporter=lcov mocha './test/**/*.test.js'", "test:cover": "nyc --reporter=lcov mocha './test/**/*.test.ts'",
"test:browser": "TEST_LIVE=1 npm run build:fixtures && npm run build:browser-test && mocha-headless-chrome -f http://localhost:8080/mocha.html --timeout 180000", "test:browser": "TEST_LIVE=1 npm run build:fixtures && npm run build:browser-test && mocha-headless-chrome -f http://localhost:8080/mocha.html --timeout 180000",
"test:browser-with-server": "start-server-and-test 'http-server -p 8080' 8080 test:browser", "test:browser-with-server": "start-server-and-test 'http-server -p 8080' 8080 test:browser",
"test:browser-with-test-validator": "start-server-and-test 'solana-test-validator --reset --quiet' http://localhost:8899/health test:browser-with-server", "test:browser-with-test-validator": "start-server-and-test 'solana-test-validator --reset --quiet' http://localhost:8899/health test:browser-with-server",
@ -116,9 +112,14 @@
"@solana/spl-token": "^0.0.13", "@solana/spl-token": "^0.0.13",
"@types/bn.js": "^5.1.0", "@types/bn.js": "^5.1.0",
"@types/bs58": "^4.0.1", "@types/bs58": "^4.0.1",
"@types/chai": "^4.2.15",
"@types/chai-as-promised": "^7.1.3",
"@types/mocha": "^8.2.1",
"@types/mz": "^2.7.3",
"@types/node": "^14.14.26", "@types/node": "^14.14.26",
"@types/node-fetch": "^2.5.8", "@types/node-fetch": "^2.5.8",
"@types/secp256k1": "^4.0.1", "@types/secp256k1": "^4.0.1",
"@types/sinon": "^9.0.11",
"@typescript-eslint/eslint-plugin": "^4.14.2", "@typescript-eslint/eslint-plugin": "^4.14.2",
"@typescript-eslint/parser": "^4.14.2", "@typescript-eslint/parser": "^4.14.2",
"chai": "^4.3.0", "chai": "^4.3.0",
@ -127,14 +128,10 @@
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "^7.19.0", "eslint": "^7.19.0",
"eslint-config-prettier": "^8.0.0", "eslint-config-prettier": "^8.0.0",
"eslint-plugin-flowtype": "^5.2.0",
"eslint-plugin-import": "2.22.1", "eslint-plugin-import": "2.22.1",
"eslint-plugin-mocha": "^8.0.0", "eslint-plugin-mocha": "^8.0.0",
"eslint-plugin-prettier": "^3.0.0", "eslint-plugin-prettier": "^3.0.0",
"esm": "^3.2.25", "esm": "^3.2.25",
"flow-bin": "0.130.0",
"flow-remove-types": "^2.143.1",
"flow-typed": "3.3.1",
"flowgen": "^1.13.0", "flowgen": "^1.13.0",
"http-server": "^0.12.3", "http-server": "^0.12.3",
"mocha": "^8.2.1", "mocha": "^8.2.1",
@ -152,6 +149,7 @@
"semantic-release": "^17.0.2", "semantic-release": "^17.0.2",
"sinon": "^9.2.4", "sinon": "^9.2.4",
"start-server-and-test": "^1.12.0", "start-server-and-test": "^1.12.0",
"ts-node": "^9.1.1",
"tslib": "^2.1.0", "tslib": "^2.1.0",
"typedoc": "^0.20.31", "typedoc": "^0.20.31",
"typescript": "^4.1.5", "typescript": "^4.1.5",

View File

@ -27,8 +27,8 @@ function generateConfig(configType, format) {
}), }),
generateTypescript generateTypescript
? typescript({ ? typescript({
browserslist: false, browserslist: false,
}) })
: undefined, : undefined,
babel({ babel({
exclude: '**/node_modules/**', exclude: '**/node_modules/**',

View File

@ -55,6 +55,12 @@ const BufferFromRawAccountData = coerce(
value => Buffer.from(value[0], 'base64'), value => Buffer.from(value[0], 'base64'),
); );
/**
* Attempt to use a recent blockhash for up to 30 seconds
* @internal
*/
export const BLOCKHASH_CACHE_TIMEOUT_MS = 30 * 1000;
type RpcRequest = (methodName: string, args: Array<any>) => any; type RpcRequest = (methodName: string, args: Array<any>) => any;
export type TokenAccountsFilter = export type TokenAccountsFilter =
@ -2578,8 +2584,6 @@ export class Connection {
while (this._pollingBlockhash) { while (this._pollingBlockhash) {
await sleep(100); await sleep(100);
} }
// Attempt to use a recent blockhash for up to 30 seconds
const BLOCKHASH_CACHE_TIMEOUT_MS = 30 * 1000;
const timeSinceFetch = Date.now() - this._blockhashInfo.lastFetch; const timeSinceFetch = Date.now() - this._blockhashInfo.lastFetch;
const expired = timeSinceFetch >= BLOCKHASH_CACHE_TIMEOUT_MS; const expired = timeSinceFetch >= BLOCKHASH_CACHE_TIMEOUT_MS;
if (this._blockhashInfo.recentBlockhash !== null && !expired) { if (this._blockhashInfo.recentBlockhash !== null && !expired) {

View File

@ -154,7 +154,7 @@ if (process.env.TEST_LIVE) {
const {signatures, message} = parsedTx.transaction; const {signatures, message} = parsedTx.transaction;
expect(signatures[0]).to.eq(signature); expect(signatures[0]).to.eq(signature);
const ix = message.instructions[0]; const ix = message.instructions[0];
if (ix.parsed) { if ('parsed' in ix) {
expect('parsed' in ix).to.eq(false); expect('parsed' in ix).to.eq(false);
} else { } else {
expect(ix.programId).to.eql(program.publicKey); expect(ix.programId).to.eql(program.publicKey);

View File

@ -5,8 +5,7 @@ import {clusterApiUrl} from '../src/util/cluster';
describe('Cluster Util', () => { describe('Cluster Util', () => {
it('invalid', () => { it('invalid', () => {
expect(() => { expect(() => {
// $FlowExpectedError clusterApiUrl('abc123' as any);
clusterApiUrl('abc123');
}).to.throw(); }).to.throw();
}); });

View File

@ -1,4 +1,5 @@
import bs58 from 'bs58'; import bs58 from 'bs58';
import invariant from 'assert';
import {Buffer} from 'buffer'; import {Buffer} from 'buffer';
import {Token, u64} from '@solana/spl-token'; import {Token, u64} from '@solana/spl-token';
import {expect, use} from 'chai'; import {expect, use} from 'chai';
@ -18,7 +19,17 @@ import {
} from '../src'; } from '../src';
import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from '../src/timing'; import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from '../src/timing';
import {MOCK_PORT, url} from './url'; import {MOCK_PORT, url} from './url';
import {BLOCKHASH_CACHE_TIMEOUT_MS} from '../src/connection'; import {
BLOCKHASH_CACHE_TIMEOUT_MS,
Commitment,
CompiledInnerInstruction,
EpochInfo,
EpochSchedule,
InflationGovernor,
ParsedInnerInstruction,
ParsedInstruction,
SlotInfo,
} from '../src/connection';
import {sleep} from '../src/util/sleep'; import {sleep} from '../src/util/sleep';
import { import {
helpers, helpers,
@ -66,14 +77,15 @@ describe('Connection', () => {
connection = new Connection(url); connection = new Connection(url);
}); });
if (!process.env.TEST_LIVE) { if (mockServer) {
const server = mockServer;
beforeEach(() => { beforeEach(() => {
mockServer.start(MOCK_PORT); server.start(MOCK_PORT);
stubRpcWebSocket(connection); stubRpcWebSocket(connection);
}); });
afterEach(() => { afterEach(() => {
mockServer.stop(); server.stop();
restoreRpcWebSocket(connection); restoreRpcWebSocket(connection);
}); });
} }
@ -289,14 +301,15 @@ describe('Connection', () => {
}); });
const inflation = await connection.getInflationGovernor(); const inflation = await connection.getInflationGovernor();
const inflationKeys: (keyof InflationGovernor)[] = [
for (const key of [
'initial', 'initial',
'terminal', 'terminal',
'taper', 'taper',
'foundation', 'foundation',
'foundationTerm', 'foundationTerm',
]) { ];
for (const key of inflationKeys) {
expect(inflation).to.have.property(key); expect(inflation).to.have.property(key);
expect(inflation[key]).to.be.greaterThan(0); expect(inflation[key]).to.be.greaterThan(0);
} }
@ -316,14 +329,15 @@ describe('Connection', () => {
}); });
const epochInfo = await connection.getEpochInfo('confirmed'); const epochInfo = await connection.getEpochInfo('confirmed');
const epochInfoKeys: (keyof EpochInfo)[] = [
for (const key of [
'epoch', 'epoch',
'slotIndex', 'slotIndex',
'slotsInEpoch', 'slotsInEpoch',
'absoluteSlot', 'absoluteSlot',
'blockHeight', 'blockHeight',
]) { ];
for (const key of epochInfoKeys) {
expect(epochInfo).to.have.property(key); expect(epochInfo).to.have.property(key);
expect(epochInfo[key]).to.be.at.least(0); expect(epochInfo[key]).to.be.at.least(0);
} }
@ -343,13 +357,14 @@ describe('Connection', () => {
}); });
const epochSchedule = await connection.getEpochSchedule(); const epochSchedule = await connection.getEpochSchedule();
const epochScheduleKeys: (keyof EpochSchedule)[] = [
for (const key of [
'firstNormalEpoch', 'firstNormalEpoch',
'firstNormalSlot', 'firstNormalSlot',
'leaderScheduleSlotOffset', 'leaderScheduleSlotOffset',
'slotsPerEpoch', 'slotsPerEpoch',
]) { ];
for (const key of epochScheduleKeys) {
expect(epochSchedule).to.have.property('warmup'); expect(epochSchedule).to.have.property('warmup');
expect(epochSchedule).to.have.property(key); expect(epochSchedule).to.have.property(key);
if (epochSchedule.warmup) { if (epochSchedule.warmup) {
@ -385,7 +400,7 @@ describe('Connection', () => {
}); });
const slot = await connection.getSlot(); const slot = await connection.getSlot();
if (!process.env.TEST_LIVE) { if (mockServer) {
expect(slot).to.eq(123); expect(slot).to.eq(123);
} else { } else {
// No idea what the correct slot value should be on a live cluster, so // No idea what the correct slot value should be on a live cluster, so
@ -402,7 +417,7 @@ describe('Connection', () => {
}); });
const slotLeader = await connection.getSlotLeader(); const slotLeader = await connection.getSlotLeader();
if (!process.env.TEST_LIVE) { if (mockServer) {
expect(slotLeader).to.eq('11111111111111111111111111111111'); expect(slotLeader).to.eq('11111111111111111111111111111111');
} else { } else {
// No idea what the correct slotLeader value should be on a live cluster, so // No idea what the correct slotLeader value should be on a live cluster, so
@ -427,7 +442,7 @@ describe('Connection', () => {
}); });
const clusterNodes = await connection.getClusterNodes(); const clusterNodes = await connection.getClusterNodes();
if (!process.env.TEST_LIVE) { if (mockServer) {
expect(clusterNodes).to.have.length(1); expect(clusterNodes).to.have.length(1);
expect(clusterNodes[0].pubkey).to.eq('11111111111111111111111111111111'); expect(clusterNodes[0].pubkey).to.eq('11111111111111111111111111111111');
expect(typeof clusterNodes[0].gossip).to.eq('string'); expect(typeof clusterNodes[0].gossip).to.eq('string');
@ -564,8 +579,8 @@ describe('Connection', () => {
// Find a block that has a transaction, usually Block 1 // Find a block that has a transaction, usually Block 1
let slot = 0; let slot = 0;
let address: ?PublicKey; let address: PublicKey | undefined;
let expectedSignature: ?string; let expectedSignature: string | undefined;
while (!address || !expectedSignature) { while (!address || !expectedSignature) {
slot++; slot++;
const block = await connection.getConfirmedBlock(slot); const block = await connection.getConfirmedBlock(slot);
@ -628,7 +643,7 @@ describe('Connection', () => {
{limit: 1}, {limit: 1},
); );
expect(confirmedSignatures2).to.have.length(1); expect(confirmedSignatures2).to.have.length(1);
if (!process.env.TEST_LIVE) { if (mockServer) {
expect(confirmedSignatures2[0].signature).to.eq(expectedSignature); expect(confirmedSignatures2[0].signature).to.eq(expectedSignature);
expect(confirmedSignatures2[0].slot).to.eq(slot); expect(confirmedSignatures2[0].slot).to.eq(slot);
expect(confirmedSignatures2[0].err).to.be.null; expect(confirmedSignatures2[0].err).to.be.null;
@ -699,7 +714,7 @@ describe('Connection', () => {
// Find a block that has a transaction, usually Block 1 // Find a block that has a transaction, usually Block 1
let slot = 0; let slot = 0;
let confirmedTransaction: ?string; let confirmedTransaction: string | undefined;
while (!confirmedTransaction) { while (!confirmedTransaction) {
slot++; slot++;
const block = await connection.getConfirmedBlock(slot); const block = await connection.getConfirmedBlock(slot);
@ -791,12 +806,12 @@ describe('Connection', () => {
expect(nullResponse).to.be.null; expect(nullResponse).to.be.null;
}); });
if (!process.env.TEST_LIVE) { if (mockServer) {
it('get parsed confirmed transaction coerces public keys of inner instructions', async () => { it('get parsed confirmed transaction coerces public keys of inner instructions', async () => {
const confirmedTransaction: TransactionSignature = const confirmedTransaction: TransactionSignature =
'4ADvAUQYxkh4qWKYE9QLW8gCLomGG94QchDLG4quvpBz1WqARYvzWQDDitKduAKspuy1DjcbnaDAnCAfnKpJYs48'; '4ADvAUQYxkh4qWKYE9QLW8gCLomGG94QchDLG4quvpBz1WqARYvzWQDDitKduAKspuy1DjcbnaDAnCAfnKpJYs48';
function getMockData(inner) { function getMockData(inner: any) {
return { return {
slot: 353050305, slot: 353050305,
transaction: { transaction: {
@ -853,15 +868,10 @@ describe('Connection', () => {
confirmedTransaction, confirmedTransaction,
); );
if ( if (result && result.meta && result.meta.innerInstructions) {
result !== null && const innerInstructions = result.meta.innerInstructions;
result.meta && const firstIx = innerInstructions[0].instructions[0];
result.meta.innerInstructions !== undefined && expect(firstIx.programId).to.be.instanceOf(PublicKey);
result.meta.innerInstructions.length > 0
) {
expect(
result.meta.innerInstructions[0].instructions[0].programId,
).to.be.instanceOf(PublicKey);
} }
await mockRpcResponse({ await mockRpcResponse({
@ -877,15 +887,21 @@ describe('Connection', () => {
}), }),
}); });
//$FlowFixMe
const result2 = await connection.getParsedConfirmedTransaction( const result2 = await connection.getParsedConfirmedTransaction(
confirmedTransaction, confirmedTransaction,
); );
let instruction = result2.meta.innerInstructions[0].instructions[0]; if (result2 && result2.meta && result2.meta.innerInstructions) {
expect(instruction.programId).to.be.instanceOf(PublicKey); const innerInstructions = result2.meta.innerInstructions;
expect(instruction.accounts[0]).to.be.instanceOf(PublicKey); const instruction = innerInstructions[0].instructions[0];
expect(instruction.accounts[1]).to.be.instanceOf(PublicKey); expect(instruction.programId).to.be.instanceOf(PublicKey);
if ('accounts' in instruction) {
expect(instruction.accounts[0]).to.be.instanceOf(PublicKey);
expect(instruction.accounts[1]).to.be.instanceOf(PublicKey);
} else {
expect('accounts' in instruction).to.be.true;
}
}
}); });
} }
@ -998,7 +1014,8 @@ describe('Connection', () => {
}); });
it('get recent blockhash', async () => { it('get recent blockhash', async () => {
for (const commitment of ['processed', 'confirmed', 'finalized']) { const commitments: Commitment[] = ['processed', 'confirmed', 'finalized'];
for (const commitment of commitments) {
const {blockhash, feeCalculator} = await helpers.recentBlockhash({ const {blockhash, feeCalculator} = await helpers.recentBlockhash({
connection, connection,
commitment, commitment,
@ -1189,7 +1206,7 @@ describe('Connection', () => {
testOwner = accountOwner; testOwner = accountOwner;
testToken = token; testToken = token;
testTokenAccount = tokenAccount; testTokenAccount = tokenAccount as PublicKey;
}); });
it('get token supply', async () => { it('get token supply', async () => {
@ -1229,7 +1246,7 @@ describe('Connection', () => {
const {signatures, message} = parsedTx.transaction; const {signatures, message} = parsedTx.transaction;
expect(signatures[0]).to.eq(testSignature); expect(signatures[0]).to.eq(testSignature);
const ix = message.instructions[0]; const ix = message.instructions[0];
if (ix.parsed) { if ('parsed' in ix) {
expect(ix.program).to.eq('spl-token'); expect(ix.program).to.eq('spl-token');
expect(ix.programId).to.eql(TOKEN_PROGRAM_ID); expect(ix.programId).to.eql(TOKEN_PROGRAM_ID);
} else { } else {
@ -1456,11 +1473,11 @@ describe('Connection', () => {
}); });
} }
if (!process.env.TEST_LIVE) { if (mockServer) {
it('stake activation should only accept state with valid string literals', async () => { it('stake activation should only accept state with valid string literals', async () => {
const publicKey = new Account().publicKey; const publicKey = new Account().publicKey;
const addStakeActivationMock = async state => { const addStakeActivationMock = async (state: any) => {
await mockRpcResponse({ await mockRpcResponse({
method: 'getStakeActivation', method: 'getStakeActivation',
params: [publicKey.toBase58(), {}], params: [publicKey.toBase58(), {}],
@ -1567,7 +1584,7 @@ describe('Connection', () => {
if (parsedAccountInfo === null) { if (parsedAccountInfo === null) {
expect(parsedAccountInfo).not.to.be.null; expect(parsedAccountInfo).not.to.be.null;
return; return;
} else if (parsedAccountInfo.data.parsed) { } else if ('parsed' in parsedAccountInfo.data) {
expect(parsedAccountInfo.data.parsed).not.to.be.ok; expect(parsedAccountInfo.data.parsed).not.to.be.ok;
return; return;
} }
@ -1616,6 +1633,7 @@ describe('Connection', () => {
).value; ).value;
expect(confirmResult.err).to.eql(expectedErr); expect(confirmResult.err).to.eql(expectedErr);
invariant(transaction.signature);
const signature = bs58.encode(transaction.signature); const signature = bs58.encode(transaction.signature);
await mockRpcResponse({ await mockRpcResponse({
method: 'getSignatureStatuses', method: 'getSignatureStatuses',
@ -1814,7 +1832,7 @@ describe('Connection', () => {
}); });
// it('account change notification', async () => { // it('account change notification', async () => {
// if (!process.env.TEST_LIVE) { // if (mockServer) {
// console.log('non-live test skipped'); // console.log('non-live test skipped');
// return; // return;
// } // }
@ -1939,7 +1957,7 @@ describe('Connection', () => {
}); });
it('slot notification', async () => { it('slot notification', async () => {
let notifiedSlotInfo; let notifiedSlotInfo: SlotInfo | undefined;
const subscriptionId = connection.onSlotChange(slotInfo => { const subscriptionId = connection.onSlotChange(slotInfo => {
notifiedSlotInfo = slotInfo; notifiedSlotInfo = slotInfo;
}); });
@ -1963,7 +1981,7 @@ describe('Connection', () => {
}); });
it('root notification', async () => { it('root notification', async () => {
let roots = []; let roots: number[] = [];
const subscriptionId = connection.onRootChange(root => { const subscriptionId = connection.onRootChange(root => {
roots.push(root); roots.push(root);
}); });

View File

@ -1,20 +1,21 @@
import bs58 from 'bs58'; import bs58 from 'bs58';
import BN from 'bn.js'; import BN from 'bn.js';
import invariant from 'assert';
import * as mockttp from 'mockttp'; import * as mockttp from 'mockttp';
import {mockRpcMessage} from './rpc-websockets'; import {mockRpcMessage} from './rpc-websockets';
import {Account, Connection, PublicKey, Transaction} from '../../src'; import {Account, Connection, PublicKey, Transaction} from '../../src';
import type {Commitment} from '../../src/connection'; import type {Commitment} from '../../src/connection';
export const mockServer: mockttp.Mockttp = export const mockServer: mockttp.Mockttp | undefined =
process.env.TEST_LIVE || mockttp.getLocal(); process.env.TEST_LIVE === undefined ? mockttp.getLocal() : undefined;
let uniqueCounter = 0; let uniqueCounter = 0;
export const uniqueSignature = () => { export const uniqueSignature = () => {
return bs58.encode(new BN(++uniqueCounter).toArray(null, 64)); return bs58.encode(new BN(++uniqueCounter).toArray(undefined, 64));
}; };
export const uniqueBlockhash = () => { export const uniqueBlockhash = () => {
return bs58.encode(new BN(++uniqueCounter).toArray(null, 32)); return bs58.encode(new BN(++uniqueCounter).toArray(undefined, 32));
}; };
export const mockErrorMessage = 'Invalid'; export const mockErrorMessage = 'Invalid';
@ -30,13 +31,13 @@ export const mockRpcResponse = async ({
error, error,
withContext, withContext,
}: { }: {
method: string, method: string;
params: Array<any>, params: Array<any>;
value?: any, value?: any;
error?: any, error?: any;
withContext?: boolean, withContext?: boolean;
}) => { }) => {
if (process.env.TEST_LIVE) return; if (!mockServer) return;
let result = value; let result = value;
if (withContext) { if (withContext) {
@ -70,8 +71,8 @@ const recentBlockhash = async ({
connection, connection,
commitment, commitment,
}: { }: {
connection: Connection, connection: Connection;
commitment?: Commitment, commitment?: Commitment;
}) => { }) => {
const blockhash = uniqueBlockhash(); const blockhash = uniqueBlockhash();
const params = []; const params = [];
@ -101,17 +102,18 @@ const processTransaction = async ({
commitment, commitment,
err, err,
}: { }: {
connection: Connection, connection: Connection;
transaction: Transaction, transaction: Transaction;
signers: Array<Account>, signers: Array<Account>;
commitment: Commitment, commitment: Commitment;
err?: any, err?: any;
}) => { }) => {
const blockhash = (await recentBlockhash({connection})).blockhash; const blockhash = (await recentBlockhash({connection})).blockhash;
transaction.recentBlockhash = blockhash; transaction.recentBlockhash = blockhash;
transaction.sign(...signers); transaction.sign(...signers);
const encoded = transaction.serialize().toString('base64'); const encoded = transaction.serialize().toString('base64');
invariant(transaction.signature !== null);
const signature = bs58.encode(transaction.signature); const signature = bs58.encode(transaction.signature);
await mockRpcResponse({ await mockRpcResponse({
method: 'sendTransaction', method: 'sendTransaction',
@ -146,9 +148,9 @@ const airdrop = async ({
address, address,
amount, amount,
}: { }: {
connection: Connection, connection: Connection;
address: PublicKey, address: PublicKey;
amount: number, amount: number;
}) => { }) => {
await mockRpcResponse({ await mockRpcResponse({
method: 'requestAirdrop', method: 'requestAirdrop',

View File

@ -5,15 +5,15 @@ import sinon from 'sinon';
import {Connection} from '../../src'; import {Connection} from '../../src';
type RpcRequest = { type RpcRequest = {
method: string, method: string;
params?: Array<any>, params?: Array<any>;
}; };
type RpcResponse = { type RpcResponse = {
context: { context: {
slot: number, slot: number;
}, };
value: any, value: any;
}; };
const mockRpcSocket: Array<[RpcRequest, RpcResponse]> = []; const mockRpcSocket: Array<[RpcRequest, RpcResponse]> = [];
@ -24,9 +24,9 @@ export const mockRpcMessage = ({
params, params,
result, result,
}: { }: {
method: string, method: string;
params: Array<any>, params: Array<any>;
result: any, result: any;
}) => { }) => {
mockRpcSocket.push([ mockRpcSocket.push([
{method, params}, {method, params},
@ -46,9 +46,11 @@ export const stubRpcWebSocket = (connection: Connection) => {
sandbox.stub(rpcWebSocket, 'close').callsFake(() => { sandbox.stub(rpcWebSocket, 'close').callsFake(() => {
mockClient.close(); mockClient.close();
}); });
sandbox.stub(rpcWebSocket, 'call').callsFake((method, params) => { sandbox
return mockClient.call(method, params); .stub(rpcWebSocket, 'call')
}); .callsFake((method: string, params: any) => {
return mockClient.call(method, params);
});
}; };
export const restoreRpcWebSocket = (connection: Connection) => { export const restoreRpcWebSocket = (connection: Connection) => {
@ -85,7 +87,10 @@ class MockClient {
call(method: string, params: Array<any>): Promise<Object> { call(method: string, params: Array<any>): Promise<Object> {
expect(mockRpcSocket.length).to.be.at.least(1); expect(mockRpcSocket.length).to.be.at.least(1);
const [mockRequest, mockResponse] = mockRpcSocket.shift(); const [mockRequest, mockResponse] = mockRpcSocket.shift() as [
RpcRequest,
RpcResponse,
];
expect(method).to.eq(mockRequest.method); expect(method).to.eq(mockRequest.method);
expect(params).to.eql(mockRequest.params); expect(params).to.eql(mockRequest.params);

View File

@ -31,14 +31,15 @@ describe('Nonce', () => {
connection = new Connection(url); connection = new Connection(url);
}); });
if (!process.env.TEST_LIVE) { if (mockServer) {
const server = mockServer;
beforeEach(() => { beforeEach(() => {
mockServer.start(MOCK_PORT); server.start(MOCK_PORT);
stubRpcWebSocket(connection); stubRpcWebSocket(connection);
}); });
afterEach(() => { afterEach(() => {
mockServer.stop(); server.stop();
restoreRpcWebSocket(connection); restoreRpcWebSocket(connection);
}); });
} }

View File

@ -1,17 +1,18 @@
import alias from '@rollup/plugin-alias'; import alias from '@rollup/plugin-alias';
import babel from '@rollup/plugin-babel'; import babel from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs'; import commonjs from '@rollup/plugin-commonjs';
import flowRemoveTypes from 'flow-remove-types';
import json from '@rollup/plugin-json'; import json from '@rollup/plugin-json';
import multi from '@rollup/plugin-multi-entry'; import multi from '@rollup/plugin-multi-entry';
import nodeResolve from '@rollup/plugin-node-resolve'; import nodeResolve from '@rollup/plugin-node-resolve';
import nodePolyfills from 'rollup-plugin-node-polyfills'; import nodePolyfills from 'rollup-plugin-node-polyfills';
import replace from '@rollup/plugin-replace'; import replace from '@rollup/plugin-replace';
const extensions = ['.js', '.ts'];
export default { export default {
input: { input: {
include: ['test/**/*.test.js'], include: ['test/**/*.test.ts'],
exclude: ['test/agent-manager.test.js', 'test/bpf-loader.test.js'], exclude: ['test/agent-manager.test.ts', 'test/bpf-loader.test.ts'],
}, },
external: ['node-forge', 'http2', '_stream_wrap'], external: ['node-forge', 'http2', '_stream_wrap'],
output: { output: {
@ -20,16 +21,17 @@ export default {
sourcemap: true, sourcemap: true,
}, },
plugins: [ plugins: [
flow(),
multi(), multi(),
commonjs(), commonjs(),
nodeResolve({ nodeResolve({
browser: true, browser: true,
preferBuiltins: false, preferBuiltins: false,
extensions,
dedupe: ['bn.js', 'buffer'], dedupe: ['bn.js', 'buffer'],
}), }),
babel({ babel({
exclude: '**/node_modules/**', exclude: '**/node_modules/**',
extensions,
babelHelpers: 'runtime', babelHelpers: 'runtime',
plugins: ['@babel/plugin-transform-runtime'], plugins: ['@babel/plugin-transform-runtime'],
}), }),
@ -54,18 +56,6 @@ export default {
} }
}, },
treeshake: { treeshake: {
moduleSideEffects: path => path.endsWith('test.js'), moduleSideEffects: path => path.endsWith('test.ts'),
}, },
}; };
// Using this instead of rollup-plugin-flow due to
// https://github.com/leebyron/rollup-plugin-flow/issues/5
function flow() {
return {
name: 'flow-remove-types',
transform: code => ({
code: flowRemoveTypes(code).toString(),
map: null,
}),
};
}

View File

@ -21,7 +21,7 @@ function checkEncodedArray(
describe('shortvec', () => { describe('shortvec', () => {
it('decodeLength', () => { it('decodeLength', () => {
let array = []; let array: number[] = [];
checkDecodedArray(array, 0); checkDecodedArray(array, 0);
array = [5]; array = [5];
@ -47,7 +47,7 @@ describe('shortvec', () => {
}); });
it('encodeLength', () => { it('encodeLength', () => {
let array = []; let array: number[] = [];
let prevLength = 1; let prevLength = 1;
checkEncodedArray(array, 0, prevLength, 1, [0]); checkEncodedArray(array, 0, prevLength, 1, [0]);
@ -63,7 +63,7 @@ describe('shortvec', () => {
checkEncodedArray(array, 0x7fff, (prevLength += 3), 3, [0xff, 0xff, 0x01]); checkEncodedArray(array, 0x7fff, (prevLength += 3), 3, [0xff, 0xff, 0x01]);
prevLength = checkEncodedArray(array, 0x200000, (prevLength += 4), 4, [ checkEncodedArray(array, 0x200000, (prevLength += 4), 4, [
0x80, 0x80,
0x80, 0x80,
0x80, 0x80,

View File

@ -1,4 +1,5 @@
import base58 from 'bs58'; import base58 from 'bs58';
import invariant from 'assert';
import {expect} from 'chai'; import {expect} from 'chai';
import { import {
@ -18,14 +19,15 @@ describe('Transaction Payer', () => {
connection = new Connection(url); connection = new Connection(url);
}); });
if (!process.env.TEST_LIVE) { if (mockServer) {
const server = mockServer;
beforeEach(() => { beforeEach(() => {
mockServer.start(MOCK_PORT); server.start(MOCK_PORT);
stubRpcWebSocket(connection); stubRpcWebSocket(connection);
}); });
afterEach(() => { afterEach(() => {
mockServer.stop(); server.stop();
restoreRpcWebSocket(connection); restoreRpcWebSocket(connection);
}); });
} }
@ -76,6 +78,7 @@ describe('Transaction Payer', () => {
commitment: 'confirmed', commitment: 'confirmed',
}); });
invariant(transaction.signature);
const signature = base58.encode(transaction.signature); const signature = base58.encode(transaction.signature);
await mockRpcResponse({ await mockRpcResponse({

View File

@ -1,4 +1,5 @@
import bs58 from 'bs58'; import bs58 from 'bs58';
import invariant from 'assert';
import {Buffer} from 'buffer'; import {Buffer} from 'buffer';
import nacl from 'tweetnacl'; import nacl from 'tweetnacl';
import {expect} from 'chai'; import {expect} from 'chai';
@ -9,6 +10,7 @@ import {Transaction} from '../src/transaction';
import {StakeProgram} from '../src/stake-program'; import {StakeProgram} from '../src/stake-program';
import {SystemProgram} from '../src/system-program'; import {SystemProgram} from '../src/system-program';
import {Message} from '../src/message'; import {Message} from '../src/message';
import {toBuffer} from '../src/util/to-buffer';
describe('Transaction', () => { describe('Transaction', () => {
describe('compileMessage', () => { describe('compileMessage', () => {
@ -160,22 +162,17 @@ describe('Transaction', () => {
expect(partialTransaction).to.eql(transaction); expect(partialTransaction).to.eql(transaction);
if ( invariant(partialTransaction.signatures[0].signature);
partialTransaction.signatures[0].signature != null /* <-- pacify flow */ partialTransaction.signatures[0].signature[0] = 0;
) { expect(() =>
partialTransaction.signatures[0].signature[0] = 0; partialTransaction.serialize({requireAllSignatures: false}),
expect(() => ).to.throw();
partialTransaction.serialize({requireAllSignatures: false}), expect(() =>
).to.throw(); partialTransaction.serialize({
expect(() => verifySignatures: false,
partialTransaction.serialize({ requireAllSignatures: false,
verifySignatures: false, }),
requireAllSignatures: false, ).not.to.throw();
}),
).not.to.throw();
} else {
throw new Error('unreachable');
}
}); });
describe('dedupe', () => { describe('dedupe', () => {
@ -261,7 +258,6 @@ describe('Transaction', () => {
const newTransaction = new Transaction({ const newTransaction = new Transaction({
recentBlockhash: orgTransaction.recentBlockhash, recentBlockhash: orgTransaction.recentBlockhash,
feePayer: orgTransaction.feePayer,
signatures: orgTransaction.signatures, signatures: orgTransaction.signatures,
}).add(transfer1, transfer2); }).add(transfer1, transfer2);
@ -451,7 +447,7 @@ describe('Transaction', () => {
// Serializing the message is allowed when signature array has null signatures // Serializing the message is allowed when signature array has null signatures
expectedTransaction.serializeMessage(); expectedTransaction.serializeMessage();
expectedTransaction.feePayer = null; expectedTransaction.feePayer = undefined;
expectedTransaction.setSigners(sender.publicKey); expectedTransaction.setSigners(sender.publicKey);
expect(expectedTransaction.signatures).to.have.length(1); expect(expectedTransaction.signatures).to.have.length(1);
@ -510,7 +506,7 @@ describe('Transaction', () => {
tx.setSigners(from.publicKey); tx.setSigners(from.publicKey);
const tx_bytes = tx.serializeMessage(); const tx_bytes = tx.serializeMessage();
const signature = nacl.sign.detached(tx_bytes, from.secretKey); const signature = nacl.sign.detached(tx_bytes, from.secretKey);
tx.addSignature(from.publicKey, signature); tx.addSignature(from.publicKey, toBuffer(signature));
expect(tx.verifySignatures()).to.be.true; expect(tx.verifySignatures()).to.be.true;
}); });
@ -532,7 +528,7 @@ describe('Transaction', () => {
tx.feePayer = from.publicKey; tx.feePayer = from.publicKey;
const tx_bytes = tx.serializeMessage(); const tx_bytes = tx.serializeMessage();
const signature = nacl.sign.detached(tx_bytes, from.secretKey); const signature = nacl.sign.detached(tx_bytes, from.secretKey);
tx.addSignature(from.publicKey, signature); tx.addSignature(from.publicKey, toBuffer(signature));
expect(tx.verifySignatures()).to.be.true; expect(tx.verifySignatures()).to.be.true;
}); });
}); });

View File

@ -16,7 +16,11 @@
"isolatedModules": true, "isolatedModules": true,
"baseUrl": "src", "baseUrl": "src",
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noImplicitReturns": true "noImplicitReturns": true,
"paths": {
// This is needed so that @solana/spl-token's @solana/web3.js doesn't conflict
"@solana/web3.js": ["."]
}
}, },
"include": ["src"] "include": ["src"]
} }