-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: add
launchTestNode
utility (#1356)
- Loading branch information
Showing
57 changed files
with
2,481 additions
and
489 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
"@fuel-ts/utils": patch | ||
"@fuel-ts/account": minor | ||
"@fuel-ts/contract": patch | ||
"fuels": patch | ||
--- | ||
|
||
feat!: add `launchTestNode` utility |
251 changes: 251 additions & 0 deletions
251
apps/docs-snippets/src/guide/testing/launching-a-test-node.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
import { WalletUnlocked } from 'fuels'; | ||
import { AssetId, TestMessage, launchTestNode } from 'fuels/test-utils'; | ||
import { join } from 'path'; | ||
|
||
import { CounterAbi__factory as TestContract__factory } from '../../../test/typegen/contracts'; | ||
import bytecode from '../../../test/typegen/contracts/CounterAbi.hex'; | ||
|
||
/** | ||
* @group node | ||
*/ | ||
describe('launching a test node', () => { | ||
test(`instantiating test nodes - automatic cleanup`, async () => { | ||
// #region automatic-cleanup | ||
// #import { launchTestNode }; | ||
|
||
using launched = await launchTestNode(); | ||
|
||
/* | ||
The method `launched.cleanup()` will be automatically | ||
called when the variable `launched` goes out of block scope. | ||
*/ | ||
|
||
// #endregion automatic-cleanup | ||
}); | ||
|
||
test('instantiating test nodes - manual cleanup', async () => { | ||
// #region manual-cleanup | ||
// #import { launchTestNode }; | ||
|
||
const launched = await launchTestNode(); | ||
|
||
/* | ||
Do your things, run your tests, and then call | ||
`launched.cleanup()` to dispose of everything. | ||
*/ | ||
|
||
launched.cleanup(); | ||
// #endregion manual-cleanup | ||
}); | ||
|
||
test('options', async () => { | ||
// #region options | ||
// #import { launchTestNode }; | ||
|
||
using launched = await launchTestNode(/* options */); | ||
// #endregion options | ||
}); | ||
|
||
test('simple contract deployment', async () => { | ||
// #region basic-example | ||
// #import { launchTestNode }; | ||
|
||
// #context import { TestContract__factory } from 'path/to/typegen/output'; | ||
// #context import bytecode from 'path/to/typegen/output/TestContract.hex.ts'; | ||
|
||
using launched = await launchTestNode({ | ||
contractsConfigs: [ | ||
{ | ||
deployer: TestContract__factory, | ||
bytecode, | ||
}, | ||
], | ||
}); | ||
|
||
const { | ||
contracts: [contract], | ||
provider, | ||
wallets, | ||
} = launched; | ||
|
||
const response = await contract.functions.get_count().call(); | ||
// #endregion basic-example | ||
expect(response.value.toNumber()).toBe(0); | ||
expect(provider).toBeDefined(); | ||
expect(wallets).toBeDefined(); | ||
}); | ||
|
||
test('multiple contracts and wallets', async () => { | ||
// #region advanced-example | ||
// #import { launchTestNode, AssetId, TestMessage }; | ||
|
||
// #context import { TestContract__factory } from 'path/to/typegen/output'; | ||
// #context import bytecode from 'path/to/typegen/output/TestContract.hex.ts'; | ||
|
||
const assets = AssetId.random(2); | ||
const message = new TestMessage({ amount: 1000 }); | ||
|
||
using launched = await launchTestNode({ | ||
walletsConfig: { | ||
count: 4, | ||
assets, | ||
coinsPerAsset: 2, | ||
amountPerCoin: 1_000_000, | ||
messages: [message], | ||
}, | ||
contractsConfigs: [ | ||
{ | ||
deployer: TestContract__factory, | ||
bytecode, | ||
walletIndex: 3, | ||
options: { storageSlots: [] }, | ||
}, | ||
], | ||
}); | ||
|
||
const { | ||
contracts: [contract], | ||
wallets: [wallet1, wallet2, wallet3, wallet4], | ||
} = launched; | ||
// #endregion advanced-example | ||
|
||
expect(contract).toBeDefined(); | ||
expect(wallet1).toBeDefined(); | ||
expect(wallet2).toBeDefined(); | ||
expect(wallet3).toBeDefined(); | ||
expect(wallet4).toBeDefined(); | ||
}); | ||
|
||
test('configuring custom fuel-core args', async () => { | ||
// #region custom-fuel-core-args | ||
// #import { launchTestNode }; | ||
|
||
process.env.DEFAULT_FUEL_CORE_ARGS = `--tx-max-depth 20`; | ||
|
||
// `nodeOptions.args` will override the above values if provided. | ||
|
||
using launched = await launchTestNode(); | ||
// #endregion custom-fuel-core-args | ||
|
||
const { provider } = launched; | ||
|
||
expect(provider.getNode().maxDepth.toNumber()).toEqual(20); | ||
process.env.DEFAULT_FUEL_CORE_ARGS = ''; | ||
}); | ||
|
||
test('configuring a base chain config', async () => { | ||
const snapshotDirPath = join(__dirname, '../../../../../', '.fuel-core', 'configs'); | ||
|
||
// #region custom-chain-config | ||
// #import { launchTestNode }; | ||
|
||
process.env.DEFAULT_CHAIN_SNAPSHOT_DIR = snapshotDirPath; | ||
|
||
using launched = await launchTestNode(); | ||
// #endregion custom-chain-config | ||
|
||
const { provider } = launched; | ||
|
||
const { name } = await provider.fetchChain(); | ||
|
||
expect(name).toEqual('local_testnet'); | ||
}); | ||
|
||
test('customizing node options', async () => { | ||
// #region custom-node-options | ||
// #import { launchTestNode, AssetId }; | ||
|
||
const [baseAssetId] = AssetId.random(); | ||
|
||
using launched = await launchTestNode({ | ||
nodeOptions: { | ||
snapshotConfig: { | ||
chainConfig: { | ||
consensus_parameters: { | ||
V1: { | ||
base_asset_id: baseAssetId.value, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}); | ||
// #endregion custom-node-options | ||
}); | ||
|
||
test('using assetId', async () => { | ||
// #region asset-ids | ||
// #import { launchTestNode, AssetId }; | ||
|
||
const assets = AssetId.random(); | ||
|
||
using launched = await launchTestNode({ | ||
walletsConfig: { | ||
assets, | ||
}, | ||
}); | ||
|
||
const { | ||
wallets: [wallet], | ||
} = launched; | ||
|
||
const coins = await wallet.getCoins(assets[0].value); | ||
// #endregion asset-ids | ||
expect(coins[0].assetId).toEqual(assets[0].value); | ||
}); | ||
|
||
test('generating test messages', async () => { | ||
// #region test-messages | ||
// #import { launchTestNode, TestMessage }; | ||
|
||
const testMessage = new TestMessage({ amount: 1000 }); | ||
|
||
using launched = await launchTestNode({ | ||
walletsConfig: { | ||
messages: [testMessage], | ||
}, | ||
}); | ||
|
||
const { | ||
wallets: [wallet], | ||
} = launched; | ||
|
||
const [message] = await wallet.getMessages(); | ||
// message.nonce === testMessage.nonce | ||
// #endregion test-messages | ||
|
||
expect(message.nonce).toEqual(testMessage.nonce); | ||
}); | ||
|
||
test('generating test messages directly on chain', async () => { | ||
// #region test-messages-chain | ||
// #import { launchTestNode, TestMessage, WalletUnlocked }; | ||
|
||
const recipient = WalletUnlocked.generate(); | ||
const testMessage = new TestMessage({ | ||
amount: 1000, | ||
recipient: recipient.address, | ||
}); | ||
|
||
using launched = await launchTestNode({ | ||
nodeOptions: { | ||
snapshotConfig: { | ||
stateConfig: { | ||
messages: [testMessage.toChainMessage()], | ||
}, | ||
}, | ||
}, | ||
}); | ||
|
||
const { provider } = launched; | ||
|
||
recipient.provider = provider; | ||
|
||
const [message] = await recipient.getMessages(); | ||
// message.nonce === testMessage.nonce | ||
// #endregion test-messages-chain | ||
|
||
expect(message.nonce).toEqual(testMessage.nonce); | ||
}); | ||
}); |
57 changes: 39 additions & 18 deletions
57
apps/docs-snippets/src/guide/testing/tweaking-the-blockchain.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,45 @@ | ||
import { launchNode } from '@fuel-ts/account/test-utils'; | ||
import { Provider, DateTime } from 'fuels'; | ||
/* eslint-disable @typescript-eslint/no-non-null-assertion */ | ||
import { DateTime } from 'fuels'; | ||
import { launchTestNode } from 'fuels/test-utils'; | ||
|
||
/** | ||
* @group node | ||
*/ | ||
test('produceBlocks with custom timestamp docs snippet', async () => { | ||
// TODO: reevaluate/replace after #1356 | ||
const { cleanup, ip, port } = await launchNode({}); | ||
const url = `http://${ip}:${port}/v1/graphql`; | ||
const provider = await Provider.create(url); | ||
const latestBlock = await provider.getBlock('latest'); | ||
if (!latestBlock) { | ||
throw new Error('No latest block'); | ||
} | ||
const lastBlockNumber = latestBlock.height; | ||
// #region Provider-produceBlocks-custom-timestamp | ||
const lastBlockTimestamp = DateTime.fromTai64(latestBlock.time).toUnixMilliseconds(); | ||
const latestBlockNumber = await provider.produceBlocks(3, lastBlockTimestamp + 1000); | ||
// #endregion Provider-produceBlocks-custom-timestamp | ||
expect(latestBlockNumber.toHex()).toBe(lastBlockNumber.add(3).toHex()); | ||
describe('tweaking the blockchain', () => { | ||
test('produceBlocks', async () => { | ||
// #region produce-blocks | ||
using launched = await launchTestNode(); | ||
const { provider } = launched; | ||
const block = await provider.getBlock('latest'); | ||
if (!block) { | ||
throw new Error('No latest block'); | ||
} | ||
const { time: timeLastBlockProduced } = block; | ||
|
||
cleanup(); | ||
const producedBlockHeight = await provider.produceBlocks(3); | ||
|
||
const producedBlock = await provider.getBlock(producedBlockHeight.toNumber()); | ||
|
||
const oldest = DateTime.fromTai64(timeLastBlockProduced); | ||
const newest = DateTime.fromTai64(producedBlock!.time); | ||
// newest >= oldest | ||
// #endregion produce-blocks | ||
expect(producedBlock).toBeDefined(); | ||
expect(newest >= oldest).toBeTruthy(); | ||
}); | ||
|
||
test('produceBlocks with custom timestamp docs snippet', async () => { | ||
// #region produceBlocks-custom-timestamp | ||
using launched = await launchTestNode(); | ||
const { provider } = launched; | ||
|
||
const latestBlock = await provider.getBlock('latest'); | ||
if (!latestBlock) { | ||
throw new Error('No latest block'); | ||
} | ||
const latestBlockTimestamp = DateTime.fromTai64(latestBlock.time).toUnixMilliseconds(); | ||
const newBlockHeight = await provider.produceBlocks(3, latestBlockTimestamp + 1000); | ||
// #endregion produceBlocks-custom-timestamp | ||
expect(newBlockHeight.toHex()).toBe(latestBlock.height.add(3).toHex()); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.